Single source of truth for vertical scrollbars in the TUI.
One namespace owns geometry, drawing, hit-test, wheel handling and click/drag → scroll math. EVERY scrollbar — chat messages, slash menu, settings, file picker, provider/model cards, sessions, every other dialog — goes through here. No more bespoke thumb math per call site, no more drift between painter and hit-test layers.
Terminal.app renders stacked U+2588 FULL BLOCK cells as a ladder of separate boxes when the window is tall, which reads as multiple scroll positions instead of one current-position marker. So the visible thumb is intentionally one row high; the thumb's row index inside the track encodes the scroll fraction.
Single source of truth for vertical scrollbars in the TUI. One namespace owns geometry, drawing, hit-test, wheel handling and click/drag → scroll math. EVERY scrollbar — chat messages, slash menu, settings, file picker, provider/model cards, sessions, every other dialog — goes through here. No more bespoke thumb math per call site, no more drift between painter and hit-test layers. Why one cell tall thumbs ------------------------ Terminal.app renders stacked U+2588 FULL BLOCK cells as a ladder of separate boxes when the window is tall, which reads as multiple scroll positions instead of one current-position marker. So the visible thumb is intentionally one row high; the thumb's row index inside the track encodes the scroll fraction.
(draw! g
{:keys [col top track-h total-h inner-h scroll track-fg track-bg thumb-fg
thumb-bg]
:or {track-fg t/dialog-border
track-bg t/dialog-bg
thumb-fg t/dialog-hint-key
thumb-bg t/dialog-bg}})Paint a vertical scrollbar.
Required opts: :col :top :track-h :total-h :inner-h :scroll.
Optional opts: :track-fg :track-bg :thumb-fg :thumb-bg
(default = dialog palette, used by every modal scrollbar).
Returns the geometry map (or nil when no overflow, in which case nothing is painted).
Paint a vertical scrollbar. Required opts: `:col :top :track-h :total-h :inner-h :scroll`. Optional opts: `:track-fg :track-bg :thumb-fg :thumb-bg` (default = dialog palette, used by every modal scrollbar). Returns the geometry map (or nil when no overflow, in which case nothing is painted).
(geometry total-h inner-h scroll)(geometry total-h inner-h track-h scroll)Compute thumb geometry for a vertical scrollbar.
Arguments
total-h total content size (rows OR item count — same math
applies to row-pixels and item-windows).inner-h visible viewport size in the same unit as total-h.track-h optional track length in screen rows; defaults to
inner-h. The track may extend past the viewport
(e.g. into top/bottom breathing-room rows).scroll current scroll offset; nil ⇒ pinned to bottom
(max-scroll). Out-of-range values are clamped.Returns {:thumb-top-rel :thumb-h :max-scroll :track-h} when the
content overflows the viewport, otherwise nil (no thumb should be
drawn and every click hit-test below should return off-thumb).
Compute thumb geometry for a vertical scrollbar.
Arguments
- `total-h` total content size (rows OR item count — same math
applies to row-pixels and item-windows).
- `inner-h` visible viewport size in the same unit as `total-h`.
- `track-h` optional track length in screen rows; defaults to
`inner-h`. The track may extend past the viewport
(e.g. into top/bottom breathing-room rows).
- `scroll` current scroll offset; `nil` ⇒ pinned to bottom
(`max-scroll`). Out-of-range values are clamped.
Returns `{:thumb-top-rel :thumb-h :max-scroll :track-h}` when the
content overflows the viewport, otherwise `nil` (no thumb should be
drawn and every click hit-test below should return off-thumb).(on-thumb? mx
my
{:keys [col top x-band] :or {x-band 1}}
{:keys [thumb-top-rel thumb-h]})True when (mx,my) lands on the thumb. bar carries :col :top [:x-band]; geom is the result of geometry.
True when (mx,my) lands on the thumb. `bar` carries `:col :top [:x-band]`; `geom` is the result of `geometry`.
(on-track? mx my {:keys [col top track-h x-band] :or {x-band 1}})True when (mx,my) falls inside the scrollbar column at any track row.
bar is {:col :top :track-h [:x-band]}. :x-band widens the
x-axis tolerance (default 1) — chat messages use 3 so users don't
need pixel-perfect aim on the right gutter.
True when (mx,my) falls inside the scrollbar column at any track row.
`bar` is `{:col :top :track-h [:x-band]}`. `:x-band` widens the
x-axis tolerance (default 1) — chat messages use 3 so users don't
need pixel-perfect aim on the right gutter.(scroll-from-mouse-y mouse-y top track-h total-h inner-h)(scroll-from-mouse-y mouse-y top track-h total-h inner-h grip-offset)Convert a mouse Y on the track into a clamped scroll value.
mouse-y absolute row the cursor is attop track's first-row screen Ytrack-h track lengthtotal-h total content sizeinner-h viewport sizegrip-offset rows between thumb top and the user's grip-point;
pass the offset saved at CLICK_DOWN so dragging
preserves the grip (so the row under the finger
stays glued to the same point on the thumb). Use
0 for click-to-position behaviour.Convert a mouse Y on the track into a clamped scroll value.
- `mouse-y` absolute row the cursor is at
- `top` track's first-row screen Y
- `track-h` track length
- `total-h` total content size
- `inner-h` viewport size
- `grip-offset` rows between thumb top and the user's grip-point;
pass the offset saved at CLICK_DOWN so dragging
preserves the grip (so the row under the finger
stays glued to the same point on the thumb). Use
`0` for click-to-position behaviour.(wheel-delta event)Return -1 for SCROLL_UP, +1 for SCROLL_DOWN, else nil. Accepts a Lanterna KeyStroke or MouseAction; non-mouse events return nil.
Return -1 for SCROLL_UP, +1 for SCROLL_DOWN, else nil. Accepts a Lanterna KeyStroke or MouseAction; non-mouse events return nil.
(wheel-step event)Wheel delta multiplied by the coalesced event count carried in
MouseAction#getButton. Lanterna stuffs the coalesced count into
the button field when an upstream input loop drains a wheel flood,
so one mouse tick can move multiple rows.
Wheel delta multiplied by the coalesced event count carried in `MouseAction#getButton`. Lanterna stuffs the coalesced count into the button field when an upstream input loop drains a wheel flood, so one mouse tick can move multiple rows.
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 |