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 {
|
||||
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.
|
||||
///
|
||||
/// 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
|
||||
/// the whole tree still stands -- and nothing above the root moved, the
|
||||
/// window being no entry to rewrite. Where it does not admit it, the
|
||||
/// ordinary bottom-up walk draws the root and `Holds` decides, per widget
|
||||
/// and as the walk reaches it, how far down the new length reaches.
|
||||
/// the whole tree still stands. Where it does not, the ordinary walk
|
||||
/// draws the root, and each widget's own range decides how far down the
|
||||
/// new length reaches.
|
||||
pub fn resize(&mut self, size: impl Into<Vec2>, widgets: &mut Widgets) {
|
||||
let size = PxVec2::from_f32(size.into());
|
||||
if size == self.output_size {
|
||||
@@ -101,12 +100,11 @@ impl UiRenderState {
|
||||
self.output_size = size;
|
||||
self.resized = true;
|
||||
let Some(root) = self.old_root else { return };
|
||||
let Some(active) = self.active.get(&root) else {
|
||||
return;
|
||||
};
|
||||
let px = active.given_len.to_px(size);
|
||||
let holds = active.answer.1;
|
||||
if !(holds[0].contains(px.x) && holds[1].contains(px.y)) {
|
||||
let stands = self
|
||||
.active
|
||||
.get(&root)
|
||||
.is_some_and(|active| active.answers_at(active.given_len.to_px(size)));
|
||||
if !stands {
|
||||
widgets.needs_redraw.insert(root);
|
||||
}
|
||||
}
|
||||
@@ -204,10 +202,9 @@ impl UiRenderState {
|
||||
diag::draw_request(id, info.parent, region, info.px, info.region_node);
|
||||
}
|
||||
let align = rsc.widgets().alignment(id);
|
||||
// Only the widget's own mark is asked about. Nothing it measured can
|
||||
// be dirty while it draws: layout is one bottom-up walk, so anything
|
||||
// deeper has already settled or deferred to its own parent, and a
|
||||
// deferred one leaves that parent marked.
|
||||
// Nothing this widget measured can be dirty while it draws: layout is
|
||||
// one bottom-up walk, so anything deeper has settled or deferred to
|
||||
// its own parent, and a deferred one leaves that parent marked.
|
||||
let stale = rsc.widgets().needs_redraw.contains(&id);
|
||||
let replace_answer = self.answer_invalid.remove(&id) || (self.replace_answers && stale);
|
||||
let retained = match replace_answer || stale {
|
||||
@@ -256,7 +253,18 @@ impl UiRenderState {
|
||||
active.answer = settled;
|
||||
active.decided = info.decided;
|
||||
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
|
||||
}
|
||||
|
||||
@@ -514,8 +522,7 @@ impl UiRenderState {
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let (size, holds) = active.answer;
|
||||
(holds[0].contains(info.px.x) && holds[1].contains(info.px.y)).then_some((size, holds))
|
||||
active.answers_at(info.px).then_some(active.answer)
|
||||
}
|
||||
|
||||
/// 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.redepth(id, info.depth);
|
||||
let active = self.active.get_mut(&id).unwrap();
|
||||
active.region = region;
|
||||
active.given = region;
|
||||
active.given_len = info.given_len;
|
||||
active.offer_len = info.offer_len;
|
||||
active.depth = info.depth;
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
{
|
||||
match (moved, has_region_node) {
|
||||
@@ -658,6 +665,24 @@ impl UiRenderState {
|
||||
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.
|
||||
/// An independently movable descendant needs only its own region changed;
|
||||
/// its contents stay in that region's coordinate space.
|
||||
|
||||
Reference in new issue
Block a user