iris: Scroll pans on a finger drag; a vertical drag in a focused field scrolls rather than selects
IRIS_TODO.md's "the composer has no touch-drag scroll". `Scroll::drag`
takes its pan from the same `sense::DragGesture` `List` is driven by --
arbitration, DRAG_SLOP, velocity and pointer capture all stay in sense.rs
and only what a committed pan *means* is decided per caller -- and
`WidgetLike::scrollable()` registers it beside the wheel handler it already
registered, so every scroll area pans on a finger with nothing added at the
call site. No fling: `Scroll` has no per-frame tick to animate one and the
areas it wraps are at most a screenful. `Scroll::amt()` exposes the pan
position.
`attr.rs`'s `on_press` treated an already-focused field as the plain
click_or_drag case, so every Pressing frame extended a selection. It now
applies the same DRAG_SLOP rule its unfocused branch already did: a press
past the slop vertically abandons its pending selection for the rest of the
gesture, so the scroll area around the field wins it. That is Android
EditText's own behaviour and it is what lets a swipe up over the composer
scroll instead of dragging a highlight through what you typed.
Also fixed, found doing it: `ActiveData::mask` stored the mask a widget
*set* rather than the one it was drawn *under*, and `redraw` feeds that
field back in as the inherited mask -- so a targeted redraw of any `Masked`
handed it its own mask and aborted on `set_mask`'s nested-mask assert. A
real abort on the emulator, `assertion failed: self.mask == MaskIdx::NONE`.
And the per-frame orphan guard from 76b1f99 is now a count comparison
(O(active widgets)); the O(primitives) walk only runs to build the failure
message, because running it per frame made a debug build on the emulator too
slow to finish a bench run at all.
Tests: four in scroll.rs (pan past the slop, a tap inside it, a horizontal
drag, the end clamp), `a_finger_drag_over_a_scroll_area_pans_it` in
sense_tests.rs driving the whole registration/dispatch/capture path (fails
with "got 0" without the new registration), and
`redrawing_a_masked_widget_does_not_nest_its_own_mask` in layout_tests.rs
(aborts on the pre-fix code).
The composer itself is deliberately still not `.scrollable()`: `Scroll`
measures against the window rather than its own offered box, so inside the
`MaxSize` capping it at six lines it pans the field out of the bar --
measured, reverted and written down in RUST.md and DECISIONS.md.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
76b1f99277
commit
fb6b459c2c
13 files changed
+608
-31
No files matched your search
@@ -8,6 +8,48 @@ capability that moved. Small and trivial changes do not go here.
|
||||
An entry gives the date, what changed, why, and a short before/after where
|
||||
it helps judge the change without the session that made it. Newest first.
|
||||
|
||||
## 2026-09-06: `Scroll` pans on a finger drag, and a vertical drag in a focused text field no longer selects
|
||||
|
||||
Three related public changes, all in aid of IRIS_TODO.md's "the composer
|
||||
has no touch-drag scroll".
|
||||
|
||||
**`Scroll::drag(render, id, sense, pos_window, now)` is new**, and
|
||||
`WidgetLike::scrollable()` now registers it alongside the wheel handler it
|
||||
already registered -- so anything built with `.scrollable()` pans on a
|
||||
finger drag with no extra wiring at the call site. It goes through the same
|
||||
`sense::DragGesture` that `transcript-ui::Selection::drag` drives `List`
|
||||
with (arbitration, `DRAG_SLOP`, velocity, pointer capture), rather than a
|
||||
second copy of that widget's wiring: `DragGesture` owns the mechanics and
|
||||
each caller decides only what a committed pan *means*. `Scroll::amt()` is
|
||||
new too, the read-only pan position a test or a scroll indicator needs.
|
||||
|
||||
There is deliberately **no fling** on `Scroll`. Unlike `List` it has no
|
||||
per-frame tick to animate one with (`List::set_redraw_handle`/`tick_fling`),
|
||||
and the areas it wraps today are at most a screenful, where Android does not
|
||||
fling either. The released velocity is dropped rather than approximated.
|
||||
|
||||
**A vertical drag inside an already-focused `TextEdit` no longer extends a
|
||||
selection.** `iris::attr`'s `on_press` used to treat a focused field as the
|
||||
plain `click_or_drag` case -- every `Pressing` frame updated the selection.
|
||||
It now applies the same `DRAG_SLOP` rule the *unfocused* branch already
|
||||
applied: a press that moves past the slop vertically abandons its pending
|
||||
selection for the rest of the gesture, so the scroll area around the field
|
||||
gets the drag instead. Horizontal drag-to-select is unchanged, and a long
|
||||
press still starts a selection. This is Android's own `EditText` behaviour
|
||||
(a vertical drag scrolls; only a long press selects), and it is what makes
|
||||
"swipe up over the composer to scroll the transcript" work without dragging
|
||||
a highlight through the message you were typing.
|
||||
|
||||
**`UiRenderState::orphaned_primitives()` is new**, and `update` now
|
||||
`debug_assert!`s (debug builds only) that nothing is orphaned. An orphan is
|
||||
a primitive still bound for the GPU that no live `ActiveData` names -- a
|
||||
copy nothing can move, clip or free. That was the doubled `Compacted:` row
|
||||
on the phone; see the same date's commit `76b1f99` and docs/RUST.md. The
|
||||
per-frame guard is a count comparison (O(active widgets)); the walk that
|
||||
names the offenders only runs when the counts disagree, because the walk is
|
||||
O(primitives) and made a debug build on a phone too slow to finish a
|
||||
benchmark run.
|
||||
|
||||
## 2026-09-06: a tap on a text field always leaves a caret
|
||||
|
||||
`TextEditCtx::select` used to compare the tap position against the
|
||||
|
||||
Reference in new issue
Block a user