Name the values layout carries, and say what a span's slot is

`along` said nothing about what it did. It is `Span::slot` now: the
stretch of the row between two distances from where the span starts
laying out, as a span of its own box, with the mirror for a negative
direction in one place. `far` is `row`, which is what the comment above
it already called it, and `shares` is `has_room` beside the
`any_leftover` it was folded into. `reached` now guards on the leftover
weight it divides by rather than on the numerator that happened to be
zero with it.

The pairs layout returns are named rather than positional: `Answer`
{size, holds} and `Drawn` {answer, drawing_holds} replace
`(Size, LayoutHolds)` and a three-tuple with two `LayoutHolds` in it,
which was the one shape the cold dump exists to catch. `try_reuse`
answers `bool` rather than `Option<()>`, and the four hand-written
copies of `move_idx != parent_move` are `ActiveData::is_region_node`.

`AXES` was declared in three modules; it is `Axis::BOTH`. `rel_min`,
`rel_max` and the unused `select_len` are gone -- `ZERO` and `FULL`
already said those. Three doc comments sat on `impl` blocks instead of
the single method inside them. `reposition` and `redepth` walked their
children by index, looking the parent up again per child; both take the
list and put it back. `Scroll`'s `fixed` and `fixed_len` are
`answer_px` and `answer_is_px`, which says which one is the length.

fmt, workspace clippy under `-D warnings` with and without
`layout-diagnostics`, and the workspace tests are clean. The cold dump
over 400 depth-5 trees is byte-identical to `6c84b6f`: 34,492 boxes,
no seed moved.
This commit is contained in:
iris-ai committed 2026-09-19 23:24:55 -04:00
1 parent 6c84b6f2cb
commit 3da1c71870
9 files changed
+167 -150

No files matched your search

