literate: restructure all 19 org files with per-function blocks and prose
Every function, defclass, defstruct, defgeneric, defmethod, defmacro, defvar, and defparameter in every org file now has its own #+BEGIN_SRC block with literate prose above it explaining the design reasoning. Block counts before → after: package.org: 1 → 7 container-package.org: 1 → 1 (prose expanded) dirty.org: 4 → 6 render.org: 10 → 25 theme.org: 6 → 19 box-renderable.org: 9 → 29 scrollbox.org: 8 → 26 tabbar.org: 5 → 10 backend-protocol.org: 8 → 66 modern-backend.org: 17 → 53 detection.org: 4 → 6 layout-engine.org: 9 → 36 framebuffer.org: 8 → 37 markdown-renderer.org:13 → 38 dialog.org: 17 → 23 (merged dual structure) mouse.org: 4 → 25 select.org: 12 → 30 slot.org: 4 → 12 text-input.org: 11 → 53 Total: ~153 blocks → ~502 blocks Bugs fixed during restructuring: - render.org: stray π character typo (backenπd → backend) - modern-backend.org: sgr-attr missing closing paren + #+END_SRC - detection.org: invalid #\Esc character reference - select.org: extra closing paren in select-visible-options All 13 test suites pass at 100%.
This commit is contained in:
104
org/tabbar.org
104
org/tabbar.org
@@ -25,15 +25,30 @@ pipeline and layout engine.
|
||||
|
||||
* Implementation
|
||||
|
||||
** TabBar class
|
||||
** Package declaration
|
||||
|
||||
~tab-bar~ stores a list of tab plists ~((:id :tab1 :title "One") ...)~
|
||||
and the currently active tab id. ~tab-bar-add~ creates a new tab with
|
||||
the given id and title, returns the id.
|
||||
All TabBar code lives in the ~cl-tty.container~ package alongside the
|
||||
other container components (scrollbox, box, slot, etc.). This keeps
|
||||
the symbol namespace clean and avoids accidental collisions with
|
||||
user-level code.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/components/tabbar.lisp
|
||||
(in-package #:cl-tty.container)
|
||||
#+END_SRC
|
||||
|
||||
** TabBar class
|
||||
|
||||
~tab-bar~ stores a list of tab plists ~((:id :tab1 :title "One") ...)~
|
||||
and the currently active tab id. It inherits from ~dirty-mixin~ so that
|
||||
any mutation (adding a tab, switching tabs) automatically marks the
|
||||
component for re-render. A layout node holds its geometry; the
|
||||
~focusable~ slot allows the keyboard navigation system to discover it.
|
||||
|
||||
The ~tabs~ slot is a simple plist list rather than a hash table or
|
||||
alist because the total number of tabs in a UI is typically small
|
||||
(< 20) and we need ordered iteration for rendering.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/components/tabbar.lisp
|
||||
(defclass tab-bar (dirty-mixin)
|
||||
((tabs :initform nil :initarg :tabs
|
||||
:accessor tab-bar-tabs :type list)
|
||||
@@ -41,10 +56,30 @@ the given id and title, returns the id.
|
||||
:accessor tab-bar-active)
|
||||
(layout-node :initform (make-layout-node) :accessor tab-bar-layout-node)
|
||||
(focusable :initform t :accessor tab-bar-focusable)))
|
||||
#+END_SRC
|
||||
|
||||
** make-tab-bar constructor
|
||||
|
||||
Convenience constructor that forwards keyword arguments to
|
||||
~make-instance~. Using a dedicated function instead of inlining
|
||||
~make-instance~ everywhere gives us a single place to add
|
||||
defaulting, validation, or initialization hooks in the future.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/components/tabbar.lisp
|
||||
(defun make-tab-bar (&key tabs active)
|
||||
(make-instance 'tab-bar :tabs (or tabs nil) :active active))
|
||||
#+END_SRC
|
||||
|
||||
** tab-bar-add: adding tabs
|
||||
|
||||
~tab-bar-add~ appends a new tab plist to the end of the tab list.
|
||||
The callers supply both an ~id~ (for programmatic selection) and a
|
||||
~title~ (for display). If no tab is currently active, the newly added
|
||||
tab becomes active automatically — this ensures there is always a
|
||||
sensible default when the first tab is created. Returns the ~id~ so
|
||||
callers can chain or store it.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/components/tabbar.lisp
|
||||
(defun tab-bar-add (tb id title)
|
||||
"Add a tab with ID and TITLE. Sets as active if first tab."
|
||||
(setf (tab-bar-tabs tb)
|
||||
@@ -54,18 +89,26 @@ the given id and title, returns the id.
|
||||
id)
|
||||
#+END_SRC
|
||||
|
||||
** TabBar: component protocol
|
||||
** component-layout-node protocol
|
||||
|
||||
Returns the layout node so the layout engine can position and size
|
||||
the tab bar within its parent. Every component that participates in
|
||||
automatic layout must implement this method.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/components/tabbar.lisp
|
||||
(defmethod component-layout-node ((tb tab-bar))
|
||||
(tab-bar-layout-node tb))
|
||||
#+END_SRC
|
||||
|
||||
** TabBar: navigation
|
||||
** tab-bar-next: cycling forward
|
||||
|
||||
~tab-bar-next~ and ~tab-bar-prev~ cycle through tabs. ~tab-bar-select~
|
||||
activates a tab by id. ~tab-bar-handle-key~ dispatches key events
|
||||
(Left/Right to navigate, optional Enter to select).
|
||||
~tab-bar-next~ moves the active cursor to the next tab in the list,
|
||||
wrapping around from the last tab to the first (~mod~ arithmetic).
|
||||
It calls ~mark-dirty~ so the rendering pass picks up the change.
|
||||
|
||||
The lookup strategy — mapcar ids, position, mod — is O(n) but
|
||||
acceptable since tab lists are small. A hash-based index would be
|
||||
premature optimization at this scale.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/components/tabbar.lisp
|
||||
(defun tab-bar-next (tb)
|
||||
@@ -78,7 +121,16 @@ activates a tab by id. ~tab-bar-handle-key~ dispatches key events
|
||||
(let ((next (nth (mod (1+ pos) (length ids)) ids)))
|
||||
(setf (tab-bar-active tb) next)
|
||||
(mark-dirty tb)))))
|
||||
#+END_SRC
|
||||
|
||||
** tab-bar-prev: cycling backward
|
||||
|
||||
Mirror of ~tab-bar-next~; decrements the position index instead of
|
||||
incrementing it. ~mod~ handles negative wrap-around correctly in
|
||||
Common Lisp (returns a non-negative remainder), so ~(mod (1- 0) 3)~
|
||||
produces 2 rather than −1.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/components/tabbar.lisp
|
||||
(defun tab-bar-prev (tb)
|
||||
"Move to previous tab."
|
||||
(let* ((tabs (tab-bar-tabs tb))
|
||||
@@ -89,18 +141,29 @@ activates a tab by id. ~tab-bar-handle-key~ dispatches key events
|
||||
(let ((prev (nth (mod (1- pos) (length ids)) ids)))
|
||||
(setf (tab-bar-active tb) prev)
|
||||
(mark-dirty tb)))))
|
||||
#+END_SRC
|
||||
|
||||
** tab-bar-select: direct tab selection
|
||||
|
||||
~tab-bar-select~ sets the active tab directly by id, bypassing the
|
||||
cyclic navigation. This is used when a user clicks a tab (via mouse
|
||||
binding), when a programmatic action needs to switch views, or when
|
||||
activating a tab from outside the keyboard flow. Always marks dirty.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/components/tabbar.lisp
|
||||
(defun tab-bar-select (tb id)
|
||||
"Select a tab by ID."
|
||||
(setf (tab-bar-active tb) id)
|
||||
(mark-dirty tb))
|
||||
#+END_SRC
|
||||
|
||||
** TabBar: keyboard handler
|
||||
** tab-bar-handle-key: keyboard dispatch
|
||||
|
||||
~tab-bar-handle-key~ dispatches Left → previous tab, Right → next tab.
|
||||
Returns T if the key was handled, NIL otherwise (for composability with
|
||||
the keybinding system).
|
||||
Dispatches key events for tab navigation. Left arrow goes to the
|
||||
previous tab, right arrow to the next. Returns ~t~ when the key was
|
||||
consumed and ~nil~ otherwise, which lets the keybinding system fall
|
||||
through to other handlers — important for composable UIs where a tab
|
||||
bar lives alongside other focusable elements.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/components/tabbar.lisp
|
||||
(defun tab-bar-handle-key (tb event)
|
||||
@@ -111,14 +174,17 @@ the keybinding system).
|
||||
(t nil)))
|
||||
#+END_SRC
|
||||
|
||||
** TabBar: rendering
|
||||
** render: drawing the tab row
|
||||
|
||||
~render~ iterates tabs, drawing each as ~[ Title ]~ with the active
|
||||
tab highlighted (bold, accent color) and inactive tabs dimmed. Tabs
|
||||
are separated by two spaces.
|
||||
~render~ iterates the tab list and draws each one as ~[ Title ]~.
|
||||
The active tab uses the ~:accent~ foreground color and
|
||||
~:background-element~ background for visual prominence; inactive tabs
|
||||
are rendered in ~:text-muted~. Tabs are separated by two spaces.
|
||||
|
||||
The available width comes from the layout node. If tabs overflow,
|
||||
they are truncated with an ellipsis.
|
||||
Available width comes from the layout node. If the total tab width
|
||||
exceeds the available space, tabs are truncated and an ellipsis
|
||||
~...~ is drawn at the overflow point. This prevents the tab bar from
|
||||
breaking the layout on narrow terminals.
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/components/tabbar.lisp
|
||||
(defmethod render ((tb tab-bar) backend)
|
||||
|
||||
Reference in New Issue
Block a user