diff --git a/demo.lisp b/demo.lisp index 323f849..f6a80f7 100644 --- a/demo.lisp +++ b/demo.lisp @@ -1,72 +1,155 @@ -;;; demo.lisp — cl-tty demo application +;;; demo.lisp — cl-tty interactive demo ;;; Run: sbcl --script demo.lisp (load "~/quicklisp/setup.lisp") (ql:register-local-projects) (ql:quickload :cl-tty :silent t) -;; ─── Demo ─────────────────────────────────────────────────────────────────── +;;; ─── Low-level input ─────────────────────────────────────────────────────── + +(defun read-raw (&optional timeout) + (let ((fn (symbol-function (find-symbol "READ-RAW-BYTE" :cl-tty.input)))) + (funcall fn :timeout (or timeout 10)))) + +(defun read-key () + (let ((b (read-raw))) + (unless b (return-from read-key nil)) + (case b + (#x1b ; ESC — could be Arrow, Escape, or Alt + (let ((b2 (read-raw 1))) + (unless b2 (return-from read-key :escape)) + (if (= b2 #x5b) ; ESC [ + (let ((b3 (read-raw 1))) + (case b3 + (#x41 :up) (#x42 :down) + (#x43 :right) (#x44 :left) + (#x48 :home) (#x46 :end) + (#x5e ; ESC [ N ~ + (let ((b4 (read-raw 1))) + (case b4 + (#x31 :home) (#x34 :end) + (#x35 :page-up) (#x36 :page-down) + (t :unknown)))) + (t :unknown))) + (t :unknown)))) + (#x03 :ctrl-c) + (#x0d :enter) + (#x09 :tab) + (#x7f :backspace) + (t (code-char b))))) ; printable + +;;; ─── Tab content renderers ───────────────────────────────────────────────── + +(defun render-home (be) + (cl-tty.backend:draw-border be 6 7 68 10 :style :single :title " Welcome ") + (cl-tty.backend:draw-text be 8 9 "cl-tty — Pure CL terminal UI framework" + :bright-white :default :bold t) + (cl-tty.backend:draw-text be 8 11 " - 11 versions, 12 components" + :white :default) + (cl-tty.backend:draw-text be 8 12 " - No ncurses, no FFI, no external deps" + :white :default) + (cl-tty.backend:draw-text be 8 13 " - 280+ tests, 100% passing" + :green :default) + (cl-tty.backend:draw-text be 8 15 "Arrows: switch tabs Enter/q: quit" + :bright-cyan :default :bold t)) + +(defun render-components (be) + (cl-tty.backend:draw-border be 6 7 68 12 :style :single :title " Components ") + (loop for i from 0 below 6 + for pair = (nth i '(("Box" "Bordered containers, title, bg") + ("Text" "Styled text, word-wrap, spans") + ("ScrollBox" "Scrollable viewport, scrollbars") + ("TabBar" "Tab navigation — you're using it!") + ("Select" "Dropdown with fuzzy filter") + ("Dialog" "Modal overlays + Toast notifs"))) + do (cl-tty.backend:draw-text be 8 (+ 9 i) (first pair) + :bright-yellow :default :bold t) + (cl-tty.backend:draw-text be 24 (+ 9 i) (second pair) + :white :default))) + +(defun render-stats (be) + (cl-tty.backend:draw-border be 6 7 68 10 :style :single :title " Stats ") + (cl-tty.backend:draw-text be 8 9 "Metric" :bright-white :default :bold t) + (cl-tty.backend:draw-text be 40 9 "Value" :bright-white :default :bold t) + (loop for i from 0 below 8 + for pair = (nth i '(("Versions" "11") + ("Components" "12") + ("Tests" "280+") + ("Lines" "~3060") + ("Dependencies" "0") + ("FFI" "0") + ("ncurses" "no") + ("License" "TBD"))) + do (cl-tty.backend:draw-text be 8 (+ 11 i) (first pair) + :white :default) + (cl-tty.backend:draw-text be 40 (+ 11 i) (second pair) + :bright-green :default :bold t))) + +;;; ─── Tab bar ─────────────────────────────────────────────────────────────── + +(defun render-tabs (be tabs active) + (let ((x 8)) + (cl-tty.backend:draw-rect be 6 4 68 1 :bg :default) + (loop for label in tabs for i from 0 + do (let* ((text (format nil " ~a " label)) (len (length text))) + (if (= i active) + (progn (cl-tty.backend:draw-rect be x 4 len 1 :bg :bright-blue) + (cl-tty.backend:draw-text be x 4 text + :bright-white :bright-blue :bold t)) + (cl-tty.backend:draw-text be x 4 text :bright-white :default)) + (incf x (+ len 2)))))) + +;;; ─── Main loop ───────────────────────────────────────────────────────────── (defun run-demo () - (let ((backend (make-instance 'cl-tty.backend:modern-backend)) - (read-fn (symbol-function (find-symbol "READ-RAW-BYTE" :cl-tty.input)))) - ;; Initialize - (cl-tty.backend:initialize-backend backend) - (cl-tty.backend:backend-clear backend) - (cl-tty.backend:cursor-hide backend) + (let* ((be (make-instance 'cl-tty.backend:modern-backend)) + (tabs '(" Home " " Components " " Stats ")) + (active 0) + (running t)) - ;; Title box - (cl-tty.backend:draw-border backend 1 1 78 3 :style :double :title " cl-tty Demo ") - (cl-tty.backend:draw-text backend 3 2 - "A pure-CL terminal UI framework. No ncurses, no FFI." - :white :default :bold t) + (cl-tty.backend:initialize-backend be) + (cl-tty.backend:cursor-hide be) - ;; Components grid - (cl-tty.backend:draw-border backend 1 5 78 12 :style :single :title " Components ") - (loop for i from 0 below 5 - for item = (nth i '((" Box Bordered containers with title and background" - " Text Styled text with word-wrap and spans") - (" ScrollBox Scrollable viewport with scrollbars" - " TabBar Horizontal tab navigation") - (" Select Dropdown with fuzzy filter" - " TextInput / TextArea Single/multi-line input with undo") - (" Markdown Renders markdown with syntax highlighting" - " Dialog / Toast Modal overlays and notifications") - (" Mouse Event handlers and text selection" - " Slot System Named slots for extensible UI"))) - do (cl-tty.backend:draw-text backend 3 (+ 7 i) (first item) :white :default) - (cl-tty.backend:draw-text backend 42 (+ 7 i) (second item) :white :default)) + (loop while running + do (cl-tty.backend:backend-clear be) - ;; Backend features table - (cl-tty.backend:draw-border backend 1 18 78 5 :style :single :title " Backend Support ") - (cl-tty.backend:draw-text backend 3 20 "Feature" :bright-white :default :bold t) - (cl-tty.backend:draw-text backend 25 20 "modern" :bright-white :default :bold t) - (cl-tty.backend:draw-text backend 40 20 "simple" :bright-white :default :bold t) - (cl-tty.backend:draw-text backend 3 21 "Truecolor (24-bit)" :white :default) - (cl-tty.backend:draw-text backend 25 21 "yes" :green :default) - (cl-tty.backend:draw-text backend 40 21 "no" :red :default) - (cl-tty.backend:draw-text backend 3 22 "OSC 8 hyperlinks" :white :default) - (cl-tty.backend:draw-text backend 25 22 "yes" :green :default) - (cl-tty.backend:draw-text backend 40 22 "no" :red :default) + ;; Title + (cl-tty.backend:draw-border be 2 1 76 3 :style :double :title " cl-tty ") + (cl-tty.backend:draw-text be 4 2 + "Interactive demo — navigate with arrows, q to quit" + :bright-white :default) - ;; Footer bar - (cl-tty.backend:draw-rect backend 1 24 78 1 :bg :blue) - (cl-tty.backend:draw-text backend 2 24 " Press q to quit " :bright-white :blue :bold t) + ;; Tabs + content + (render-tabs be tabs active) + (case active + (0 (render-home be)) + (1 (render-components be)) + (2 (render-stats be))) - (cl-tty.backend:cursor-show backend) + ;; Footer + (cl-tty.backend:draw-rect be 2 23 76 1 :bg :blue) + (cl-tty.backend:draw-text be 2 23 + (format nil " Tab ~d/3: ~a " + (1+ active) (string-trim " " (nth active tabs))) + :bright-white :blue :bold t) - ;; Wait for q keypress - (loop - (let ((ch (funcall read-fn :timeout 1))) - (when ch - (when (or (char= (code-char ch) #\q) (= ch 3)) - (return))))) + ;; Input + (let ((key (read-key))) + (case key + (:ctrl-c (setf running nil)) + (:enter (setf running nil)) + (#\q (setf running nil)) + (#\Q (setf running nil)) + (:right (setf active (mod (1+ active) (length tabs)))) + (:left (setf active (mod (1- active) (length tabs)))) + (:tab (setf active (mod (1+ active) (length tabs))))))) - ;; Cleanup - (cl-tty.backend:backend-clear backend) - (cl-tty.backend:shutdown-backend backend))) + (cl-tty.backend:cursor-show be) + (cl-tty.backend:backend-clear be) + (cl-tty.backend:shutdown-backend be))) -;; ─── Run ──────────────────────────────────────────────────────────────────── +;;; ─── Entry ────────────────────────────────────────────────────────────────── -(when (probe-file "/dev/tty") - (run-demo)) +(if (probe-file "/dev/tty") + (run-demo) + (format t "No TTY detected. Run in a terminal for the interactive demo.~%"))