;;; 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) ;;; ─── 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 (let ((b2 (read-raw 1))) (unless b2 (return-from read-key :escape)) (if (= b2 #x5b) (let ((b3 (read-raw 1))) (case b3 (#x41 :up) (#x42 :down) (#x43 :right) (#x44 :left) (#x48 :home) (#x46 :end) (t :unknown))) :unknown))) (#x03 :ctrl-c) (#x0d :enter) (#x09 :tab) (#x7f :backspace) (t (code-char b))))) ;;; ─── 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 are using") ("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* ((raw (find-symbol "SET-RAW-MODE" :cl-tty.input)) (restore (find-symbol "RESTORE-TERMINAL-STATE" :cl-tty.input)) (saved (funcall raw))) (unwind-protect (let* ((backend (cl-tty.backend:detect-backend)) (tabs '(" Home " " Components " " Stats ")) (active 0) (running t)) (cl-tty.backend:initialize-backend backend) (cl-tty.backend:cursor-hide backend) (loop while running do (cl-tty.backend:backend-clear backend) (cl-tty.backend:draw-border backend 2 1 76 3 :style :double :title " cl-tty ") (cl-tty.backend:draw-text backend 4 2 "Interactive demo arrows: tabs q: quit" :bright-white :default) (render-tabs backend tabs active) (case active (0 (render-home backend)) (1 (render-components backend)) (2 (render-stats backend))) (cl-tty.backend:draw-rect backend 2 23 76 1 :bg :blue) (cl-tty.backend:draw-text backend 2 23 (format nil " Tab ~d/3: ~a " (1+ active) (string-trim " " (nth active tabs))) :bright-white :blue :bold t) (case (read-key) ((:ctrl-c :enter #\q #\Q) (setf running nil)) ((:right :tab) (setf active (mod (1+ active) (length tabs)))) (:left (setf active (mod (1- active) (length tabs)))))) (cl-tty.backend:cursor-show backend) (cl-tty.backend:backend-clear backend) (cl-tty.backend:shutdown-backend backend)) (when saved (funcall restore saved))))) ;;; ─── Entry ────────────────────────────────────────────────────────────────── (if (probe-file "/dev/tty") (run-demo) (format t "No TTY detected. Run in a terminal for the interactive demo.~%"))