Eliminates the cl-tty.container package by merging scrollbox and tabbar components directly into cl-tty.box, where the component system lives. Changes: - added scrollbox/tabbar exports to cl-tty.box defpackage in package.org - changed scrollbox.org in-package from cl-tty.container to cl-tty.box - changed tabbar.org in-package from cl-tty.container to cl-tty.box - tabbar's key-event-key references are qualified with cl-tty.input: (avoids circular :use dependency with cl-tty.input which :uses cl-tty.box) - deleted container-package.org - updated test packages, integration tests, scripts, ASDF - all 14 test suites pass at 100%
7.3 KiB
Base Component Package
Overview
The cl-tty.box package is the central namespace for the component
system. It aggregates all component-related symbols — box, text,
dirty tracking, render dispatch, theme engine — under one package.
Why box as the package name? Historically the package was created
for the box and text renderables, and the name stuck as the
package grew to encompass the entire component layer. The package
:use~s ~cl-tty.backend (for drawing primitives) and cl-tty.layout
(for layout nodes). All component code lives in this package.
This org file is documentation-only: it explains the package design
but the code itself is just a defpackage form.
Contract
The cl-tty.box package exports these symbol groups:
- Box:
box,make-box,render-box, border style/title accessors - Span:
span, span attribute readers - Text:
text,make-text,render-text, text accessors - Dirty:
dirty-mixin,dirty-p,mark-clean,mark-dirty - Render:
render,render-screen,render-node, tree navigation - Theme:
theme,make-theme,theme-color,load-preset,define-preset
Implementation
cl-tty.box uses cl-tty.backend for draw-text, draw-border,
etc., and cl-tty.layout for layout-node, compute-layout, and the
vbox~/~hbox macros.
The only direct dependencies are these two packages — no other application code is needed to define components.
Box exports
The box class is the primary rectangular container: it renders a
bordered region with optional title and background color. The accessor
family (box-border-style, box-title, box-title-align,
box-fg, box-bg) follows a consistent naming convention so that
users can infer slot names from the class name. render-box is the
specialized method that draws the border and fills the interior.
The box-layout-node accessor connects the box to its layout tree
node, which is essential for the render pipeline's coordinate
computation. We export it separately from the rendering symbols
because it is also needed by code that walks the component tree
without triggering a full render.
(defpackage :cl-tty.box
(:use :cl :cl-tty.backend :cl-tty.layout)
(:export
;; Box
#:box #:make-box
#:box-layout-node
#:box-border-style #:box-title #:box-title-align
#:box-fg #:box-bg
#:render-box
Span exports
Spans are lightweight inline-style records — not full classes with
layout. Each span stores a substring of the parent text along with
its visual attributes. The reader-named accessors (span-text,
span-bold, span-italic, etc.) let rendering code inspect span
properties without pulling in the internal representation. We keep
the accessor list flat (no grouping macro) to make the package
surface easy to grep and to keep the API browser-friendly.
;; Span
#:span
#:span-text #:span-bold #:span-italic #:span-underline
#:span-reverse #:span-dim #:span-fg #:span-bg
Text exports
text and make-text are the construction interface for the text
renderable. The text-layout-node accessor follows the same pattern
as box-layout-node, bridging the component and layout layers.
text-content and text-spans expose the raw data for rendering;
text-fg, text-bg, and text-wrap-mode control global text
appearance. render-text is the CLOS method that walks the span list
and calls draw-text from the backend.
These symbols live in the cl-tty.box package rather than a
separate cl-tty.text package to keep inter-component references
trivial — boxes can hold text children, and text can be nested inside
other components, all without cross-package imports.
;; Text
#:text #:make-text
#:text-layout-node #:text-content #:text-spans
#:text-fg #:text-bg #:text-wrap-mode
#:render-text
Utility exports (for tests)
word-wrap and split-string are internal text-processing utilities
used by the text renderer to break lines and tokenize input. They are
exported specifically so the test suite can unit-test them in
isolation. They are not part of the public component API and should
not be relied upon by application code outside of tests.
;; Utilities (for tests)
#:word-wrap #:split-string #:char-width
Dirty tracking
The dirty-mixin protocol lets any component class participate in the
change-propagation system. dirty-mixin is the mixin class, and
dirty-p, mark-clean, mark-dirty are the three operations that
the render pipeline calls to decide whether a subtree needs
re-rendering.
Having these as generic functions (rather than a single (setf
dirty-p)) makes it easy for subclasses to add side effects on dirty
transitions — for example, invalidating a cached bitmap or
recomputing string metrics.
;; Dirty tracking
#:dirty-mixin #:dirty-p #:mark-clean #:mark-dirty
Rendering pipeline
render, render-screen, and render-node are the three entry
points into the rendering dispatch. component-layout-node,
component-children, and component-parent form the tree-navigation
interface that render-node uses to walk the component hierarchy.
available-width and available-height are passed down the tree to
constrain layout. propagate-dirty walks upward from a changed
component to mark ancestors as dirty, ensuring the screen is
re-drawn from the correct root.
Collecting these under a single "Rendering pipeline" group signals to readers that they form a coherent subsystem — if you override one, you likely need to understand all of them.
;; Rendering pipeline
#:render #:render-screen #:render-node
#:component-layout-node #:component-children #:component-parent
#:available-width #:available-height
#:propagate-dirty
Theme engine
theme and make-theme are the constructor and class for theme
objects. theme-mode selects the active color mode (light/dark).
theme-color looks up a named color in the current theme.
load-preset loads a theme from a file, and define-preset registers
a preset at compile time.
The theme engine is isolated from the rest of the component layer — boxes and text reference theme colors by name at render time, and the theme object is passed in from the application level. This separation means themes can be swapped without touching component instances.
;; Theme engine
#:theme #:make-theme #:theme-mode
#:theme-color #:load-preset #:define-preset
;; Container components (merged from cl-tty.container)
#:scroll-box #:make-scroll-box
#:scroll-box-scroll-y #:scroll-box-scroll-x
#:scroll-box-children
#:scroll-by #:sticky-scroll-p
#:clamp-scroll
#:tab-bar #:make-tab-bar
#:tab-bar-active #:tab-bar-tabs
#:tab-bar-add #:tab-bar-next #:tab-bar-prev
#:tab-bar-select #:tab-bar-handle-key))