Liking cljdoc? Tell your friends :D

com.blockether.vis.ext.channel-tui.scroll

Messages-area scroll state — one tagged value, no scattered flags.

The old model smeared scroll across four fields with two different storage scopes (:messages-scroll workspace-local; the animation target + two intent booleans top-level), plus a nil-vs-concrete overload on :messages-scroll. Every handler had to hand-maintain their consistency, and forgetting one dissoc produced the "flash to the top, then scroll to the bottom" jump.

This namespace replaces all of it with ONE workspace-local value, :scroll, that is a tagged variant:

{:mode :follow} ;; stick to the bottom (auto-track) {:mode :follow, :pos p} ;; …easing down toward the bottom {:mode :at, :offset n} ;; parked at row n (user scrolled up) {:mode :at, :offset n, :pos p} ;; …easing toward row n

Two orthogonal concerns, cleanly separated:

  • INTENT — :mode (+ :offset). :follow means "desired = bottom"; :at means "desired = offset". The user's scroll-up intent is just :at — no separate :scroll-pinned-up? flag.
  • DISPLAY — :pos, the eased on-screen row. Present only while an animation is in flight (and held at the bottom in :follow so the NEXT content growth has somewhere to ease FROM instead of teleporting). Absent ⇒ snapped exactly to desired.

Every transition REPLACES the whole value, so nothing can dangle. ease is called once per render frame; in :follow mode the desired row simply IS the growing bottom, so streaming content eases in for free — there is no special "animated follow" path any more.

Pure: no re-frame, no atoms. state.clj events are thin wrappers and screen.clj reads layout-offset / animating? to drive the loop.

Messages-area scroll state — one tagged value, no scattered flags.

The old model smeared scroll across four fields with two different
storage scopes (`:messages-scroll` workspace-local; the animation
target + two intent booleans top-level), plus a `nil`-vs-concrete
overload on `:messages-scroll`. Every handler had to hand-maintain
their consistency, and forgetting one `dissoc` produced the
"flash to the top, then scroll to the bottom" jump.

This namespace replaces all of it with ONE workspace-local value,
`:scroll`, that is a tagged variant:

  {:mode :follow}                ;; stick to the bottom (auto-track)
  {:mode :follow, :pos p}        ;;   …easing down toward the bottom
  {:mode :at, :offset n}         ;; parked at row n (user scrolled up)
  {:mode :at, :offset n, :pos p} ;;   …easing toward row n

Two orthogonal concerns, cleanly separated:

- INTENT  — `:mode` (+ `:offset`). `:follow` means "desired = bottom";
  `:at` means "desired = offset". The user's scroll-up intent is just
  `:at` — no separate `:scroll-pinned-up?` flag.
- DISPLAY — `:pos`, the eased on-screen row. Present only while an
  animation is in flight (and held at the bottom in `:follow` so the
  NEXT content growth has somewhere to ease FROM instead of
  teleporting). Absent ⇒ snapped exactly to desired.

Every transition REPLACES the whole value, so nothing can dangle.
`ease` is called once per render frame; in `:follow` mode the desired
row simply IS the growing bottom, so streaming content eases in for
free — there is no special "animated follow" path any more.

Pure: no re-frame, no atoms. `state.clj` events are thin wrappers and
`screen.clj` reads `layout-offset` / `animating?` to drive the loop.
raw docstring

animating?clj

(animating? sc max-s)

True when an ease is still in flight (displayed ≠ desired). Drives the render loop's fast tick cadence; settles to false so an idle view stops repainting.

True when an ease is still in flight (displayed ≠ desired). Drives the
render loop's fast tick cadence; settles to false so an idle view
stops repainting.
sourceraw docstring

bottom-hidden?clj

(bottom-hidden? sc max-s)

True when the live bottom sits BELOW the current viewport — i.e. there is content to jump DOWN to. FALSE when following at the bottom, AND false when nothing overflows (max-s 0: an empty or short session). The jump-to-bottom affordance keys off this, NOT scrolled-up? — parking :at offset 0 in an empty session is still 'at the bottom', so the chip must stay hidden.

True when the live bottom sits BELOW the current viewport — i.e. there is
content to jump DOWN to. FALSE when following at the bottom, AND false when
nothing overflows (`max-s` 0: an empty or short session). The jump-to-bottom
affordance keys off this, NOT `scrolled-up?` — parking `:at` offset 0 in an
empty session is still 'at the bottom', so the chip must stay hidden.
sourceraw docstring

decay-wheel-momentumclj

(decay-wheel-momentum momentum)

Step the running momentum toward zero for one IDLE poll (no wheel event). Returns 0 once small enough that the directional lock should release.

Step the running `momentum` toward zero for one IDLE poll (no wheel event).
Returns 0 once small enough that the directional lock should release.
sourceraw docstring

desiredclj

(desired sc max-s)

The row the view WANTS to show: bottom in :follow, the clamped offset in :at.

The row the view WANTS to show: bottom in `:follow`, the clamped
offset in `:at`.
sourceraw docstring

