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
+22
-11
@@ -44,28 +44,39 @@ impl Widget for Span {
|
||||
let total = lens.iter().fold(Len::px(gap), |sum, len| sum + *len);
|
||||
|
||||
// Whether anything is left over is a question in pixels: `rel(0.5)`
|
||||
// beside 300 px is full at 600 and overfull at 400. The answer is
|
||||
// the same on either side of the length the fixed parts alone fill.
|
||||
// beside 300 px is full at 600 and overfull at 400. The room to divide
|
||||
// is `len * fixed - total.px`, and under `HOLDS_EPSILON_PX` of it is
|
||||
// none -- because the length where the room runs out is exactly the
|
||||
// box a parent sizing itself from this answer hands back, and that box
|
||||
// returns through the chain a few bits either way. Without the margin
|
||||
// 0.00003 px of rounding decides whether a leftover-only child is
|
||||
// drawn at all. The validity range is split at the moved boundary, and
|
||||
// exact there, since no box lands on it any more.
|
||||
let fixed = 1.0 - total.rel;
|
||||
let mut shares = false;
|
||||
if total.leftover > 0.0 {
|
||||
let current = painter.px_len(axis);
|
||||
let holds = if fixed > 0.0 {
|
||||
let full = total.px / fixed;
|
||||
shares = current > full;
|
||||
// The box length at which the room reaches the margin.
|
||||
let enough = (total.px + HOLDS_EPSILON_PX) / fixed;
|
||||
shares = current > enough;
|
||||
match shares {
|
||||
true => Holds::exact(full.next_up()..=f32::INFINITY),
|
||||
false => Holds::exact(f32::NEG_INFINITY..=full),
|
||||
true => Holds::exact(enough.next_up()..=f32::INFINITY),
|
||||
false => Holds::exact(f32::NEG_INFINITY..=enough),
|
||||
}
|
||||
} else if fixed < 0.0 {
|
||||
let full = total.px / fixed;
|
||||
shares = current < full;
|
||||
// The relative parts grow faster than the box does, so here
|
||||
// a shorter box is the one that leaves room.
|
||||
let enough = (total.px + HOLDS_EPSILON_PX) / fixed;
|
||||
shares = current < enough;
|
||||
match shares {
|
||||
true => Holds::exact(f32::NEG_INFINITY..=full.next_down()),
|
||||
false => Holds::exact(full..=f32::INFINITY),
|
||||
true => Holds::exact(f32::NEG_INFINITY..=enough.next_down()),
|
||||
false => Holds::exact(enough..=f32::INFINITY),
|
||||
}
|
||||
} else {
|
||||
shares = total.px < 0.0;
|
||||
// The relative parts take exactly the box, whatever it is, so
|
||||
// the only room is what negative pixels leave.
|
||||
shares = total.px < -HOLDS_EPSILON_PX;
|
||||
Holds::ANY
|
||||
};
|
||||
painter.holds(axis, holds);
|
||||
|
||||
Reference in new issue
Block a user