`desired_width`/`desired_height`, `WidgetAxisFns`, `SizeCtx` and the size cache are gone. A widget states what it used with `Painter::set_size` while it draws, and `Painter::widget` hands back a `DrawResult` whose `size()` both reads the child and records that this widget's size depends on it. Reading nothing keeps the parent independent of what the child came to. `Span` is what the change is for. It takes each child's `size_hint` where there is one, draws only the children that cannot answer, allocates the flexible space, then places everything -- which deletes `desired_ortho`, whose own comment said it "literally copies draw ... which makes this slow and not cool". Invalidation follows the dependency edges the draw recorded: a widget that needs redrawing hands off to the highest ancestor that read its size, instead of re-running a measurement to find out whether anything changed. `Widget::size_dependence(axis)` says how much of its box a widget's drawing depends on -- none of it, its own extent, or the whole box -- so the retained path can keep a drawing and write a new box into it. Asked per axis, because wrapped text depends on the width it is offered and not on the height. `Internal` does not yet buy more than `External`: keeping a drawing when only the room around it changed is a translation, which waits for the move chain. `tests/retained.rs` covers the second frame rather than the first, which is where the bugs were: a placed child that was not recorded as one got pruned as departed on the next draw. `examples/text.rs` is new, since wrapping was the one thing here with no way to see it on its own.
138 lines
4.2 KiB
Rust
138 lines
4.2 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.
|
|
struct Counted {
|
|
draws: Rc<Cell<usize>>,
|
|
size: Size,
|
|
dependence: SizeDependence,
|
|
}
|
|
|
|
impl Widget for Counted {
|
|
fn draw(&mut self, painter: &mut Painter) {
|
|
self.draws.set(self.draws.get() + 1);
|
|
painter.set_size(self.size);
|
|
}
|
|
|
|
fn size_dependence(&self, _: Axis) -> SizeDependence {
|
|
self.dependence
|
|
}
|
|
}
|
|
|
|
struct Counts(Rc<Cell<usize>>);
|
|
|
|
impl Counts {
|
|
fn get(&self) -> usize {
|
|
self.0.get()
|
|
}
|
|
}
|
|
|
|
fn counted(
|
|
h: &mut Harness,
|
|
size: Size,
|
|
dependence: SizeDependence,
|
|
) -> (WeakWidget<Counted>, Counts) {
|
|
let draws = Rc::new(Cell::new(0));
|
|
let id = Counted {
|
|
draws: draws.clone(),
|
|
size,
|
|
dependence,
|
|
}
|
|
.add(&mut h.rsc);
|
|
(id, Counts(draws))
|
|
}
|
|
|
|
/// A fixed-width leaf beside one that takes the rest, so changing the first
|
|
/// hands the second a different box without the output changing.
|
|
fn pair(h: &mut Harness, rest: SizeDependence) -> (WeakWidget<Counted>, Counts, WidgetId) {
|
|
let (first, _) = counted(h, Size::from((100, 200)), SizeDependence::Internal);
|
|
let (second, draws) = counted(h, Size::REST, rest);
|
|
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, SizeDependence::None);
|
|
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 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, SizeDependence::External);
|
|
let settled = draws.get();
|
|
|
|
h.rsc[first].size = Size::from((150, 200));
|
|
h.frame();
|
|
|
|
// Twice: once for the span to measure it, once for its real box. A child
|
|
// that can hint its length is spared the first, and a smaller number here
|
|
// means someone has made that cheaper rather than broken it.
|
|
assert_eq!(draws.get(), settled + 2);
|
|
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)), SizeDependence::Internal);
|
|
let (asked, asked_draws) = counted(&mut h, Size::from((100, 200)), SizeDependence::Internal);
|
|
// 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 it is placed.
|
|
let hinted = told.width(100).add(&mut h.rsc);
|
|
h.set_root((hinted, asked).span(Dir::RIGHT));
|
|
|
|
assert_eq!(told_draws.get(), 1);
|
|
assert_eq!(
|
|
asked_draws.get(),
|
|
2,
|
|
"drawn to be measured, then again to be placed"
|
|
);
|
|
}
|
|
|
|
#[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, SizeDependence::Internal);
|
|
|
|
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_placed_child_survives_the_next_frame() {
|
|
let mut h = Harness::new((400, 200));
|
|
// Both children declare a length, so the span places them from their 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));
|
|
}
|