1# llimphi-widget-scroll
2
3Reusable vertical scroll area.
4
5Until now every app rebuilt scrolling by hand: `App::on_wheel` + an offset in the
6`Model` + `clip` + virtualization. This widget packages that pattern into a single
7builder, **with no state of its own** (the offset still lives in the `Model`,
8true to the Elm loop):
9
10- a **clipped viewport** of fixed height (`viewport_len`),
11- **content displaced** by `-offset` px (overflow clipped),
12- a **draggable scrollbar** on the right (only when the content exceeds the
13 viewport),
14- a **self-contained wheel** through `View::on_scroll`: turning the wheel with
15 the cursor over the area emits a `Msg` without the app routing anything through
16 its global `on_wheel`.
17
18The caller must know the content's **total height** (`content_len`) and the
19**visible height** (`viewport_len`) — just as `list`/`grid` already ask for the
20visible window. For uniform rows that is `n_rows * row_height`.
21
22## The `on_scroll` callback convention
23
24`on_scroll` receives the **delta in px** to add to the offset (not the absolute
25offset): both the wheel and the scrollbar drag emit deltas, and the caller
26accumulates and clamps in its `update` with `clamp_offset`. It is the same idea
27as the `splitter` (the drag handler is reused for the whole drag, so a captured
28absolute offset would go stale; a delta per event is always correct).
29
30```ignore
31// view:
32scroll_y(
33 model.offset,
34 model.rows.len() as f32 * ROW_H,
35 panel_h,
36 list_view,
37 Msg::ScrollBy, // Fn(f32) -> Msg, arg = delta px
38 &ScrollPalette::default(),
39)
40// update:
41Msg::ScrollBy(d) => {
42 m.offset = clamp_offset(m.offset + d, content_len, viewport_len);
43}
44```
45
46To bring a selection into view (keyboard) see `ensure_visible`; for smooth or
47inertial scrolling see `approach`.
48
49---
50
51Part of **llimphi** — see [llimphi](../../README.md).
README.md
llimphi-widget-scroll
Reusable vertical scroll area.
Until now every app rebuilt scrolling by hand: App::on_wheel + an offset in the
Model + clip + virtualization. This widget packages that pattern into a single
builder, with no state of its own (the offset still lives in the Model,
true to the Elm loop):
- a clipped viewport of fixed height (
viewport_len),
- content displaced by
-offset px (overflow clipped),
- a draggable scrollbar on the right (only when the content exceeds the
viewport),
- a self-contained wheel through
View::on_scroll: turning the wheel with
the cursor over the area emits a Msg without the app routing anything through
its global on_wheel.
The caller must know the content's total height (content_len) and the
visible height (viewport_len) — just as list/grid already ask for the
visible window. For uniform rows that is n_rows * row_height.
The on_scroll callback convention
on_scroll receives the delta in px to add to the offset (not the absolute
offset): both the wheel and the scrollbar drag emit deltas, and the caller
accumulates and clamps in its update with clamp_offset. It is the same idea
as the splitter (the drag handler is reused for the whole drag, so a captured
absolute offset would go stale; a delta per event is always correct).
// view:
scroll_y(
model.offset,
model.rows.len() as f32 * ROW_H,
panel_h,
list_view,
Msg::ScrollBy, // Fn(f32) -> Msg, arg = delta px
&ScrollPalette::default(),
)
// update:
Msg::ScrollBy(d) => {
m.offset = clamp_offset(m.offset + d, content_len, viewport_len);
}
To bring a selection into view (keyboard) see ensure_visible; for smooth or
inertial scrolling see approach.
Part of llimphi — see llimphi.