iris: a widget's move slot has one owner -- move_applied + repositioned
`mov` accumulates a delta onto the slot and `reposition` overwrote it, and both legitimately land on one widget in one frame: `List::place`'s Bottom-known branch offers a row a same-size box that has moved (`mov`), then corrects the placement inside it when the row's cached height no longer matches what the row reports (`reposition`). That is what a wrapped transcript row hit, and what the `move_applied == ZERO` debug assert was standing in for -- an assert against a case that happens is not a guarantee, it is a crash. The slot means `move_applied + repositioned` now, both halves recorded on `ActiveData`, so `reposition` adds the move rather than dropping it and stays idempotent. The assert it replaces is a `debug_assert_eq!` that the slot still holds that sum on entry -- i.e. that nothing but those two ever wrote it. Test: `a_widget_moved_by_its_parent_and_then_placed_inside_it_lands_at_the_placement`, which draws the child at the offered position (-100px) rather than the placement (100px) without the fix. Verified against the `.wrap(true)` repro from docs/IRIS_TODO.md (draws correctly, no panic) and an emulator bench run with assertions live.
This commit is contained in:
1 parent
3cb18ac5c2
commit
f5b88932b4
4 files changed
+155
-18
No files matched your search
@@ -53,4 +53,17 @@ pub struct ActiveData {
|
||||
/// after a finger pan (2026-09-06). Reset to zero whenever the widget
|
||||
/// is really redrawn, since `draw_inner` zeroes the slot then too.
|
||||
pub move_applied: Vec2,
|
||||
/// The offset the last `Painter::reposition` placed this widget's
|
||||
/// content at *within* `region`, in window pixels. The move slot has
|
||||
/// exactly one owner and one meaning:
|
||||
/// `move_offsets[move_slot] == move_applied + repositioned`. `mov`
|
||||
/// adds to the first, `reposition` overwrites the second (it
|
||||
/// recomputes `from` afresh every call, so repeating it must land on
|
||||
/// the same answer rather than drifting), and both then rewrite the
|
||||
/// slot from the sum -- which is what lets a parent both move a child
|
||||
/// with its own layout and place it inside that moved region in one
|
||||
/// frame. `List::place`'s Bottom-known branch does exactly that once a
|
||||
/// row's blocks wrap. Reset to zero on a real redraw, with
|
||||
/// `move_applied` and the slot itself.
|
||||
pub repositioned: Vec2,
|
||||
}
|
||||
@@ -380,6 +380,7 @@ impl UiRenderState {
|
||||
move_slot,
|
||||
own_mask,
|
||||
move_applied: Vec2::ZERO,
|
||||
repositioned: Vec2::ZERO,
|
||||
};
|
||||
|
||||
// remove old children that weren't kept
|
||||
@@ -442,12 +443,8 @@ impl UiRenderState {
|
||||
let Some(active) = self.active.get(&id) else {
|
||||
return;
|
||||
};
|
||||
debug_assert!(
|
||||
active.move_applied == Vec2::ZERO,
|
||||
"widget {id:?} is both moved by its parent's own layout (`mov`) and repositioned \
|
||||
within it; the two write the same slot with different conventions -- see \
|
||||
`ActiveData::move_applied`"
|
||||
);
|
||||
let move_applied = active.move_applied;
|
||||
let repositioned = active.repositioned;
|
||||
let from = active
|
||||
.size
|
||||
.to_uivec2(self.density)
|
||||
@@ -457,8 +454,27 @@ impl UiRenderState {
|
||||
let from_px = from.top_left().to_abs(self.output_size);
|
||||
let to_px = to.top_left().to_abs(self.output_size);
|
||||
let delta = to_px - from_px;
|
||||
// Not `delta` alone: a parent may have `mov`ed this widget to a
|
||||
// region that itself moved earlier in the same frame, and that
|
||||
// part of the slot is `move_applied`'s, not this call's. Writing
|
||||
// `delta` on its own dropped it and put the content back at the
|
||||
// pre-move position. `from` is computed against `active.region`,
|
||||
// which `mov` already updated, so `delta` is purely the placement
|
||||
// inside the region and the two summands never overlap.
|
||||
let entry = rsc.ui_mut().move_offsets.get_mut(slot);
|
||||
entry.delta = [delta.x, delta.y];
|
||||
debug_assert_eq!(
|
||||
entry.delta,
|
||||
[
|
||||
move_applied.x + repositioned.x,
|
||||
move_applied.y + repositioned.y
|
||||
],
|
||||
"widget {id:?}'s move slot was written by something other than `mov`/`reposition`; \
|
||||
the slot is theirs and means `move_applied + repositioned` -- see `ActiveData`"
|
||||
);
|
||||
entry.delta = [move_applied.x + delta.x, move_applied.y + delta.y];
|
||||
if let Some(active) = self.active.get_mut(&id) {
|
||||
active.repositioned = delta;
|
||||
}
|
||||
self.mov_count += 1;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user