A widget is asked in two boxes rather than one. Its frame is what a fraction it declares or reports is a fraction of, and it passes through a span, a stack and a scroll unchanged, so `rel(0.5)` is half the same area however many containers sit between: a frame is narrowed only by what is decided above the widget -- a declared length, the root. Its extent is where the drawing goes, given as a `Place` per axis: a part of the parent's own box, measured in frame lengths from where that box starts, which the child either fills or has its answer placed inside. What that buys is that nothing under a container depends on where the container sits. A container reads `extent_len` for the length it divides and nothing about the start, so moving it re-places its children by re-adding that start and draws nobody again; and a fraction is resolved once, against the frame, rather than once per box it is composed through -- a stack sized by a child that reports `rel(0.5)` no longer takes half of half. `Place` replaces `DrawRegion`, `ExtentPlacement`, `widget_within`, `measure_len`, `region()`, `placement()` and `box_of`. Primitives and masks are written in the widget's own box's coordinates alone, so the drawing has one reference rather than two. The placement pin goes with them: reading the extent's length pins that length symbolically, and pins compose only where a child's box is its parent's own. Placing an answer waits for the end of the parent's draw or for the next ask of that child in it, so a span child is one drawing and one move rather than two moves. `Pad` is transparent: its padding goes around what it pads and its child keeps the outer frame, which is where `Outset` was going anyway. A fraction under a pad is now a fraction of the frame rather than of the inset box. Checked: fmt, clippy with -D warnings, 109 suite tests and 20 core tests in debug, the 11 generated cases, and the shrinker at 400 trees of depth 5 over all fifteen cases -- which still finds seed 108 under `reorder`, where a wrapping text measured in one box and drawn in another settles differently warm than cold. `redraw` therefore keeps the baseline's deferral for a box that is not as long as the one the widget was measured in; the plan's step 6 is not done, and the next commit message or the handoff says why.
1383 lines
45 KiB
Rust
1383 lines
45 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 top = UiSpan::new(Len::ZERO, Len::from_parts(Rel::ZERO, len.px));
|
|
painter.widget_at(
|
|
&self.inner,
|
|
UiRegion::FULL,
|
|
[Place::Within(None), Place::Within(Some(top))],
|
|
);
|
|
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));
|
|
}
|
|
|
|
/// 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 + 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 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_at(
|
|
&self.child,
|
|
UiRegion::FULL,
|
|
[
|
|
Place::Within(Some(self.region.x)),
|
|
Place::Within(Some(self.region.y)),
|
|
],
|
|
);
|
|
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 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, 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,
|
|
[
|
|
Place::Fill(Some(self.extent.x)),
|
|
Place::Fill(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_boxes_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,
|
|
[
|
|
Place::Fill(Some(self.extent.x)),
|
|
Place::Fill(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)
|
|
};
|
|
// 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 extent = at(start);
|
|
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,
|
|
[
|
|
Place::Fill(Some(UiSpan::new(
|
|
Len::px(self.start),
|
|
Len::px(self.start + 200.0),
|
|
))),
|
|
Place::Fill(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_at(
|
|
&self.child,
|
|
UiRegion::FULL,
|
|
[
|
|
Place::Within(Some(self.region.x)),
|
|
Place::Within(Some(self.region.y)),
|
|
],
|
|
)
|
|
.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,
|
|
[
|
|
Place::Fill(Some(self.extent.x)),
|
|
Place::Fill(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));
|
|
}
|
|
}
|
|
}
|
|
}
|