Tiny declarative layout layer — the React/Ink flavour for the TUI.
Instead of threading col/row/width by hand (the (reduce (fn [x …] (+ x (widget! g x row …))) start items) pattern repeated across the header,
footer, tab strip and dialogs), you describe a UI as a tree of measured
NODES and let render! place them.
A NODE is a map {:w <cols> :h <rows> :paint (fn [g col row])}. Every
builder returns a node, and row/col are themselves nodes, so layouts
NEST like React elements:
(flex/render! g x y
(flex/row [(flex/text " help " {:bold? true})
(flex/node w #(button! %1 %2 %3 …))] ; bridge existing widgets
{:gap 1 :align :right :width slot-w}))
The bridge node wraps any existing foo! paint-thunk (which already knows
its own width and click registration), so nothing about the drawing or hover
contract changes — only the coordinate arithmetic disappears.
Tiny declarative layout layer — the React/Ink flavour for the TUI.
Instead of threading `col`/`row`/width by hand (the `(reduce (fn [x …] (+ x
(widget! g x row …))) start items)` pattern repeated across the header,
footer, tab strip and dialogs), you describe a UI as a tree of measured
NODES and let `render!` place them.
A NODE is a map `{:w <cols> :h <rows> :paint (fn [g col row])}`. Every
builder returns a node, and `row`/`col` are themselves nodes, so layouts
NEST like React elements:
(flex/render! g x y
(flex/row [(flex/text " help " {:bold? true})
(flex/node w #(button! %1 %2 %3 …))] ; bridge existing widgets
{:gap 1 :align :right :width slot-w}))
The bridge `node` wraps any existing `foo!` paint-thunk (which already knows
its own width and click registration), so nothing about the drawing or hover
contract changes — only the coordinate arithmetic disappears.(col children)(col children {:keys [gap] :or {gap 0}})Vertical box: stack children top→bottom, each advanced by its own
height, with :gap blank rows between them. Width is the widest child.
Vertical box: stack `children` top→bottom, each advanced by its own height, with `:gap` blank rows between them. Width is the widest child.
(node w paint)(node w h paint)Wrap an explicit size + paint thunk as a layout node. paint is
(fn [g col row]); its return value is ignored (the node's width is w).
Height defaults to 1 (single row) — pass h for multi-row widgets.
Wrap an explicit size + paint thunk as a layout node. `paint` is `(fn [g col row])`; its return value is ignored (the node's width is `w`). Height defaults to 1 (single row) — pass `h` for multi-row widgets.
(render! g col row n)Paint node n at absolute cell (col,row) and return its width. Accepts a
bare string (lifted to a text node) or nil (paints nothing, returns 0).
Paint node `n` at absolute cell (col,row) and return its width. Accepts a bare string (lifted to a `text` node) or nil (paints nothing, returns 0).
(row children)(row children {:keys [gap align] fixed-w :width :or {gap 0 align :left}})Horizontal box: lay children out left→right with :gap blank cells
between them. children are nodes (bare strings are lifted to text
nodes; nils are dropped). Returns a NODE whose intrinsic width is the sum
of child widths plus gaps and whose height is the tallest child.
With :width the row occupies EXACTLY that many cells and :align
(:left | :center | :right, default :left) positions the child
cluster within the slack.
Horizontal box: lay `children` out left→right with `:gap` blank cells between them. `children` are nodes (bare strings are lifted to `text` nodes; nils are dropped). Returns a NODE whose intrinsic width is the sum of child widths plus gaps and whose height is the tallest child. With `:width` the row occupies EXACTLY that many cells and `:align` (`:left` | `:center` | `:right`, default `:left`) positions the child cluster within the slack.
(spacer w)(spacer w h)A fixed-size empty node — a horizontal (or vertical) gap that paints nothing.
A fixed-size empty node — a horizontal (or vertical) gap that paints nothing.
(text s)(text s {:keys [fg bg bold?]})A one-row text node. Paints s in optional :fg/:bg with optional
:bold?. Width is the string's DISPLAY width, so wide glyphs measure right.
A one-row text node. Paints `s` in optional `:fg`/`:bg` with optional `:bold?`. Width is the string's DISPLAY width, so wide glyphs measure right.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |