`Pad` and `Stack` read `Painter::placement` to put their children inside their own drawing, and reading it is what says the drawing holds for that placement alone. So a pad or a stack anywhere in a row was drawn again -- with its whole subtree -- the moment an earlier sibling changed length, however little else had moved. `widget_within` now takes a `DrawRegion`, and `DrawRegion::Extent(part)` gives the child a part of the extent without reading it. What is retained is the part rather than the box it resolved to, so moving the extent re-places the child through the same rule instead of redrawing the parent: `inherited_children` becomes `extent_children`, carrying `Inherit` for the wrapper case `Painter::widget` already had and `Within(part)` for the new one. The dependency that goes up is a range on the container's extent rather than on its frame, since only the part's *length* reaches the child and where the part sits is re-placed. A declared length is unchanged: it is a length of the frame wherever the box it sits in came from. What still pins the placement is a report with a fraction in it -- the same fraction of a different extent is a different length -- and that pin is on the answer, which `extent_frames_keep_fractional_reports_and_numeric_dependencies_valid` fails without. Three tests from the first attempt at this come with it, and the diagnostics rig now says which of the three contracts refused a reuse, which is what found the above. Measured, seed 1 at depth 8, median frame: `many` 0.667 -> 0.613 ms and `resize` 48 -> 32 us; seed 13's `many` 6.35 -> 5.15 ms. Green: fmt, clippy, 109 suite and 20 core tests, the oracle at 100 seeds, the shrinker at 400 trees of depth 5, 1000 seeds at depth 6, and 2000 seeds at depth 4 over all fifteen cases. The five reference renders are byte-identical to `0e107f0` on Venus, as are `tabs` resized to 900x1200 and `random` to 1280x800 against cold renders there. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1338 lines
43 KiB
Rust
1338 lines
43 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);
|
|
// Only the available length changes: positioning the final slot does
|
|
// not invalidate a numeric size read.
|
|
assert_eq!(asked_draws.get(), 2);
|
|
}
|
|
|
|
#[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"
|
|
);
|
|
}
|
|
|
|
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 frame_geometry_and_extent_geometry_keep_their_references() {
|
|
struct Both(Rc<Cell<usize>>);
|
|
impl Widget for Both {
|
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
|
self.0.set(self.0.get() + 1);
|
|
painter.primitive_within(RectPrimitive::color(Color::RED), UiRegion::FULL);
|
|
painter.primitive(RectPrimitive::color(Color::BLUE));
|
|
Size::LEFTOVER
|
|
}
|
|
}
|
|
for node in [false, true] {
|
|
let mut h = Harness::new((400, 200));
|
|
let first = rect(Color::GREEN).width(100).add(&mut h.rsc);
|
|
let draws = Rc::new(Cell::new(0));
|
|
let both = Both(draws.clone()).add(&mut h.rsc);
|
|
h.rsc.widgets_mut().set_region_node(both, node);
|
|
h.set_root((first, both).span(Dir::RIGHT));
|
|
let count = draws.get();
|
|
h.set_len(first, Axis::X, 200);
|
|
h.frame();
|
|
assert_eq!(draws.get(), count);
|
|
let bounds = primitive_bounds(&h, both.id());
|
|
assert_eq!(bounds[0].top_left.x, Px::ZERO);
|
|
assert_eq!(bounds[0].bot_right.x, Px::from_int(400));
|
|
assert_eq!(bounds[1].top_left.x, Px::from_int(200));
|
|
assert_eq!(bounds[1].bot_right.x, Px::from_int(400));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn changing_an_inherited_extent_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_within(&self.child, self.region);
|
|
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(DrawRegion::Extent(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 a_span_does_not_place_its_measurement_before_assigning_the_childs_slot() {
|
|
struct MeasuredBox(Rc<Cell<usize>>);
|
|
|
|
impl Widget for MeasuredBox {
|
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
|
self.0.set(self.0.get() + 1);
|
|
painter.px_size();
|
|
painter.primitive(RectPrimitive::color(Color::BLUE));
|
|
Size::from((100, 50))
|
|
}
|
|
}
|
|
|
|
let mut h = Harness::new((400, 200));
|
|
let draws = Rc::new(Cell::new(0));
|
|
let leaf = MeasuredBox(draws.clone()).add(&mut h.rsc);
|
|
h.set_root((leaf,).span(Dir::RIGHT).width(rel(1.0)).height(rel(1.0)));
|
|
|
|
assert_eq!(draws.get(), 3);
|
|
assert_corners!(h, leaf, (0, 75), (100, 125));
|
|
assert_eq!(
|
|
primitive_bounds(&h, leaf.id()),
|
|
vec![h.region(&leaf.id()).unwrap()]
|
|
);
|
|
h.frame();
|
|
assert_eq!(draws.get(), 3);
|
|
|
|
h.resize((600, 300));
|
|
h.frame();
|
|
assert_eq!(draws.get(), 6);
|
|
assert_corners!(h, leaf, (0, 125), (100, 175));
|
|
assert_eq!(
|
|
primitive_bounds(&h, leaf.id()),
|
|
vec![h.region(&leaf.id()).unwrap()]
|
|
);
|
|
}
|
|
|
|
#[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, DrawRegion::Frame(origin));
|
|
painter.glyphs(text, DrawRegion::Extent(origin));
|
|
Size::LEFTOVER
|
|
}
|
|
}
|
|
struct Frame {
|
|
child: StrongWidget,
|
|
region: UiRegion,
|
|
extent: UiRegion,
|
|
}
|
|
impl Widget for Frame {
|
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
|
painter.widget_at(
|
|
&self.child,
|
|
self.region,
|
|
[Some(self.extent.x), Some(self.extent.y)],
|
|
);
|
|
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),
|
|
region: UiRegion::FULL,
|
|
extent: 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].region.x = UiSpan::new(Len::px(13.125), Len::px(287.375));
|
|
h.rsc[root].extent = 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_frames_follow_the_extent_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,
|
|
extent: UiRegion,
|
|
}
|
|
impl Widget for Frame {
|
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
|
painter.widget_at(
|
|
&self.child,
|
|
UiRegion::FULL,
|
|
[Some(self.extent.x), Some(self.extent.y)],
|
|
);
|
|
Size::LEFTOVER
|
|
}
|
|
}
|
|
for node in [false, true] {
|
|
let plant = |h: &mut Harness, extent| {
|
|
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, extent }.add(&mut h.rsc);
|
|
h.set_root(root);
|
|
(root, leaf, fixed, draws)
|
|
};
|
|
let mut warm = Harness::new((403, 211));
|
|
let (root, leaf, fixed, draws) = plant(&mut warm, UiRegion::FULL);
|
|
for (start, end) in [(0.13, 0.83), (-0.17, 1.23), (0.31, 0.67)] {
|
|
let extent = UiRegion::new(
|
|
UiSpan::new(Len::rel(start) + Len::px(3.125), Len::rel(end)),
|
|
UiSpan::new(Len::px(11.25), Len::rel(end)),
|
|
);
|
|
let before = draws.get();
|
|
warm.rsc[root].extent = extent;
|
|
warm.frame();
|
|
assert_eq!(draws.get(), before);
|
|
let mut cold = Harness::new((403, 211));
|
|
let (_, other, other_fixed, _) = plant(&mut cold, extent);
|
|
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_an_extent_child_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,
|
|
UiRegion::FULL,
|
|
[
|
|
Some(UiSpan::new(
|
|
Len::px(self.start),
|
|
Len::px(self.start + 200.0),
|
|
)),
|
|
Some(UiSpan::FULL),
|
|
],
|
|
);
|
|
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 extent_frames_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_within(&self.child, DrawRegion::Extent(self.region))
|
|
.size()
|
|
}
|
|
}
|
|
struct Frame {
|
|
child: StrongWidget,
|
|
extent: 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,
|
|
UiRegion::FULL,
|
|
[Some(self.extent.x), Some(self.extent.y)],
|
|
)
|
|
.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, extent| {
|
|
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,
|
|
extent,
|
|
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 extent =
|
|
UiRegion::new(UiSpan::new(Len::px(13.125), Len::px(width)), UiSpan::FULL);
|
|
warm.rsc[root].extent = extent;
|
|
warm.frame();
|
|
let mut cold = Harness::new((403, 211));
|
|
let (_, other, other_answer) = plant(&mut cold, extent);
|
|
assert_eq!(answer.get(), other_answer.get());
|
|
assert_eq!(warm.region(&leaf), cold.region(&other));
|
|
}
|
|
}
|
|
}
|
|
}
|