Track retained layout validity explicitly

This commit is contained in:
iris-ai committed 2026-09-15 16:03:53 -04:00
1 parent 691e3eb23c
commit 29c7881c8a
25 files changed
+836 -795

No files matched your search

+83 -53
View File
@@ -6,22 +6,22 @@ use iris::harness::{Harness, assert_corners};
use iris::prelude::*;
/// A leaf that counts its draws and reports whatever size it is given, so a
/// test can see what the retained path skipped.
/// test can see what the retained path skipped. One that reads its box in
/// pixels has a drawing that holds for that box alone.
struct Counted {
draws: Rc<Cell<usize>>,
size: Size,
dependence: OnResize,
reads_box: bool,
}
impl Widget for Counted {
fn draw(&mut self, _: &mut Painter) -> Size {
fn draw(&mut self, painter: &mut Painter) -> Size {
self.draws.set(self.draws.get() + 1);
if self.reads_box {
painter.px_size();
}
self.size
}
fn on_resize(&self, _: Axis) -> OnResize {
self.dependence
}
}
struct Counts(Rc<Cell<usize>>);
@@ -32,22 +32,63 @@ impl Counts {
}
}
fn counted(h: &mut Harness, size: Size, dependence: OnResize) -> (WeakWidget<Counted>, Counts) {
fn counted(h: &mut Harness, size: Size, reads_box: bool) -> (WeakWidget<Counted>, Counts) {
let draws = Rc::new(Cell::new(0));
let id = Counted {
draws: draws.clone(),
size,
dependence,
reads_box,
}
.add(&mut h.rsc);
(id, Counts(draws))
}
struct Layered {
children: [StrongWidget<Rect>; 2],
_revision: usize,
}
impl Widget for Layered {
fn draw(&mut self, painter: &mut Painter) -> Size {
painter.child_layer();
painter.widget(&self.children[0]);
painter.next_layer();
painter.widget(&self.children[1]);
Size::default()
}
}
#[test]
fn a_redrawn_layered_widget_keeps_the_layer_it_was_entered_on() {
let mut h = Harness::new((400, 200));
let children = [
rect(Color::RED).add_strong(&mut h.rsc),
rect(Color::BLUE).add_strong(&mut h.rsc),
];
let root = Layered {
children,
_revision: 0,
}
.add(&mut h.rsc);
h.set_root(root);
h.rsc[root]._revision += 1;
h.frame();
let label = h.rsc.widgets().label(root.id());
let active = h
.render
.debug(h.rsc.widgets(), label)
.find(|active| active.id == root.id())
.unwrap();
assert_eq!(active.layer, 0);
}
/// A fixed-width leaf beside one that takes what is left over, so changing
/// the first hands the second a different box without the output changing.
fn pair(h: &mut Harness, leftover: OnResize) -> (WeakWidget<Counted>, Counts, WidgetId) {
let (first, _) = counted(h, Size::from((100, 200)), OnResize::Translate);
let (second, draws) = counted(h, Size::LEFTOVER, leftover);
fn pair(h: &mut Harness, reads_box: bool) -> (WeakWidget<Counted>, Counts, WidgetId) {
let (first, _) = counted(h, Size::from((100, 200)), false);
let (second, draws) = counted(h, Size::LEFTOVER, reads_box);
h.set_root((first, second).span(Dir::RIGHT));
(first, draws, second.id())
}
@@ -55,7 +96,7 @@ fn pair(h: &mut Harness, leftover: OnResize) -> (WeakWidget<Counted>, Counts, Wi
#[test]
fn a_leaf_that_ignores_its_box_is_not_drawn_again_when_the_box_changes() {
let mut h = Harness::new((400, 200));
let (first, draws, second) = pair(&mut h, OnResize::Scale);
let (first, draws, second) = pair(&mut h, false);
let settled = draws.get();
assert_corners!(h, second, (100, 0), (400, 200));
@@ -73,7 +114,7 @@ fn a_leaf_that_ignores_its_box_is_not_drawn_again_when_the_box_changes() {
#[test]
fn a_leaf_that_depends_on_its_box_is_drawn_again_when_the_box_changes() {
let mut h = Harness::new((400, 200));
let (first, draws, second) = pair(&mut h, OnResize::Redraw);
let (first, draws, second) = pair(&mut h, true);
let settled = draws.get();
h.rsc[first].size = Size::from((150, 200));
@@ -88,8 +129,8 @@ fn a_leaf_that_depends_on_its_box_is_drawn_again_when_the_box_changes() {
#[test]
fn a_span_child_that_declares_its_length_is_drawn_once() {
let mut h = Harness::new((400, 200));
let (told, told_draws) = counted(&mut h, Size::from((100, 200)), OnResize::Translate);
let (asked, asked_draws) = counted(&mut h, Size::from((100, 200)), OnResize::Translate);
let (told, told_draws) = counted(&mut h, Size::from((100, 200)), false);
let (asked, asked_draws) = counted(&mut h, Size::from((100, 200)), true);
// The span takes one child's length from its hint and has to draw the
// other to find out, so only the second is drawn before it is placed.
let hinted = told.width(100).add(&mut h.rsc);
@@ -106,7 +147,7 @@ fn a_span_child_that_declares_its_length_is_drawn_once() {
#[test]
fn a_span_relays_out_when_a_child_it_measured_changes() {
let mut h = Harness::new((400, 200));
let (first, _, second) = pair(&mut h, OnResize::Translate);
let (first, _, second) = pair(&mut h, false);
h.rsc[first].size = Size::from((250, 200));
h.frame();
@@ -118,8 +159,8 @@ fn a_span_relays_out_when_a_child_it_measured_changes() {
#[test]
fn a_repaint_that_keeps_its_size_does_not_relay_out() {
let mut h = Harness::new((400, 200));
let (first, draws) = counted(&mut h, Size::from((100, 200)), OnResize::Translate);
let (second, _) = counted(&mut h, Size::LEFTOVER, OnResize::Translate);
let (first, draws) = counted(&mut h, Size::from((100, 200)), false);
let (second, _) = counted(&mut h, Size::LEFTOVER, false);
h.set_root((first, second).span(Dir::RIGHT));
let settled = draws.get();
@@ -179,20 +220,20 @@ fn a_parent_that_only_read_a_hint_relays_out_when_the_hint_changes() {
assert_corners!(h, inner, (0, 0), (400, 120));
}
/// Reads the output's size, which nothing but its own draw can put right.
struct ReadsOutput {
/// Reads its box's size, which nothing but its own draw can put right.
struct ReadsBox {
draws: Rc<Cell<usize>>,
}
impl Widget for ReadsOutput {
impl Widget for ReadsBox {
fn draw(&mut self, painter: &mut Painter) -> Size {
self.draws.set(self.draws.get() + 1);
Size::px(painter.output_size() / 4.0)
Size::px(painter.px_size() / 4.0)
}
}
/// Reads the output across one axis only, and says so: its drawing follows
/// a taller box on its own, so only a wider one is worth a draw.
/// Reads its box across one axis only, so its drawing holds for a taller
/// box on its own and only a wider one is worth a draw.
struct ReadsWidth {
draws: Rc<Cell<usize>>,
}
@@ -200,21 +241,14 @@ struct ReadsWidth {
impl Widget for ReadsWidth {
fn draw(&mut self, painter: &mut Painter) -> Size {
self.draws.set(self.draws.get() + 1);
Size::px((painter.output_len(Axis::X) / 4.0, 20.0).into())
}
fn on_resize(&self, axis: Axis) -> OnResize {
match axis {
Axis::X => OnResize::Redraw,
Axis::Y => OnResize::Scale,
}
Size::px((painter.px_len(Axis::X) / 4.0, 20.0).into())
}
}
#[test]
fn a_resize_does_not_redraw_what_the_shader_can_move() {
let mut h = Harness::new((400, 200));
let (leaf, draws) = counted(&mut h, Size::LEFTOVER, OnResize::Scale);
let (leaf, draws) = counted(&mut h, Size::LEFTOVER, false);
h.set_root(leaf);
let settled = draws.get();
@@ -231,12 +265,12 @@ fn a_resize_does_not_redraw_what_the_shader_can_move() {
}
/// The output is the root of the box chain, so a resize is a box that changed
/// length and `OnResize` answers for it -- there is not a second rule for the
/// window. A drawing that does not scale is redrawn whichever box moved.
/// length like any other -- there is not a second rule for the window. A
/// drawing that holds for one length is drawn again whichever box moved.
#[test]
fn a_resize_redraws_what_does_not_scale() {
let mut h = Harness::new((400, 200));
let (leaf, draws) = counted(&mut h, Size::LEFTOVER, OnResize::Redraw);
let (leaf, draws) = counted(&mut h, Size::LEFTOVER, true);
h.set_root(leaf);
let settled = draws.get();
@@ -248,10 +282,10 @@ fn a_resize_redraws_what_does_not_scale() {
}
#[test]
fn a_resize_redraws_what_read_the_output() {
fn a_resize_redraws_what_read_its_box() {
let mut h = Harness::new((400, 200));
let draws = Rc::new(Cell::new(0));
let leaf = ReadsOutput {
let leaf = ReadsBox {
draws: draws.clone(),
}
.add(&mut h.rsc);
@@ -265,7 +299,7 @@ fn a_resize_redraws_what_read_the_output() {
}
#[test]
fn a_resize_only_redraws_read_output_axes() {
fn a_resize_only_redraws_read_axes() {
let mut h = Harness::new((400, 200));
let draws = Rc::new(Cell::new(0));
let leaf = ReadsWidth {
@@ -309,7 +343,7 @@ fn subpixel_resize_changes_accumulate_from_the_last_layout() {
#[test]
fn subpixel_box_changes_accumulate_from_the_last_draw() {
let mut h = Harness::new((400, 200));
let (first, draws, _) = pair(&mut h, OnResize::Redraw);
let (first, draws, _) = pair(&mut h, true);
let settled = draws.get();
for width in [100.02, 100.04, 100.05] {
@@ -327,7 +361,7 @@ fn subpixel_box_changes_accumulate_from_the_last_draw() {
fn reporting_the_same_output_size_does_not_start_a_resize() {
let mut h = Harness::new((400, 200));
let draws = Rc::new(Cell::new(0));
let leaf = ReadsOutput {
let leaf = ReadsBox {
draws: draws.clone(),
}
.add(&mut h.rsc);
@@ -368,7 +402,7 @@ fn a_change_two_levels_under_its_reader_still_reaches_it() {
// Every wrapper up to the outer pad read the size below it, so the outer
// pad is what draws again -- and the span it hands the box to is the same
// size as before, which is what lets a draw reuse its way past the leaf.
let (leaf, _) = counted(&mut h, Size::px((100, 100).into()), OnResize::Redraw);
let (leaf, _) = counted(&mut h, Size::px((100, 100).into()), true);
let padded = leaf.pad(10).add(&mut h.rsc);
let below = rect(Color::RED).add(&mut h.rsc);
h.set_root((padded, below).span(Dir::DOWN).pad(12));
@@ -380,8 +414,8 @@ fn a_change_two_levels_under_its_reader_still_reaches_it() {
assert_corners!(h, below, (12, 232), (388, 388));
}
/// Claims its drawing survives its box changing length, and has a child so
/// that the walk looking for what does not has one to reach.
/// Reads nothing of its box, so its drawing holds for any length, and has a
/// child so that whatever asks about the subtree has one to reach.
struct Stretchy {
inner: StrongWidget,
draws: Rc<Cell<usize>>,
@@ -392,10 +426,6 @@ impl Widget for Stretchy {
self.draws.set(self.draws.get() + 1);
painter.widget(&self.inner).size()
}
fn on_resize(&self, _: Axis) -> OnResize {
OnResize::Scale
}
}
#[test]
@@ -430,8 +460,8 @@ fn a_widened_row_redraws_what_reads_its_length_and_nothing_else() {
let mut h = Harness::new((400, 200));
// What a transcript row is: something whose shaping depends on the width
// it is given, beside something that only has to be the right shape.
let (wraps, wrap_draws) = counted(&mut h, Size::LEFTOVER, OnResize::Redraw);
let (backing, back_draws) = counted(&mut h, Size::LEFTOVER, OnResize::Scale);
let (wraps, wrap_draws) = counted(&mut h, Size::LEFTOVER, true);
let (backing, back_draws) = counted(&mut h, Size::LEFTOVER, false);
let row = (backing, wraps).span(Dir::RIGHT).add(&mut h.rsc);
let bar = rect(Color::RED).width(100).add(&mut h.rsc);
h.set_root((bar, row).span(Dir::RIGHT));
@@ -455,9 +485,9 @@ fn a_declared_length_child_is_not_redrawn_when_the_box_around_it_grows() {
// 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 (counter, draws) = counted(&mut h, Size::from((80, 200)), true);
let fixed = counter.width(80).add(&mut h.rsc);
let (leftover, _) = counted(&mut h, Size::LEFTOVER, OnResize::Scale);
let (leftover, _) = counted(&mut h, Size::LEFTOVER, false);
let row = (fixed, leftover).span(Dir::RIGHT).add(&mut h.rsc);
let bar = rect(Color::RED).width(100).add(&mut h.rsc);
h.set_root((bar, row).span(Dir::RIGHT));