displayedclj

(displayed sc max-s)

The row currently ON SCREEN: the eased :pos if mid-animation, else desired.

The row currently ON SCREEN: the eased `:pos` if mid-animation, else
`desired`.
sourceraw docstring

downclj

(down sc amount max-s)

Wheel/key scroll DOWN by amount. Landing within slack-rows of the bottom re-arms FOLLOW (and eases the rest of the way); otherwise parks amount rows below the COMMITTED offset.

As with up, the target anchors to the committed :offset (or the live bottom while FOLLOWING) so an alternating momentum stream can't walk the viewport backward off a half-eased position.

Wheel/key scroll DOWN by `amount`. Landing within `slack-rows` of the
bottom re-arms FOLLOW (and eases the rest of the way); otherwise parks
`amount` rows below the COMMITTED offset.

As with `up`, the target anchors to the committed `:offset` (or the live
bottom while FOLLOWING) so an alternating momentum stream can't walk the
viewport backward off a half-eased position.
sourceraw docstring

easeclj

(ease sc max-s)

Advance the on-screen :pos one step toward desired. Called once per render frame.

  • Mid-ease ⇒ step :pos toward the target.
  • Settled while PARKED ⇒ drop :pos (clean snap; no further repaint).
  • Settled while FOLLOWING ⇒ KEEP :pos pinned at the bottom so the next content growth eases FROM here instead of teleporting.
Advance the on-screen `:pos` one step toward `desired`. Called once per
render frame.

- Mid-ease ⇒ step `:pos` toward the target.
- Settled while PARKED ⇒ drop `:pos` (clean snap; no further repaint).
- Settled while FOLLOWING ⇒ KEEP `:pos` pinned at the bottom so the
  next content growth eases FROM here instead of teleporting.
sourceraw docstring

followclj

The resting auto-bottom intent: track the latest message.

The resting auto-bottom intent: track the latest message.
sourceraw docstring

layout-offsetclj

(layout-offset sc max-s)

The offset to feed virtual/layout and the scrollbar geometry.

nil ⇒ auto-bottom (never anchored, always tracks the latest message — used when FOLLOW is settled at the bottom). A concrete row is returned while parked or mid-ease so the painter anchors / animates against it.

The offset to feed `virtual/layout` and the scrollbar geometry.

`nil` ⇒ auto-bottom (never anchored, always tracks the latest message
— used when FOLLOW is settled at the bottom). A concrete row is
returned while parked or mid-ease so the painter anchors / animates
against it.
sourceraw docstring

merge-wheel-deltaclj

(merge-wheel-delta momentum delta)

Smooth a single raw wheel delta (signed rows, +down / -up) into a running momentum, returning [new-momentum effective-delta].

This is the input-side debouncer that stops a slow macOS trackpad's sign-flipping inertia tail from bouncing the viewport. It is the discrete, phase-less analog of kitty's add_velocity (glfw/momentum-scroll.c) and WebKit's kinetic-scroll velocity accumulation:

  • SAME sign as the running momentum (or momentum at rest): accumulate, and the raw delta dispatches verbatim. A solo tap or a clean drag is unchanged.
  • OPPOSITE sign: the delta can only BRAKE the existing momentum toward zero, never cross it. A small opposing tick is absorbed (effective-delta nil) and just shaves momentum; only once the accumulated opposition exceeds the remaining momentum does the leftover re-seed in the new direction.

effective-delta is nil when the tick was fully absorbed — the caller drops it. new-momentum is what the caller stores back for the next poll.

Smooth a single raw wheel `delta` (signed rows, +down / -up) into a running
`momentum`, returning `[new-momentum effective-delta]`.

This is the input-side debouncer that stops a slow macOS trackpad's
sign-flipping inertia tail from bouncing the viewport. It is the discrete,
phase-less analog of kitty's `add_velocity` (glfw/momentum-scroll.c) and
WebKit's kinetic-scroll velocity accumulation:

- SAME sign as the running momentum (or momentum at rest): accumulate, and
  the raw delta dispatches verbatim. A solo tap or a clean drag is unchanged.
- OPPOSITE sign: the delta can only BRAKE the existing momentum toward zero,
  never cross it. A small opposing tick is absorbed (effective-delta nil) and
  just shaves momentum; only once the accumulated opposition exceeds the
  remaining momentum does the leftover re-seed in the new direction.

`effective-delta` is nil when the tick was fully absorbed — the caller drops
it. `new-momentum` is what the caller stores back for the next poll.
sourceraw docstring

momentum-capclj

Upper bound on the running wheel-momentum used by merge-wheel-delta. Caps how much a long, continuous same-direction drag can accumulate so a single stray opposing tick can't be absorbed forever (which would make the scroll feel sticky / unable to reverse). Chosen well above any realistic coalesced batch (amount is (* 3 |wheel-delta|)), so normal scrolling never clamps, but a frantic flail can't build unbounded inertia.

