diff --git a/core/src/ui/painter.rs b/core/src/ui/painter.rs index 9a6319d..e5098ce 100644 --- a/core/src/ui/painter.rs +++ b/core/src/ui/painter.rs @@ -22,8 +22,6 @@ pub struct Painter<'a> { /// The children whose size this widget read while drawing. pub(super) size_deps: Vec, pub(super) reads_output: bool, - /// Whether this widget is drawing in the same pixel-sized box as before. - pub(super) same_box: bool, /// The slot this widget's primitives are positioned through: its own if /// its parent placed it, otherwise the nearest ancestor that has one. pub(super) move_idx: MoveIdx, @@ -146,21 +144,6 @@ impl<'a> Painter<'a> { Some(hint) } - /// A clean child's retained size, when this widget's own constraints are - /// unchanged. This is the answer from its last real draw, not a guess. - pub fn retained_size(&mut self, id: &StrongWidget) -> Option { - if !self.same_box || self.rsc.widgets().needs_redraw.contains(&id.id()) { - return None; - } - let active = self.state.active.get(&id.id())?; - if active.parent != Some(self.id) { - return None; - } - let size = active.size; - self.depend_on_size(id); - Some(size) - } - fn depend_on_size(&mut self, child: &StrongWidget) { if !self.size_deps.contains(&child.id()) { self.size_deps.push(child.id()); diff --git a/core/src/ui/render_state.rs b/core/src/ui/render_state.rs index 658ad99..8de4c53 100644 --- a/core/src/ui/render_state.rs +++ b/core/src/ui/render_state.rs @@ -74,10 +74,10 @@ impl UiRenderState { } } } - self.resized = false; if rsc.widgets().has_updates() { self.redraw_updates(rsc); } + self.resized = false; } fn redraw_all(&mut self, root: Option<&StrongWidget>, rsc: &mut dyn UiRsc) { @@ -109,19 +109,18 @@ impl UiRenderState { parent_move: MoveIdx, slotted: bool, mask: MaskIdx, - old_active: Option, + old_children: Option>, rsc: &mut dyn UiRsc, ) -> Size { - let mut old_active = old_active; + let mut old_children = old_children.unwrap_or_default(); if self.active.contains_key(&id) { if let Some(size) = self.try_reuse(id, region, parent_move, rsc) { return size; } // if not, then maintain resize and track old children to remove unneeded - old_active = self.remove(id, false, rsc); + let active = self.remove(id, false, rsc).unwrap(); + old_children = active.children; } - let previous_px = old_active.as_ref().map(|active| active.px); - let old_children = old_active.map(|active| active.children).unwrap_or_default(); // draw widget let (move_idx, local) = match slotted { @@ -134,7 +133,6 @@ impl UiRenderState { } }; let px = self.px_of(move_idx, local); - let same_box = previous_px == Some(px); rsc.widgets_mut().needs_redraw.remove(&id); self.draw_started.insert(id); @@ -149,7 +147,6 @@ impl UiRenderState { children: Vec::new(), size_deps: Vec::new(), reads_output: false, - same_box, move_idx, rsc, }; @@ -168,7 +165,6 @@ impl UiRenderState { children, size_deps, reads_output, - same_box: _, move_idx, layer, id, @@ -401,12 +397,31 @@ impl UiRenderState { } pub fn redraw_updates(&mut self, rsc: &mut dyn UiRsc) { - while let Some(&id) = rsc.widgets().needs_redraw.iter().next() { + // A reader's answer is only valid after every dirty size it reads has + // settled. Equal-depth widgets are independent, so their order does + // not matter. + while let Some(id) = rsc + .widgets() + .needs_redraw + .iter() + .copied() + .max_by_key(|&id| self.depth(id)) + { self.redraw(id, rsc); } rsc.free(); } + fn depth(&self, id: WidgetId) -> usize { + let mut depth = 0; + let mut at = Some(id); + while let Some(id) = at { + at = self.active.get(&id).and_then(|active| active.parent); + depth += 1; + } + depth + } + pub fn root_changed<'a>(&self, root: impl Into>) -> bool { root.into().map(|r| r.id()) != self.old_root } @@ -462,7 +477,9 @@ impl UiRenderState { .active .get(&id) .is_some_and(|active| self.px_of(active.parent_move, active.region) != active.px); - if box_changed && let Some(top) = self.mark_readers(id, rsc) { + if (self.resized || box_changed) + && let Some(top) = self.mark_readers(id, rsc) + { self.redraw(top, rsc); rsc.widgets_mut().needs_redraw.remove(&id); return; @@ -486,15 +503,20 @@ impl UiRenderState { active.parent_move, active.move_idx != active.parent_move, active.mask, - Some(active), + Some(active.children), rsc, ); if size != was - && let Some(top) = self.mark_readers(id, rsc) + && let Some(parent) = self.active.get(&id).and_then(|active| active.parent) + && self + .active + .get(&parent) + .is_some_and(|active| active.size_deps.contains(&id)) { - self.redraw(top, rsc); - rsc.widgets_mut().needs_redraw.remove(&id); + // Propagate one dependency edge at a time. If drawing the reader + // does not change its own size, nothing above it can observe this. + rsc.widgets_mut().needs_redraw.insert(parent); } } diff --git a/src/random.rs b/src/random.rs index ca5b9c4..921b1e8 100644 --- a/src/random.rs +++ b/src/random.rs @@ -148,6 +148,22 @@ impl Grow<'_, Rsc> { } } + fn align(&mut self) -> Align { + let mut axis = || match self.rng.below(4) { + 0 => None, + 1 => Some(AxisAlign::Neg), + 2 => Some(AxisAlign::Center), + _ => Some(AxisAlign::Pos), + }; + let (mut x, y) = (axis(), axis()); + // Aligning on neither axis is just another transparent wrapper and + // would leave this branch unexercised. + if x.is_none() && y.is_none() { + x = Some(AxisAlign::Center); + } + Align { x, y } + } + /// A declared size over half the tree, kept where a test can change it. fn sized(&mut self, inner: StrongWidget) -> StrongWidget { if !self.rng.chance() { @@ -171,7 +187,8 @@ impl Grow<'_, Rsc> { if depth == 0 { return self.leaf(); } - if self.rng.below(6) == 0 { + let positioned = self.rng.below(6); + if positioned == 0 { // Scrolling reads the pixel length of its box, which nothing // else here does, and gives its child a box longer than its own. let inner = self.node(depth - 1); @@ -182,6 +199,17 @@ impl Grow<'_, Rsc> { self.tree.ids.push(id.id()); return id.add_strong(self.rsc); } + if positioned == 1 { + let inner = self.node(depth - 1); + let inner = self.sized(inner); + let id = Aligned { + inner, + align: self.align(), + } + .add_strong(self.rsc); + self.tree.ids.push(id.id()); + return id; + } if self.rng.below(4) == 0 { let inner = self.node(depth - 1); let inner = self.sized(inner); diff --git a/src/widget/position/scroll.rs b/src/widget/position/scroll.rs index 3150128..0b033e8 100644 --- a/src/widget/position/scroll.rs +++ b/src/widget/position/scroll.rs @@ -13,13 +13,9 @@ impl Widget for Scroll { fn draw(&mut self, painter: &mut Painter) -> Size { let output_len = painter.output_size().axis(self.axis); let container_len = UiScalar::abs(painter.px_size().axis(self.axis)); - // Its last measured size stays valid while neither the child nor this - // container's constraints changed. Otherwise draw it in the whole - // container to learn its length, then place it at the scrolled offset. - let child = match painter.retained_size(&self.inner) { - Some(size) => size, - None => painter.place(&self.inner, UiRegion::FULL).size(), - }; + // Draw in the whole container to learn the content's length, then + // place it at the scrolled offset. + let child = painter.place(&self.inner, UiRegion::FULL).size(); let content_len = child .axis(self.axis) .apply_rest() diff --git a/tests/retained.rs b/tests/retained.rs index 019a33c..c7005b9 100644 --- a/tests/retained.rs +++ b/tests/retained.rs @@ -131,34 +131,6 @@ fn a_repaint_that_keeps_its_size_does_not_relay_out() { assert_eq!(draws.get(), settled + 1); } -#[test] -fn scrolling_reuses_the_clean_contents_size() { - let mut h = Harness::new((400, 200)); - let (inner, draws) = counted(&mut h, Size::from((400, 600)), OnResize::Translate); - let scroll = Scroll::new(inner.add_strong(&mut h.rsc), Axis::Y).add(&mut h.rsc); - h.set_root(scroll); - let settled = draws.get(); - - h.rsc[scroll].scroll(40.0); - h.frame(); - - assert_eq!(draws.get(), settled); - assert_corners!(h, inner, (0, -360), (400, 240)); -} - -#[test] -fn scrolling_remeasures_changed_contents() { - let mut h = Harness::new((400, 200)); - let (inner, _) = counted(&mut h, Size::from((400, 600)), OnResize::Translate); - let scroll = Scroll::new(inner.add_strong(&mut h.rsc), Axis::Y).add(&mut h.rsc); - h.set_root(scroll); - - h.rsc[inner].size = Size::from((400, 800)); - h.frame(); - - assert_corners!(h, inner, (0, -600), (400, 200)); -} - #[test] fn a_placed_child_survives_the_next_frame() { let mut h = Harness::new((400, 200));