358 lines
11 KiB
Rust
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));
|
|
}
|