Prune commentary and stale Rust port notes

This commit is contained in:
iris committed 2026-09-10 00:44:13 -04:00
1 parent 5428cd75c9
commit 25370731d0
193 files changed
+693 -16219

No files matched your search

+19 -81
View File
@@ -1,8 +1,6 @@
# Scrolling in iris
How anything in iris scrolls, as of 2026-09-09. This is the current
design, not a history — the git log has the account of
how it got here, and `docs/IRIS_TODO.md` has what is still open.
This is the current scrolling design; `docs/IRIS_TODO.md` holds open work.
Read this before touching `iris/src/widget/position/scrollable.rs`,
`scroll_area.rs`, `lazy_span.rs`, or anything that pans, flings or lays
@@ -29,13 +27,8 @@ Two widgets have one, and they differ only in how they spend a delta:
`.scrollable()` registers the same two senses against the controller it
already has.
Do not give a widget its own fling, its own scroll amount, or a
`RequestRedraw` handle. And do not add a scrolling method to the `Widget`
trait: the three that used to be there (`scrolls_itself`, `apply_scroll`,
`scroll_offset`) existed only so a `Scroll` could drive a `LazySpan` it
had no business wrapping, and they are gone (Iris, 2026-09-08: "I don't
like adding methods to widget, it seems like we can structure things
better instead").
Do not give a widget its own fling, scroll amount, or `RequestRedraw`
handle, and do not add scrolling methods to the general `Widget` trait.
## One convention for a delta
@@ -45,25 +38,9 @@ positive delta — the finger's direction — and that is `Scroll::scroll`'s
sign, `Scroll::fling`'s, and `Widget::apply_scroll`'s, from the gesture
all the way down to a row's anchor.
**It is a screen direction, not a logical one** (Iris, 2026-09-08:
"positive should always scroll up / left, and negative down / right ...
that way it always works as the user would expect"). The earlier wording
— "positive brings *earlier* content into view" — is true only of a span
laid out forwards: a `Dir::UP` list's earlier content is *below*, so the
same delta panned it the opposite way from every other scrollable in
iris. `LazySpan::flip_delta` is the conversion into the walk's own
direction-relative space, the exact counterpart of `flip_pos` for
positions, and its `scroll` (private) is the only thing that speaks that
space.
There used to be two public conventions under the same name, and every
call site had to know which widget it was talking to. If you add a third
scrolling thing, it takes this one. Two tests pin it, and neither is
redundant: `a_negative_delta_moves_toward_the_end` follows the sign
across the whole handoff, and `a_delta_moves_both_directions_the_same_
way_on_screen` checks the two `dir`s against **where rows were drawn**
an assertion written in the walk's own space passes with the flip
deleted, because it checks the bookkeeping against itself.
It is a screen direction, not a logical content direction. A `Dir::UP`
span's earlier content is below, so `LazySpan::flip_delta` converts public
screen-space deltas into the walk's direction-relative space.
## The contract between a controller and its owner
@@ -98,8 +75,7 @@ on the clock its own velocity was measured on. Frames are presented on an
even cadence whatever clock they are computed on, so sampling the spline
at "whenever the callback got to run" moves the content unevenly between
frames that are shown evenly -- a shimmer that no frame-time percentile
can see, since no frame was late. Found 2026-09-09; docs/RUST.md's
"The fling stutter" has the rest.
can see, since no frame was late. `docs/RUST.md` records the measurements.
### Why a remainder was not enough
@@ -152,8 +128,6 @@ can say how far it may go, so nothing above it is in a position to.
### Why it is not a `Span` inside a `ScrollArea`
Measured 2026-09-08, and worth not re-deriving:
- A `Span` is skipped entirely in the steady state. When redrawn, it uses
exact hints first, draws unknown fixed children forward from the cursor,
and places retained drawings after flexible allocation. A child is
@@ -179,14 +153,9 @@ A transcript is `Dir::DOWN` (oldest message is item 0, at the top) with
`Pin::End` (the view sits at the bottom). Conflating the two would stand
it on its head.
**`Pin` says it either way round**, because there are two questions and
they are not the same one (Iris, 2026-09-08). `Start`/`End` are
content-relative — the first row or the newest one, wherever the layout
puts it — and `Neg`/`Pos` are axis-absolute: the top/left edge and the
bottom/right one, whichever end of the content is there. They coincide for
everything except a reversed `LazySpan`, where they are exact opposites,
which is the whole reason both exist. The one question a scrollable acts
on is `pinned_to_end`, and `dir` is what resolves a `Pin` into it.
`Start`/`End` are content-relative; `Neg`/`Pos` are axis-absolute. They
diverge for a reversed `LazySpan`. A scrollable acts on `pinned_to_end`,
with `dir` resolving the chosen `Pin`.
### Two coordinate spaces, two conversion points
@@ -217,8 +186,8 @@ framework:
old-children diff calls `remove_rec`, and the `ActiveData` — with its
`size` — is freed. The framework's copy is gone for precisely the rows
the walk has to pass through without drawing.
2. **A widget may one day render in two places at once** (Iris,
2026-09-08), so anything keyed by `WidgetId` alone that describes where
2. **A widget may render in two places at once**, so anything keyed by
`WidgetId` alone that describes where
or how big a widget was drawn will be wrong then. Where and how big
belongs to the owner that placed it.
@@ -242,8 +211,8 @@ inside the same frame**. `moved_by` counts that correction along with the
move that caused it, which is why `amt` stays equal to what is on screen
rather than drifting by every overshoot.
Layout is a pure function of the state, not of how many frames have been
drawn (Iris, 2026-09-08). A correction that lands next frame is a frame
Layout is a pure function of state, not of how many frames have been
drawn. A correction that lands next frame is a frame
drawn wrong, and there may be no next frame — a fling that stopped is not
asking for one.
@@ -305,40 +274,9 @@ cannot pan; there is a `debug_assert` in `drag` naming that.
rebased after 65,536 pixels to preserve `f32` precision, a rare O(visible)
move-slot pass rather than steady-state work.
## Tests that pin the behaviour
## Verification
In `lazy_span.rs`, all of these fail if the corresponding piece is undone:
- `a_negative_delta_moves_toward_the_end` — the sign, end to end.
- `a_delta_moves_both_directions_the_same_way_on_screen` — the sign is a
screen direction, checked against where rows were *drawn*.
- `amt_counts_only_what_the_child_could_take` — why the owner reports what
it did rather than the caller adding up what it asked for.
- `a_fling_stops_at_the_first_row`,
`scrolling_past_the_start_lands_on_it_in_the_same_frame` — the walls,
with no settling frame drawn on purpose.
- `a_dir_up_span_grows_upward_from_item_zero`,
`a_reversed_span_hit_tests_in_screen_space` — the position conversions
(`flip_pos`), as `a_delta_moves_both_directions_the_same_way_on_screen`
is the delta one (`flip_delta`).
- `a_registered_fling_is_driven_by_tick_animations_and_then_unregisters`
a fling that nothing registers never moves, whatever its velocity.
In `app-rust/tests/` (layer 1, no window or GPU):
- `top_edge.rs`'s `scrolling_past_the_first_row_settles_on_it` /
`scrolling_past_the_last_row_settles_on_it` — both ends, no settling
frame.
- `phone_screen.rs`'s `a_recorded_flick_releases_with_a_velocity_and_
flings_the_list` — the velocity against
`benches/velocity_reference.py`'s number, and the fling's travel against
`benches/fling_spline_reference.py`'s.
- `phone_screen.rs`'s `a_long_press_and_drag_selects_text` — what caught
two `DragGesture`s fighting over the transcript.
- `catch_a_fling.rs`, `gesture_cancel.rs`, `fence_fling.rs` — press-catches
a coasting area, cancels, and a code fence panning sideways
independently of the transcript.
`docs/RUST.md`'s "Three test layers" says which layer answers what. Test
at the cheapest one that can answer the question; the emulator is for JNI,
the IME, insets and one verification run, not for iterating on layout.
The unit and headless integration tests exercise direction, both walls,
reversed hit-testing, fling registration, cancellation, nested horizontal
pans, and transcript selection. `docs/RUST.md` defines the three test layers;
use the cheapest layer that can observe the behavior under test.