//! What a second frame draws again, and what it keeps. use std::{cell::Cell, rc::Rc}; 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. One that reads its box in /// pixels has a drawing that holds for that box alone. struct Counted { draws: Rc>, size: Size, reads_box: bool, } impl Widget for Counted { fn draw(&mut self, painter: &mut Painter) -> Size { self.draws.set(self.draws.get() + 1); if self.reads_box { painter.px_size(); } self.size } } struct Counts(Rc>); impl Counts { fn get(&self) -> usize { self.0.get() } } fn counted(h: &mut Harness, size: Size, reads_box: bool) -> (WeakWidget, Counts) { let draws = Rc::new(Cell::new(0)); let id = Counted { draws: draws.clone(), size, reads_box, } .add(&mut h.rsc); (id, Counts(draws)) } struct Layered { children: [StrongWidget; 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, reads_box: bool) -> (WeakWidget, 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()) } #[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, false); let settled = draws.get(); assert_corners!(h, second, (100, 0), (400, 200)); h.rsc[first].size = Size::from((150, 200)); h.frame(); assert_eq!( draws.get(), settled, "its box is a field to write, not a reason to draw" ); assert_corners!(h, second, (150, 0), (400, 200)); } #[test] fn moving_an_ordinary_subtree_remaps_its_mask() { let mut h = Harness::new((400, 200)); let (first, _) = counted(&mut h, Size::from((100, 200)), false); let inner = rect(Color::BLUE).add(&mut h.rsc); let masked = inner.masked().add(&mut h.rsc); h.set_root((first, masked).span(Dir::RIGHT)); h.rsc[first].size = Size::from((150, 200)); h.frame(); let active = &h.render.active[&masked.id()]; assert_eq!( h.rsc.ui().masks[active.mask.idx()].region, UiRegion::new( UiSpan::new(UiScalar::px(150.0), UiScalar::rel_max()), UiSpan::FULL, ) ); assert_corners!(h, inner, (150, 0), (400, 200)); } #[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, true); let settled = draws.get(); h.rsc[first].size = Size::from((150, 200)); h.frame(); // The preceding fixed child makes the remaining box this child's real // box, so measuring it also draws it in its final box. assert_eq!(draws.get(), settled + 1); assert_corners!(h, second, (150, 0), (400, 200)); } #[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)), 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 its final box. let hinted = told.width(100).add(&mut h.rsc); h.set_root((hinted, asked).span(Dir::RIGHT)); assert_eq!(told_draws.get(), 1); // Reading its box makes its drawing hold for the measuring box alone, // and it reports less than that box: so it is drawn again in the box its // answer places it in, and once more in the final box the span chooses. // A widget that says what it holds for, as text does, skips the middle // one. assert_eq!( asked_draws.get(), 3, "drawn to be measured, in its placed box, then in its final box" ); } #[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, false); h.rsc[first].size = Size::from((250, 200)); h.frame(); assert_corners!(h, first, (0, 0), (250, 200)); assert_corners!(h, second, (250, 0), (400, 200)); } #[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)), false); let (second, _) = counted(&mut h, Size::LEFTOVER, false); h.set_root((first, second).span(Dir::RIGHT)); let settled = draws.get(); // Taking mutable access is the ordinary content-change signal. This // widget returns the same size, so the parent has nothing to lay out. let _ = h.rsc.widgets_mut().get_dyn_mut(first.id()); h.frame(); assert_eq!(draws.get(), settled + 1); } #[test] fn a_span_child_survives_the_next_frame() { let mut h = Harness::new((400, 200)); // Both children declare a length, so the span chooses their boxes from // hints rather than drawing them to find out. let top = rect(Color::RED).height(80).add(&mut h.rsc); let bottom = rect(Color::BLUE).height(120).add(&mut h.rsc); h.set_root((top, bottom).span(Dir::DOWN)); h.rsc.widgets_mut().get_dyn_mut(top.id()); h.frame(); assert_corners!(h, top, (0, 0), (400, 80)); assert_corners!(h, bottom, (0, 80), (400, 200)); } /// Lays its child out from the hint alone, never reading what it drew. struct FromHint { inner: StrongWidget, } impl Widget for FromHint { fn draw(&mut self, painter: &mut Painter) -> Size { let len = painter.size_hint(&self.inner, Axis::Y).unwrap(); let mut region = UiRegion::FULL; region.y.end = region.y.start.offset(len.px); painter.widget_within(&self.inner, region); Size::LEFTOVER } } #[test] fn a_parent_that_only_read_a_hint_relays_out_when_the_hint_changes() { let mut h = Harness::new((400, 200)); let inner = rect(Color::RED).height(80).add(&mut h.rsc); let parent = FromHint { inner: inner.add_strong(&mut h.rsc), } .add(&mut h.rsc); h.set_root(parent); assert_corners!(h, inner, (0, 0), (400, 80)); h.set_len(inner, Axis::Y, 120); h.frame(); assert_corners!(h, inner, (0, 0), (400, 120)); } /// Reads its box's size, which nothing but its own draw can put right. struct ReadsBox { draws: Rc>, } impl Widget for ReadsBox { fn draw(&mut self, painter: &mut Painter) -> Size { self.draws.set(self.draws.get() + 1); Size::px(painter.px_size() / 4.0) } } /// 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. /// /// Both of these report a quarter of what they read, without saying that the /// drawing holds there too, so each length they are asked at costs two draws: /// one to answer, and one in the quarter-sized box that answer places them /// in. The counts below are in those pairs. struct ReadsWidth { draws: Rc>, } impl Widget for ReadsWidth { fn draw(&mut self, painter: &mut Painter) -> Size { self.draws.set(self.draws.get() + 1); 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, false); h.set_root(leaf); let settled = draws.get(); h.resize((800, 100)); assert!(h.needs_redraw()); h.frame(); assert_eq!( draws.get(), settled, "a scaling drawing follows its box, and the output is one" ); assert_corners!(h, leaf, (0, 0), (800, 100)); } #[test] fn a_full_ortho_span_moves_its_child_without_redrawing_it() { let mut h = Harness::new((400, 200)); let (leaf, draws) = counted(&mut h, Size::LEFTOVER, false); let span = (leaf,) .span(Dir::RIGHT) .ortho(OrthoSize::Full) .add(&mut h.rsc); h.set_root(span); let settled = draws.get(); h.resize((400, 100)); h.frame(); assert_eq!(draws.get(), settled); assert_corners!(h, leaf, (0, 0), (400, 100)); assert_eq!(h.render.active[&span.id()].size.y, Len::rel(1.0)); } /// The output is the root of the box chain, so a resize is a box that changed /// 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, true); h.set_root(leaf); let settled = draws.get(); h.resize((800, 100)); h.frame(); assert_eq!(draws.get(), settled + 1, "its box is a different length"); assert_corners!(h, leaf, (0, 0), (800, 100)); } #[test] fn a_resize_redraws_what_read_its_box() { let mut h = Harness::new((400, 200)); let draws = Rc::new(Cell::new(0)); let leaf = ReadsBox { draws: draws.clone(), } .add(&mut h.rsc); h.set_root(leaf); let settled = draws.get(); h.resize((800, 100)); h.frame(); assert_eq!(draws.get(), settled + 2); } #[test] fn a_resize_only_redraws_read_axes() { let mut h = Harness::new((400, 200)); let draws = Rc::new(Cell::new(0)); let leaf = ReadsWidth { draws: draws.clone(), } .add(&mut h.rsc); h.set_root(leaf); let settled = draws.get(); h.resize((400, 300)); h.frame(); assert_eq!(draws.get(), settled, "height was never read"); h.resize((800, 300)); h.frame(); assert_eq!(draws.get(), settled + 2, "width changes its answer"); } #[test] fn subpixel_resize_changes_accumulate_from_the_last_layout() { let mut h = Harness::new((400, 200)); let draws = Rc::new(Cell::new(0)); let leaf = ReadsWidth { draws: draws.clone(), } .add(&mut h.rsc); h.set_root(leaf); let settled = draws.get(); for width in [400.02, 400.04, 400.05] { h.resize((width, 200.0)); h.frame(); assert_eq!(draws.get(), settled); } h.resize((400.06, 200.0)); h.frame(); assert_eq!(draws.get(), settled + 2); } #[test] fn subpixel_box_changes_accumulate_from_the_last_draw() { let mut h = Harness::new((400, 200)); let (first, draws, _) = pair(&mut h, true); let settled = draws.get(); for width in [100.02, 100.04, 100.05] { h.rsc[first].size.x = Len::px(width); h.frame(); assert_eq!(draws.get(), settled); } h.rsc[first].size.x = Len::px(100.06); h.frame(); assert_eq!(draws.get(), settled + 1); } #[test] 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 = ReadsBox { draws: draws.clone(), } .add(&mut h.rsc); h.set_root(leaf); let settled = draws.get(); h.resize((400, 200)); assert!(!h.needs_redraw()); h.frame(); assert_eq!(draws.get(), settled); } #[test] fn narrowing_the_output_reflows_text_and_relays_out_around_it() { let mut h = Harness::new((600, 400)); let para = wtext( "Wrapping shapes one source into as many lines as its container leaves \ room for, so the height of a paragraph is an answer rather than a setting.", ) .size(20) .wrap(true) .add(&mut h.rsc); let below = rect(Color::RED).add(&mut h.rsc); h.set_root((para, below).span(Dir::DOWN)); let top = h.region(&below).expect("drew nothing").top_left.y; h.resize((300, 400)); h.frame(); let lower = h.region(&below).expect("drew nothing").top_left.y; assert!(lower > top, "same words, half the width: {top} -> {lower}"); } #[test] fn a_change_two_levels_under_its_reader_still_reaches_it() { let mut h = Harness::new((400, 400)); // 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()), 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)); assert_corners!(h, below, (12, 132), (388, 388)); h.rsc[leaf].size = Size::px((100, 200).into()); h.frame(); assert_corners!(h, below, (12, 232), (388, 388)); } /// 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>, } impl Widget for Stretchy { fn draw(&mut self, painter: &mut Painter) -> Size { self.draws.set(self.draws.get() + 1); painter.widget(&self.inner).size() } } #[test] fn stretching_a_subtree_carries_the_children_in_it() { let mut h = Harness::new((400, 400)); let first = rect(Color::RED).height(40).add(&mut h.rsc); let inner = rect(Color::BLUE).add(&mut h.rsc); let draws = Rc::new(Cell::new(0)); let outer = Stretchy { inner: inner.add_strong(&mut h.rsc), draws: draws.clone(), } .add(&mut h.rsc); h.set_root((first, outer).span(Dir::DOWN)); let settled = draws.get(); assert_corners!(h, inner, (0, 40), (400, 400)); h.set_len(first, Axis::Y, 80); h.frame(); assert_eq!( draws.get(), settled, "its drawing follows its box, rather than being made again" ); assert_corners!(h, outer, (0, 80), (400, 400)); assert_corners!(h, inner, (0, 80), (400, 400)); } #[test] 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, 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)); let (settled_wrap, settled_back) = (wrap_draws.get(), back_draws.get()); h.set_len(bar, Axis::X, 200); h.frame(); // The span reads every child's size, so redrawing one takes the span // with it -- and the span then measures and places the redrawn child. assert!(wrap_draws.get() > settled_wrap, "reads the width it got"); assert_eq!(back_draws.get(), settled_back, "only has to be the shape"); assert_corners!(h, backing, (200, 0), (300, 200)); assert_corners!(h, wraps, (300, 0), (400, 200)); } #[test] fn a_declared_length_child_is_not_redrawn_when_the_box_around_it_grows() { let mut h = Harness::new((400, 200)); // 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)), true); let fixed = counter.width(80).add(&mut h.rsc); 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)); let settled = draws.get(); h.set_len(bar, Axis::X, 200); h.frame(); assert_eq!(draws.get(), settled, "its own length did not change"); assert_corners!(h, fixed, (200, 0), (280, 200)); }