Files
iris/tests/retained.rs
T

358 lines
11 KiB
Rust

//! 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.
struct Counted {
draws: Rc<Cell<usize>>,
size: Size,
dependence: OnResize,
}
impl Widget for Counted {
fn draw(&mut self, _: &mut Painter) -> Size {
self.draws.set(self.draws.get() + 1);
self.size
}
fn on_resize(&self, _: Axis) -> OnResize {
self.dependence
}
}
struct Counts(Rc<Cell<usize>>);
impl Counts {
fn get(&self) -> usize {
self.0.get()
}
}
fn counted(h: &mut Harness, size: Size, dependence: OnResize) -> (WeakWidget<Counted>, Counts) {
let draws = Rc::new(Cell::new(0));
let id = Counted {
draws: draws.clone(),
size,
dependence,
}
.add(&mut h.rsc);
(id, Counts(draws))
}
/// A fixed-width leaf beside one that takes the rest, so changing the first
/// hands the second a different box without the output changing.
fn pair(h: &mut Harness, rest: OnResize) -> (WeakWidget<Counted>, Counts, WidgetId) {
let (first, _) = counted(h, Size::from((100, 200)), OnResize::Translate);
let (second, draws) = counted(h, Size::REST, rest);
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, OnResize::Scale);
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 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 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 place.
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)), OnResize::Translate);
let (asked, asked_draws) = counted(&mut h, Size::from((100, 200)), OnResize::Translate);
// 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);
h.set_root((hinted, asked).span(Dir::RIGHT));
assert_eq!(told_draws.get(), 1);
assert_eq!(
asked_draws.get(),
2,
"drawn to be measured, then again to be placed"
);
}
#[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);
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)), OnResize::Translate);
let (second, _) = counted(&mut h, Size::REST, OnResize::Translate);
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_placed_child_survives_the_next_frame() {
let mut h = Harness::new((400, 200));
// Both children declare a length, so the span places them from their 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.abs);
painter.widget_within(&self.inner, region);
Size::REST
}
}
#[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.rsc[inner].y = Some(Len::abs(120));
h.frame();
assert_corners!(h, inner, (0, 0), (400, 120));
}
/// Reads the output's size, which nothing but its own draw can put right.
struct ReadsOutput {
draws: Rc<Cell<usize>>,
}
impl Widget for ReadsOutput {
fn draw(&mut self, painter: &mut Painter) -> Size {
self.draws.set(self.draws.get() + 1);
Size::abs(painter.output_size() / 4.0)
}
}
#[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::REST, OnResize::Redraw);
h.set_root(leaf);
let settled = draws.get();
h.resize((800, 100));
assert!(h.needs_redraw());
h.frame();
assert_eq!(
draws.get(),
settled,
"its box is the same fraction of a different output"
);
assert_corners!(h, leaf, (0, 0), (800, 100));
}
#[test]
fn a_resize_redraws_what_read_the_output() {
let mut h = Harness::new((400, 200));
let draws = Rc::new(Cell::new(0));
let leaf = ReadsOutput {
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 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::abs((100, 100).into()), OnResize::Redraw);
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::abs((100, 200).into());
h.frame();
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.
struct Stretchy {
inner: StrongWidget,
draws: Rc<Cell<usize>>,
}
impl Widget for Stretchy {
fn draw(&mut self, painter: &mut Painter) -> Size {
self.draws.set(self.draws.get() + 1);
painter.widget(&self.inner).size()
}
fn on_resize(&self, _: Axis) -> OnResize {
OnResize::Scale
}
}
#[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.rsc[first].y = Some(Len::abs(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::REST, OnResize::Redraw);
let (backing, back_draws) = counted(&mut h, Size::REST, OnResize::Scale);
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.rsc[bar].x = Some(Len::abs(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)), 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);
h.set_root((bar, row).span(Dir::RIGHT));
let settled = draws.get();
h.rsc[bar].x = Some(Len::abs(200));
h.frame();
assert_eq!(draws.get(), settled, "its own length did not change");
assert_corners!(h, fixed, (200, 0), (280, 200));
}