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

+74 -71
View File
@@ -1,3 +1,4 @@
use crate::util::impl_axis_index;
use crate::{Axis, AxisAlign, Len, PrimitiveHandle, RegionAlign, UiRegion, UiSpan};
/// How a child's region along one axis comes from the region of the widget
@@ -35,53 +36,7 @@ enum RelBase {
impl PlaceDescAxis {
/// The whole of the caller's box.
pub const WHOLE: Self = Self::within(UiSpan::FULL);
/// `span` composed into the caller's own box, so it moves and scales
/// with it: [`UiSpan::within`], which is what a container that insets
/// one speaks. Taking eleven pixels off the end needs no length, where
/// saying the same thing in window lengths would make the container read
/// its own box -- and a box chosen from its own answer then feeds back
/// into the answer.
///
/// The child's rel base is narrowed the same way, so padding takes its
/// pixels off both and `rel(1)` under it fills the caller rather than
/// overflowing it.
pub const fn within(span: UiSpan) -> Self {
Self {
span: PlaceSpan::Within(span),
fills: false,
rel_base: RelBase::WithRegion,
}
}
/// `span` shifted to where the caller's own box starts: window lengths
/// along a cursor, which is what a container dividing room among its
/// children speaks. A child's report is a window length, so the cursor
/// that sums those reports is one too, and a moved box re-places every
/// child by re-adding its start, exactly.
///
/// The child's rel base passes through: how far along the cursor a child
/// sits says nothing about what a fraction under it is of.
pub const fn shifted(span: UiSpan) -> Self {
Self {
span: PlaceSpan::Shifted(span),
fills: false,
rel_base: RelBase::Inherit,
}
}
/// A box this long, placed in the caller's own by the child's alignment:
/// the rule that places an answer, with the length given from above
/// rather than reported. What a stack's sizing child decides for the
/// rest. It is the child's rel base too.
pub const fn sized(len: Len) -> Self {
Self {
span: PlaceSpan::Sized(len),
fills: false,
rel_base: RelBase::Len(len),
}
}
pub const WHOLE: Self = UiSpan::FULL.within_desc();
/// This region is the child's placement: its answer is not placed inside
/// it again. A container uses it where it hands back exactly what the
@@ -91,6 +46,14 @@ impl PlaceDescAxis {
self
}
/// This along `axis`, and the whole of the caller's box across it: what
/// a container dividing one axis says, since nothing divides the other.
/// [`PlaceDesc::from_axis`] says the across one where it is not the
/// whole.
pub const fn axis(self, axis: Axis) -> PlaceDesc {
PlaceDesc::from_axis(axis, self, Self::WHOLE)
}
/// What the child's fractions are of, as a length of the window: a
/// resolved share, or a box a sibling's answer decided.
pub const fn rel_base(mut self, len: Len) -> Self {
@@ -112,10 +75,7 @@ impl PlaceDescAxis {
span.shift(own.start);
span
}
PlaceSpan::Sized(len) => {
let start = own.start + (own.len() - len).scale(align.rel());
UiSpan::new(start, start + len)
}
PlaceSpan::Sized(len) => own.place(len, align),
}
}
@@ -185,29 +145,21 @@ impl PlaceDesc {
Self { x: place, y: place }
}
/// A description per axis, where the two differ and neither is the
/// axis a container divides.
pub fn per_axis(f: impl Fn(Axis) -> PlaceDescAxis) -> Self {
Self::new(f(Axis::X), f(Axis::Y))
}
/// `aligned` on `axis` and `ortho` on the other, which is how a
/// container that divides one axis says what it is doing.
pub fn from_axis(axis: Axis, aligned: PlaceDescAxis, ortho: PlaceDescAxis) -> Self {
pub const fn from_axis(axis: Axis, aligned: PlaceDescAxis, ortho: PlaceDescAxis) -> Self {
match axis {
Axis::X => Self::new(aligned, ortho),
Axis::Y => Self::new(ortho, aligned),
}
}
pub const fn axis(&self, axis: Axis) -> &PlaceDescAxis {
match axis {
Axis::X => &self.x,
Axis::Y => &self.y,
}
}
pub const fn axis_mut(&mut self, axis: Axis) -> &mut PlaceDescAxis {
match axis {
Axis::X => &mut self.x,
Axis::Y => &mut self.y,
}
}
/// Both regions are the child's placement. See [`PlaceDescAxis::fills`].
pub const fn fills(self) -> Self {
Self::new(self.x.fills(), self.y.fills())
@@ -215,7 +167,7 @@ impl PlaceDesc {
/// The child's rel base on one axis. See [`PlaceDescAxis::rel_base`].
pub const fn rel_base(mut self, axis: Axis, len: Len) -> Self {
*self.axis_mut(axis) = self.axis(axis).rel_base(len);
self[axis] = self[axis].rel_base(len);
self
}
@@ -225,12 +177,61 @@ impl PlaceDesc {
}
}
impl UiSpan {
/// This span composed into the caller's own box, so it moves and scales
/// with it: [`UiSpan::within`], which is what a container that insets
/// one speaks. Taking eleven pixels off the end needs no length, where
/// saying the same thing in window lengths would make the container read
/// its own box -- and a box chosen from its own answer then feeds back
/// into the answer.
///
/// The child's rel base is narrowed the same way, so padding takes its
/// pixels off both and `rel(1)` under it fills the caller rather than
/// overflowing it.
pub const fn within_desc(self) -> PlaceDescAxis {
PlaceDescAxis {
span: PlaceSpan::Within(self),
fills: false,
rel_base: RelBase::WithRegion,
}
}
/// This span shifted to where the caller's own box starts: window
/// lengths along a cursor, which is what a container dividing room among
/// its children speaks. A child's report is a window length, so the
/// cursor that sums those reports is one too, and a moved box re-places
/// every child by re-adding its start, exactly.
///
/// The child's rel base passes through: how far along the cursor a child
/// sits says nothing about what a fraction under it is of. The same span
/// says [`Self::within_desc`] as a part of that box instead, and which is
/// meant cannot be read off the numbers.
pub const fn shifted_desc(self) -> PlaceDescAxis {
PlaceDescAxis {
span: PlaceSpan::Shifted(self),
fills: false,
rel_base: RelBase::Inherit,
}
}
}
impl Len {
/// A box this long, placed in the caller's own by the child's alignment:
/// the rule that places an answer, with the length given from above
/// rather than reported. What a stack's sizing child decides for the
/// rest. It is the child's rel base too.
pub const fn as_desc(self) -> PlaceDescAxis {
PlaceDescAxis {
span: PlaceSpan::Sized(self),
fills: false,
rel_base: RelBase::Len(self),
}
}
}
impl From<UiRegion> for PlaceDesc {
fn from(region: UiRegion) -> Self {
Self::new(
PlaceDescAxis::within(region.x),
PlaceDescAxis::within(region.y),
)
Self::new(region.x.within_desc(), region.y.within_desc())
}
}
@@ -247,3 +248,5 @@ pub struct RetainedPrimitive {
pub handle: PrimitiveHandle,
pub region: UiRegion,
}
impl_axis_index!(PlaceDesc => PlaceDescAxis);