553 lines
17 KiB
Rust
553 lines
17 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. One that reads its box in
|
|
/// pixels has a drawing that holds for that box alone.
|
|
struct Counted {
|
|
draws: Rc<Cell<usize>>,
|
|
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<Cell<usize>>);
|
|
|
|
impl Counts {
|
|
fn get(&self) -> usize {
|
|
self.0.get()
|
|
}
|
|
}
|
|
|
|
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,
|
|
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, 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())
|
|
}
|
|
|
|
#[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<Cell<usize>>,
|
|
}
|
|
|
|
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<Cell<usize>>,
|
|
}
|
|
|
|
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<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()
|
|
}
|
|
}
|
|
|
|
#[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));
|
|
}
|