Interactive demo with tab navigation
- Three tabs: Home, Components, Stats with different content - Real keyboard input: arrow keys to switch tabs, q to quit - CSI escape sequence parsing for arrow keys - Footer bar shows current tab position - Tab bar highlights active tab in bright blue
This commit is contained in:
193
demo.lisp
193
demo.lisp
@@ -1,72 +1,155 @@
|
|||||||
;;; demo.lisp — cl-tty demo application
|
;;; demo.lisp — cl-tty interactive demo
|
||||||
;;; Run: sbcl --script demo.lisp
|
;;; Run: sbcl --script demo.lisp
|
||||||
|
|
||||||
(load "~/quicklisp/setup.lisp")
|
(load "~/quicklisp/setup.lisp")
|
||||||
(ql:register-local-projects)
|
(ql:register-local-projects)
|
||||||
(ql:quickload :cl-tty :silent t)
|
(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 ()
|
(defun run-demo ()
|
||||||
(let ((backend (make-instance 'cl-tty.backend:modern-backend))
|
(let* ((be (make-instance 'cl-tty.backend:modern-backend))
|
||||||
(read-fn (symbol-function (find-symbol "READ-RAW-BYTE" :cl-tty.input))))
|
(tabs '(" Home " " Components " " Stats "))
|
||||||
;; Initialize
|
(active 0)
|
||||||
(cl-tty.backend:initialize-backend backend)
|
(running t))
|
||||||
(cl-tty.backend:backend-clear backend)
|
|
||||||
(cl-tty.backend:cursor-hide backend)
|
|
||||||
|
|
||||||
;; Title box
|
(cl-tty.backend:initialize-backend be)
|
||||||
(cl-tty.backend:draw-border backend 1 1 78 3 :style :double :title " cl-tty Demo ")
|
(cl-tty.backend:cursor-hide be)
|
||||||
(cl-tty.backend:draw-text backend 3 2
|
|
||||||
"A pure-CL terminal UI framework. No ncurses, no FFI."
|
|
||||||
:white :default :bold t)
|
|
||||||
|
|
||||||
;; Components grid
|
(loop while running
|
||||||
(cl-tty.backend:draw-border backend 1 5 78 12 :style :single :title " Components ")
|
do (cl-tty.backend:backend-clear be)
|
||||||
(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))
|
|
||||||
|
|
||||||
;; Backend features table
|
;; Title
|
||||||
(cl-tty.backend:draw-border backend 1 18 78 5 :style :single :title " Backend Support ")
|
(cl-tty.backend:draw-border be 2 1 76 3 :style :double :title " cl-tty ")
|
||||||
(cl-tty.backend:draw-text backend 3 20 "Feature" :bright-white :default :bold t)
|
(cl-tty.backend:draw-text be 4 2
|
||||||
(cl-tty.backend:draw-text backend 25 20 "modern" :bright-white :default :bold t)
|
"Interactive demo — navigate with arrows, q to quit"
|
||||||
(cl-tty.backend:draw-text backend 40 20 "simple" :bright-white :default :bold t)
|
:bright-white :default)
|
||||||
(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)
|
|
||||||
|
|
||||||
;; Footer bar
|
;; Tabs + content
|
||||||
(cl-tty.backend:draw-rect backend 1 24 78 1 :bg :blue)
|
(render-tabs be tabs active)
|
||||||
(cl-tty.backend:draw-text backend 2 24 " Press q to quit " :bright-white :blue :bold t)
|
(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
|
;; Input
|
||||||
(loop
|
(let ((key (read-key)))
|
||||||
(let ((ch (funcall read-fn :timeout 1)))
|
(case key
|
||||||
(when ch
|
(:ctrl-c (setf running nil))
|
||||||
(when (or (char= (code-char ch) #\q) (= ch 3))
|
(:enter (setf running nil))
|
||||||
(return)))))
|
(#\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:cursor-show be)
|
||||||
(cl-tty.backend:backend-clear backend)
|
(cl-tty.backend:backend-clear be)
|
||||||
(cl-tty.backend:shutdown-backend backend)))
|
(cl-tty.backend:shutdown-backend be)))
|
||||||
|
|
||||||
;; ─── Run ────────────────────────────────────────────────────────────────────
|
;;; ─── Entry ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
(when (probe-file "/dev/tty")
|
(if (probe-file "/dev/tty")
|
||||||
(run-demo))
|
(run-demo)
|
||||||
|
(format t "No TTY detected. Run in a terminal for the interactive demo.~%"))
|
||||||
|
|||||||
Reference in New Issue
Block a user