128 lines
5.6 KiB
Org Mode
128 lines
5.6 KiB
Org Mode
#+TITLE: Container Package
|
|
#+STARTUP: content
|
|
#+FILETAGS: :cl-tty:container:
|
|
|
|
* Overview
|
|
|
|
The ~cl-tty.container~ package defines the container component types:
|
|
ScrollBox and TabBar. It uses ~cl-tty.backend~, ~cl-tty.box~,
|
|
~cl-tty.layout~, and ~cl-tty.input~.
|
|
|
|
The package exports both ScrollBox and TabBar classes, constructors,
|
|
accessors, and navigation functions.
|
|
|
|
* Why a Separate Package?
|
|
|
|
The base ~cl-tty.box~ package was designed for the fundamental
|
|
renderable types — box, text, spans, dirty-tracking, the render
|
|
pipeline, and the theme engine. These are the building blocks that
|
|
virtually every component depends on. Container components —
|
|
ScrollBox and TabBar — are higher-level composite widgets with
|
|
specific behavioral contracts (viewport scrolling, tab navigation,
|
|
keyboard dispatch) that are not needed by every component user.
|
|
|
|
Separating them into ~cl-tty.container~ achieves two things:
|
|
|
|
1. It keeps ~cl-tty.box~ lean. Users who only need basic
|
|
renderables (boxes, text) do not pull in scroll-logic or
|
|
tab-navigation code. This is especially important for the
|
|
test suite — container tests have their own setup, backend
|
|
capture, and assertion patterns that are unrelated to the
|
|
base component tests.
|
|
|
|
2. It establishes a clean dependency boundary. ~cl-tty.box~
|
|
depends only on ~cl-tty.backend~ and ~cl-tty.layout~.
|
|
Container components additionally depend on ~cl-tty.input~,
|
|
because TabBar handles key events. By putting container
|
|
code in its own package, we avoid creating a circular or
|
|
incidental dependency between the input system and the
|
|
base component layer.
|
|
|
|
* What the Container Package Provides
|
|
|
|
The package exports two full component families:
|
|
|
|
- **ScrollBox**: A viewport-based container that holds a list of
|
|
child components and provides vertical/horizontal scrolling with
|
|
viewport culling (only visible children are rendered), scrollbar
|
|
display, sticky-scroll (auto-scroll to bottom on new content),
|
|
and scroll-offset clamping. ScrollBox inherits ~dirty-mixin~,
|
|
implements the component protocol (~render~, ~component-children~,
|
|
~component-layout-node~), and integrates with the layout engine.
|
|
Its constructor ~make-scroll-box~ accepts ~:children~,
|
|
~:scroll-y~, ~:scroll-x~, and ~:sticky-scroll-p~ keyword args.
|
|
|
|
- **TabBar**: A horizontal tab-navigation widget that manages a
|
|
list of named tabs, tracks the active tab, and dispatches
|
|
keyboard events (Left/Right for prev/next). TabBar also inherits
|
|
~dirty-mixin~ and implements ~render~ and ~component-layout-node~.
|
|
It provides ~tab-bar-add~ for dynamic tab creation, ~tab-bar-next~
|
|
/ ~tab-bar-prev~ for cycling, ~tab-bar-select~ for direct
|
|
activation, and ~tab-bar-handle-key~ for keyboard integration.
|
|
|
|
Both components export the generic ~render~ method, allowing the
|
|
rendering pipeline to dispatch ~(render instance backend)~ uniformly.
|
|
|
|
* Design Decisions: ScrollBox and TabBar in One Package
|
|
|
|
ScrollBox and TabBar are very different widgets — one manages a
|
|
scrollable viewport, the other renders a row of selectable labels.
|
|
They are kept in the same package rather than split into
|
|
~cl-tty.scroll-box~ and ~cl-tty.tab-bar~ for several reasons:
|
|
|
|
1. **Shared dependencies**: Both components :use the same four
|
|
packages (~cl-tty.backend~, ~cl-tty.box~, ~cl-tty.layout~,
|
|
~cl-tty.input~). They both inherit from ~dirty-mixin~ and
|
|
implement the component protocol. A shared package avoids
|
|
duplicating the ~:use~ and ~:export~ boilerplate.
|
|
|
|
2. **Co-located tests**: The test suite
|
|
(~tests/scrollbox-tabbar-tests.lisp~) tests both components
|
|
in one file and one FiveAM suite. They share test helpers,
|
|
backend-capture patterns, and the same package dependency.
|
|
Keeping them in one source package means the test defpackage
|
|
only needs one ~:use~ clause for the container, and symbols
|
|
from both components are visible together.
|
|
|
|
3. **Common contract**: Both components are "containers" in the
|
|
architectural sense — they manage a collection of sub-items
|
|
(children or tabs) and provide navigation over them. A
|
|
TabBar is conceptually a horizontal container of selectable
|
|
entries; a ScrollBox is a vertical container with scroll.
|
|
Placing them under the same ~:cl-tty.container~ namespace
|
|
signals to users that these are the composite widget types,
|
|
as opposed to the atomic renderables in ~:cl-tty.box~.
|
|
|
|
4. **Practical usage patterns**: In typical TUI applications, a
|
|
TabBar switches between views and a ScrollBox displays the
|
|
content of each view. They are often used together in the
|
|
same composition. Having them in one package eliminates
|
|
cross-package qualification or redundant ~:import-from~
|
|
declarations when building combined layouts.
|
|
|
|
If either component grows substantial internal logic in the future
|
|
(say, ScrollBox develops virtual scrolling, infinite loading, or
|
|
its own input model), it could be split into its own package at
|
|
that point. The current scope favors simplicity and co-location.
|
|
|
|
* Package Definition
|
|
|
|
#+BEGIN_SRC lisp :tangle ~/.local/share/cl-tty/src/components/container-package.lisp
|
|
(defpackage :cl-tty.container
|
|
(:use :cl :cl-tty.backend :cl-tty.box :cl-tty.layout :cl-tty.input)
|
|
(:export
|
|
;; ScrollBox
|
|
#:scroll-box #:make-scroll-box
|
|
#:scroll-box-scroll-y #:scroll-box-scroll-x
|
|
#:scroll-box-children
|
|
#:scroll-by #:sticky-scroll-p
|
|
#:clamp-scroll
|
|
;; TabBar
|
|
#: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
|
|
;; Rendering
|
|
#:render))
|
|
#+END_SRC
|