Take two roundings out of where a box comes from

Traced what was left of the warm-against-cold difference after fixed point.
It is not accumulation and not one place: it is the same box reached two
ways, and each way rounds where the other does not.

`Scroll` was writing a box it had been given back out as its own length in
pixels. That is the same box in another form, and centring a part in `rel 1`
lands a step from centring it in `px 900`, because halving a difference is
not halving each part of it. Content that fills the viewport and has not been
scrolled is now handed back as it came, which makes the shrinker's `repaint`
and `resize-repaint` cases agree exactly rather than within a step.

`Span` placed each child a step from where the last one ended, so the
rounding of every share was carried along the row. A position is now the
fixed parts before it -- a sum, exact -- plus one rounded share of the room.
Measured: two hundred equal shares of a 1000 px row ended at 999.999 and now
end at 1000, and `tests/cases/layout.rs` pins it at 2, 3, 7, 64 and 200.

What is left is a step per level of nesting between the two ways, which is
what the fuzzers now allow: four of the five shrinker cases pass at one step
and the fifth is five spans deep. Closing it needs one way of asking where a
box is, which is a bigger change than this.

Checked: fmt, clippy, 103 tests, all five shrinker cases at 300 seeds, 100
generated seeds, five examples byte-identical at 1920x1200.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
iris-aiandClaude Opus 5 committed 2026-09-16 03:42:09 -04:00
1 parent 9d8415d65f
commit bdab55824f
6 files changed
+90 -25

No files matched your search

+26 -9
View File
@@ -83,6 +83,15 @@ impl Widget for Span {
// rule beside it already says how long it is, and then reading them
// answers nothing and makes its size depend on theirs for it.
let shrinks = !painter.ruled(!axis);
// What the fixed parts and the gaps before here take, which is a sum
// of lengths and exact, and how much of the leftover weight is
// spoken for. A position is one from the other rather than a step
// from the last child: the share of the room is rounded, and taking
// each from the one before it would carry every rounding along the
// row.
let mut fixed = UiScalar::rel_min();
let mut taken = Weight::ZERO;
let room = UiScalar::rel_max() - UiScalar::from_parts(total.rel, total.px);
let mut start = UiScalar::rel_min();
let mut ortho = Len::ZERO;
for (child, len) in self.children.iter().zip(&lens) {
@@ -92,20 +101,17 @@ impl Widget for Span {
if len.leftover > Weight::ZERO && len.px == Px::ZERO && len.rel == Rel::ZERO && !shares
{
painter.undraw(child);
start.px += self.gap;
fixed.px += self.gap;
continue;
}
let mut span = UiSpan::FULL;
span.start = start;
if len.leftover > Weight::ZERO && shares {
let offset = UiScalar::from_parts(total.rel, total.px);
let share = Rel::ratio(len.leftover, total.leftover);
let rel_end = UiScalar::from_parts(share, Px::ZERO);
let end = (UiScalar::rel_max() + start) - offset;
start = rel_end.within(&start.to(end));
taken += len.leftover;
}
start.px += len.px;
start.rel += len.rel;
fixed.px += len.px;
fixed.rel += len.rel;
start = shared(fixed, taken, total.leftover, room);
span.end = start;
let mut region = UiRegion::from_axis(axis, span, UiSpan::FULL);
if self.dir.sign == Sign::Neg {
@@ -124,7 +130,8 @@ impl Widget for Span {
ortho.px = ortho.px.max(used.px);
}
}
start.px += self.gap;
fixed.px += self.gap;
start = shared(fixed, taken, total.leftover, room);
}
// Carried whole rather than collapsed to one share: a span that sizes
@@ -143,6 +150,16 @@ impl Widget for Span {
}
}
/// Where a row has reached: everything fixed before this point, which is a
/// sum and exact, plus the share of the room the weights so far are worth,
/// which is one rounding wherever it is asked for.
fn shared(fixed: UiScalar, taken: Weight, weight: Weight, room: UiScalar) -> UiScalar {
if taken == Weight::ZERO {
return fixed;
}
fixed + room.scale(Rel::ratio(taken, weight))
}
impl Span {
pub fn empty(dir: Dir) -> Self {
Self {