Hand a redrawn widget the mask it inherited, not its own

`ActiveData::mask` is the mask a widget's drawing is clipped to, which is
either one it set itself or the one it inherited. `redraw` passed it back as
the *inherited* mask, so a `Masked` widget settled on its own was handed its
own mask and `set_mask` asserted -- a panic on any local redraw of one, for
as long as there has been a local-redraw path. The two are separate facts, so
`parent_mask` keeps the second.

That also states the question `remap_subtree` was asking. It compared a
widget's mask with the one threaded down from its parent to find out whether
the widget owned it; the comparison is now between the two fields on the
widget, which is the same question asked where the answer lives, and the
parameter goes.

Checked: fmt, clippy, 87 suite tests including the new one, which panics
without this; 18 core unit tests; the release oracle at 100 seeds; the
fifteen shrinker cases at 400 seeds of depth 5; and `tabs` renders
byte-identical at 1920x1200.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
iris-aiandClaude Opus 5 committed 2026-09-17 00:18:39 -04:00
1 parent 32542d0c0b
commit ea6dbae0dc
3 files changed
+35 -14

No files matched your search

+13 -14
View File
@@ -437,6 +437,7 @@ impl UiRenderState {
move_idx,
parent_move: info.parent_move,
mask,
parent_mask: info.mask,
layer: info.layer,
};
rsc.on_draw(&active);
@@ -623,18 +624,14 @@ impl UiRenderState {
return None;
}
let moved = active.region != region;
let (answer, old_region, slot, mask) = (
(active.size, active.holds),
active.region,
active.move_idx,
info.mask,
);
let (answer, old_region, slot) =
((active.size, active.holds), active.region, active.move_idx);
if moved {
if has_region_node {
self.moves.set(slot, region);
} else {
let remap = RegionRemap::new(old_region, region)?;
self.remap_subtree(id, &remap, info.parent_move, mask, rsc);
self.remap_subtree(id, &remap, info.parent_move, rsc);
}
}
let active = self.active.get_mut(&id).unwrap();
@@ -674,7 +671,6 @@ impl UiRenderState {
id: WidgetId,
remap: &RegionRemap,
parent_move: MoveIdx,
inherited_mask: MaskIdx,
rsc: &mut dyn UiRsc,
) {
let active = self.active.get_mut(&id).unwrap();
@@ -690,16 +686,18 @@ impl UiRenderState {
*region = remap.apply(*region);
}
active.region = remap.apply(active.region);
let mask = active.mask;
let own_mask = (active.mask != active.parent_mask).then_some(active.mask);
let children = active.children.len();
if mask != inherited_mask && mask != MaskIdx::NONE {
let mask = rsc.ui_mut().masks.get_mut(mask);
// A mask the widget set itself moves with it; one it inherited
// belongs to the widget that set it, and moves there or not at all.
if let Some(idx) = own_mask {
let mask = rsc.ui_mut().masks.get_mut(idx);
debug_assert_eq!(mask.move_idx, parent_move);
mask.region = remap.apply(mask.region);
}
for index in 0..children {
let child = self.active[&id].children[index];
self.remap_subtree(child, remap, parent_move, mask, rsc);
self.remap_subtree(child, remap, parent_move, rsc);
}
}
@@ -793,6 +791,7 @@ impl UiRenderState {
own_align: rsc.widgets().alignment(id),
parent_move: info.parent_move,
mask: info.mask,
parent_mask: info.mask,
layer: info.layer,
},
);
@@ -963,7 +962,7 @@ impl UiRenderState {
let Some(parent) = active.parent else {
let region = Self::root_region(id, rsc.widgets());
let info = DrawInfo {
mask: active.mask,
mask: active.parent_mask,
..self.root_info(region)
};
#[cfg(feature = "layout-diagnostics")]
@@ -990,7 +989,7 @@ impl UiRenderState {
depth: active.depth,
parent_move: active.parent_move,
region_node: rsc.widgets().is_region_node(id),
mask: active.mask,
mask: active.parent_mask,
given_len: active.given_len,
offer_len: active.offer_len,
px: given_px,