`redraw` keeps the narrower of an old and a fresh contract so that widening and narrowing back do not churn the parent that reads it. The drawing's half asked whether the old range still covers this window and box before keeping it; the answer's half did not, so a widget whose answer contract widened in a frame that also resized the window kept a range the new window is outside. The parent's next ask then refuses that answer and draws the whole subtree again -- throwing away the drawing the widget had just made. Cost, not geometry: the size kept is the size just reported. `a_contract_this_window_is_outside_is_not_kept` draws the leaf twice before the change and once after.
1576 lines
51 KiB
Rust
1576 lines
51 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::FULL), 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().on_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<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. 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<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));
|
|
}
|
|
|
|
/// 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<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"
|
|
);
|
|
}
|
|
|
|
fn primitive_bounds(h: &Harness, id: WidgetId) -> Vec<PixelRegion> {
|
|
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<Text>, WeakWidget<Rect>) {
|
|
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<Cell<usize>>,
|
|
}
|
|
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<Cell<usize>>);
|
|
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<Cell<usize>>,
|
|
}
|
|
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<Cell<usize>>,
|
|
}
|
|
|
|
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<W> {
|
|
widget: W,
|
|
draws: Rc<Cell<usize>>,
|
|
}
|
|
impl<W: Widget> Widget for Observed<W> {
|
|
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<Cell<Size>>,
|
|
}
|
|
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<MaskIdx> {
|
|
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<StrongWidget>);
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// A leaf that reports less than the box it is given and states which lengths
|
|
/// of that box its drawing holds for, so a test can widen the contract
|
|
/// without changing the answer. It counts its draws, since what a kept
|
|
/// contract costs is whether the parent has to make it draw again.
|
|
struct Contracted {
|
|
holds: std::ops::RangeInclusive<Px>,
|
|
size: Size,
|
|
draws: Rc<Cell<usize>>,
|
|
}
|
|
|
|
impl Widget for Contracted {
|
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
|
self.draws.set(self.draws.get() + 1);
|
|
painter.holds(Axis::X, self.holds.clone());
|
|
self.size
|
|
}
|
|
}
|
|
|
|
struct CountedParent {
|
|
inner: StrongWidget,
|
|
draws: Rc<Cell<usize>>,
|
|
}
|
|
|
|
impl Widget for CountedParent {
|
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
|
self.draws.set(self.draws.get() + 1);
|
|
painter.widget(&self.inner).size()
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn widening_what_a_drawing_holds_for_does_not_relay_out_the_parent() {
|
|
let mut h = Harness::new((400, 200));
|
|
let child = Contracted {
|
|
holds: Px::from_int(300)..=Px::from_int(500),
|
|
size: Size::from((100, 200)),
|
|
draws: Rc::new(Cell::new(0)),
|
|
}
|
|
.add(&mut h.rsc);
|
|
let draws = Rc::new(Cell::new(0));
|
|
let root = CountedParent {
|
|
inner: child.upgrade(&mut h.rsc),
|
|
draws: draws.clone(),
|
|
}
|
|
.add(&mut h.rsc);
|
|
h.set_root(root);
|
|
let settled = draws.get();
|
|
|
|
// The same answer, good for more boxes than before, so the guarantee the
|
|
// parent kept still holds.
|
|
h.rsc[child].holds = Px::from_int(200)..=Px::from_int(600);
|
|
h.frame();
|
|
|
|
assert_eq!(
|
|
draws.get(),
|
|
settled,
|
|
"a wider contract for the same answer is not a change to lay out"
|
|
);
|
|
}
|
|
|
|
/// The other half of the rule above: a kept contract is the narrower one, so
|
|
/// it is only worth keeping where it still holds. A window the old range is
|
|
/// outside is not one its parent can be handed back, and keeping it there
|
|
/// throws away the drawing the widget just made.
|
|
#[test]
|
|
fn a_contract_this_window_is_outside_is_not_kept() {
|
|
let mut h = Harness::new((400, 200));
|
|
let leaf_draws = Rc::new(Cell::new(0));
|
|
let child = Contracted {
|
|
holds: Px::from_int(300)..=Px::from_int(500),
|
|
size: Size::from((100, 200)),
|
|
draws: leaf_draws.clone(),
|
|
}
|
|
.add(&mut h.rsc);
|
|
let root = CountedParent {
|
|
inner: child.upgrade(&mut h.rsc),
|
|
draws: Rc::new(Cell::new(0)),
|
|
}
|
|
.add(&mut h.rsc);
|
|
h.set_root(root);
|
|
|
|
// Wide enough that the old contract leaves the new box out, and the leaf
|
|
// is marked in the same frame -- so it settles itself first and its
|
|
// parent draws afterwards, asking about what it settled.
|
|
h.resize((600, 200));
|
|
h.rsc[child].holds = Px::from_int(200)..=Px::from_int(700);
|
|
let settled = leaf_draws.get();
|
|
h.frame();
|
|
|
|
assert_eq!(
|
|
leaf_draws.get(),
|
|
settled + 1,
|
|
"the leaf settled once and its parent kept what it settled"
|
|
);
|
|
}
|