Index: main/textbox-test.lisp
===================================================================
--- main/textbox-test.lisp	(revision main,35)
+++ main/textbox-test.lisp	(revision main,35)
@@ -0,0 +1,21 @@
+(defpackage #:textbox-test
+  (:use #:cl #:textbox #:tui-input #:tui-window)
+  (:export #:test))
+
+(in-package #:textbox-test)
+
+
+
+(defclass data () ((text :initform "Now is the time!" :accessor text)))
+
+
+
+(defun test ()
+  (with-screen (s)
+    (let* ((data (make-instance 'data))
+           (tb (create-textbox data 8 s 10 10)))
+      (unwind-protect
+           (flet ((cb (key)
+                    (eq key :key-f12)))
+             (activate-textbox tb #'cb))
+        (destroy-textbox tb)))))
Index: main/textbox.lisp
===================================================================
--- main/textbox.lisp	(revision main,35)
+++ main/textbox.lisp	(revision main,35)
@@ -0,0 +1,116 @@
+(defpackage #:textbox
+  (:use #:cl #:dso-util #:tui-cursor #:tui-input #:tui-output #:tui-window)
+  (:export #:text #:textbox #:insertion-point #:scroll #:draw-textbox
+           #:create-textbox #:destroy-textbox #:activate-textbox))
+
+(in-package #:textbox)
+
+
+
+(defgeneric text (textbox-data))
+
+(defgeneric (setf text) (s textbox-data))
+
+
+
+(defclass textbox ()
+  ((data :initarg :data)
+   (window :initarg :window)
+   (insertion-point :type (integer 0)
+                    :initform 0
+                    :accessor insertion-point)
+   (scroll :type (integer 0) :initform 0 :accessor scroll)))
+
+
+
+(defun screen-cp (textbox)
+  (with-slots (insertion-point scroll) textbox
+    (- insertion-point scroll)))
+
+(defun ip-dist (textbox)
+  (with-slots (window) textbox
+    (let ((max (1- (nth-value 1 (size window))))
+          (cp (screen-cp textbox)))
+      (- cp (bound cp 0 max)))))
+
+(defun ip-visible-p (textbox)
+  (= (ip-dist textbox) 0))
+
+(defun draw-textbox (textbox)
+  (with-slots (data window scroll) textbox
+    (erase window)
+    (add-clipped-string window 0 (- scroll) (text data))
+    (setf (cursor-position window) (list 0 (screen-cp textbox)))))
+
+(defmethod (setf insertion-point) :around (i (textbox textbox))
+  (with-slots (data window) textbox
+    (let ((len (length (text data))))
+      (if (eq i :end)
+          (setf i len)
+          (boundf i 0 len)))
+    (let ((r (call-next-method i textbox)))
+      (if (ip-visible-p textbox)
+          (draw-textbox textbox)
+          (incf (scroll textbox) (ip-dist textbox)))
+      r)))
+
+(defmethod (setf scroll) :around (i (textbox textbox))
+  (with-slots (data window) textbox
+    (let* ((len (length (text data)))
+           (width (nth-value 1 (size window)))
+           (max (max 0 (- len width -1))))
+      (boundf i 0 max))
+    (let ((r (call-next-method i textbox)))
+      (draw-textbox textbox)
+      r)))
+
+
+
+(defun create-textbox (data width parent-window y x)
+  (let* ((window (tui-window::create-subwindow parent-window 1 width y x))
+         (inst (make-instance 'textbox :data data :window window)))
+    (draw-textbox inst)
+    inst))
+
+(defun destroy-textbox (textbox)
+  (with-slots (window) textbox
+    (tui-window::destroy-subwindow window)))
+
+
+
+(defun split (textbox)
+  (with-slots (data insertion-point) textbox
+    (let ((s (text data)))
+      (values (subseq s 0 insertion-point)
+              (subseq s insertion-point (length s))))))
+
+(defun activate-textbox (textbox callback)
+  (with-slots (data window) textbox
+    (cdk::c-keypad (window-pointer window) t)
+    (loop
+       (let ((key (read-key window)))
+         (case key
+           (:key-left (decf (insertion-point textbox)))
+           (:key-right (incf (insertion-point textbox)))
+           (:key-home (setf (insertion-point textbox) 0))
+           (:key-end (setf (insertion-point textbox) :end))
+           (:key-backspace
+            (multiple-value-bind (left right) (split textbox)
+              (when (string/= left "")
+                (setf left (subseq left 0 (1- (length left))))
+                (setf (text data) (concatenate 'string left right))
+                (decf (insertion-point textbox)))))
+           (:key-dc
+            (multiple-value-bind (left right) (split textbox)
+              (when (string/= right "")
+                (setf right (subseq right 1))
+                (setf (text data) (concatenate 'string left right))
+                (setf (insertion-point textbox) (insertion-point textbox)))))
+           (t
+            (if (or (keywordp key) (member key '(#\Return #\Tab #\Esc)))
+                (when (funcall callback key)
+                  (return-from activate-textbox))
+                (multiple-value-bind (left right) (split textbox)
+                  (setf (text data)
+                        (concatenate 'string left (string key) right))
+                  (incf (insertion-point textbox))))))))))
Index: main/tui.asd
===================================================================
--- main/tui.asd	(revision main,28)
+++ main/tui.asd	(revision main,35)
@@ -14,4 +14,6 @@
                (:file "output"
                       :depends-on ("cursor" "window"))
+               (:file "textbox"
+                      :depends-on ("cursor" "input" "output" "window"))
                (:file "display-string"
                       ;; "input" only for testing
