diff --git a/core/src/ui/render_state.rs b/core/src/ui/render_state.rs index bf57bf9..adadb74 100644 --- a/core/src/ui/render_state.rs +++ b/core/src/ui/render_state.rs @@ -1108,20 +1108,23 @@ impl UiRenderState { let old = self.remove(id, false, rsc); let drawn = self.draw_inner(id, info, old, rsc); let active = self.active.get_mut(&id).unwrap(); - // A wider contract does not invalidate the guarantee the parent kept. - // Retain that guarantee so widening and narrowing back do not churn it. - if let Some(was) = was_answer - && drawn.answer.size == was.size - && drawn.answer.holds.covers(was.holds) - { - active.answer = was_answer; - } // Against the box it was asked in, which is what both contracts are // about. Where the answer put the drawing is shorter than that // wherever the widget reported less than it was offered. - if active.holds.covers(was_holds) - && was_holds.contains(self.output_size, active.rel_base, active.region) + let (window, rel_base, region) = (self.output_size, active.rel_base, active.region); + // A wider contract does not invalidate the guarantee the parent kept. + // Retain that guarantee so widening and narrowing back do not churn + // it -- but only where the narrower range still holds here: one this + // window is outside is refused by the parent's next ask, and refusing + // it throws away the drawing this one just made. + if let Some(was) = was_answer + && drawn.answer.size == was.size + && drawn.answer.holds.covers(was.holds) + && was.holds.contains(window, rel_base, region) { + active.answer = was_answer; + } + if active.holds.covers(was_holds) && was_holds.contains(window, rel_base, region) { active.holds = was_holds; } if active.answer != was_answer || active.holds != was_holds { diff --git a/tests/cases/retained.rs b/tests/cases/retained.rs index 8b473a7..358097d 100644 --- a/tests/cases/retained.rs +++ b/tests/cases/retained.rs @@ -1480,14 +1480,17 @@ fn a_redrawn_subtree_is_not_undrawn_by_the_parent_it_left() { /// A leaf that reports less than the box it is given and states which lengths /// of that box its drawing holds for, so a test can widen the contract -/// without changing the answer. +/// without changing the answer. It counts its draws, since what a kept +/// contract costs is whether the parent has to make it draw again. struct Contracted { holds: std::ops::RangeInclusive, size: Size, + draws: Rc>, } impl Widget for Contracted { fn draw(&mut self, painter: &mut Painter) -> Size { + self.draws.set(self.draws.get() + 1); painter.holds(Axis::X, self.holds.clone()); self.size } @@ -1511,6 +1514,7 @@ fn widening_what_a_drawing_holds_for_does_not_relay_out_the_parent() { let child = Contracted { holds: Px::from_int(300)..=Px::from_int(500), size: Size::from((100, 200)), + draws: Rc::new(Cell::new(0)), } .add(&mut h.rsc); let draws = Rc::new(Cell::new(0)); @@ -1533,3 +1537,39 @@ fn widening_what_a_drawing_holds_for_does_not_relay_out_the_parent() { "a wider contract for the same answer is not a change to lay out" ); } + +/// The other half of the rule above: a kept contract is the narrower one, so +/// it is only worth keeping where it still holds. A window the old range is +/// outside is not one its parent can be handed back, and keeping it there +/// throws away the drawing the widget just made. +#[test] +fn a_contract_this_window_is_outside_is_not_kept() { + let mut h = Harness::new((400, 200)); + let leaf_draws = Rc::new(Cell::new(0)); + let child = Contracted { + holds: Px::from_int(300)..=Px::from_int(500), + size: Size::from((100, 200)), + draws: leaf_draws.clone(), + } + .add(&mut h.rsc); + let root = CountedParent { + inner: child.upgrade(&mut h.rsc), + draws: Rc::new(Cell::new(0)), + } + .add(&mut h.rsc); + h.set_root(root); + + // Wide enough that the old contract leaves the new box out, and the leaf + // is marked in the same frame -- so it settles itself first and its + // parent draws afterwards, asking about what it settled. + h.resize((600, 200)); + h.rsc[child].holds = Px::from_int(200)..=Px::from_int(700); + let settled = leaf_draws.get(); + h.frame(); + + assert_eq!( + leaf_draws.get(), + settled + 1, + "the leaf settled once and its parent kept what it settled" + ); +}