`UiScalar` is `Rel` beside `Px` rather than two floats, so composing a position down a chain of boxes adds exactly and rounds only at the two multiplies `within` makes. `UiSpan`, `UiRegion` and `UiVec2` follow it, the hand-written `Hash` goes away with the bits it hashed, and `impl_op!` grows a `same` form for a type whose fields are not the same kind of number. `Len` is still floats, so the seam converts: `Px::from_f32` where a span adds a child's length to its cursor, and `to_f32` where something outside layout wants pixels. Those go when `Len` follows. The GPU reads what the CPU wrote: the instance attributes are `Sint32x2` and the shader decodes by `1/64` and `1/2^24`, both exact in `f32`, then composes the move chain in floats as before. It has to agree with itself frame to frame rather than with the CPU to the last bit. Two things fell out of making the numbers exact. `floor` at the rasteriser was picking the pixel below wherever a fraction divided a window exactly. A fifth of 1920 is 383.99998 through a rounded `Rel` -- and was 384.0 through an `f32` that happened to round up -- so five tabs each lost their last column. `snap_floor` takes a coordinate within half a step of a boundary to be on it, which is the same rule as everywhere else here: decide where values do not land. A widget measured on one layer and drawn again on another kept the first layer, because `try_reuse` compared everything about a retained drawing except which list it sits in. `Stack` does exactly that for its background, so every panel's text went under its own background. It only worked before because the two asks differed by a rounding and forced a redraw; `tests/retained.rs` pins it now, and `ReuseOutcome` can say `WrongLayer`. Checked: fmt, clippy, 100 tests, 100 generated seeds in 70 s, all five shrinker cases at 300 seeds. `tabs`, `view` and `minimal` render byte-identical at 1920x1200; `random` differs in 36 pixels by one level; `text` differs where glyph origins moved onto the grid -- same positions, same spacing, different subpixel coverage, checked at 6x against the old render. Measured on the way: with the fuzzer comparing for *equality* rather than within 0.05 px, `resize`, `repaint` and `size-change` already pass 100 seeds. `reorder` fails one seed by exactly one step, which is the `Len` seam above. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
586 lines
18 KiB
Rust
586 lines
18 KiB
Rust
//! What a second frame draws again, and what it keeps.
|
|
|
|
use std::{cell::Cell, rc::Rc};
|
|
|
|
use iris::harness::{Harness, assert_corners};
|
|
use iris::prelude::*;
|
|
|
|
/// A leaf that counts its draws and reports whatever size it is given, so a
|
|
/// test can see what the retained path skipped. One that reads its box in
|
|
/// pixels has a drawing that holds for that box alone.
|
|
struct Counted {
|
|
draws: Rc<Cell<usize>>,
|
|
size: Size,
|
|
reads_box: bool,
|
|
}
|
|
|
|
impl Widget for Counted {
|
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
|
self.draws.set(self.draws.get() + 1);
|
|
if self.reads_box {
|
|
painter.px_size();
|
|
}
|
|
self.size
|
|
}
|
|
}
|
|
|
|
struct Counts(Rc<Cell<usize>>);
|
|
|
|
impl Counts {
|
|
fn get(&self) -> usize {
|
|
self.0.get()
|
|
}
|
|
}
|
|
|
|
fn counted(h: &mut Harness, size: Size, reads_box: bool) -> (WeakWidget<Counted>, Counts) {
|
|
let draws = Rc::new(Cell::new(0));
|
|
let id = Counted {
|
|
draws: draws.clone(),
|
|
size,
|
|
reads_box,
|
|
}
|
|
.add(&mut h.rsc);
|
|
(id, Counts(draws))
|
|
}
|
|
|
|
struct Layered {
|
|
children: [StrongWidget<Rect>; 2],
|
|
_revision: usize,
|
|
}
|
|
|
|
impl Widget for Layered {
|
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
|
painter.child_layer();
|
|
painter.widget(&self.children[0]);
|
|
painter.next_layer();
|
|
painter.widget(&self.children[1]);
|
|
Size::default()
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn a_redrawn_layered_widget_keeps_the_layer_it_was_entered_on() {
|
|
let mut h = Harness::new((400, 200));
|
|
let children = [
|
|
rect(Color::RED).add_strong(&mut h.rsc),
|
|
rect(Color::BLUE).add_strong(&mut h.rsc),
|
|
];
|
|
let root = Layered {
|
|
children,
|
|
_revision: 0,
|
|
}
|
|
.add(&mut h.rsc);
|
|
h.set_root(root);
|
|
|
|
h.rsc[root]._revision += 1;
|
|
h.frame();
|
|
|
|
let label = h.rsc.widgets().label(root.id());
|
|
let active = h
|
|
.render
|
|
.debug(h.rsc.widgets(), label)
|
|
.find(|active| active.id == root.id())
|
|
.unwrap();
|
|
assert_eq!(active.layer, 0);
|
|
}
|
|
|
|
/// A fixed-width leaf beside one that takes what is left over, so changing
|
|
/// the first hands the second a different box without the output changing.
|
|
fn pair(h: &mut Harness, reads_box: bool) -> (WeakWidget<Counted>, Counts, WidgetId) {
|
|
let (first, _) = counted(h, Size::from((100, 200)), false);
|
|
let (second, draws) = counted(h, Size::LEFTOVER, reads_box);
|
|
h.set_root((first, second).span(Dir::RIGHT));
|
|
(first, draws, second.id())
|
|
}
|
|
|
|
#[test]
|
|
fn a_leaf_that_ignores_its_box_is_not_drawn_again_when_the_box_changes() {
|
|
let mut h = Harness::new((400, 200));
|
|
let (first, draws, second) = pair(&mut h, false);
|
|
let settled = draws.get();
|
|
assert_corners!(h, second, (100, 0), (400, 200));
|
|
|
|
h.rsc[first].size = Size::from((150, 200));
|
|
h.frame();
|
|
|
|
assert_eq!(
|
|
draws.get(),
|
|
settled,
|
|
"its box is a field to write, not a reason to draw"
|
|
);
|
|
assert_corners!(h, second, (150, 0), (400, 200));
|
|
}
|
|
|
|
#[test]
|
|
fn moving_an_ordinary_subtree_remaps_its_mask() {
|
|
let mut h = Harness::new((400, 200));
|
|
let (first, _) = counted(&mut h, Size::from((100, 200)), false);
|
|
let inner = rect(Color::BLUE).add(&mut h.rsc);
|
|
let masked = inner.masked().add(&mut h.rsc);
|
|
h.set_root((first, masked).span(Dir::RIGHT));
|
|
|
|
h.rsc[first].size = Size::from((150, 200));
|
|
h.frame();
|
|
|
|
let active = &h.render.active[&masked.id()];
|
|
assert_eq!(
|
|
h.rsc.ui().masks[active.mask.idx()].region,
|
|
UiRegion::new(
|
|
UiSpan::new(UiScalar::px(150.0), UiScalar::rel_max()),
|
|
UiSpan::FULL,
|
|
)
|
|
);
|
|
assert_corners!(h, inner, (150, 0), (400, 200));
|
|
}
|
|
|
|
#[test]
|
|
fn a_leaf_that_depends_on_its_box_is_drawn_again_when_the_box_changes() {
|
|
let mut h = Harness::new((400, 200));
|
|
let (first, draws, second) = pair(&mut h, true);
|
|
let settled = draws.get();
|
|
|
|
h.rsc[first].size = Size::from((150, 200));
|
|
h.frame();
|
|
|
|
// The preceding fixed child makes the remaining box this child's real
|
|
// box, so measuring it also draws it in its final box.
|
|
assert_eq!(draws.get(), settled + 1);
|
|
assert_corners!(h, second, (150, 0), (400, 200));
|
|
}
|
|
|
|
#[test]
|
|
fn a_span_child_that_declares_its_length_is_drawn_once() {
|
|
let mut h = Harness::new((400, 200));
|
|
let (told, told_draws) = counted(&mut h, Size::from((100, 200)), false);
|
|
let (asked, asked_draws) = counted(&mut h, Size::from((100, 200)), true);
|
|
// The span takes one child's length from its hint and has to draw the
|
|
// other to find out, so only the second is drawn before its final box.
|
|
let hinted = told.width(100).add(&mut h.rsc);
|
|
h.set_root((hinted, asked).span(Dir::RIGHT));
|
|
|
|
assert_eq!(told_draws.get(), 1);
|
|
// Reading its box makes its drawing hold for the measuring box alone,
|
|
// and it reports less than that box: so it is drawn again in the box its
|
|
// answer places it in, and once more in the final box the span chooses.
|
|
// A widget that says what it holds for, as text does, skips the middle
|
|
// one.
|
|
assert_eq!(
|
|
asked_draws.get(),
|
|
3,
|
|
"drawn to be measured, in its placed box, then in its final box"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn a_span_relays_out_when_a_child_it_measured_changes() {
|
|
let mut h = Harness::new((400, 200));
|
|
let (first, _, second) = pair(&mut h, false);
|
|
|
|
h.rsc[first].size = Size::from((250, 200));
|
|
h.frame();
|
|
|
|
assert_corners!(h, first, (0, 0), (250, 200));
|
|
assert_corners!(h, second, (250, 0), (400, 200));
|
|
}
|
|
|
|
#[test]
|
|
fn a_repaint_that_keeps_its_size_does_not_relay_out() {
|
|
let mut h = Harness::new((400, 200));
|
|
let (first, draws) = counted(&mut h, Size::from((100, 200)), false);
|
|
let (second, _) = counted(&mut h, Size::LEFTOVER, false);
|
|
h.set_root((first, second).span(Dir::RIGHT));
|
|
let settled = draws.get();
|
|
|
|
// Taking mutable access is the ordinary content-change signal. This
|
|
// widget returns the same size, so the parent has nothing to lay out.
|
|
let _ = h.rsc.widgets_mut().get_dyn_mut(first.id());
|
|
h.frame();
|
|
|
|
assert_eq!(draws.get(), settled + 1);
|
|
}
|
|
|
|
#[test]
|
|
fn a_span_child_survives_the_next_frame() {
|
|
let mut h = Harness::new((400, 200));
|
|
// Both children declare a length, so the span chooses their boxes from
|
|
// hints rather than drawing them to find out.
|
|
let top = rect(Color::RED).height(80).add(&mut h.rsc);
|
|
let bottom = rect(Color::BLUE).height(120).add(&mut h.rsc);
|
|
h.set_root((top, bottom).span(Dir::DOWN));
|
|
|
|
h.rsc.widgets_mut().get_dyn_mut(top.id());
|
|
h.frame();
|
|
|
|
assert_corners!(h, top, (0, 0), (400, 80));
|
|
assert_corners!(h, bottom, (0, 80), (400, 200));
|
|
}
|
|
|
|
/// Lays its child out from the hint alone, never reading what it drew.
|
|
struct FromHint {
|
|
inner: StrongWidget,
|
|
}
|
|
|
|
impl Widget for FromHint {
|
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
|
let len = painter.size_hint(&self.inner, Axis::Y).unwrap();
|
|
let mut region = UiRegion::FULL;
|
|
region.y.end = region.y.start.offset(len.px);
|
|
painter.widget_within(&self.inner, region);
|
|
Size::LEFTOVER
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn a_parent_that_only_read_a_hint_relays_out_when_the_hint_changes() {
|
|
let mut h = Harness::new((400, 200));
|
|
let inner = rect(Color::RED).height(80).add(&mut h.rsc);
|
|
let parent = FromHint {
|
|
inner: inner.add_strong(&mut h.rsc),
|
|
}
|
|
.add(&mut h.rsc);
|
|
h.set_root(parent);
|
|
assert_corners!(h, inner, (0, 0), (400, 80));
|
|
|
|
h.set_len(inner, Axis::Y, 120);
|
|
h.frame();
|
|
|
|
assert_corners!(h, inner, (0, 0), (400, 120));
|
|
}
|
|
|
|
/// Reads its box's size, which nothing but its own draw can put right.
|
|
struct ReadsBox {
|
|
draws: Rc<Cell<usize>>,
|
|
}
|
|
|
|
impl Widget for ReadsBox {
|
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
|
self.draws.set(self.draws.get() + 1);
|
|
Size::px(painter.px_size() / 4.0)
|
|
}
|
|
}
|
|
|
|
/// Reads its box across one axis only, so its drawing holds for a taller
|
|
/// box on its own and only a wider one is worth a draw.
|
|
///
|
|
/// Both of these report a quarter of what they read, without saying that the
|
|
/// drawing holds there too, so each length they are asked at costs two draws:
|
|
/// one to answer, and one in the quarter-sized box that answer places them
|
|
/// in. The counts below are in those pairs.
|
|
struct ReadsWidth {
|
|
draws: Rc<Cell<usize>>,
|
|
}
|
|
|
|
impl Widget for ReadsWidth {
|
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
|
self.draws.set(self.draws.get() + 1);
|
|
Size::px((painter.px_len(Axis::X) / 4.0, 20.0).into())
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn a_resize_does_not_redraw_what_the_shader_can_move() {
|
|
let mut h = Harness::new((400, 200));
|
|
let (leaf, draws) = counted(&mut h, Size::LEFTOVER, false);
|
|
h.set_root(leaf);
|
|
let settled = draws.get();
|
|
|
|
h.resize((800, 100));
|
|
assert!(h.needs_redraw());
|
|
h.frame();
|
|
|
|
assert_eq!(
|
|
draws.get(),
|
|
settled,
|
|
"a scaling drawing follows its box, and the output is one"
|
|
);
|
|
assert_corners!(h, leaf, (0, 0), (800, 100));
|
|
}
|
|
|
|
#[test]
|
|
fn a_full_ortho_span_moves_its_child_without_redrawing_it() {
|
|
let mut h = Harness::new((400, 200));
|
|
let (leaf, draws) = counted(&mut h, Size::LEFTOVER, false);
|
|
let span = (leaf,)
|
|
.span(Dir::RIGHT)
|
|
.ortho(OrthoSize::Full)
|
|
.add(&mut h.rsc);
|
|
h.set_root(span);
|
|
let settled = draws.get();
|
|
|
|
h.resize((400, 100));
|
|
h.frame();
|
|
|
|
assert_eq!(draws.get(), settled);
|
|
assert_corners!(h, leaf, (0, 0), (400, 100));
|
|
assert_eq!(h.render.active[&span.id()].size.y, Len::rel(1.0));
|
|
}
|
|
|
|
/// The output is the root of the box chain, so a resize is a box that changed
|
|
/// length like any other -- there is not a second rule for the window. A
|
|
/// drawing that holds for one length is drawn again whichever box moved.
|
|
#[test]
|
|
fn a_resize_redraws_what_does_not_scale() {
|
|
let mut h = Harness::new((400, 200));
|
|
let (leaf, draws) = counted(&mut h, Size::LEFTOVER, true);
|
|
h.set_root(leaf);
|
|
let settled = draws.get();
|
|
|
|
h.resize((800, 100));
|
|
h.frame();
|
|
|
|
assert_eq!(draws.get(), settled + 1, "its box is a different length");
|
|
assert_corners!(h, leaf, (0, 0), (800, 100));
|
|
}
|
|
|
|
#[test]
|
|
fn a_resize_redraws_what_read_its_box() {
|
|
let mut h = Harness::new((400, 200));
|
|
let draws = Rc::new(Cell::new(0));
|
|
let leaf = ReadsBox {
|
|
draws: draws.clone(),
|
|
}
|
|
.add(&mut h.rsc);
|
|
h.set_root(leaf);
|
|
let settled = draws.get();
|
|
|
|
h.resize((800, 100));
|
|
h.frame();
|
|
|
|
assert_eq!(draws.get(), settled + 2);
|
|
}
|
|
|
|
#[test]
|
|
fn a_resize_only_redraws_read_axes() {
|
|
let mut h = Harness::new((400, 200));
|
|
let draws = Rc::new(Cell::new(0));
|
|
let leaf = ReadsWidth {
|
|
draws: draws.clone(),
|
|
}
|
|
.add(&mut h.rsc);
|
|
h.set_root(leaf);
|
|
let settled = draws.get();
|
|
|
|
h.resize((400, 300));
|
|
h.frame();
|
|
assert_eq!(draws.get(), settled, "height was never read");
|
|
|
|
h.resize((800, 300));
|
|
h.frame();
|
|
assert_eq!(draws.get(), settled + 2, "width changes its answer");
|
|
}
|
|
|
|
#[test]
|
|
fn subpixel_resize_changes_accumulate_from_the_last_layout() {
|
|
let mut h = Harness::new((400, 200));
|
|
let draws = Rc::new(Cell::new(0));
|
|
let leaf = ReadsWidth {
|
|
draws: draws.clone(),
|
|
}
|
|
.add(&mut h.rsc);
|
|
h.set_root(leaf);
|
|
let settled = draws.get();
|
|
|
|
for width in [400.02, 400.04, 400.05] {
|
|
h.resize((width, 200.0));
|
|
h.frame();
|
|
assert_eq!(draws.get(), settled);
|
|
}
|
|
|
|
h.resize((400.06, 200.0));
|
|
h.frame();
|
|
assert_eq!(draws.get(), settled + 2);
|
|
}
|
|
|
|
#[test]
|
|
fn subpixel_box_changes_accumulate_from_the_last_draw() {
|
|
let mut h = Harness::new((400, 200));
|
|
let (first, draws, _) = pair(&mut h, true);
|
|
let settled = draws.get();
|
|
|
|
for width in [100.02, 100.04, 100.05] {
|
|
h.rsc[first].size.x = Len::px(width);
|
|
h.frame();
|
|
assert_eq!(draws.get(), settled);
|
|
}
|
|
|
|
h.rsc[first].size.x = Len::px(100.06);
|
|
h.frame();
|
|
assert_eq!(draws.get(), settled + 1);
|
|
}
|
|
|
|
#[test]
|
|
fn reporting_the_same_output_size_does_not_start_a_resize() {
|
|
let mut h = Harness::new((400, 200));
|
|
let draws = Rc::new(Cell::new(0));
|
|
let leaf = ReadsBox {
|
|
draws: draws.clone(),
|
|
}
|
|
.add(&mut h.rsc);
|
|
h.set_root(leaf);
|
|
let settled = draws.get();
|
|
|
|
h.resize((400, 200));
|
|
|
|
assert!(!h.needs_redraw());
|
|
h.frame();
|
|
assert_eq!(draws.get(), settled);
|
|
}
|
|
|
|
#[test]
|
|
fn narrowing_the_output_reflows_text_and_relays_out_around_it() {
|
|
let mut h = Harness::new((600, 400));
|
|
let para = wtext(
|
|
"Wrapping shapes one source into as many lines as its container leaves \
|
|
room for, so the height of a paragraph is an answer rather than a setting.",
|
|
)
|
|
.size(20)
|
|
.wrap(true)
|
|
.add(&mut h.rsc);
|
|
let below = rect(Color::RED).add(&mut h.rsc);
|
|
h.set_root((para, below).span(Dir::DOWN));
|
|
let top = h.region(&below).expect("drew nothing").top_left.y;
|
|
|
|
h.resize((300, 400));
|
|
h.frame();
|
|
|
|
let lower = h.region(&below).expect("drew nothing").top_left.y;
|
|
assert!(lower > top, "same words, half the width: {top} -> {lower}");
|
|
}
|
|
|
|
#[test]
|
|
fn a_change_two_levels_under_its_reader_still_reaches_it() {
|
|
let mut h = Harness::new((400, 400));
|
|
// Every wrapper up to the outer pad read the size below it, so the outer
|
|
// pad is what draws again -- and the span it hands the box to is the same
|
|
// size as before, which is what lets a draw reuse its way past the leaf.
|
|
let (leaf, _) = counted(&mut h, Size::px((100, 100).into()), true);
|
|
let padded = leaf.pad(10).add(&mut h.rsc);
|
|
let below = rect(Color::RED).add(&mut h.rsc);
|
|
h.set_root((padded, below).span(Dir::DOWN).pad(12));
|
|
assert_corners!(h, below, (12, 132), (388, 388));
|
|
|
|
h.rsc[leaf].size = Size::px((100, 200).into());
|
|
h.frame();
|
|
|
|
assert_corners!(h, below, (12, 232), (388, 388));
|
|
}
|
|
|
|
/// Reads nothing of its box, so its drawing holds for any length, and has a
|
|
/// child so that whatever asks about the subtree has one to reach.
|
|
struct Stretchy {
|
|
inner: StrongWidget,
|
|
draws: Rc<Cell<usize>>,
|
|
}
|
|
|
|
impl Widget for Stretchy {
|
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
|
self.draws.set(self.draws.get() + 1);
|
|
painter.widget(&self.inner).size()
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn stretching_a_subtree_carries_the_children_in_it() {
|
|
let mut h = Harness::new((400, 400));
|
|
let first = rect(Color::RED).height(40).add(&mut h.rsc);
|
|
let inner = rect(Color::BLUE).add(&mut h.rsc);
|
|
let draws = Rc::new(Cell::new(0));
|
|
let outer = Stretchy {
|
|
inner: inner.add_strong(&mut h.rsc),
|
|
draws: draws.clone(),
|
|
}
|
|
.add(&mut h.rsc);
|
|
h.set_root((first, outer).span(Dir::DOWN));
|
|
let settled = draws.get();
|
|
assert_corners!(h, inner, (0, 40), (400, 400));
|
|
|
|
h.set_len(first, Axis::Y, 80);
|
|
h.frame();
|
|
|
|
assert_eq!(
|
|
draws.get(),
|
|
settled,
|
|
"its drawing follows its box, rather than being made again"
|
|
);
|
|
assert_corners!(h, outer, (0, 80), (400, 400));
|
|
assert_corners!(h, inner, (0, 80), (400, 400));
|
|
}
|
|
|
|
#[test]
|
|
fn a_widened_row_redraws_what_reads_its_length_and_nothing_else() {
|
|
let mut h = Harness::new((400, 200));
|
|
// What a transcript row is: something whose shaping depends on the width
|
|
// it is given, beside something that only has to be the right shape.
|
|
let (wraps, wrap_draws) = counted(&mut h, Size::LEFTOVER, true);
|
|
let (backing, back_draws) = counted(&mut h, Size::LEFTOVER, false);
|
|
let row = (backing, wraps).span(Dir::RIGHT).add(&mut h.rsc);
|
|
let bar = rect(Color::RED).width(100).add(&mut h.rsc);
|
|
h.set_root((bar, row).span(Dir::RIGHT));
|
|
let (settled_wrap, settled_back) = (wrap_draws.get(), back_draws.get());
|
|
|
|
h.set_len(bar, Axis::X, 200);
|
|
h.frame();
|
|
|
|
// The span reads every child's size, so redrawing one takes the span
|
|
// with it -- and the span then measures and places the redrawn child.
|
|
assert!(wrap_draws.get() > settled_wrap, "reads the width it got");
|
|
assert_eq!(back_draws.get(), settled_back, "only has to be the shape");
|
|
assert_corners!(h, backing, (200, 0), (300, 200));
|
|
assert_corners!(h, wraps, (300, 0), (400, 200));
|
|
}
|
|
|
|
#[test]
|
|
fn a_declared_length_child_is_not_redrawn_when_the_box_around_it_grows() {
|
|
let mut h = Harness::new((400, 200));
|
|
// Its box is a fixed 80 wherever the row's edges end up, so drawing it
|
|
// again would be for a width it does not have. The declared width is what
|
|
// lets the span say that without drawing it: a width the span learnt by
|
|
// drawing the child in its own box is only an answer for that box.
|
|
let (counter, draws) = counted(&mut h, Size::from((80, 200)), true);
|
|
let fixed = counter.width(80).add(&mut h.rsc);
|
|
let (leftover, _) = counted(&mut h, Size::LEFTOVER, false);
|
|
let row = (fixed, leftover).span(Dir::RIGHT).add(&mut h.rsc);
|
|
let bar = rect(Color::RED).width(100).add(&mut h.rsc);
|
|
h.set_root((bar, row).span(Dir::RIGHT));
|
|
let settled = draws.get();
|
|
|
|
h.set_len(bar, Axis::X, 200);
|
|
h.frame();
|
|
|
|
assert_eq!(draws.get(), settled, "its own length did not change");
|
|
assert_corners!(h, fixed, (200, 0), (280, 200));
|
|
}
|
|
|
|
/// `Stack` measures the child that sizes it by drawing it, then draws it
|
|
/// again above the background it stacks over. The second ask is for the same
|
|
/// box, so nothing geometric says the answer has gone stale, and reusing it
|
|
/// leaves the drawing under the background. The same tree got away with it
|
|
/// while the two asks differed by a rounding.
|
|
#[test]
|
|
fn a_widget_asked_again_on_another_layer_is_drawn_there() {
|
|
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()),
|
|
"the measured drawing was left on the stack's own layer"
|
|
);
|
|
assert_ne!(layer(front.id()), layer(background.id()));
|
|
// Measured once for the size and once where it goes, which is what the
|
|
// stack costs and not something this test is asserting a number for.
|
|
assert_eq!(draws.get(), 2);
|
|
}
|