Record which widget is drawing a subtree that changed hands
A subtree can be reused whole under a different parent -- same box, same layer, same region node, clean -- and nothing in the drawing says it moved. Two things read who its parent is, and both were wrong after one of these. The old parent still listed it as a child, and a parent's next draw undraws whatever is missing from that list: two spans under one root, with the root swapping which of them it holds, drew the subtree under the new span and then erased it when the old one drew. The move is recorded on both sides where `draw_inner` already writes what the ask decided, rather than guarded at each reader. Its depth was also the one it had under the old parent, which is what the settling walk orders by, so a change made under it afterwards settled at the wrong point in the frame. `try_reuse` re-walks the subtree's depths, and only where the top of it moved, which is what makes that free in the ordinary case. Two tests: one shape where the subtree's box does not move and the span it left erases it, one where it changes depth and the change made under it has to reach the span it moved to. Each fails without one half.
This commit is contained in:
1 parent
a0693acc56
commit
e44dea34b4
3 files changed
+161
-19
No files matched your search
@@ -74,4 +74,12 @@ impl ActiveData {
|
|||||||
pub fn holds_at(&self, px: crate::PxVec2) -> bool {
|
pub fn holds_at(&self, px: crate::PxVec2) -> bool {
|
||||||
self.holds[0].contains(px.x) && self.holds[1].contains(px.y)
|
self.holds[0].contains(px.x) && self.holds[1].contains(px.y)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether what it answered still stands for a box of these pixel
|
||||||
|
/// lengths -- the box it was asked in, where `holds` is about the box its
|
||||||
|
/// answer then chose.
|
||||||
|
pub fn answers_at(&self, px: crate::PxVec2) -> bool {
|
||||||
|
let (_, holds) = self.answer;
|
||||||
|
holds[0].contains(px.x) && holds[1].contains(px.y)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+44
-19
@@ -87,12 +87,11 @@ impl UiRenderState {
|
|||||||
/// retained entry at all.
|
/// retained entry at all.
|
||||||
///
|
///
|
||||||
/// The root is the only widget a resize marks, and only where the new
|
/// The root is the only widget a resize marks, and only where the new
|
||||||
/// output is outside what its answer holds for. Its range is the
|
/// output falls outside what its answer holds for: that range is the
|
||||||
/// intersection of everything under it, so admitting the new output says
|
/// intersection of everything under it, so admitting the new output says
|
||||||
/// the whole tree still stands -- and nothing above the root moved, the
|
/// the whole tree still stands. Where it does not, the ordinary walk
|
||||||
/// window being no entry to rewrite. Where it does not admit it, the
|
/// draws the root, and each widget's own range decides how far down the
|
||||||
/// ordinary bottom-up walk draws the root and `Holds` decides, per widget
|
/// new length reaches.
|
||||||
/// and as the walk reaches it, how far down the new length reaches.
|
|
||||||
pub fn resize(&mut self, size: impl Into<Vec2>, widgets: &mut Widgets) {
|
pub fn resize(&mut self, size: impl Into<Vec2>, widgets: &mut Widgets) {
|
||||||
let size = PxVec2::from_f32(size.into());
|
let size = PxVec2::from_f32(size.into());
|
||||||
if size == self.output_size {
|
if size == self.output_size {
|
||||||
@@ -101,12 +100,11 @@ impl UiRenderState {
|
|||||||
self.output_size = size;
|
self.output_size = size;
|
||||||
self.resized = true;
|
self.resized = true;
|
||||||
let Some(root) = self.old_root else { return };
|
let Some(root) = self.old_root else { return };
|
||||||
let Some(active) = self.active.get(&root) else {
|
let stands = self
|
||||||
return;
|
.active
|
||||||
};
|
.get(&root)
|
||||||
let px = active.given_len.to_px(size);
|
.is_some_and(|active| active.answers_at(active.given_len.to_px(size)));
|
||||||
let holds = active.answer.1;
|
if !stands {
|
||||||
if !(holds[0].contains(px.x) && holds[1].contains(px.y)) {
|
|
||||||
widgets.needs_redraw.insert(root);
|
widgets.needs_redraw.insert(root);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -204,10 +202,9 @@ impl UiRenderState {
|
|||||||
diag::draw_request(id, info.parent, region, info.px, info.region_node);
|
diag::draw_request(id, info.parent, region, info.px, info.region_node);
|
||||||
}
|
}
|
||||||
let align = rsc.widgets().alignment(id);
|
let align = rsc.widgets().alignment(id);
|
||||||
// Only the widget's own mark is asked about. Nothing it measured can
|
// Nothing this widget measured can be dirty while it draws: layout is
|
||||||
// be dirty while it draws: layout is one bottom-up walk, so anything
|
// one bottom-up walk, so anything deeper has settled or deferred to
|
||||||
// deeper has already settled or deferred to its own parent, and a
|
// its own parent, and a deferred one leaves that parent marked.
|
||||||
// deferred one leaves that parent marked.
|
|
||||||
let stale = rsc.widgets().needs_redraw.contains(&id);
|
let stale = rsc.widgets().needs_redraw.contains(&id);
|
||||||
let replace_answer = self.answer_invalid.remove(&id) || (self.replace_answers && stale);
|
let replace_answer = self.answer_invalid.remove(&id) || (self.replace_answers && stale);
|
||||||
let retained = match replace_answer || stale {
|
let retained = match replace_answer || stale {
|
||||||
@@ -256,7 +253,18 @@ impl UiRenderState {
|
|||||||
active.answer = settled;
|
active.answer = settled;
|
||||||
active.decided = info.decided;
|
active.decided = info.decided;
|
||||||
active.own_align = align;
|
active.own_align = align;
|
||||||
active.depth = info.depth;
|
// A subtree can be reused whole under a different parent -- same box,
|
||||||
|
// same layer, same region node -- and nothing in the drawing says it
|
||||||
|
// changed hands. Two things read who its parent is: a deferral, which
|
||||||
|
// marks whoever has it to draw, and the old parent's list of children,
|
||||||
|
// which its next draw undraws whatever is missing from.
|
||||||
|
let old_parent = std::mem::replace(&mut active.parent, info.parent);
|
||||||
|
if old_parent != info.parent
|
||||||
|
&& let Some(old_parent) = old_parent
|
||||||
|
&& let Some(old_parent) = self.active.get_mut(&old_parent)
|
||||||
|
{
|
||||||
|
old_parent.children.retain(|child| *child != id);
|
||||||
|
}
|
||||||
settled
|
settled
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -514,8 +522,7 @@ impl UiRenderState {
|
|||||||
{
|
{
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let (size, holds) = active.answer;
|
active.answers_at(info.px).then_some(active.answer)
|
||||||
(holds[0].contains(info.px.x) && holds[1].contains(info.px.y)).then_some((size, holds))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The pixel lengths of the box a widget was given and of the box it was
|
/// The pixel lengths of the box a widget was given and of the box it was
|
||||||
@@ -629,12 +636,12 @@ impl UiRenderState {
|
|||||||
self.remap_subtree(id, &remap, info.parent_move, rsc);
|
self.remap_subtree(id, &remap, info.parent_move, rsc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.redepth(id, info.depth);
|
||||||
let active = self.active.get_mut(&id).unwrap();
|
let active = self.active.get_mut(&id).unwrap();
|
||||||
active.region = region;
|
active.region = region;
|
||||||
active.given = region;
|
active.given = region;
|
||||||
active.given_len = info.given_len;
|
active.given_len = info.given_len;
|
||||||
active.offer_len = info.offer_len;
|
active.offer_len = info.offer_len;
|
||||||
active.depth = info.depth;
|
|
||||||
#[cfg(feature = "layout-diagnostics")]
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
{
|
{
|
||||||
match (moved, has_region_node) {
|
match (moved, has_region_node) {
|
||||||
@@ -658,6 +665,24 @@ impl UiRenderState {
|
|||||||
Some(answer)
|
Some(answer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A reused subtree keeps its shape, so every widget in it moves by the
|
||||||
|
/// same amount -- and where the top of it did not move, none of it did,
|
||||||
|
/// which is what makes this free in the ordinary case.
|
||||||
|
fn redepth(&mut self, id: WidgetId, depth: usize) {
|
||||||
|
let Some(active) = self.active.get_mut(&id) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if active.depth == depth {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
active.depth = depth;
|
||||||
|
let children = active.children.len();
|
||||||
|
for index in 0..children {
|
||||||
|
let child = self.active[&id].children[index];
|
||||||
|
self.redepth(child, depth + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Re-expresses an ordinary retained subtree in a new parent region.
|
/// Re-expresses an ordinary retained subtree in a new parent region.
|
||||||
/// An independently movable descendant needs only its own region changed;
|
/// An independently movable descendant needs only its own region changed;
|
||||||
/// its contents stay in that region's coordinate space.
|
/// its contents stay in that region's coordinate space.
|
||||||
|
|||||||
@@ -628,3 +628,112 @@ fn a_masked_widget_redrawn_on_its_own_sets_its_mask_again() {
|
|||||||
h.frame();
|
h.frame();
|
||||||
assert_corners!(h, inner, (100, 0), (400, 200));
|
assert_corners!(h, inner, (100, 0), (400, 200));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The two spans a subtree changes hands between, and the branch that is not
|
||||||
|
/// in the tree yet -- kept alive by the test until it is.
|
||||||
|
struct Handover {
|
||||||
|
leaf: WidgetId,
|
||||||
|
first: WeakWidget<Span>,
|
||||||
|
second: WeakWidget<Span>,
|
||||||
|
root: WeakWidget<Span>,
|
||||||
|
spare: StrongWidget,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A subtree that changes hands while its box does not move, so nothing about
|
||||||
|
/// reusing its drawing says it changed parents. `deeper` puts a span between
|
||||||
|
/// the root and `second`, so it changes depth by changing hands as well.
|
||||||
|
fn plant_handover(h: &mut Harness, moved: bool, deeper: bool, width: f32) -> Handover {
|
||||||
|
let leaf = rect(Color::RED).add(&mut h.rsc);
|
||||||
|
let sized = leaf.width(width).add(&mut h.rsc);
|
||||||
|
let holder = (sized,).span(Dir::RIGHT).add(&mut h.rsc);
|
||||||
|
let first = Span {
|
||||||
|
children: match moved {
|
||||||
|
true => Vec::new(),
|
||||||
|
false => vec![holder.add_strong(&mut h.rsc)],
|
||||||
|
},
|
||||||
|
dir: Dir::RIGHT,
|
||||||
|
gap: Px::ZERO,
|
||||||
|
}
|
||||||
|
.add(&mut h.rsc);
|
||||||
|
let second = Span {
|
||||||
|
children: match moved {
|
||||||
|
true => vec![holder.add_strong(&mut h.rsc)],
|
||||||
|
false => Vec::new(),
|
||||||
|
},
|
||||||
|
dir: Dir::RIGHT,
|
||||||
|
gap: Px::ZERO,
|
||||||
|
}
|
||||||
|
.add(&mut h.rsc);
|
||||||
|
let branch = match deeper {
|
||||||
|
true => (second,).span(Dir::RIGHT).add_strong(&mut h.rsc),
|
||||||
|
false => second.add_strong(&mut h.rsc),
|
||||||
|
};
|
||||||
|
let (in_tree, spare) = match moved {
|
||||||
|
true => (branch, first.add_strong(&mut h.rsc)),
|
||||||
|
false => (first.add_strong(&mut h.rsc), branch),
|
||||||
|
};
|
||||||
|
let root = Span {
|
||||||
|
children: vec![in_tree],
|
||||||
|
dir: Dir::RIGHT,
|
||||||
|
gap: Px::ZERO,
|
||||||
|
}
|
||||||
|
.add(&mut h.rsc);
|
||||||
|
h.state.root = Some(root.add_strong(&mut h.rsc));
|
||||||
|
Handover {
|
||||||
|
leaf: sized.id(),
|
||||||
|
first,
|
||||||
|
second,
|
||||||
|
root,
|
||||||
|
spare,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Moves the subtree and swaps the branch it sits in for the one it left.
|
||||||
|
fn hand_over(h: &mut Harness, tree: Handover) -> WidgetId {
|
||||||
|
let holder = h.rsc[tree.first].children.remove(0);
|
||||||
|
h.rsc[tree.second].children.push(holder);
|
||||||
|
h.rsc[tree.root].children.clear();
|
||||||
|
h.rsc[tree.root].children.push(tree.spare);
|
||||||
|
h.frame();
|
||||||
|
tree.leaf
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_subtree_that_changed_parents_is_not_undrawn_by_the_one_it_left() {
|
||||||
|
let mut warm = Harness::new((400, 200));
|
||||||
|
let tree = plant_handover(&mut warm, false, false, 40.0);
|
||||||
|
warm.frame();
|
||||||
|
let leaf = hand_over(&mut warm, tree);
|
||||||
|
|
||||||
|
let mut cold = Harness::new((400, 200));
|
||||||
|
let grown = plant_handover(&mut cold, true, false, 40.0);
|
||||||
|
cold.frame();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
warm.region(&leaf),
|
||||||
|
cold.region(&grown.leaf),
|
||||||
|
"the span it left still listed it and undrew it"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_subtree_that_changed_parents_settles_at_the_depth_it_moved_to() {
|
||||||
|
let mut warm = Harness::new((400, 200));
|
||||||
|
let tree = plant_handover(&mut warm, false, true, 40.0);
|
||||||
|
warm.frame();
|
||||||
|
let leaf = hand_over(&mut warm, tree);
|
||||||
|
// After it has changed hands, so what has to reach the new parent is a
|
||||||
|
// change made under the subtree it now holds.
|
||||||
|
warm.set_len(leaf, Axis::X, LayoutLen::px(90.0));
|
||||||
|
warm.frame();
|
||||||
|
|
||||||
|
let mut cold = Harness::new((400, 200));
|
||||||
|
let grown = plant_handover(&mut cold, true, true, 90.0);
|
||||||
|
cold.frame();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
warm.region(&leaf),
|
||||||
|
cold.region(&grown.leaf),
|
||||||
|
"the span it moved to is the one the change has to reach"
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user