diff --git a/core/src/ui/active.rs b/core/src/ui/active.rs index 2391a7b..fb61086 100644 --- a/core/src/ui/active.rs +++ b/core/src/ui/active.rs @@ -1,4 +1,6 @@ -use crate::{LayerId, MaskIdx, MoveIdx, PrimitiveHandle, Size, TextureHandle, UiRegion, WidgetId}; +use crate::{ + LayerId, MaskIdx, MoveIdx, PrimitiveHandle, Size, TextureHandle, UiRegion, WidgetId, util::Vec2, +}; /// important non rendering data for retained drawing #[derive(Debug)] @@ -7,6 +9,10 @@ pub struct ActiveData { pub region: UiRegion, /// What the widget said it used of `region`, the last time it drew. pub size: Size, + /// The pixel size of the box it drew against. `region` alone cannot say: + /// it is a fraction of a slot's box, and the same fraction of a box that + /// has since changed is a different number of pixels. + pub px: Vec2, pub parent: Option, pub textures: Vec, pub primitives: Vec, diff --git a/core/src/ui/render_state.rs b/core/src/ui/render_state.rs index 195ea70..531463f 100644 --- a/core/src/ui/render_state.rs +++ b/core/src/ui/render_state.rs @@ -132,6 +132,7 @@ impl UiRenderState { (parent_move, region) } }; + let px = self.px_of(move_idx, local); rsc.widgets_mut().needs_redraw.remove(&id); self.draw_started.insert(id); @@ -180,6 +181,7 @@ impl UiRenderState { id, region, size, + px, parent, textures, primitives, @@ -227,6 +229,14 @@ impl UiRenderState { } } + /// The pixel size of a region held in `slot`'s coordinates. + fn px_of(&self, slot: MoveIdx, region: UiRegion) -> Vec2 { + self.moves + .resolve(slot, region) + .size() + .to_abs(self.output_size) + } + /// The drawing a widget already has, kept for a new box if the box has not /// changed in a way it depends on. fn try_reuse( @@ -245,8 +255,16 @@ impl UiRenderState { if active.parent_move != parent_move { return None; } - let (size, old, slot) = (active.size, active.region, active.move_idx); - if old == region { + let (size, old, slot, was) = (active.size, active.region, active.move_idx, active.px); + // In pixels, because `region` is a fraction of a slot's box and that + // box may be what changed -- an unchanged fraction of a box half the + // size is half the widget. + let px = self.px_of(parent_move, region); + let mut changed = [false; 2]; + for (axis, c) in AXES.into_iter().zip(changed.iter_mut()) { + *c = px.axis(axis) != was.axis(axis); + } + if !changed.iter().any(|&c| c) && old == region { return Some(size); } // Only a placed widget can be given a different box without drawing @@ -255,28 +273,23 @@ impl UiRenderState { if slot == parent_move { return None; } - let mut changed = [false; 2]; - for (axis, c) in AXES.into_iter().zip(changed.iter_mut()) { - *c = region.axis(axis).len() != old.axis(axis).len(); - } if changed.iter().any(|&c| c) { let widget = rsc.widgets().get_dyn(id)?; let redraws = AXES .into_iter() .zip(changed) .any(|(axis, c)| c && widget.on_resize(axis) != OnResize::Scale); - if redraws { + // Anything under it that has to be drawn again is drawn by drawing + // this, because whatever reads that widget's size sits in between + // and has to lay out around what it comes to. + if redraws || self.redraws_under(id, changed, rsc) { return None; } } - // Anything under it that has to be drawn again is drawn by drawing - // this, because whatever reads that widget's size sits in between and - // has to lay out around whatever it comes to. - if changed.iter().any(|&c| c) && self.redraws_under(id, changed, rsc) { - return None; - } self.moves.set(slot, region); - self.active.get_mut(&id).unwrap().region = region; + let active = self.active.get_mut(&id).unwrap(); + active.region = region; + active.px = px; Some(size) } @@ -293,10 +306,26 @@ impl UiRenderState { let Some(active) = self.active.get(&id) else { return false; }; + let size_deps = &active.size_deps; active.children.iter().any(|&child| { let Some(data) = self.active.get(&child) else { return false; }; + let Some(widget) = rsc.widgets().get_dyn(child) else { + return true; + }; + // What it drew to learn this child's size was the child in *this* + // box, so a different box is a different answer -- unless the + // child gave an exact one without being drawn at all. + if size_deps.contains(&child) { + let measured = AXES + .into_iter() + .zip(changed) + .any(|(axis, c)| c && widget.size_hint(axis).is_none()); + if measured { + return true; + } + } let mut own = changed; for (axis, c) in AXES.into_iter().zip(own.iter_mut()) { *c &= data.region.axis(axis).len().rel != 0.0; @@ -304,13 +333,10 @@ impl UiRenderState { if !own.iter().any(|&c| c) { return false; } - let redraws = match rsc.widgets().get_dyn(child) { - Some(widget) => AXES - .into_iter() - .zip(own) - .any(|(axis, c)| c && widget.on_resize(axis) != OnResize::Scale), - None => true, - }; + let redraws = AXES + .into_iter() + .zip(own) + .any(|(axis, c)| c && widget.on_resize(axis) != OnResize::Scale); redraws || self.redraws_under(child, own, rsc) }) } diff --git a/tests/generated.rs b/tests/generated.rs index 2d75ade..2dc3f90 100644 --- a/tests/generated.rs +++ b/tests/generated.rs @@ -17,9 +17,7 @@ use iris::prelude::*; use iris::random::{Lens, Rng, Tree, grow}; const DEPTH: usize = 4; -/// Seeds whose trees agree. The ones left out are `a_wrapping_child_of_a_row` -/// below, which is a defect older than the chain. -const SEEDS: [u64; 6] = [2, 3, 4, 5, 8, 9]; +const SEEDS: [u64; 6] = [1, 2, 3, 5, 8, 13]; fn plant(h: &mut Harness, seed: u64, edits: &HashMap) -> Tree { let (root, tree) = grow(&mut h.rsc, seed, DEPTH, edits); @@ -161,9 +159,9 @@ fn a_size_change_after_a_resize_lands_the_same_way() { /// same defect, and it wants fixing where the two draws meet -- LAYOUT.md ยง4 -- /// rather than anywhere in the chain. #[test] -#[ignore = "known divergence, and the reproduction for fixing it"] -fn a_wrapping_child_of_a_row_settles_somewhere_else_each_time() { - for seed in 1..=30 { +#[ignore = "a hundred seeds, rather than the six the others check"] +fn a_long_run_of_seeds_agrees() { + for seed in 1..=100 { changed_size(seed); resized(seed); resized_then_changed(seed); diff --git a/tests/retained.rs b/tests/retained.rs index 7beceaf..73fdc8c 100644 --- a/tests/retained.rs +++ b/tests/retained.rs @@ -320,11 +320,14 @@ fn a_widened_row_redraws_what_reads_its_length_and_nothing_else() { } #[test] -fn a_fixed_length_child_is_not_redrawn_when_the_box_around_it_grows() { +fn a_declared_length_child_is_not_redrawn_when_the_box_around_it_grows() { let mut h = Harness::new((400, 200)); - // It would be drawn again for a width it does not have: its own box is - // a fixed 80 wherever the row's edges end up. - let (fixed, draws) = counted(&mut h, Size::from((80, 200)), OnResize::Redraw); + // Its box is a fixed 80 wherever the row's edges end up, so drawing it + // again would be for a width it does not have. The declared width is what + // lets the span say that without drawing it: a width the span learnt by + // drawing the child in its own box is only an answer for that box. + let (counter, draws) = counted(&mut h, Size::from((80, 200)), OnResize::Redraw); + let fixed = counter.width(80).add(&mut h.rsc); let (rest, _) = counted(&mut h, Size::REST, OnResize::Scale); let row = (fixed, rest).span(Dir::RIGHT).add(&mut h.rsc); let bar = rect(Color::RED).width(100).add(&mut h.rsc);