sparkles:ui
A canvas-first, backend-neutral UI toolkit — the shared visual language behind the twoslash overlay across the raylib GUI (hue --gui), the interactive terminal TUI (hue --twoslash), and HTML. One widget tree, one palette, four backends: the --twoslash-* chrome that was triplicated across CSS, hand-copied raylib literals, and ANSI SGR now traces to a single source here.
The pipeline is view() → layout() → buildDisplayList() → paint(canvas); every stage before paint is @safe and GL-free (the pure model is fully unit-testable through a RecordingCanvas). A widget names a semantic Slot, never a concrete color — the Palette resolves it to a Visual during display-list construction.
Modules (libs/ui/src/sparkles/ui/)
| Module | Role |
|---|---|
geometry | Point/Size (specializing sparkles:math's Vector, like TermSize/TermPosition), Rect/Insets in abstract cells; SizeSpec; cellsOf — the one width authority |
style | Slot, the resolved Visual (color + border/radius/shadow/font), authoring Decoration/TextStyle, Palette, defaultTwoslashPalette, resolveSlot/resolveVisual |
canvas | the DbI isCanvas!T capability concept (not an interface), DrawOp, the @safe RecordingCanvas |
widget | the flat-arena Widget (currently a tagged record; WGT3 targets a closed sum) with explicit uint[] child-index lists + Builder |
layout | the two-pass box-flow layout (row/column/stack/panel/popup) |
state | presentation-free interaction machines for hover, scroll, selection, disclosure, focus, activation and pointer capture |
display_list | buildDisplayList — resolves each node's slot + decoration + text style into DrawOps |
interp/immediate | paint(canvas, ops) — the immediate-mode replay (attributes inferred from the canvas) |
interp/cells | a retained cell-grid interpreter (diffed minimal updates) |
interp/html | the widget → semantic HTML + inline CSS emitter — the parity ground-truth oracle (see below) |
The concrete canvases are sibling adapters that depend on sparkles:ui: RaylibCanvas in sparkles:ui-raylib and GridCanvas in sparkles:ui-tui.
The two-direction parity harness
Visual parity is attacked from two directions, each machine-assisted:
Do the widget settings match the CSS? The
Paletteauthors the canonical twoslash colors and scalar chrome (border widths, radius, shadow geometry, font scales, arrow) once. Thestyle.twoslashCss.paletteLockstepandmetricsLocksteptests (insparkles:twoslash) assert those values equal the ones inviews/twoslash.css— each expected token built from the D value, so drift on either side fails the build.Are the widget settings rendered correctly?
interp/htmlrenders the same widget tree the GUI/TUI paint to a self-contained HTML page; a browser then establishes the ground truth for what the widget spec should look like. Thecapture-modesQA tool (apps/hue/tools/capture-modes.d,widgets-htmlmode) screenshots it headlessly, so the raylib and terminal rasters can be compared against a browser's rendering of their own spec — and the generatedwidgets-htmlagainst the hand-authoredhtml(render_html) mode.
dub run --single apps/hue/tools/capture-modes.d -- --out /tmp/parity --hover 0Backend degradations (honest, documented)
The abstract model expresses sub-cell chrome; a cell grid cannot, so the GridCanvas (TUI) approximates and drops what it can't draw:
- a bottom-only border (the
.twoslash-hoverdotted underline) → a dotted/single cell underline; - a full box border (the popup) → box-drawing glyphs on the popup's blank 1-cell padding ring, with rounded corners (
╭╮╰╯) approximatingborderRadiusand a┴notch for the arrow; - a single-side sub-cell accent (the docs top divider, the error/tag left bar), the corner radius, the drop shadow, and the underline fade alpha have no cell analog and are dropped — the block's background tint still conveys it.
The GUI honors all of the above except FontRole/fontScale: the fixed-size cell grid keeps monospace at 1em (so popup docs render mono, not sans). HTML honors everything.
See also
sparkles:twoslash— the overlay this renders; hosts therender_widgetsview (viewTwoslash/viewHoverPopup) and the CSS lockstep tests.sparkles:syntax— theRgbColor/Color/theme layer reused here (the library adds no color type).