iris: scrolling belongs to Scroll, and a LazySpan only lays out
Steps 2 and 3 of the plan in docs/IRIS_TODO.md, together because
deleting the fling before `Scroll` could drive it would leave the app
unable to scroll at all. IRIS.md has the account and the measurements.
`LazySpan` loses its `Flinger`, its `density`, its
`Arc<dyn RequestRedraw>` -- which had no business existing in a
single-threaded frame loop -- its `tick`, and the whole
`fling`/`cancel_fling`/`tick_fling`/`is_scrolling`/`fling_velocity`
surface. `Scroll` was the only other `Flinger` user, so there is now one
implementation of the physics rather than two, and a transcript is
`list.scrollable_to_end()` like anything else.
Three new `Widget` methods carry the handoff:
fn scrolls_itself(&self) -> bool { false }
fn apply_scroll(&mut self, delta: &mut f32) {}
fn scroll_offset(&self) -> f32 { 0.0 }
`Scroll` asks the first, and a child that says yes is handed deltas
instead of being slid about as a lump -- which a lazy layout cannot be,
since which rows exist at all is a function of where it is scrolled to,
and it has no content length to be clamped against. `scrolls_itself` is
`&self` deliberately: `Widgets::get_dyn_mut` marks a widget dirty, so
asking through `apply_scroll` would dirty every ordinary child on every
tick and cost exactly the O(1) move the scheme exists for.
`Scroll::draw` is measure, apply, place -- the idiom it already used for
its own content length. The measuring draw is free in the common case
(unchanged region, nothing dirty, `draw_inner` returns immediately and
the child's stored walls are still correct) and really walks exactly
when the content changed. Nothing is marked by hand: reaching the child
to hand it the delta is what dirties it, which is why `draw_again` could
stay deleted.
`scroll_offset` was not in the plan and is needed. A lazy span usually
cannot say where its content ends until it has walked there, so it takes
a delta in full whenever the wall is not already in view and the walk
gives part of it back; the remainder is exact only when the wall was
already visible, and `Scroll` adding remainders up would over-count by
every overshoot and never correct. It reads the child's accumulated
movement after the placing draw instead, so `amt` equals what is on
screen. `amt_counts_only_what_the_child_could_take` is the test.
One convention for a scroll delta, the finger's. `Scroll::scroll(+)`
moved toward the start while `LazySpan::scroll(+)` moved toward the end,
with the latter's doc claiming to mirror the former -- so every call site
had to know which it was talking to. `LazySpan::scroll` is private now
and the single negation is inside its `apply_scroll`; call sites that
passed `-dy`/`-v` pass them through, and `phone_screen.rs`'s recorded
velocity flips sign with its magnitude unchanged.
`a_negative_delta_moves_toward_the_end` pins the sign across the whole
handoff, since nothing else can catch a list scrolling backwards.
The transcript builds its `Scroll` by hand rather than through
`.scrollable_to_end()`: that helper registers a finger drag, and
`Selection` is already the arbiter for those frames -- two `DragGesture`s
seeing one gesture is what its own doc rules out. Caught by
`a_long_press_and_drag_selects_text`, which failed when both were live.
Deferred, in DECISIONS.md and IRIS_TODO.md: the *pin* is still each
widget's own. Applying one happens when a row is appended, between
frames with no painter in hand, so moving it to `Scroll` needs a fourth
`Widget` method or a parameter on `apply_scroll`; nothing external edits
a pin today.
Verified: cargo fmt --check, clippy --workspace --all-targets clean,
cargo test --workspace green (21 suites), the arm64 release APK builds,
and the phone-shaped headless window replaying flick-120hz.touch scrolls
back through the transcript in the direction it did before.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8e5928cc6a
commit
b7474f61b0
19 files changed
+1016
-611
No files matched your search
@@ -5,6 +5,34 @@ they can be judged and reversed later. Detail lives in RUST.md (and IRIS.md
|
||||
for iris API changes); this file is only the summary. Newest first. Items
|
||||
marked **DEFERRED** are ones the agent chose not to decide alone.
|
||||
|
||||
## 2026-09-08 (last: scrolling moves out of the list)
|
||||
|
||||
Agreed with Iris in the exchange that followed, so most of this is her
|
||||
call rather than mine. IRIS.md has the account. What I decided along the
|
||||
way, and would flag for reversal:
|
||||
|
||||
- **A third `Widget` method, `scroll_offset`**, beyond the two we agreed.
|
||||
`apply_scroll`'s remainder is exact only when the wall was already in
|
||||
view, and a lazy span usually cannot see its wall until it has walked
|
||||
there -- so `Scroll` reads the child's accumulated movement after the
|
||||
placing draw instead of adding remainders up, which would drift.
|
||||
- **One scroll-delta convention, the finger's.** The two widgets had
|
||||
opposite ones under the same name; `LazySpan::scroll` is now private and
|
||||
the single negation lives in its `apply_scroll`. Call sites that passed
|
||||
`-dy`/`-v` pass them straight through, and one fixture's expected
|
||||
velocity flipped sign with its magnitude unchanged.
|
||||
- **The transcript builds its `Scroll` by hand rather than through
|
||||
`.scrollable_to_end()`**, because that helper registers a finger drag
|
||||
and `Selection` is already the arbiter for those frames -- two
|
||||
`DragGesture`s seeing one gesture is what `DragGesture`'s own doc rules
|
||||
out. The wheel is registered identically; only the drag differs.
|
||||
- **DEFERRED: the pin stays in each widget.** Iris asked for `amt` and
|
||||
the at-end control to live in `Scroll`; `amt` does, the pin does not,
|
||||
because applying a pin happens when a row is appended -- between frames,
|
||||
with no painter -- so moving it needs a fourth `Widget` method or a
|
||||
parameter on `apply_scroll`. Nothing external edits a pin today.
|
||||
docs/IRIS_TODO.md carries it.
|
||||
|
||||
## 2026-09-08 (later still: the list's overscroll clamp, in frame)
|
||||
|
||||
Finishes the item the previous entry deferred. IRIS.md has the account.
|
||||
|
||||
Reference in new issue
Block a user