iris: the composer scrolls on a finger -- a dp cap worth zero, a stale mask slot, a hit box moved twice
Wrapping the composer's field in .scrollable().masked() needed three layout defects fixed first, each with a headless regression test that was confirmed to fail without its fix: - MaxSize/Sized reported a caller's declared dp length unresolved, and Span places a child from the abs/rel of what it reported, so dp(168) was worth zero: the bar got a slot of nothing the moment its content passed six lines and the Scroll inside measured its container at -63px (container=-63 content=415.8 amt=478.8 on the emulator). Len::fold_dp, used on the way out, plus a debug_assert in draw_inner that a reported Size carries no dp -- the rule is about every widget, not those two. - Masked allocated a fresh mask slot per draw, and draw_inner's unchanged-region fast path does not revisit descendants, so they kept clipping against a box the bar had moved away from: four live mask entries, none of them current, and the field drew nothing. ActiveData::own_mask, allocated once and rewritten in place. - mov updates active.region and accumulates the same delta on the move slot, and resolved_region added both, so a panned widget's own hit box sat at twice the pan -- the composer's field was untappable after a drag. ActiveData::move_applied. Scroll itself measured the right number by a misleading route; it is written against painter.px_size() now and still reports its content's size, since reporting the container makes the answer a function of itself. Verified on this checkout's emulator: swipe 540 1200 -> 540 1460 moved the field's Message box 31,1041..1048,1509 -> 31,1131..1048,1651 with its height unchanged at 468px. run-bench.sh polled logcat for a prefix copy_report also logs at startup, so it printed a report that had never been run. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
d73db97629
commit
167862ca1b
14 files changed
+567
-39
No files matched your search
@@ -8,6 +8,50 @@ 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: a reported `Size` may not carry `dp`; `Len::fold_dp`
|
||||
|
||||
**New: `Len::fold_dp(density) -> Len`** -- the same fold `apply_rest` does
|
||||
(`dp` becomes physical pixels), but staying a `Len` so `rest` survives.
|
||||
|
||||
**New rule, and it is a rule about every widget, not about the two that
|
||||
broke it**: a `Len` a widget *reports* from `draw` must not carry an
|
||||
unresolved `dp`. `dp` is an input unit -- a number the widget author wrote
|
||||
-- and the containers that consume a reported length read `abs`, `rel` and
|
||||
`rest` straight off it (`Span`'s placement arithmetic, `Pad`'s addition),
|
||||
so a reported `dp` is silently worth **zero**. `MaxSize` and `Sized` both
|
||||
returned the caller's declared `Len` as written; a `.max_height(dp(168))`
|
||||
therefore gave its child a slot of nothing the moment the cap actually
|
||||
applied, which is what made the composer's bar collapse. Both put their
|
||||
declared lengths through `fold_dp` now, and
|
||||
`UiRenderState::draw_inner` `debug_assert!`s the invariant after every
|
||||
`Widget::draw`, so a widget that gets this wrong says so at the mistake
|
||||
rather than laying out at zero somewhere else.
|
||||
|
||||
Nothing changes for a caller: `.max_height(dp(48))` is written the same
|
||||
way. It is only widget *authors* who now have a rule to follow, and a
|
||||
debug build that enforces it.
|
||||
|
||||
## 2026-09-06: `Painter::set_mask` reuses one slot; `ActiveData` gains two fields
|
||||
|
||||
**`Painter::set_mask(region)` allocates its widget's mask slot once and
|
||||
rewrites it in place** on every later draw, instead of pushing a new one
|
||||
each time. It has to: `draw_inner`'s unchanged-region fast path does not
|
||||
revisit a descendant whose own region did not change, so those descendants
|
||||
go on referencing whichever slot they were first drawn under. Pushing a
|
||||
fresh slot per draw left the composer's field clipped to a box the bar had
|
||||
long since moved away from -- four live mask entries, none of them the
|
||||
`Masked`'s current region -- and it drew nothing at all. Same call, same
|
||||
signature; only the lifetime changed.
|
||||
|
||||
**`ActiveData` gains `own_mask` and `move_applied`** (both public, since
|
||||
`ActiveData` is). `own_mask` is the slot above, `MaskIdx::NONE` for a
|
||||
widget that sets no mask. `move_applied` is how much of a widget's own
|
||||
move-slot delta its `region` already accounts for: `mov` shifts both,
|
||||
`Painter::reposition` shifts only the slot, and `resolved_region` -- and so
|
||||
every hit test -- has to subtract it. Without that a widget that had been
|
||||
panned had its *own* hit box at twice the pan while its descendants were
|
||||
correct, which made the composer's field untappable after a finger drag.
|
||||
|
||||
## 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
|
||||
|
||||
Reference in new issue
Block a user