Say layout's operations by name, and index a pair by its axis

Four rounds over the same idea: an expression that needed a comment to say
what it computed wanted to be a named operation.

The placement description is built by chaining off the value that says it.
`UiSpan::within_desc`/`shifted_desc` and `Len::as_desc` replace the
`PlaceDescAxis::` constructors, `PlaceDescAxis::axis` lifts one axis into a
pair with the whole box across it, and `PlaceDesc::per_axis` covers the case
where the two axes differ. `beside` is dropped: `from_axis` already said it.

Seven module-level functions become methods on the value each took first --
`Widgets::declared_lens`, `LayoutLen::fills`, `PlaceDesc::placement` and
`::rel_base_and_region`, `Size::within_box`, `UiRegion::at_origin` and
`::as_translation`.

`UiSpan::place` is the aligned-placement rule, which was written out three
times; `LayoutLen::without_leftover` is the sibling `apply_leftover` never
had, at six sites; `is_px` and `is_only_leftover` name field comparisons the
surrounding comments had to translate; `Holds::covers` was interval
containment spelled out by hand. A span's `shared` loses the two arguments
that did not vary across its loop.

`LayoutHolds` was four two-element arrays where every other pair here is a
struct of two per-axis values, so nothing it did could be written once.
It becomes `AxisHolds` on `x` and `y`, and `and`, `covers` and `contains`
lose their loops.

Every pair gets `Index<Axis>`/`IndexMut<Axis>` through one macro, and the
eighteen `axis`/`axis_mut` methods go. `const_index` keeps the accessors
usable in const context.

Cold layout is unchanged: `layout_dump` over 400 depth-5 trees is identical
to 58ce74d byte for byte, across all 34,492 boxes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
iris-aiandClaude Opus 5 committed 2026-09-19 20:56:59 -04:00
1 parent 58ce74dd7d
commit 55df32a33c
21 files changed
+465 -490

No files matched your search

+17 -29
View File
@@ -18,10 +18,6 @@ impl Widget for Span {
Sign::Pos => UiSpan::new(from, to),
Sign::Neg => UiSpan::new(far - to, far - from),
};
// 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 across = PlaceDescAxis::WHOLE;
// 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
@@ -34,10 +30,11 @@ impl Widget for Span {
let len = match painter.size_hint(child, axis) {
Some(len) => len,
None => {
let room = PlaceDescAxis::shifted(along(cursor, far));
painter
.widget_at(child, PlaceDesc::from_axis(axis, room, across))
.len(axis)
// 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().axis(axis);
painter.widget_at(child, room).len(axis)
}
};
cursor.px += len.px + self.gap;
@@ -58,7 +55,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 - Len::from_parts(total.rel, total.px);
let room = far - 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
@@ -94,24 +91,26 @@ impl Widget for Span {
let mut taken = Weight::ZERO;
let mut start = Len::rel_min();
let mut ortho = LayoutLen::ZERO;
let shared = |fixed: Len, taken: Weight| match taken == Weight::ZERO {
true => fixed,
false => 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.leftover > Weight::ZERO && len.px == Px::ZERO && len.rel == Rel::ZERO && !shares
{
if len.is_only_leftover() && !shares {
painter.undraw(child);
fixed.px += self.gap;
start = shared(fixed, taken, total.leftover, room);
start = shared(fixed, taken);
continue;
}
let from = start;
if len.leftover > Weight::ZERO && shares {
taken += len.leftover;
}
fixed.px += len.px;
fixed.rel += len.rel;
start = shared(fixed, taken, total.leftover, room);
fixed += len.without_leftover();
start = shared(fixed, taken);
// Along the row the span says where the child goes, and that slot
// is the child's box outright rather than something to place an
// answer inside again. A share is decided here and nowhere
@@ -120,8 +119,7 @@ impl Widget for Span {
// 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, start);
let slot_place = PlaceDescAxis::shifted(slot).fills();
let mut place = PlaceDesc::from_axis(axis, slot_place, across);
let mut place = slot.shifted_desc().fills().axis(axis);
if len.leftover > Weight::ZERO && shares {
place = place.rel_base(axis, slot.len());
}
@@ -131,14 +129,14 @@ impl Widget for Span {
// span's own eventual width admits multiple fixed points.
// A scalable child therefore makes Children scalable too;
// only fixed children are compared with one another.
if used.rel != Rel::ZERO || used.leftover != Weight::ZERO {
if !used.is_px() {
ortho = LayoutLen::LEFTOVER;
} else if ortho.leftover == Weight::ZERO {
ortho.px = ortho.px.max(used.px);
}
}
fixed.px += self.gap;
start = shared(fixed, taken, total.leftover, room);
start = shared(fixed, taken);
}
// Carried whole rather than collapsed to one share: a span that sizes
@@ -157,16 +155,6 @@ 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: Len, taken: Weight, weight: Weight, room: Len) -> Len {
if taken == Weight::ZERO {
return fixed;
}
fixed + room.scale(Rel::ratio(taken, weight))
}
impl Span {
pub fn empty(dir: Dir) -> Self {
Self {