Bordered, scrollable, single-selection data table for TUI dialogs.
Composes the lower-level table border/row primitives with
scrollbar so callers don't repeat the same boilerplate (top
border + header row + middle separator + N body rows + selection
marker gutter + scrollbar) at every dialog site.
Given a dialog bounds ({:left :inner-w}) the layout reserves:
col left+1 → table │ left border (flush to dialog edge)
col left+3 → selection marker, INSIDE the first column
cols left+1 .. R-1 → boxed table (own │ borders included)
col R → scrollbar (right of right table border)
where R = left + inner-w. This guarantees the scrollbar never
overpaints the table's right │ border.
The selection marker lives INSIDE the first column: the first data
column is internally widened by p/SELECTION_WIDTH, its cell text
is indented by that many cols, and the marker glyph is painted over
the reserved gutter (matching the Ctrl+G navigator). Callers size
their columns against the reported :table-content-w, which already
excludes the marker reserve — no caller-side change needed.
:top)top+0 ┌── top border ──┐
top+1 │ header row │
top+2 ├── separator ──┤
top+3 │ first body row │
…
top+2+body-h │ last body row │
top+3+body-h └── bottom ──┘ (only when :closed? true)
By default the table is open at the bottom; callers usually follow
the body with a divider + status line and don't want a closing
└┘ cap. Pass :closed? true to draw the closing border (matches
the navigator-style boxed picker).
Bordered, scrollable, single-selection data table for TUI dialogs.
Composes the lower-level `table` border/row primitives with
`scrollbar` so callers don't repeat the same boilerplate (top
border + header row + middle separator + N body rows + selection
marker gutter + scrollbar) at every dialog site.
Geometry rules
--------------
Given a dialog `bounds` ({:left :inner-w}) the layout reserves:
col `left+1` → table `│` left border (flush to dialog edge)
col `left+3` → selection marker, INSIDE the first column
cols `left+1 .. R-1` → boxed table (own `│` borders included)
col `R` → scrollbar (right of right table border)
where `R = left + inner-w`. This guarantees the scrollbar never
overpaints the table's right `│` border.
The selection marker lives INSIDE the first column: the first data
column is internally widened by `p/SELECTION_WIDTH`, its cell text
is indented by that many cols, and the marker glyph is painted over
the reserved gutter (matching the Ctrl+G navigator). Callers size
their columns against the reported `:table-content-w`, which already
excludes the marker reserve — no caller-side change needed.
Row layout (relative to caller-provided `:top`)
-----------------------------------------------
top+0 ┌── top border ──┐
top+1 │ header row │
top+2 ├── separator ──┤
top+3 │ first body row │
…
top+2+body-h │ last body row │
top+3+body-h └── bottom ──┘ (only when `:closed? true`)
By default the table is open at the bottom; callers usually follow
the body with a divider + status line and don't want a closing
`└┘` cap. Pass `:closed? true` to draw the closing border (matches
the navigator-style boxed picker).(draw! g
{:keys [bounds top body-h headers widths total scroll selected cell-fn
empty-cells empty-message aligns closed?]
:or {empty-message "No items." aligns (repeat :left) closed? false}})Render a bordered scrollable table in one call.
Positional arg matches scrollbar/draw!:
^TextGraphics g — the live dialog graphics surface.
Required opts:
:bounds {:left :inner-w} (from draw-dialog-chrome!)
:top first row to paint (top border lands here)
:body-h visible body rows
:headers vector of header strings (count must match :widths)
:widths per-column widths in cells (from caller's sizing fn)
:total total row count in the data set
:scroll current scroll offset (0-based row index)
:selected current selection (0-based row index)
:cell-fn (fn [idx] -> seq of strings) for row at absolute idx
Optional opts:
:empty-cells per-column cells painted when total = 0; defaults to
blanks with :empty-message in column 1
:empty-message string shown in the default empty row
:aligns per-column alignment vec (default (repeat :left))
:closed? when true, paint a closing └──┘ border one row
below the last body row (default false)
Returns the full layout map (merge of layout + rows) so callers
can place mode lines, hint bars, hit-tests, etc.
Render a bordered scrollable table in one call.
Positional arg matches `scrollbar/draw!`:
`^TextGraphics g` — the live dialog graphics surface.
Required opts:
:bounds {:left :inner-w} (from `draw-dialog-chrome!`)
:top first row to paint (top border lands here)
:body-h visible body rows
:headers vector of header strings (count must match :widths)
:widths per-column widths in cells (from caller's sizing fn)
:total total row count in the data set
:scroll current scroll offset (0-based row index)
:selected current selection (0-based row index)
:cell-fn (fn [idx] -> seq of strings) for row at absolute idx
Optional opts:
:empty-cells per-column cells painted when total = 0; defaults to
blanks with `:empty-message` in column 1
:empty-message string shown in the default empty row
:aligns per-column alignment vec (default `(repeat :left)`)
:closed? when true, paint a closing `└──┘` border one row
below the last body row (default false)
Returns the full layout map (merge of `layout` + `rows`) so callers
can place mode lines, hint bars, hit-tests, etc.(hit-row {:keys [table-x rendered-w table-content-w]} top body-h scroll mx my)Map a mouse (mx, my) into a body row index, or nil if outside the
table body. geom is (layout bounds), top and body-h match the
draw! call, scroll is the current scroll offset.
Map a mouse `(mx, my)` into a body row index, or nil if outside the table body. `geom` is `(layout bounds)`, `top` and `body-h` match the `draw!` call, `scroll` is the current scroll offset.
(layout {:keys [left inner-w]})Compute table geometry inside a dialog bounds ({:left :inner-w}).
Pure: no drawing. Useful for mouse hit-testing before draw!.
Compute table geometry inside a dialog `bounds` ({:left :inner-w}).
Pure: no drawing. Useful for mouse hit-testing before `draw!`.(rows top body-h)Resolve the absolute row indices for each painted line, given the
caller-supplied top and body-h.
Resolve the absolute row indices for each painted line, given the caller-supplied `top` and `body-h`.
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 |