A subtree can be reused whole under a different parent -- same box, same layer, same region node, clean -- and nothing in the drawing says it moved. Two things read who its parent is, and both were wrong after one of these. The old parent still listed it as a child, and a parent's next draw undraws whatever is missing from that list: two spans under one root, with the root swapping which of them it holds, drew the subtree under the new span and then erased it when the old one drew. The move is recorded on both sides where `draw_inner` already writes what the ask decided, rather than guarded at each reader. Its depth was also the one it had under the old parent, which is what the settling walk orders by, so a change made under it afterwards settled at the wrong point in the frame. `try_reuse` re-walks the subtree's depths, and only where the top of it moved, which is what makes that free in the ordinary case. Two tests: one shape where the subtree's box does not move and the span it left erases it, one where it changes depth and the change made under it has to reach the span it moved to. Each fails without one half.
740 lines
23 KiB
Rust
740 lines
23 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(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);
|
|
// 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::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, 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::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));
|
|
}
|
|
|
|
/// 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");
|
|
}
|
|
|
|
/// 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 + 2);
|
|
}
|
|
|
|
/// 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<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));
|
|
}
|
|
|
|
/// 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<Span>,
|
|
second: WeakWidget<Span>,
|
|
root: WeakWidget<Span>,
|
|
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"
|
|
);
|
|
}
|