;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; multiline textbox
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
register-widget textbox:
  lambda textbox-ctor ()
    define super new-basic-widget()
    define text-lines #nil
    define top-skip-y 0
    define last-width -666
    ;;
    define wrap-text(line width)
      define tmex lgfx:new-text-meter()
      if {width <= 0} {width := (fixnum-max)}
      define idx 0
      define last-word-idx -1
      {text-lines := make-vector()}
      while {not empty-string?(line)}
        iterate
          init
            {idx := 0}
            {last-word-idx := -1}
            tmex[reset:]
          exit {idx = string-length(line)}
            vector-push! text-lines line
            {line := ""}
          exit {line[idx] = #\newline}
            vector-push! text-lines substring(line 0 idx)
            {line := substring(line {idx + 1})}
          repeat {idx = 0}
            tmex[put-char: line[0]]
            inc! idx
          exit {tmex[put-char: line[idx]] > width}
            if {negative? last-word-idx}
              then
                vector-push! text-lines substring(line 0 {idx - 1})
                {line := substring(line idx)}
              else
                vector-push! text-lines substring(line 0 last-word-idx)
                {line := substring(line last-word-idx)}
            {idx := 0}
            while {{idx <> string-length(line)} and {line[idx] <= 32}}
              inc! idx
            {line := substring(line idx)}
          repeat {{line[idx] <= 32} and {line[{idx - 1}] > 32}}
            {last-word-idx := idx}
            inc! idx
          repeat #t
            inc! idx
    ;;
    method-lambda textbox-widget
      super: (self)
        super
      type: (self)
        textbox:
      selectable?: (self)
        #t
      ;;
      v-scrollbar-min: (self)
        0
      v-scrollbar-max: (self)
        max 0 {{vector-length(text-lines) * lgfx:text-height()} - self[final-height:] - -4}
      v-scrollbar-current: (self)
        top-skip-y
      v-scrollbar-scroll-to: (self position)
        define th lgfx:text-height()
        define pgs {self[final-height:] - 4}
        define max-skip-y max(0 {{vector-length(text-lines) * th} - pgs})
        {top-skip-y := clamp(trunc(position) 0 max-skip-y)}
        self
      ;;
      final-width-set!: (self value)
        inherited final-width-set!: value
        if {last-width <> value}
          then
            {last-width := value}
            {self[text:] := self[text:]} ;; this rewraps the text
        self
      ;;
      text-set!: (self value)
        assert {string? value} "invalid text property"
        inherited text-set!: value
        wrap-text value {self[final-width:] - 4}
        {top-skip-y := 0}
        need-repaint!()
        self
      ;;
      paint: (self)
        define wdt self[final-width:]
        define hgt self[final-height:]
        lgfx:draw-round-rect 0 0 wdt hgt self[style-color-prop: "frame"]
        self[update-clip-rect: 1 1 {wdt - 2} {hgt - 2}]
        lgfx:fill-rect 0 0 wdt hgt self[style-color-prop: "back"]
        if {vector? text-lines}
          then
            dec! wdt 4
            dec! hgt 4
            self[update-clip-rect: 2 2 wdt hgt]
            lgfx:global-shift 2 2
            define th lgfx:text-height()
            define tc self[style-color-prop: "text"]
            define iidx {top-skip-y div th}
            define y0 {- {top-skip-y - {iidx * th}}}
            while {{y0 < hgt} and {iidx < vector-length(text-lines)}}
              lgfx:print-str 0 {y0 + lgfx:text-ascender()} text-lines[iidx] tc
              inc! iidx
              inc! y0 th
        self
      ;;
      handle-event: (self evt)
        define calc-page-size()
          max(0 {self[final-height:] - 4})
        ;;
        define calc-scroll-page-size()
          max(1 {self[final-height:] - 4 - lgfx:text-height()})
        ;;
        define calc-max-skip-y()
          max(0 {{vector-length(text-lines) * lgfx:text-height()} - calc-page-size()})
        ;;
        define go-up/down(lines)
          {top-skip-y := clamp({top-skip-y + {lgfx:text-height() * lines}} 0 calc-max-skip-y())}
        ;;
        if evt[alive?:]
          case evt[type:]
            (KEY-DOWN)
              define ks evt[keysym:]
              define th lgfx:text-height()
              define pgs {self[final-height:] - 4}
              define max-skip-y max(0 {{vector-length(text-lines) * th} - pgs})
              {pgs := max(th {pgs - th})}
              cond
                self[active?:]
                  cond
                    {sdl:names-equal?("up" ks) or
                     sdl:names-equal?("kp-8" ks)}
                      evt[eat!:]
                      go-up/down -1
                    {sdl:names-equal?("down" ks) or
                     sdl:names-equal?("kp-2" ks)}
                      evt[eat!:]
                      go-up/down 1
                    {sdl:names-equal?("home" ks) or
                     sdl:names-equal?("kp-7" ks)}
                      evt[eat!:]
                      {top-skip-y := 0}
                    {sdl:names-equal?("end" ks) or
                     sdl:names-equal?("kp-1" ks)}
                      {top-skip-y := calc-max-skip-y()}
                      evt[eat!:]
                    {sdl:names-equal?("page-up" ks) or
                     sdl:names-equal?("kp-9" ks)}
                      evt[eat!:]
                      {top-skip-y := max(0 {top-skip-y - calc-scroll-page-size()})}
                    {sdl:names-equal?("page-down" ks) or
                     sdl:names-equal?("kp-3" ks)}
                      evt[eat!:]
                      {top-skip-y := min(max-skip-y {top-skip-y + calc-scroll-page-size()})}
                    {sdl:names-equal?("return" ks) and self[has-action?:]}
                      evt[eat!:]
                      self[perform-action:]
                    else
                      #void
                (and self[selectable?:]
                     evt[key-broadcast?:]
                     sdl:names-equal?(self[hotkey:] ks))
                  evt[eat!:]
                  self[top-widget:][activate-widget: self]
                else #void
            (MOUSE-DOWN)
              evt[eat!:]
            (MOUSE-WHEEL)
              evt[eat!:]
              go-up/down evt[wheel-y:]
            else #void
        if evt[alive?:]
          inherited handle-event: evt
      ;;
      else
        super
