//! 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(Len::px(150.0), Len::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); // Asked once, from the cursor; its slot is its answer and the drawing is // moved there. assert_eq!(asked_draws.get(), 1); } #[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 top = UiSpan::new(Len::ZERO, Len::from_parts(Rel::ZERO, len.px)); painter.widget_at(&self.inner, top.shifted_desc().axis(Axis::Y)); 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::from_px(painter.px_size().div_int(4)) } } /// 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. The quarter-sized box /// the answer places them in is not a question: the drawing is moved there, /// so each length they are asked at costs one draw. struct ReadsWidth { draws: Rc>, } impl Widget for ReadsWidth { fn draw(&mut self, painter: &mut Painter) -> Size { self.draws.set(self.draws.get() + 1); Size::from_px(PxVec2::new( painter.px_len(Axis::X).div_int(4), Px::from_int(20), )) } } #[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_span_ruled_across_itself_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).height(rel(1.0)).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, LayoutLen::rel(1.0)); } /// A row places its children as lengths from where its own box starts, so a /// child that grew moves the ones after it and nothing else: each of them is /// the same box in a new place, which the retained drawing follows without /// being made again. Both kinds of length: one the row resolves from a rule, /// and one it takes from what the child reported. #[test] fn a_row_moves_what_follows_a_child_that_grew_rather_than_drawing_it() { for declared in [false, true] { let mut h = Harness::new((400, 200)); let first = rect(Color::RED).width(50).add(&mut h.rsc); let ruled = Rc::new(Cell::new(0)); let second = Counted { draws: ruled.clone(), size: Size::LEFTOVER, reads_box: false, }; let second = match declared { true => second.width(rel(0.25)).add(&mut h.rsc), false => second.width(60).add(&mut h.rsc), }; let (third, reported) = counted(&mut h, Size::from((70, 20)), false); h.set_root((first, second, third).span(Dir::RIGHT).width(rel(1.0))); let (was_ruled, was_reported) = (ruled.get(), reported.get()); // A quarter of the row is a quarter of the row, wherever it sits in // it and whatever the first child takes. let width = match declared { true => 100, false => 60, }; assert_corners!(h, second, (50, 0), (50 + width, 200)); h.set_len(first, Axis::X, 80); h.frame(); assert_eq!(ruled.get(), was_ruled, "the ruled child was drawn again"); assert_eq!( reported.get(), was_reported, "the reported child was drawn again" ); assert_corners!(h, second, (80, 0), (80 + width, 200)); assert_corners!(h, third, (80 + width, 90), (150 + width, 110)); } } /// 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 + 1); } #[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 + 1, "width changes its answer"); } /// A window is measured onto the grid like everything else, so a resize too /// small to reach the next step is not a resize at all -- and one that does /// reach it is, however little of a pixel it is worth. #[test] fn a_resize_within_one_step_is_not_a_resize() { 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(); // All of these are 400 px to the nearest step. let step = Px::STEP.to_f32(); for part in [0.1, 0.2, 0.3] { h.resize((400.0 + step * part, 200.0)); h.frame(); assert_eq!(draws.get(), settled); } h.resize((400.0 + step, 200.0)); h.frame(); assert_eq!(draws.get(), settled + 1); } /// The same for a box that changes because a sibling did: what is compared /// is the length on the grid, and three lengths that land on one step are /// one length. #[test] fn a_box_change_within_one_step_is_not_a_change() { let mut h = Harness::new((400, 200)); let (first, draws, _) = pair(&mut h, true); let settled = draws.get(); let step = Px::STEP.to_f32(); for part in [0.1, 0.2, 0.3] { h.rsc[first].size.x = LayoutLen::px(100.0 + step * part); h.frame(); assert_eq!(draws.get(), settled); } h.rsc[first].size.x = LayoutLen::px(100.0 + step); 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)); } /// A retained drawing belongs to the layer it was made on: asked for again /// on another one it has to be drawn there, since nothing about its geometry /// says it is in a list that paints at a different moment. #[test] fn a_widget_asked_again_on_another_layer_is_drawn_there() { /// Draws its child on its own layer, then again one layer in -- which is /// what a container measuring a child by drawing it used to do. struct Twice(StrongWidget); impl Widget for Twice { fn draw(&mut self, painter: &mut Painter) -> Size { let size = painter.widget(&self.0).size(); painter.child_layer(); painter.widget(&self.0); size } } let mut h = Harness::new((400, 200)); let (front, draws) = counted(&mut h, Size::from((100, 50)), false); let outer = Twice(front.add_strong(&mut h.rsc)).add(&mut h.rsc); h.set_root(outer); h.frame(); assert_ne!( h.render.active[&front.id()].layer, h.render.active[&outer.id()].layer, "the first drawing was kept, on the layer it was measured on" ); assert_eq!(draws.get(), 2, "the second ask could not reuse the first"); } /// Which is why `Stack` measures the child that sizes it on the layer that /// child draws on: one drawing, above the background it stacks over, rather /// than one on each layer and the wrong one kept. #[test] fn a_stacks_sizing_child_is_drawn_once_where_it_belongs() { let mut h = Harness::new((400, 200)); let background = rect(Color::RED).add(&mut h.rsc); let (front, draws) = counted(&mut h, Size::from((100, 50)), false); let stack = Stack { children: vec![ background.add_strong(&mut h.rsc), front.add_strong(&mut h.rsc), ], size: StackSize::Child(1), } .add(&mut h.rsc); h.set_root(stack); h.frame(); let layer = |id| h.render.active[&id].layer; assert_ne!(layer(front.id()), layer(stack.id())); assert_ne!(layer(front.id()), layer(background.id())); assert_eq!(draws.get(), 1); } /// A widget's own mask is not the one it inherited, and a redraw of it /// inherits the second: handing back the first is handing it its own mask to /// set a second time, which `set_mask` asserts against. #[test] fn a_masked_widget_redrawn_on_its_own_sets_its_mask_again() { let mut h = Harness::new((400, 200)); let inner = rect(Color::BLUE).add(&mut h.rsc); let masked = inner.masked().add(&mut h.rsc); let other = rect(Color::RED).width(100).add(&mut h.rsc); h.set_root((other, masked).span(Dir::RIGHT)); h.rsc.widgets_mut().get_dyn_mut(masked.id()); h.frame(); assert_corners!(h, inner, (100, 0), (400, 200)); } /// The two spans a subtree changes hands between, and the branch that is not /// in the tree yet -- kept alive by the test until it is. struct Handover { leaf: WidgetId, first: WeakWidget, second: WeakWidget, root: WeakWidget, spare: StrongWidget, } /// A subtree that changes hands while its box does not move, so nothing about /// reusing its drawing says it changed parents. `deeper` puts a span between /// the root and `second`, so it changes depth by changing hands as well. fn plant_handover(h: &mut Harness, moved: bool, deeper: bool, width: f32) -> Handover { let leaf = rect(Color::RED).add(&mut h.rsc); let sized = leaf.width(width).add(&mut h.rsc); let holder = (sized,).span(Dir::RIGHT).add(&mut h.rsc); let first = Span { children: match moved { true => Vec::new(), false => vec![holder.add_strong(&mut h.rsc)], }, dir: Dir::RIGHT, gap: Px::ZERO, } .add(&mut h.rsc); let second = Span { children: match moved { true => vec![holder.add_strong(&mut h.rsc)], false => Vec::new(), }, dir: Dir::RIGHT, gap: Px::ZERO, } .add(&mut h.rsc); let branch = match deeper { true => (second,).span(Dir::RIGHT).add_strong(&mut h.rsc), false => second.add_strong(&mut h.rsc), }; let (in_tree, spare) = match moved { true => (branch, first.add_strong(&mut h.rsc)), false => (first.add_strong(&mut h.rsc), branch), }; let root = Span { children: vec![in_tree], dir: Dir::RIGHT, gap: Px::ZERO, } .add(&mut h.rsc); h.state.root = Some(root.add_strong(&mut h.rsc)); Handover { leaf: sized.id(), first, second, root, spare, } } /// Moves the subtree and swaps the branch it sits in for the one it left. fn hand_over(h: &mut Harness, tree: Handover) -> WidgetId { let holder = h.rsc[tree.first].children.remove(0); h.rsc[tree.second].children.push(holder); h.rsc[tree.root].children.clear(); h.rsc[tree.root].children.push(tree.spare); h.frame(); tree.leaf } #[test] fn a_subtree_that_changed_parents_is_not_undrawn_by_the_one_it_left() { let mut warm = Harness::new((400, 200)); let tree = plant_handover(&mut warm, false, false, 40.0); warm.frame(); let leaf = hand_over(&mut warm, tree); let mut cold = Harness::new((400, 200)); let grown = plant_handover(&mut cold, true, false, 40.0); cold.frame(); assert_eq!( warm.region(&leaf), cold.region(&grown.leaf), "the span it left still listed it and undrew it" ); } #[test] fn a_subtree_that_changed_parents_settles_at_the_depth_it_moved_to() { let mut warm = Harness::new((400, 200)); let tree = plant_handover(&mut warm, false, true, 40.0); warm.frame(); let leaf = hand_over(&mut warm, tree); // After it has changed hands, so what has to reach the new parent is a // change made under the subtree it now holds. warm.set_len(leaf, Axis::X, LayoutLen::px(90.0)); warm.frame(); let mut cold = Harness::new((400, 200)); let grown = plant_handover(&mut cold, true, true, 90.0); cold.frame(); assert_eq!( warm.region(&leaf), cold.region(&grown.leaf), "the span it moved to is the one the change has to reach" ); } fn primitive_bounds(h: &Harness, id: WidgetId) -> Vec { h.render.active[&id] .primitives .iter() .map(|primitive| { let handle = &primitive.handle; let instance = &h.render.layers[handle.layer].primitives()[handle.kind as usize] .as_ref() .unwrap() .instances()[handle.inst_idx]; h.render .moves .resolve(instance.move_idx, instance.region) .to_px(h.render.output_size()) }) .collect() } #[test] fn changing_an_inherited_region_keeps_the_original_measurement_offer() { fn build(h: &mut Harness, width: i32, text: &str) -> (WeakWidget, WeakWidget) { let first = rect(Color::RED).width(width).add(&mut h.rsc); let words = wtext(text).size(20).wrap(true).add(&mut h.rsc); let through = Stretchy { inner: words.add_strong(&mut h.rsc), draws: Rc::new(Cell::new(0)), } .add(&mut h.rsc); h.set_root((first, through).span(Dir::RIGHT)); (words, first) } let short = "one two"; let long = "one two three four five six seven eight nine ten eleven twelve"; let mut warm = Harness::new((400, 200)); let (words, first) = build(&mut warm, 50, short); warm.set_len(first, Axis::X, 200); warm.frame(); *warm.rsc[words].content = long.to_string(); warm.frame(); let mut cold = Harness::new((400, 200)); let (other, _) = build(&mut cold, 200, long); assert_eq!(warm.region(&words), cold.region(&other)); assert_eq!( primitive_bounds(&warm, words.id()), primitive_bounds(&cold, other.id()) ); } #[test] fn widening_text_without_soft_breaks_reuses_its_drawing() { struct CountedText { text: Text, draws: Rc>, } impl Widget for CountedText { fn draw(&mut self, painter: &mut Painter) -> Size { self.draws.set(self.draws.get() + 1); self.text.draw(painter) } } for content in ["Short text", "Two hard\nline breaks\nhere", ""] { let plant = |h: &mut Harness| { let mut text = Text::new(content); text.wrap = true; let draws = Rc::new(Cell::new(0)); let root = CountedText { text, draws: draws.clone(), } .add(&mut h.rsc); h.set_root(root); (root, draws) }; let mut warm = Harness::new((300, 200)); let (root, draws) = plant(&mut warm); let before = draws.get(); warm.resize((500, 200)); warm.frame(); assert_eq!(draws.get(), before, "{content:?}"); let mut cold = Harness::new((500, 200)); let (other, _) = plant(&mut cold); assert_eq!(warm.region(&root), cold.region(&other)); assert_eq!( primitive_bounds(&warm, root.id()), primitive_bounds(&cold, other.id()) ); } } #[test] fn resizing_a_fixed_frame_recomposes_its_contents_without_drawing_them() { struct Frame { child: StrongWidget, region: UiRegion, } impl Widget for Frame { fn draw(&mut self, painter: &mut Painter) -> Size { painter.widget_at( &self.child, PlaceDesc::new(self.region.x.shifted_desc(), self.region.y.shifted_desc()), ); Size::LEFTOVER } } struct Painted(Rc>); impl Widget for Painted { fn draw(&mut self, painter: &mut Painter) -> Size { self.0.set(self.0.get() + 1); painter.set_mask(UiRegion::FULL); painter.primitive(RectPrimitive::color(Color::BLUE)); Size::LEFTOVER } } let fixed = |start, end| UiRegion::new(UiSpan::new(Len::px(start), Len::px(end)), UiSpan::FULL); for node in [false, true] { let plant = |h: &mut Harness, region| { let draws = Rc::new(Cell::new(0)); let leaf = Painted(draws.clone()).add(&mut h.rsc); h.rsc.widgets_mut().set_region_node(leaf, node); let inner = Frame { child: leaf.add_strong(&mut h.rsc), region: UiRegion::new(UiSpan::new(Len::rel(0.23), Len::rel(0.83)), UiSpan::FULL), } .add_strong(&mut h.rsc); let root = Frame { child: inner, region, } .add(&mut h.rsc); h.set_root(root); (root, leaf, draws) }; let mut warm = Harness::new((400, 200)); let (root, leaf, draws) = plant(&mut warm, fixed(7.0, 104.0)); let before = draws.get(); warm.rsc[root].region = fixed(19.0, 180.0); warm.frame(); assert_eq!(draws.get(), before); let mut cold = Harness::new((400, 200)); let (_, other, _) = plant(&mut cold, fixed(19.0, 180.0)); assert_eq!(warm.region(&leaf), cold.region(&other)); assert_eq!( primitive_bounds(&warm, leaf.id()), primitive_bounds(&cold, other.id()) ); let mask = |h: &Harness, id: WidgetId| { let active = &h.render.active[&id]; let mask = &h.rsc.ui().masks[active.mask.idx()]; h.render .moves .resolve(mask.move_idx, mask.region) .to_px(h.render.output_size()) }; assert_eq!(mask(&warm, leaf.id()), mask(&cold, other.id())); } } #[test] fn glyph_origins_compose_identically_when_drawn_and_when_retained() { struct Glyphs { buffer: TextBuffer, draws: Rc>, } impl Widget for Glyphs { fn draw(&mut self, painter: &mut Painter) -> Size { self.draws.set(self.draws.get() + 1); let text = painter.render_text(&mut self.buffer, &TextAttrs::default(), None); let origin = UiRegion::new( UiSpan::new(Len::rel(0.23) + Len::px(-7.125), Len::FULL), UiSpan::new(Len::rel(0.37) + Len::px(3.25), Len::FULL), ); painter.glyphs(text, origin); Size::LEFTOVER } } struct Frame { child: StrongWidget, frame: UiRegion, region: UiRegion, } impl Widget for Frame { fn draw(&mut self, painter: &mut Painter) -> Size { painter.widget_at( &self.child, PlaceDesc::new( self.region.x.shifted_desc().fills(), self.region.y.shifted_desc().fills(), ) .rel_base(Axis::X, self.frame.x.len()), ); Size::LEFTOVER } } for node in [false, true] { let mut h = Harness::new((403, 211)); let draws = Rc::new(Cell::new(0)); let text = Glyphs { buffer: TextBuffer::new("Glyphs: gj AV\nsecond line"), draws: draws.clone(), } .add(&mut h.rsc); h.rsc.widgets_mut().set_region_node(text, node); let root = Frame { child: text.add_strong(&mut h.rsc), frame: UiRegion::FULL, region: UiRegion::FULL, } .add(&mut h.rsc); h.set_root(root); for (start, end) in [(0.13, 0.83), (-0.17, 1.23), (0.31, 0.67)] { let before = draws.get(); h.rsc[root].frame.x = UiSpan::new(Len::px(13.125), Len::px(287.375)); h.rsc[root].region = UiRegion::new( UiSpan::new(Len::rel(start), Len::rel(end)), UiSpan::new(Len::px(7.25), Len::rel(end)), ); h.frame(); assert_eq!(draws.get(), before); let retained = primitive_bounds(&h, text.id()); assert!(!retained.is_empty()); let _ = h.rsc.widgets_mut().get_dyn_mut(text.id()); h.frame(); assert!(draws.get() > before); assert_eq!(retained, primitive_bounds(&h, text.id())); } } } #[test] fn resizing_does_not_remeasure_a_fixed_stack_for_its_unmeasured_overlay() { let mut h = Harness::new((400, 200)); let (sizing, _) = counted(&mut h, Size::from((100, 80)), false); let (overlay, draws) = counted(&mut h, Size::LEFTOVER, true); h.set_root((sizing, overlay).stack().size(StackSize::Child(0))); let settled = draws.get(); h.resize((800, 300)); h.frame(); assert_eq!(draws.get(), settled); assert_corners!(h, overlay, (350, 110), (450, 190)); } struct Unmeasured { child: StrongWidget, draws: Rc>, } impl Widget for Unmeasured { fn draw(&mut self, painter: &mut Painter) -> Size { self.draws.set(self.draws.get() + 1); painter.widget(&self.child); Size::LEFTOVER } } #[test] fn a_declared_size_change_stops_at_an_independent_parent() { let mut h = Harness::new((400, 200)); let leaf = rect(Color::RED).width(100).add(&mut h.rsc); let parent = Unmeasured { child: leaf.add_strong(&mut h.rsc), draws: Rc::new(Cell::new(0)), } .add_strong(&mut h.rsc); let draws = Rc::new(Cell::new(0)); h.set_root(Unmeasured { child: parent, draws: draws.clone(), }); let settled = draws.get(); h.set_len(leaf, Axis::X, 150); h.frame(); assert_corners!(h, leaf, (125, 0), (275, 200)); assert_eq!(draws.get(), settled); } #[test] fn an_unmeasured_child_still_invalidates_its_parents_drawing_on_resize() { 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,).stack()); let settled = draws.get(); h.resize((800, 200)); h.frame(); assert!(draws.get() > settled); assert_corners!(h, leaf, (300, 90), (500, 110)); } #[test] fn changed_drawing_dependencies_reach_ancestors_without_a_size_change() { let mut h = Harness::new((400, 200)); let (leaf, draws) = counted(&mut h, Size::LEFTOVER, false); h.set_root(((leaf,).stack(),).stack()); h.rsc[leaf].reads_box = true; h.frame(); let settled = draws.get(); h.resize((800, 200)); h.frame(); assert_eq!(draws.get(), settled + 1); assert_corners!(h, leaf, (0, 0), (800, 200)); } #[test] fn widening_and_restoring_a_contract_does_not_invalidate_its_reader() { let mut h = Harness::new((400, 200)); let (leaf, leaf_draws) = counted(&mut h, Size::LEFTOVER, true); let draws = Rc::new(Cell::new(0)); let child = leaf.add_strong(&mut h.rsc); h.set_root(Unmeasured { child, draws: draws.clone(), }); let settled = draws.get(); for reads_box in [false, true, false, true] { h.rsc[leaf].reads_box = reads_box; h.frame(); assert_eq!(draws.get(), settled); } let settled = leaf_draws.get(); h.resize((800, 200)); h.frame(); assert_eq!(leaf_draws.get(), settled + 1); } #[test] fn padding_and_stack_boxes_follow_the_region_without_drawing_again() { struct Observed { widget: W, draws: Rc>, } impl Widget for Observed { fn draw(&mut self, painter: &mut Painter) -> Size { self.draws.set(self.draws.get() + 1); self.widget.draw(painter) } } struct Frame { child: StrongWidget, region: UiRegion, } impl Widget for Frame { fn draw(&mut self, painter: &mut Painter) -> Size { painter.widget_at( &self.child, PlaceDesc::new( self.region.x.shifted_desc().fills(), self.region.y.shifted_desc().fills(), ), ); Size::LEFTOVER } } for node in [false, true] { let plant = |h: &mut Harness, region| { let draws = Rc::new(Cell::new(0)); let leaf = rect(Color::BLUE).masked().add(&mut h.rsc); h.rsc.widgets_mut().set_region_node(leaf, node); let fixed = rect(Color::RED).width(31).height(19).add(&mut h.rsc); let stack = Observed { widget: Stack { children: vec![leaf.add_strong(&mut h.rsc), fixed.add_strong(&mut h.rsc)], size: StackSize::Default, }, draws: draws.clone(), } .add_strong(&mut h.rsc); let pad = Observed { widget: Pad { inner: stack, padding: Padding::uniform(7).with_left(13), }, draws: draws.clone(), } .add_strong(&mut h.rsc); let root = Frame { child: pad, region }.add(&mut h.rsc); h.set_root(root); (root, leaf, fixed, draws) }; // The same box in three places. A pad places its child as lengths of // its own box measured from where that box starts, so moving it is // nothing to the pad -- where changing its length is a different // question, and does draw it again. let at = |start: f32| { let span = |start: Len| UiSpan::new(start, start + Len::rel(0.4)); UiRegion::new(span(Len::rel(start) + Len::px(3.125)), span(Len::px(11.25))) }; let mut warm = Harness::new((403, 211)); let (root, leaf, fixed, draws) = plant(&mut warm, at(0.13)); for start in [0.13, -0.17, 0.31] { let region = at(start); let before = draws.get(); warm.rsc[root].region = region; warm.frame(); assert_eq!(draws.get(), before); let mut cold = Harness::new((403, 211)); let (_, other, other_fixed, _) = plant(&mut cold, region); for (a, b) in [(leaf.id(), other.id()), (fixed.id(), other_fixed.id())] { assert_eq!(warm.region(&a), cold.region(&b)); assert_eq!(primitive_bounds(&warm, a), primitive_bounds(&cold, b)); } let mask = |h: &Harness, id: WidgetId| { let active = &h.render.active[&id]; let mask = &h.rsc.ui().masks[active.mask.idx()]; h.render .moves .resolve(mask.move_idx, mask.region) .to_px(h.render.output_size()) }; assert_eq!(mask(&warm, leaf.id()), mask(&cold, other.id())); } } } #[test] fn moving_a_childs_region_preserves_the_slot_chosen_from_its_measurement() { struct Measured; impl Widget for Measured { fn draw(&mut self, painter: &mut Painter) -> Size { let width = painter.px_len(Axis::X); painter.primitive(RectPrimitive::color(Color::BLUE)); Size::from((80, if width > Px::from_int(100) { 40 } else { 60 })) } } struct Frame { child: StrongWidget, start: f32, } impl Widget for Frame { fn draw(&mut self, painter: &mut Painter) -> Size { painter.widget_at( &self.child, PlaceDesc::new( UiSpan::new(Len::px(self.start), Len::px(self.start + 200.0)) .shifted_desc() .fills(), UiSpan::FULL.shifted_desc().fills(), ), ); Size::LEFTOVER } } let mut h = Harness::new((400, 200)); let leaf = Measured.add(&mut h.rsc); let stack = (leaf,).stack().add_strong(&mut h.rsc); let root = Frame { child: stack, start: 0.0, } .add(&mut h.rsc); h.set_root(root); assert_corners!(h, leaf, (60, 80), (140, 120)); h.rsc[root].start = 30.0; h.frame(); assert_corners!(h, leaf, (90, 80), (170, 120)); assert_eq!( primitive_bounds(&h, leaf.id()), vec![h.region(&leaf).unwrap()] ); } #[test] fn changing_regions_keep_fractional_reports_and_numeric_dependencies_valid() { struct Container { child: StrongWidget, region: UiRegion, } impl Widget for Container { fn draw(&mut self, painter: &mut Painter) -> Size { painter .widget_at( &self.child, PlaceDesc::new(self.region.x.shifted_desc(), self.region.y.shifted_desc()), ) .size() } } struct Frame { child: StrongWidget, region: UiRegion, answer: Rc>, } impl Widget for Frame { fn draw(&mut self, painter: &mut Painter) -> Size { self.answer.set( painter .widget_at( &self.child, PlaceDesc::new( self.region.x.shifted_desc().fills(), self.region.y.shifted_desc().fills(), ), ) .size(), ); Size::LEFTOVER } } for fractional in [false, true] { for region in [ UiRegion::FULL, UiRegion::new(UiSpan::new(Len::rel(0.13), Len::rel(0.79)), UiSpan::FULL), ] { let plant = |h: &mut Harness, outer| { let size = if fractional { Size { x: rel(0.5), y: LayoutLen::px(27), } } else { Size::from((80, 27)) }; let (leaf, _) = counted(h, size, !fractional); let child = Container { child: leaf.add_strong(&mut h.rsc), region, } .add_strong(&mut h.rsc); let answer = Rc::new(Cell::new(Size::ZERO)); let root = Frame { child, region: outer, answer: answer.clone(), } .add(&mut h.rsc); h.set_root(root); (root, leaf, answer) }; let mut warm = Harness::new((403, 211)); let (root, leaf, answer) = plant(&mut warm, UiRegion::FULL); for width in [191.125, 297.25, 83.75] { let region = UiRegion::new(UiSpan::new(Len::px(13.125), Len::px(width)), UiSpan::FULL); warm.rsc[root].region = region; warm.frame(); let mut cold = Harness::new((403, 211)); let (_, other, other_answer) = plant(&mut cold, region); assert_eq!(answer.get(), other_answer.get()); assert_eq!(warm.region(&leaf), cold.region(&other)); } } } } struct OptionalMask { inner: StrongWidget, enabled: bool, } impl Widget for OptionalMask { fn draw(&mut self, painter: &mut Painter) -> Size { if self.enabled { painter.set_mask(UiRegion::FULL); } painter.widget(&self.inner); Size::LEFTOVER } } fn primitive_masks(h: &Harness, id: WidgetId) -> Vec { h.render.active[&id] .primitives .iter() .map(|primitive| { let handle = &primitive.handle; h.render.layers[handle.layer].primitives()[handle.kind as usize] .as_ref() .unwrap() .instances()[handle.inst_idx] .mask_idx }) .collect() } #[test] fn a_redrawn_mask_keeps_reused_primitives_clipped_when_it_moves() { for node in [false, true] { let mut h = Harness::new((400, 200)); let first = rect(Color::RED).height(50).add(&mut h.rsc); let inner = rect(Color::BLUE).add(&mut h.rsc); let draws = Rc::new(Cell::new(0)); let child = Stretchy { inner: inner.add_strong(&mut h.rsc), draws: draws.clone(), } .add(&mut h.rsc); let masked = child.masked().add(&mut h.rsc); h.rsc.widgets_mut().set_region_node(masked, node); h.set_root((first, masked).span(Dir::DOWN)); let mask = h.render.active[&masked.id()].mask; let settled = draws.get(); h.rsc.widgets_mut().get_dyn_mut(masked.id()); h.frame(); assert_eq!(primitive_masks(&h, inner.id()), vec![mask]); assert_eq!(draws.get(), settled, "a mask repaint must reuse its child"); assert_eq!(h.render.active[&masked.id()].mask, mask); h.set_len(first, Axis::Y, 10); h.frame(); let clip = h.rsc.ui().masks[mask.idx()]; let clip = h .render .moves .resolve(clip.move_idx, clip.region) .to_px(h.render.output_size()); assert_eq!(clip, h.region(&masked).unwrap()); assert_corners!(h, inner, (0, 10), (400, 200)); } } #[test] fn adding_and_removing_a_mask_updates_existing_primitives() { let mut h = Harness::new((400, 200)); let inner = rect(Color::BLUE).add(&mut h.rsc); let masked = OptionalMask { inner: inner.add_strong(&mut h.rsc), enabled: false, } .add(&mut h.rsc); h.set_root(masked); for enabled in [true, false, true, false] { h.rsc[masked].enabled = enabled; h.frame(); let mask = h.render.active[&masked.id()].mask; assert_eq!(mask == MaskIdx::NONE, !enabled); assert_eq!(primitive_masks(&h, inner.id()), vec![mask]); } assert_eq!(h.rsc.ui().masks.len(), 1, "retired slots must be reusable"); } #[test] fn an_empty_masks_slot_is_released_when_the_mask_is_removed_or_undrawn() { let mut h = Harness::new((400, 200)); let (inner, _) = counted(&mut h, Size::LEFTOVER, false); let masked = OptionalMask { inner: inner.add_strong(&mut h.rsc), enabled: true, } .add(&mut h.rsc); let row = (masked,).span(Dir::DOWN).add(&mut h.rsc); h.set_root(row); for _ in 0..3 { h.rsc[masked].enabled = false; h.frame(); h.rsc[masked].enabled = true; h.frame(); let child = h.rsc[row].pop().unwrap(); h.frame(); h.rsc[row].push(child); h.frame(); } assert_eq!(h.rsc.ui().masks.len(), 1); } struct SharedChild(Rc); impl Widget for SharedChild { fn draw(&mut self, painter: &mut Painter) -> Size { painter.widget(self.0.as_ref()).size() } } struct SwitchParent { choices: [StrongWidget; 2], choice: usize, } impl Widget for SwitchParent { fn draw(&mut self, painter: &mut Painter) -> Size { painter.widget(&self.choices[self.choice]).size() } } #[test] fn a_redrawn_subtree_is_not_undrawn_by_the_parent_it_left() { for node in [false, true] { let mut h = Harness::new((400, 200)); let leaf = rect(Color::RED).width(40).add(&mut h.rsc); let held: StrongWidget = leaf.add_strong(&mut h.rsc); let shared = Rc::new(held); let first = SharedChild(shared.clone()).add_strong(&mut h.rsc); let second = SharedChild(shared).add_strong(&mut h.rsc); h.rsc.widgets_mut().set_region_node(&second, node); let root = SwitchParent { choices: [first, second], choice: 0, } .add(&mut h.rsc); h.set_root(root); let before = h.region(&leaf); h.rsc[root].choice = 1; h.frame(); assert_eq!(h.region(&leaf), before); } }