Carry a length as a rule beside a widget, not a widget around it
`.width()` built a `SetSize` whose whole job was to answer `size_hint`, so every declared length cost a widget, an `ActiveData` and a link of chain to say one number. It is now a `SizeRule` per axis on `WidgetData`, beside `region_node`, resolved by `Painter` where the widget is drawn. `SetSize` and `MaxSize` are gone; `MaxSize` had no caller but its own builders. That settles which of two answers is the size. A rule wins on the axis it names and the `Size` returned by `draw` answers the rest, applied once in `draw_inner` rather than by each widget that could carry one -- so the widget under a rule never learns of it. `Painter::size_hint` reads the rule first for the same reason: a rule that beats what a widget would draw has to beat what it says about itself. `declared_lens` still falls back to a non-leftover `size_hint`, which is how an image or a gap gets its own pixel size rather than the whole offer. That is the offer's business rather than a declaration's, and it falls away when a widget occupies its reported size inside the box it was offered. `known` and `declared` are separate because a share is a length to whoever divides one and not to whoever composes a box: `.width(leftover(3))` is known without drawing but cannot narrow anything. Checked: fmt, clippy, 85 tests, and 100 generated seeds agreeing warm against cold in 67.6 s. `minimal`, `text` and `view` render byte-identical at 1920x1200; `tabs` differs only in the widget count it prints about itself, which is two wrapper types smaller.
This commit is contained in:
1 parent
0283c9d6c7
commit
8220a78d4a
21 files changed
+252
-214
No files matched your search
+2
-2
@@ -10,7 +10,7 @@ use iris::prelude::*;
|
||||
|
||||
/// A row of a fixed height under a bar, so changing the bar's height moves the
|
||||
/// row without changing the box it is given: the move path, repeatedly.
|
||||
fn plant(h: &mut Harness, bar_height: f32) -> (WeakWidget<SetSize>, WeakWidget<Rect>) {
|
||||
fn plant(h: &mut Harness, bar_height: f32) -> (WeakWidget<Rect>, WeakWidget<Rect>) {
|
||||
let bar = rect(Color::RED).height(bar_height).add(&mut h.rsc);
|
||||
let inner = rect(Color::BLUE).add(&mut h.rsc);
|
||||
let row = (inner, rect(Color::GREEN)).span(Dir::RIGHT).height(100);
|
||||
@@ -33,7 +33,7 @@ fn a_subtree_moved_many_times_stays_where_a_cold_layout_puts_it() {
|
||||
let mut height = 40.0;
|
||||
for step in 0..MOVES {
|
||||
height = 40.0 + (step % 300) as f32 * 0.37;
|
||||
warm.rsc[bar].y = Some(Len::px(height));
|
||||
warm.set_len(bar, Axis::Y, height);
|
||||
warm.frame();
|
||||
}
|
||||
|
||||
|
||||
+17
-10
@@ -61,9 +61,9 @@ fn resize_one(h: &mut Harness, tree: &Tree, idx: usize, rng: &mut Rng) -> Lens {
|
||||
Some(Len::px(20.0 + rng.below(180) as f32)),
|
||||
Some(Len::px(20.0 + rng.below(180) as f32)),
|
||||
];
|
||||
let sized = &mut h.rsc[tree.sized[idx]];
|
||||
sized.x = lens[0];
|
||||
sized.y = lens[1];
|
||||
h.rsc
|
||||
.widgets_mut()
|
||||
.set_size_rules(tree.sized[idx], lens[0], lens[1]);
|
||||
lens
|
||||
}
|
||||
|
||||
@@ -174,18 +174,25 @@ fn reshuffle(
|
||||
/// written out by hand. A fuzz failure is a lead; the fast test that replaces
|
||||
/// it has to be buildable from what the failure printed.
|
||||
fn describe(id: WidgetId, h: &Harness) -> String {
|
||||
let rules = h.rsc.widgets().size_rules(id);
|
||||
let rule = |r: SizeRule| match r.known() {
|
||||
Some(len) => format!("{len}"),
|
||||
None => "-".into(),
|
||||
};
|
||||
// A size rule is a property of whatever carries it, so it prints with
|
||||
// that widget rather than as one of its own.
|
||||
match (rules.x, rules.y) {
|
||||
(SizeRule::Free, SizeRule::Free) => describe_widget(id, h),
|
||||
(x, y) => format!("{}[x:{},y:{}]", describe_widget(id, h), rule(x), rule(y)),
|
||||
}
|
||||
}
|
||||
|
||||
fn describe_widget(id: WidgetId, h: &Harness) -> String {
|
||||
let label = h.rsc.widgets().label(id).to_string();
|
||||
let Some(widget) = h.rsc.widgets().get_dyn(id) else {
|
||||
return label;
|
||||
};
|
||||
let any: &dyn std::any::Any = widget;
|
||||
let len = |l: &Option<Len>| match l {
|
||||
Some(l) => format!("{l}"),
|
||||
None => "-".into(),
|
||||
};
|
||||
if let Some(w) = any.downcast_ref::<SetSize>() {
|
||||
return format!("SetSize{{x:{},y:{}}}", len(&w.x), len(&w.y));
|
||||
}
|
||||
if let Some(w) = any.downcast_ref::<Span>() {
|
||||
let sign = if w.dir.sign == Sign::Neg { "-" } else { "+" };
|
||||
return format!(
|
||||
|
||||
+13
-26
@@ -81,7 +81,7 @@ fn a_child_drawn_twice_moves_once() {
|
||||
h.set_root((left, centered).span(Dir::RIGHT));
|
||||
assert_corners!(h, inner, (100, 0), (300, 200));
|
||||
|
||||
h.rsc[left].x = Some(Len::px(150));
|
||||
h.set_len(left, Axis::X, 150);
|
||||
h.frame();
|
||||
|
||||
assert_corners!(h, inner, (150, 0), (350, 200));
|
||||
@@ -131,7 +131,7 @@ fn a_fixed_box_is_drawn_again_rather_than_stretched() {
|
||||
h.set_root(stack.align(Align::TOP));
|
||||
assert_corners!(h, panel, (0, 0), (400, 100));
|
||||
|
||||
h.rsc[leaf].y = Some(Len::px(250));
|
||||
h.set_len(leaf, Axis::Y, 250);
|
||||
h.frame();
|
||||
|
||||
assert_corners!(h, panel, (0, 0), (400, 250));
|
||||
@@ -146,7 +146,7 @@ fn a_moved_subtree_takes_its_children_with_it() {
|
||||
h.set_root((first, row).span(Dir::DOWN));
|
||||
assert_corners!(h, inner, (10, 50), (390, 70));
|
||||
|
||||
h.rsc[first].y = Some(Len::px(80));
|
||||
h.set_len(first, Axis::Y, 80);
|
||||
h.frame();
|
||||
|
||||
// The row opted into one movable region, so its descendants follow one
|
||||
@@ -167,7 +167,7 @@ fn a_fixed_length_child_keeps_it_when_the_box_around_it_grows() {
|
||||
assert_corners!(h, fixed, (100, 0), (150, 200));
|
||||
assert_corners!(h, leftover, (150, 0), (400, 200));
|
||||
|
||||
h.rsc[bar].x = Some(Len::px(200));
|
||||
h.set_len(bar, Axis::X, 200);
|
||||
h.frame();
|
||||
|
||||
// The panel's box is 100 shorter, so the fixed child is the same 50 wide
|
||||
@@ -195,7 +195,7 @@ fn a_box_with_a_fixed_length_can_be_stretched_on_its_other_axis() {
|
||||
h.set_root((bar, column).span(Dir::RIGHT));
|
||||
assert_corners!(h, inner, (110, 10), (390, 30));
|
||||
|
||||
h.rsc[bar].x = Some(Len::px(200));
|
||||
h.set_len(bar, Axis::X, 200);
|
||||
h.frame();
|
||||
|
||||
assert_corners!(h, inner, (210, 10), (390, 30));
|
||||
@@ -282,24 +282,14 @@ fn drawn_edges(h: &Harness, id: WidgetId, axis: Axis) -> (f32, f32) {
|
||||
}
|
||||
|
||||
fn hairline(h: &mut Harness, marks: &mut Vec<WidgetId>) -> StrongWidget {
|
||||
let inner = rect(Color::RED).add_strong(&mut h.rsc);
|
||||
let mark = SetSize {
|
||||
inner,
|
||||
x: Some(Len::px(1.0)),
|
||||
y: None,
|
||||
}
|
||||
.add_strong(&mut h.rsc);
|
||||
let mark = rect(Color::RED).width(1).add_strong(&mut h.rsc);
|
||||
marks.push(mark.id());
|
||||
mark
|
||||
}
|
||||
|
||||
fn share(h: &mut Harness, inner: StrongWidget, ratio: f32) -> StrongWidget {
|
||||
SetSize {
|
||||
inner,
|
||||
x: Some(Len::leftover(ratio)),
|
||||
y: None,
|
||||
}
|
||||
.add_strong(&mut h.rsc)
|
||||
h.set_len(&inner, Axis::X, Len::leftover(ratio));
|
||||
inner
|
||||
}
|
||||
|
||||
/// Shares in weights no binary fraction lands on, a padding on one branch
|
||||
@@ -402,18 +392,15 @@ fn only_a_pure_leftover_child_disappears_when_nothing_is_left() {
|
||||
|
||||
// An undrawn child remains a dependency of the span, so making room for
|
||||
// it draws it without rebuilding the tree.
|
||||
h.rsc[fixed].x = Some(Len::px(60));
|
||||
h.set_len(fixed, Axis::X, 60);
|
||||
h.frame();
|
||||
assert_corners!(h, leftover, (60, 0), (100, 20));
|
||||
|
||||
let mut h = Harness::new((100, 20));
|
||||
let fixed = rect(Color::RED).width(100).add(&mut h.rsc);
|
||||
let mixed = SetSize {
|
||||
inner: rect(Color::BLUE).add_strong(&mut h.rsc),
|
||||
x: Some(Len::px(20) + Len::LEFTOVER),
|
||||
y: None,
|
||||
}
|
||||
.add(&mut h.rsc);
|
||||
let mixed = rect(Color::BLUE)
|
||||
.width(Len::px(20) + Len::LEFTOVER)
|
||||
.add(&mut h.rsc);
|
||||
h.set_root((fixed, mixed).span(Dir::RIGHT));
|
||||
|
||||
// Pixels and fractions still overflow; only a child whose entire length
|
||||
@@ -432,7 +419,7 @@ fn leftover_children_disappear_at_the_exact_fixed_content_boundary() {
|
||||
assert!(h.region(&a).is_some());
|
||||
assert!(h.region(&b).is_some());
|
||||
|
||||
h.rsc[first].y = Some(Len::px(96.0));
|
||||
h.set_len(first, Axis::Y, 96.0);
|
||||
h.frame();
|
||||
|
||||
assert!(h.region(&a).is_none());
|
||||
|
||||
@@ -216,7 +216,11 @@ fn layout_cost() {
|
||||
trace_selected(&tree);
|
||||
let sized = tree.sized[0];
|
||||
run("size", frames, &mut harness, move |harness, frame| {
|
||||
harness.rsc[sized].x = Some(Len::px(100.0 + (frame % 2) as f32 * 40.0));
|
||||
let len = Len::px(100.0 + (frame % 2) as f32 * 40.0);
|
||||
harness
|
||||
.rsc
|
||||
.widgets_mut()
|
||||
.set_size_rule(sized, Axis::X, SizeRule::Exact(len));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ fn remapping_rows_every_frame() {
|
||||
}
|
||||
h.set_root(span);
|
||||
for i in 0..FRAMES {
|
||||
h.rsc[first].y = Some(Len::px(40.0 + (i % 2) as f32));
|
||||
h.set_len(first, Axis::Y, 40.0 + (i % 2) as f32);
|
||||
h.frame();
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -236,7 +236,7 @@ fn a_parent_that_only_read_a_hint_relays_out_when_the_hint_changes() {
|
||||
h.set_root(parent);
|
||||
assert_corners!(h, inner, (0, 0), (400, 80));
|
||||
|
||||
h.rsc[inner].y = Some(Len::px(120));
|
||||
h.set_len(inner, Axis::Y, 120);
|
||||
h.frame();
|
||||
|
||||
assert_corners!(h, inner, (0, 0), (400, 120));
|
||||
@@ -484,7 +484,7 @@ fn stretching_a_subtree_carries_the_children_in_it() {
|
||||
let settled = draws.get();
|
||||
assert_corners!(h, inner, (0, 40), (400, 400));
|
||||
|
||||
h.rsc[first].y = Some(Len::px(80));
|
||||
h.set_len(first, Axis::Y, 80);
|
||||
h.frame();
|
||||
|
||||
assert_eq!(
|
||||
@@ -508,7 +508,7 @@ fn a_widened_row_redraws_what_reads_its_length_and_nothing_else() {
|
||||
h.set_root((bar, row).span(Dir::RIGHT));
|
||||
let (settled_wrap, settled_back) = (wrap_draws.get(), back_draws.get());
|
||||
|
||||
h.rsc[bar].x = Some(Len::px(200));
|
||||
h.set_len(bar, Axis::X, 200);
|
||||
h.frame();
|
||||
|
||||
// The span reads every child's size, so redrawing one takes the span
|
||||
@@ -534,7 +534,7 @@ fn a_declared_length_child_is_not_redrawn_when_the_box_around_it_grows() {
|
||||
h.set_root((bar, row).span(Dir::RIGHT));
|
||||
let settled = draws.get();
|
||||
|
||||
h.rsc[bar].x = Some(Len::px(200));
|
||||
h.set_len(bar, Axis::X, 200);
|
||||
h.frame();
|
||||
|
||||
assert_eq!(draws.get(), settled, "its own length did not change");
|
||||
|
||||
+2
-6
@@ -159,12 +159,8 @@ impl Node {
|
||||
}
|
||||
Node::Sized(x, y, kid) => {
|
||||
let inner = kid.build(h, out, spans);
|
||||
SetSize {
|
||||
inner,
|
||||
x: *x,
|
||||
y: *y,
|
||||
}
|
||||
.add_strong(&mut h.rsc)
|
||||
h.rsc.widgets_mut().set_size_rules(&inner, *x, *y);
|
||||
inner
|
||||
}
|
||||
Node::Scroll(down, kid) => {
|
||||
let inner = kid.build(h, out, spans);
|
||||
|
||||
@@ -10,12 +10,7 @@ use iris::prelude::*;
|
||||
fn plant(h: &mut Harness) -> Vec<WidgetId> {
|
||||
let plain = wtext("Wrapping").size(16).wrap(false).add(&mut h.rsc);
|
||||
let wrapped = wtext("Wrapping shapes").size(16).wrap(true).add(&mut h.rsc);
|
||||
let sized = SetSize {
|
||||
inner: wrapped.add_strong(&mut h.rsc),
|
||||
x: Some(Len::px(76.0)),
|
||||
y: None,
|
||||
}
|
||||
.add(&mut h.rsc);
|
||||
let sized = wrapped.width(76).add(&mut h.rsc);
|
||||
let aligned = Aligned {
|
||||
inner: sized.add_strong(&mut h.rsc),
|
||||
align: Align {
|
||||
@@ -108,12 +103,7 @@ fn plant_fixed(h: &mut Harness) -> Vec<WidgetId> {
|
||||
}
|
||||
.add(&mut h.rsc);
|
||||
let inner = (aligned,).span(Dir::RIGHT).add(&mut h.rsc);
|
||||
let sized = SetSize {
|
||||
inner: inner.add_strong(&mut h.rsc),
|
||||
x: Some(Len::px(189.0)),
|
||||
y: Some(Len::px(176.0)),
|
||||
}
|
||||
.add(&mut h.rsc);
|
||||
let sized = inner.sized((189, 176)).add(&mut h.rsc);
|
||||
let filler = rect(Color::RED).add(&mut h.rsc);
|
||||
let root = (filler, sized).span(Dir::RIGHT).add(&mut h.rsc);
|
||||
h.state.root = Some(root.add_strong(&mut h.rsc));
|
||||
|
||||
+6
-24
@@ -15,12 +15,7 @@ use iris::prelude::*;
|
||||
fn plant(h: &mut Harness) -> Vec<WidgetId> {
|
||||
let plain = wtext("Wrapping").size(16).wrap(false).add(&mut h.rsc);
|
||||
let wrapped = wtext("Wrapping shapes").size(16).wrap(true).add(&mut h.rsc);
|
||||
let sized = SetSize {
|
||||
inner: wrapped.add_strong(&mut h.rsc),
|
||||
x: Some(Len::px(76.0)),
|
||||
y: None,
|
||||
}
|
||||
.add(&mut h.rsc);
|
||||
let sized = wrapped.width(76).add(&mut h.rsc);
|
||||
let aligned = Aligned {
|
||||
inner: sized.add_strong(&mut h.rsc),
|
||||
align: Align {
|
||||
@@ -110,12 +105,7 @@ fn plant_fixed(h: &mut Harness) -> Vec<WidgetId> {
|
||||
}
|
||||
.add(&mut h.rsc);
|
||||
let inner = (aligned,).span(Dir::RIGHT).add(&mut h.rsc);
|
||||
let sized = SetSize {
|
||||
inner: inner.add_strong(&mut h.rsc),
|
||||
x: Some(Len::px(189.0)),
|
||||
y: Some(Len::px(176.0)),
|
||||
}
|
||||
.add(&mut h.rsc);
|
||||
let sized = inner.sized((189, 176)).add(&mut h.rsc);
|
||||
let filler = rect(Color::RED).add(&mut h.rsc);
|
||||
let root = (filler, sized).span(Dir::RIGHT).add(&mut h.rsc);
|
||||
h.state.root = Some(root.add_strong(&mut h.rsc));
|
||||
@@ -235,12 +225,7 @@ fn plant_scrolled(h: &mut Harness, swapped: bool) -> (Vec<WidgetId>, [WeakWidget
|
||||
}
|
||||
.add(&mut h.rsc);
|
||||
let block = rect(Color::RED).add(&mut h.rsc);
|
||||
let fixed = SetSize {
|
||||
inner: block.add_strong(&mut h.rsc),
|
||||
x: Some(Len::px(87.0)),
|
||||
y: None,
|
||||
}
|
||||
.add(&mut h.rsc);
|
||||
let fixed = block.width(87).add(&mut h.rsc);
|
||||
let mut outer_children: Vec<StrongWidget> =
|
||||
vec![fixed.add_strong(&mut h.rsc), inner.add_strong(&mut h.rsc)];
|
||||
if swapped {
|
||||
@@ -253,12 +238,9 @@ fn plant_scrolled(h: &mut Harness, swapped: bool) -> (Vec<WidgetId>, [WeakWidget
|
||||
ortho: OrthoSize::Children,
|
||||
}
|
||||
.add(&mut h.rsc);
|
||||
let through = SetSize {
|
||||
inner: outer.add_strong(&mut h.rsc),
|
||||
x: None,
|
||||
y: None,
|
||||
}
|
||||
.add(&mut h.rsc);
|
||||
// Carried no rule even before rules were a property: it is here to be a
|
||||
// widget between the span and the scroll, not to declare anything.
|
||||
let through = (outer,).span(Dir::RIGHT).add(&mut h.rsc);
|
||||
let scroll = Scroll::new(through.add_strong(&mut h.rsc), Axis::X).add(&mut h.rsc);
|
||||
h.state.root = Some(scroll.add_strong(&mut h.rsc));
|
||||
(
|
||||
|
||||
Reference in new issue
Block a user