--- /dev/null
+(in-package #:clnl-gltk)
+
+; add word wrpaping, add border optional, so can use for text boxes for the TEXTBOX widget
+
+(defstruct textbox x y width height text word-wrap border)
+
+(setf (documentation 'textbox-text 'function)
+ "TEXTBOX-TEXT TB => TEXT
+
+ARGUMENTS AND VALUES:
+
+ TB: a textbox
+ TEXT: string currently being displayed
+
+DESCRIPTION:
+
+ TEXTBOX-TEXT allows for the retrieving and setting of the internal text
+ of textbox TB.")
+
+(defun textbox (x y width height &key text (border t) word-wrap)
+ "TEXTBOX X Y WIDTH HEIGHT &key TEXT BORDER WORD-WRAP => TB
+
+ARGUMENTS AND VALUES:
+
+ X: x offset, in pixels
+ Y: y offset, in pixels
+ WIDTH: width, in characters
+ HEIGHT: height, in characters
+ TEXT: optional string for the textual display
+ BORDER: boolean, whether we draw a border, defaults to t
+ WORD-WRAP: boolean, whether we attempt to wrap the text
+ TB: a textbox that can later be rendered
+
+DESCRIPTION:
+
+ TEXTBOX creates a textbox widget.
+
+ The widget is defined in terms of characters, rather than pixels. In this
+ way, it will never clip a portion of a character off. It will also display
+ whatever it can of its text, clipping off characters that are outside.
+
+ Multiline strings are supported, and each one appears on a new line.
+
+ When BORDER is NIL, no border is drawn and the text box floats, which can be
+ useful for labels.
+
+ When WORD-WRAP is non NIL, the text is attempted to wrap by the following rules.
+ The wrapping is done at the line if possible, at a breaking character if possible,
+ or just fits as many letters as it can befoer wrapping. It then only clips off
+ on the bottom. The only breaking character currently is #\Space."
+ (make-textbox :x x :y y :width width :height height :text text :border border :word-wrap word-wrap))
+
+(defmethod resize ((tb textbox) width height)
+ (setf (textbox-width tb) width)
+ (setf (textbox-height tb) height))
+
+(defmethod reposition ((tb textbox) x y)
+ (setf (textbox-x tb) x)
+ (setf (textbox-y tb) y))
+
+(defun break-lines (text width)
+ (cond
+ ((zerop (length text)) nil)
+ ((char= #\Space (aref text 0)) (break-lines (subseq text 1) width))
+ ((char= #\Newline (aref text 0)) (break-lines (subseq text 1) width))
+ ((let*
+ ((pos-space
+ (and
+ (> (length text) width)
+ (position #\Space (subseq text 0 width) :from-end t)))
+ (pos-nl (position #\Newline text))
+ (pos
+ (cond
+ ((and pos-nl (< pos-nl width)) pos-nl)
+ ((and pos-space (< pos-space width)) pos-space)
+ ((min width (length text))))))
+ (cons
+ (subseq text 0 pos)
+ (break-lines (subseq text pos) width))))))
+
+(defmethod render ((tb textbox))
+ (gl:color 1f0 1f0 1f0)
+ (with-slots (x y width height text border word-wrap) tb
+ (gl:with-pushed-matrix
+ (let
+ ((px-width (+ (* width *font-width*) 6))
+ (px-height (+ (* height *font-height*) 6)))
+ (gl:translate x y 0)
+ (when border (draw-border 0 0 px-width px-height))
+ (gl:translate 2 (- px-height 4 *font-height*) 0)
+ (when text
+ (let
+ ((lines (if word-wrap (break-lines text width) (cl-ppcre:split "\\n" text))))
+ (loop
+ :for line :in lines
+ :for i :from 0
+ :do
+ (when (< i height)
+ (gl:with-pushed-matrix
+ (font-print (subseq line 0 (min (length line) width))))
+ (gl:translate 0 (- *font-height*) 0)))))))))