Keep a span's leftover decision off the box its parent hands back
The shrinker's `reorder` case had two red seeds at depth 5, and neither was about reordering. A span asks whether anything is left over by comparing its box in pixels with what its fixed and relative children fill. Where the parent sized that box from this span's own answer those are the same number, and the box returns through the chain a few bits off, so 0.00003 px decided it: warm rounded under and left a leftover-only child undrawn, cold rounded over and drew it at zero length. Both are stable, and the pixels are the same either way, which is why nothing but the oracle could see it. The room to divide is `len * fixed - total.px`, and under `HOLDS_EPSILON_PX` of it is now none. That moves the boundary off the length boxes land on rather than making the comparison tolerant: the validity range is still split at the boundary exactly, as generated seed 16 requires, and what it gives up is a share of under a twentieth of a pixel. The same margin answers the `fixed == 0` arm, where the only room is what negative pixels leave. `tests/unsettled.rs` gets the six-widget tree, shrunk from 266. It needs the span above the one that divides: without a box composed through it both trees round the same way and the boundary is never crossed. Checked: fmt, clippy, 89 tests, 100 generated seeds agreeing in 68.5 s, and all five shrinker cases at 1000 seeds of depth 6 (159,024 widgets each). `tabs`, `view`, `minimal`, `text` and `random` render byte-identical at 1920x1200 against the same worktree without the change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d3b0ebf90c
commit
5ed9e874a3
2 files changed
+112
-12
No files matched your search
+90
-1
@@ -3,7 +3,9 @@
|
||||
//! frame that had not settled: a wrapping text shaped at a width it was
|
||||
//! measured in rather than the one it was given. The rest are a widget
|
||||
//! measured again in a box its own answer had decided, where the old answer
|
||||
//! is a fixed point whatever the content now says.
|
||||
//! is a fixed point whatever the content now says. The last is neither: one
|
||||
//! box length, composed two ways, landing either side of the boundary that
|
||||
//! decided whether a child was drawn at all.
|
||||
|
||||
use iris::harness::Harness;
|
||||
use iris::prelude::*;
|
||||
@@ -311,3 +313,90 @@ fn a_scrolls_retained_answer_is_the_one_a_cold_layout_asks_for() {
|
||||
|
||||
assert_eq!(warm.region(&scroll), cold.region(&cold_scroll));
|
||||
}
|
||||
|
||||
/// Six widgets, shrunk from 266. `measured`'s box is exactly the height of its
|
||||
/// one fixed child, which is the box a parent sizing itself from that answer
|
||||
/// hands back -- so whether its leftover-only child was drawn at all came down
|
||||
/// to the 0.00003 px the composed length differs by, one way warm and the
|
||||
/// other cold.
|
||||
fn plant_boundary(h: &mut Harness, swapped: bool) -> (Vec<WidgetId>, [WeakWidget<Span>; 2]) {
|
||||
let filler = rect(Color::RED).add(&mut h.rsc);
|
||||
let plain = wtext("one line, overflowing whatever it is given")
|
||||
.size(16)
|
||||
.wrap(false)
|
||||
.add(&mut h.rsc);
|
||||
let mut pair: Vec<StrongWidget> =
|
||||
vec![filler.add_strong(&mut h.rsc), plain.add_strong(&mut h.rsc)];
|
||||
if swapped {
|
||||
pair.rotate_left(1);
|
||||
}
|
||||
let measured = Span {
|
||||
children: pair,
|
||||
dir: Dir::DOWN,
|
||||
gap: 0.0,
|
||||
ortho: OrthoSize::Children,
|
||||
}
|
||||
.add(&mut h.rsc);
|
||||
// Takes the whole box on its own, so the span above has nothing left to
|
||||
// divide and `measured` is given exactly the text's height.
|
||||
let whole = rect(Color::RED).add(&mut h.rsc);
|
||||
h.rsc
|
||||
.widgets_mut()
|
||||
.set_size_rules(whole, None, Some(Len::rel(1.0)));
|
||||
let mut inner_children: Vec<StrongWidget> = vec![
|
||||
measured.add_strong(&mut h.rsc),
|
||||
whole.add_strong(&mut h.rsc),
|
||||
];
|
||||
if swapped {
|
||||
inner_children.rotate_left(1);
|
||||
}
|
||||
let inner = Span {
|
||||
children: inner_children,
|
||||
dir: Dir::DOWN,
|
||||
gap: 0.0,
|
||||
ortho: OrthoSize::Children,
|
||||
}
|
||||
.add(&mut h.rsc);
|
||||
h.rsc
|
||||
.widgets_mut()
|
||||
.set_size_rules(inner, None, Some(Len::px(198.0)));
|
||||
// One more span above it: without a box composed through it, both trees
|
||||
// round the same way and the boundary is never crossed.
|
||||
let outer = (inner,).span(Dir::DOWN).add(&mut h.rsc);
|
||||
h.set_root(outer);
|
||||
(
|
||||
vec![
|
||||
filler.id(),
|
||||
plain.id(),
|
||||
measured.id(),
|
||||
whole.id(),
|
||||
inner.id(),
|
||||
outer.id(),
|
||||
],
|
||||
[measured, inner],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_box_that_only_rounds_past_its_fixed_children_leaves_nothing_over() {
|
||||
let mut warm = Harness::new((640, 900));
|
||||
let (ids, spans) = plant_boundary(&mut warm, false);
|
||||
warm.frame();
|
||||
for span in spans {
|
||||
warm.rsc[span].children.rotate_left(1);
|
||||
}
|
||||
warm.frame();
|
||||
|
||||
let mut cold = Harness::new((640, 900));
|
||||
let (cold_ids, _) = plant_boundary(&mut cold, true);
|
||||
cold.frame();
|
||||
|
||||
let mut wrong = Vec::new();
|
||||
for (i, (&w, &c)) in ids.iter().zip(&cold_ids).enumerate() {
|
||||
let (got, want) = (warm.region(&w), cold.region(&c));
|
||||
if got != want {
|
||||
wrong.push(format!("widget {i}: warm {got:?} cold {want:?}"));
|
||||
}
|
||||
}
|
||||
assert!(wrong.is_empty(), "{}", wrong.join("\n"));
|
||||
}
|
||||
Reference in new issue
Block a user