Upper bound on the running wheel-momentum used by `merge-wheel-delta`.
Caps how much a long, continuous same-direction drag can accumulate so a
single stray opposing tick can't be absorbed forever (which would make the
scroll feel sticky / unable to reverse). Chosen well above any realistic
coalesced batch (`amount` is `(* 3 |wheel-delta|)`), so normal scrolling
never clamps, but a frantic flail can't build unbounded inertia.
sourceraw docstring

momentum-idle-decayclj

Fraction of wheel-momentum retained per IDLE input poll (a poll with no wheel event). Halving each idle frame releases the directional lock within a few frames of the user lifting their fingers — so the NEXT gesture starts clean and a deliberate direction change dispatches immediately instead of being absorbed as 'opposition' to stale momentum. Mirrors kitty's 150ms sample window: momentum is only meaningful while events keep arriving.

Fraction of wheel-momentum retained per IDLE input poll (a poll with no wheel
event). Halving each idle frame releases the directional lock within a few
frames of the user lifting their fingers — so the NEXT gesture starts clean
and a deliberate direction change dispatches immediately instead of being
absorbed as 'opposition' to stale momentum. Mirrors kitty's 150ms sample
window: momentum is only meaningful while events keep arriving.
sourceraw docstring

parkedclj

(parked offset)

A concrete parked intent at row offset (snapped, no in-flight ease).

A concrete parked intent at row `offset` (snapped, no in-flight ease).
sourceraw docstring

reanchorclj

(reanchor sc anchored delta)

The layout corrected off-screen estimate→real heights and shifted the anchor by delta; anchored is the new absolute on-screen row. Shift the concrete fields so the anchored message stays visually put. Only meaningful while parked or mid-ease (FOLLOW-at-bottom feeds nil and never reaches here).

The layout corrected off-screen estimate→real heights and shifted the
anchor by `delta`; `anchored` is the new absolute on-screen row. Shift
the concrete fields so the anchored message stays visually put. Only
meaningful while parked or mid-ease (FOLLOW-at-bottom feeds `nil` and
never reaches here).
sourceraw docstring

scrolled-up?clj

(scrolled-up? sc)

True when the user has PARKED the viewport above the live bottom (:at intent) — reading history rather than following new content. The input cursor is hidden in this state so its terminal blink does not jump around as the transcript repaints during a scroll.

True when the user has PARKED the viewport above the live bottom
(`:at` intent) — reading history rather than following new content.
The input cursor is hidden in this state so its terminal blink does
not jump around as the transcript repaints during a scroll.
sourceraw docstring

slack-rowsclj

How close (in rows) to the bottom still counts as the bottom for the purpose of re-arming :follow. Scrolling DOWN to within this band sticks to the bottom again, so freshly streamed content keeps the latest message in view. Kept small so a deliberate read-up even a few rows above the live edge stays parked.

How close (in rows) to the bottom still counts as the bottom for the
purpose of re-arming `:follow`. Scrolling DOWN to within this band
sticks to the bottom again, so freshly streamed content keeps the
latest message in view. Kept small so a deliberate read-up even a
few rows above the live edge stays parked.
sourceraw docstring

step-fracclj

Ease-out fraction per animation step: bigger steps when far, smaller as we close in. The render loop ticks ease a few times a second.

Ease-out fraction per animation step: bigger steps when far, smaller
as we close in. The render loop ticks `ease` a few times a second.
sourceraw docstring

to-yclj

(to-y offset max-s)

Scrollbar drag/track click → offset (already mapped from cursor row). 1:1, so SNAP (no ease). The very bottom re-enters FOLLOW.

Scrollbar drag/track click → `offset` (already mapped from cursor row).
1:1, so SNAP (no ease). The very bottom re-enters FOLLOW.
sourceraw docstring

upclj

(up sc amount max-s)

Wheel/key scroll UP by amount: park amount rows above the current COMMITTED offset and ease there. Scrolling up is always a deliberate "let me read history" intent.

The target is anchored to the committed :offset (or the live bottom while FOLLOWING) — NOT to the eased display row. An alternating momentum stream (slow trackpad drags emit sign-flipping inertia tail events) would otherwise re-anchor each event off a half-eased position and ratchet the viewport backward, visibly fighting the user. :pos is seeded from the display row only when no ease is already in flight, so a new ease begins from where the view currently sits while a continuing ease keeps its origin.

Wheel/key scroll UP by `amount`: park `amount` rows above the current
COMMITTED offset and ease there. Scrolling up is always a deliberate
"let me read history" intent.

The target is anchored to the committed `:offset` (or the live bottom
while FOLLOWING) — NOT to the eased display row. An alternating momentum
stream (slow trackpad drags emit sign-flipping inertia tail events) would
otherwise re-anchor each event off a half-eased position and ratchet the
viewport backward, visibly fighting the user. `:pos` is seeded from the
display row only when no ease is already in flight, so a new ease begins
from where the view currently sits while a continuing ease keeps its
origin.
sourceraw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close