Place a locally redrawn widget in its box, not just at its length

A dirty widget is asked again in the box its parent asked it in, and then
again in the box its parent chose from that answer. The second ask was
skipped whenever the two boxes were the same *length*, which is not the same
question: an offer as long as the final box but somewhere else is a different
box. `d3b0ebf` already compared whole boxes for `parent_must_place` and left
this one a length comparison, so the two halves of one decision disagreed.

It shows on a region node, which draws the box it drew in into its own move
entry. A scroll inside a scrolled span is offered the outer scroll's whole
viewport and placed 24px above it, the height of the sized child the outer
scroll snaps to the end of; redrawing only its text left it at the offer and
24px too low. `tests/cases/unsettled.rs` had that five-widget tree ignored as
a known defect and now runs it.

`px_region` names the walk both comparisons and `window_region` were writing
out.

Checked: fmt, clippy, 80 tests, the release oracle at 100 seeds, all fifteen
shrinker cases at 400 seeds of depth 5, and `tabs`, `view`, `minimal`,
`text`, `random` plus the tab replay byte-identical at 1920x1200 against
`98d4e98`. The `many` fixture's twenty-five counters are unchanged.

Fixed with it, from the handoff's unreduced leads: shrinker seeds 174 and 175
on `repaint-some` and seed 2 on `region-node`, and oracle seeds 18 and 190 at
depth 6. Still failing: shrinker seed 288 on `region-node`, and oracle seed
326 at depth 6, which reduces to 43 widgets around two `Branch`es and is not
this.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
iris-aiandClaude Opus 5 committed 2026-09-16 16:25:26 -04:00
1 parent 98d4e98a29
commit aea878d141
2 files changed
+32 -31

No files matched your search

+19 -18
View File
@@ -468,6 +468,12 @@ impl UiRenderState {
.to_px(self.output_size)
}
/// Where a region held in `slot`'s coordinates lands on screen, which is
/// the walk the vertex shader does.
fn px_region(&self, slot: MoveIdx, region: UiRegion) -> PixelRegion {
self.moves.resolve(slot, region).to_px(self.output_size)
}
/// A clean widget's retained answer, if that answer holds for a box of
/// `px`. This does not move its drawing, which may already be in the box
/// that answer placed it in.
@@ -903,15 +909,12 @@ impl UiRenderState {
}
/// Where a widget is on screen: its box composed through the boxes it
/// sits within, which is the walk the vertex shader does. `None` for one
/// that is not drawn.
/// sits within. `None` for one that is not drawn.
pub fn window_region(&self, id: &impl IdLike) -> Option<PixelRegion> {
let active = self.active.get(&id.id())?;
if !active.drawn {
return None;
}
let region = self.moves.resolve(active.parent_move, active.region);
Some(region.to_px(self.output_size))
active
.drawn
.then(|| self.px_region(active.parent_move, active.region))
}
/// Settles a dirty widget: asks it again where its parent asked, and
@@ -955,17 +958,15 @@ impl UiRenderState {
None => Self::root_region(id, rsc.widgets()),
};
let offered_px = self.px_of(active.parent_move, asked_in);
let at_offer = same_px(self.px_of(active.parent_move, region), offered_px);
let parent_must_place = active.parent.is_some()
&& (!region_node || active.align_override)
&& !same_pixel_region(
self.moves
.resolve(active.parent_move, region)
.to_px(self.output_size),
self.moves
.resolve(active.parent_move, asked_in)
.to_px(self.output_size),
);
// Whole boxes rather than lengths: an offer as long as the final box
// but somewhere else is a different box, and a region node drawing at
// its offer writes the box it drew in into its own entry.
let at_offer = same_pixel_region(
self.px_region(active.parent_move, region),
self.px_region(active.parent_move, asked_in),
);
let parent_must_place =
active.parent.is_some() && (!region_node || active.align_override) && !at_offer;
// An independently positioned region node can redraw at its offer
// and move its slot to its own placement. Every other widget needs
// its parent to reproduce a different final position.