+6 -6
View File
@@ -16,9 +16,9 @@ impl Widget for Scroll {
let answer_len = painter
.widget_at(&self.inner, PlaceDesc::WHOLE.fills())
.len(self.axis);
let fixed = painter.to_px(answer_len.without_leftover(), self.axis);
let answer_px = painter.to_px(answer_len.without_leftover(), self.axis);
self.container_len = container_len;
self.content_len = fixed.max(container_len);
self.content_len = answer_px.max(container_len);
if self.snap_end {
self.amt = self.content_len - self.container_len;
@@ -31,10 +31,10 @@ impl Widget for Scroll {
// the drawing holds for that length alone. One scrolled part way sits
// where it is until the box shrinks past what is left of it. Kept to
// the end, it moves with every length.
let fixed_len = answer_len.is_px();
if fixed_len && self.content_len <= self.container_len && align == AxisAlign::NEG {
painter.holds(self.axis, fixed..=Px::MAX);
} else if fixed_len && !self.snap_end {
let answer_is_px = answer_len.is_px();
if answer_is_px && self.content_len <= self.container_len && align == AxisAlign::NEG {
painter.holds(self.axis, answer_px..=Px::MAX);
} else if answer_is_px && !self.snap_end {
let left = self.content_len - self.amt;
painter.holds(self.axis, Px::MIN..=left);
}
+33 -23
View File
@@ -10,21 +10,18 @@ pub struct Span {
impl Widget for Span {
fn draw(&mut self, painter: &mut Painter) -> Size {
let axis = self.dir.axis;
// The row: this span's own box, as a length of the rel base its children
// are laid out against. Its start is nothing's business -- a slot is
// a length from it -- so what this reads is the length alone.
let far = painter.region_len(axis);
let along = |from: Len, to: Len| match self.dir.sign {
Sign::Pos => UiSpan::new(from, to),
Sign::Neg => UiSpan::new(far - to, far - from),
};
// The row this span lays its children out along, as a length of the
// rel base they are laid out against. Where it starts is nothing's
// business -- a slot is a length from there -- so what this reads is
// the length alone.
let row = painter.region_len(axis);
// A length for every child before their final slots are chosen: from
// a hint where one says, and from drawing otherwise. The rel base passes
// through unchanged, so `rel(0.5)` is half the area this span was
// given whatever else is in it and wherever this child sits among
// them; what a drawn child is asked in is the room left from the
// cursor, because a text has to wrap at the width actually there.
let mut cursor = Len::rel_min();
let mut cursor = Len::ZERO;
let mut lens = Vec::with_capacity(self.children.len());
for child in &self.children {
let len = match painter.size_hint(child, axis) {
@@ -33,7 +30,7 @@ impl Widget for Span {
// Across itself the child sits where its own alignment
// says, in the whole of the row: a span is what contains
// its children there, and nothing divides that axis.
let room = along(cursor, far).shifted_desc().on_axis(axis);
let room = self.slot(row, cursor, row).shifted_desc().on_axis(axis);
painter.widget_at(child, room).len(axis)
}
};
@@ -55,7 +52,7 @@ impl Widget for Span {
// What is left for the shares to divide: the row less everything
// fixed, as a length of the rel base rather than a number of pixels.
let room = far - total.without_leftover();
let room = row - total.without_leftover();
// Whether anything is left over is a question in pixels: `rel(0.5)`
// beside 300 px is full at 600 and overfull at 400. Asked of `room`
// itself, and answered back through the same expression, so the
@@ -65,10 +62,10 @@ impl Widget for Span {
// sign of `room.rel`, which `through` already reads. What the
// generated oracle checks is the consequence, since which children
// exist at all turns on this.
let mut shares = false;
if total.leftover > Weight::ZERO {
shares = painter.to_px(room, axis) > Px::ZERO;
let holds = match shares {
let any_leftover = total.leftover > Weight::ZERO;
let has_room = any_leftover && painter.to_px(room, axis) > Px::ZERO;
if any_leftover {
let holds = match has_room {
true => Holds::from(Px::STEP..=Px::MAX),
false => Holds::from(Px::MIN..=Px::ZERO),
};
@@ -87,24 +84,26 @@ impl Widget for Span {
// than stepped from the last child: the share of the room is
// rounded, and taking each end from the one before it would carry
// every rounding along the row.
let mut fixed = Len::rel_min();
let mut fixed = Len::ZERO;
let mut taken = Weight::ZERO;
let mut ortho = LayoutLen::ZERO;
let reached = |fixed: Len, taken: Weight| match taken == Weight::ZERO {
true => fixed,
false => fixed + room.scale(Rel::ratio(taken, total.leftover)),
// Nothing divides the room where no child asked for any of it, and a
// ratio of a whole of nothing has no answer.
let reached = |fixed: Len, taken: Weight| match any_leftover {
false => fixed,
true => fixed + room.scale(Rel::ratio(taken, total.leftover)),
};
for (child, &len) in self.children.iter().zip(&lens) {
// A child asking for nothing but a part of what is left over,
// when nothing is, is not drawn at all. One that also asked for
// pixels or a fraction keeps those and overflows.
if len.is_only_leftover() && !shares {
if len.is_only_leftover() && !has_room {
painter.undraw(child);
fixed.px += self.gap;
continue;
}
let from = reached(fixed, taken);
if len.leftover > Weight::ZERO && shares {
if len.leftover > Weight::ZERO && has_room {
taken += len.leftover;
}
fixed += len.without_leftover();
@@ -116,9 +115,9 @@ impl Widget for Span {
// it, since a text wraps at the width it is actually given. A
// fixed child's slot is its own answer, so a drawing made in the
// room is put there as it is, and one not made yet is made here.
let slot = along(from, to);
let slot = self.slot(row, from, to);
let mut place = slot.shifted_desc().fills().on_axis(axis);
if len.leftover > Weight::ZERO && shares {
if len.leftover > Weight::ZERO && has_room {
place = place.rel_base(axis, slot.len());
}
let used = painter.place_at(child, place).len(!axis);
@@ -152,6 +151,17 @@ impl Widget for Span {
}
impl Span {
/// The stretch of the row between two distances from where this span
/// starts laying children out, as a span of its own box. A negative
/// direction lays out from the far end, so the same two distances mirror
/// in a row `row` long.
fn slot(&self, row: Len, from: Len, to: Len) -> UiSpan {
match self.dir.sign {
Sign::Pos => from.to(to),
Sign::Neg => (row - to).to(row - from),
}
}
pub fn empty(dir: Dir) -> Self {
Self {
children: Vec::new(),