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

+118 -119
View File
@@ -3,7 +3,7 @@ use crate::layout_diagnostics::{self as diag, Counter};
use crate::{
Axis, Holds, LayoutHolds, LayoutLen, Len, PlaceDesc, Px, PxVec2, RegionAlign, Rel,
RenderedText, RetainedPrimitive, Size, StrongWidget, TextAttrs, TextBuffer, TextData,
TextureHandle, UiRegion, UiRenderState, UiRsc, UiSpan, UiVec2, Weight, WidgetId, Widgets,
TextureHandle, UiRegion, UiRenderState, UiRsc, UiVec2, Weight, WidgetId, Widgets,
render::{
GlyphPrimitive, Mask, MaskIdx, MoveIdx, Primitive, PrimitiveInst, PrimitiveKind,
TexturePrimitive,
@@ -157,10 +157,10 @@ impl<'a> Painter<'a> {
/// only moves its child does not pin its drawing to a rel base.
fn state_rel_base(&mut self, mut place: PlaceDesc) -> PlaceDesc {
for axis in AXES {
if let Some(span) = place.axis(axis).narrows_rel_base() {
if let Some(span) = place[axis].narrows_rel_base() {
let len = span.len();
let stated = (len != Len::FULL).then(|| len.within_len(self.rel_base(axis)));
*place.axis_mut(axis) = place.axis(axis).with_rel_base(stated);
place[axis] = place[axis].with_rel_base(stated);
}
}
place
@@ -186,7 +186,7 @@ impl<'a> Painter<'a> {
let declared = self.declared_lens(id);
let align = self.rsc.widgets().alignment(id.id());
let (rel_base, region) =
rel_base_and_region(self.region, self.rel_base, place, declared, align);
place.rel_base_and_region(self.region, self.rel_base, declared, align);
#[cfg(feature = "layout-diagnostics")]
if region_node {
diag::bump(Counter::RegionNodeDraws);
@@ -258,7 +258,7 @@ impl<'a> Painter<'a> {
let place = self.state_rel_base(place.into());
let states_rel_base = AXES
.iter()
.any(|&axis| place.axis(axis).stated_rel_base().is_some());
.any(|&axis| place[axis].stated_rel_base().is_some());
if states_rel_base || !self.children.contains(&id.id()) {
return self.widget_at(id, place);
}
@@ -293,7 +293,7 @@ impl<'a> Painter<'a> {
/// that comes of them is kept on the child, and `redraw` compares it
/// there.
fn declared_lens<W: ?Sized>(&self, id: &StrongWidget<W>) -> [Option<LayoutLen>; 2] {
declared_lens(self.rsc.widgets(), id.id())
self.rsc.widgets().declared_lens(id.id())
}
/// What a child says its length is without being drawn, if it can say,
@@ -304,12 +304,12 @@ impl<'a> Painter<'a> {
let widgets = self.rsc.widgets();
// A rule is the answer where there is one: it wins over whatever the
// widget would draw, so it has to win over what the widget says too.
let hint = widgets.size_rules(id.id()).axis(axis).exact().or_else(|| {
let hint = widgets.size_rules(id.id())[axis].exact().or_else(|| {
widgets
.get_dyn(id.id())
.and_then(|widget| widget.size_hint(axis))
});
let rel_base = self.rel_base.axis(axis);
let rel_base = self.rel_base[axis];
let resolved = hint.map(|hint| hint.within_len(rel_base));
#[cfg(feature = "layout-diagnostics")]
{
@@ -326,7 +326,7 @@ impl<'a> Painter<'a> {
// the child's own: resolved against a rel base of pixels, none is
// left to see it by.
if hint.rel != Rel::ZERO {
self.own.rel_base[axis as usize] = Some(rel_base);
self.own[axis].rel_base = Some(rel_base);
}
}
resolved
@@ -392,8 +392,8 @@ impl<'a> Painter<'a> {
/// again. One axis at a time, because a container that divides one axis
/// holds for any length of the other.
pub fn region_len(&mut self, axis: Axis) -> Len {
let len = self.region.axis(axis).len();
self.own.region_len[axis as usize] = Some(len);
let len = self.region[axis].len();
self.own[axis].region_len = Some(len);
len
}
@@ -403,8 +403,8 @@ impl<'a> Painter<'a> {
/// Reading it pins the drawing to that rel base, the way
/// [`Self::region_len`] pins it to the box.
pub fn rel_base(&mut self, axis: Axis) -> Len {
let len = self.rel_base.axis(axis);
self.own.rel_base[axis as usize] = Some(len);
let len = self.rel_base[axis];
self.own[axis].rel_base = Some(len);
len
}
@@ -425,10 +425,7 @@ impl<'a> Painter<'a> {
/// worth anything, since reading one is also what makes its own size
/// depend on it.
pub fn has_exact_size(&self, axis: Axis) -> bool {
self.rsc
.widgets()
.size_rules(self.id)
.axis(axis)
self.rsc.widgets().size_rules(self.id)[axis]
.exact()
.is_some()
}
@@ -442,9 +439,9 @@ impl<'a> Painter<'a> {
/// One axis of this widget's own box in pixels. Prefer this to
/// [`Self::px_size`] when the other axis cannot affect the drawing.
pub fn px_len(&mut self, axis: Axis) -> Px {
let len = self.region.axis(axis).len();
let px = len.to_px(self.window.axis(axis));
let own = &mut self.own.region[axis as usize];
let len = self.region[axis].len();
let px = len.to_px(self.window[axis]);
let own = &mut self.own[axis].region;
if *own == Holds::ANY {
*own = Holds::at(px);
}
@@ -456,15 +453,15 @@ impl<'a> Painter<'a> {
/// of the box, and the same reported size. A widget that read its length
/// in pixels holds for that one alone until it says otherwise.
pub fn holds(&mut self, axis: Axis, holds: impl Into<Holds>) {
let len = self.region.axis(axis).len();
let len = self.region[axis].len();
let holds = holds.into();
debug_assert!(
holds.contains(len.to_px(self.window.axis(axis))),
holds.contains(len.to_px(self.window[axis])),
"'{}' ({:?}) says its drawing holds for lengths that leave out its own box",
self.label(),
self.id
);
self.own.region[axis as usize] = holds;
self.own[axis].region = holds;
}
/// A window length in pixels, which is what every length in layout is
@@ -472,9 +469,9 @@ impl<'a> Painter<'a> {
/// length is a fraction of it; one that is only pixels is that many
/// pixels in any window and pins nothing.
pub fn to_px(&mut self, len: Len, axis: Axis) -> Px {
let window = self.window.axis(axis);
let window = self.window[axis];
if len.rel != Rel::ZERO {
let own = &mut self.own.window[axis as usize];
let own = &mut self.own[axis].window;
if *own == Holds::ANY {
*own = Holds::at(window);
}
@@ -489,12 +486,12 @@ impl<'a> Painter<'a> {
pub fn window_holds(&mut self, axis: Axis, holds: impl Into<Holds>) {
let holds = holds.into();
debug_assert!(
holds.contains(self.window.axis(axis)),
holds.contains(self.window[axis]),
"'{}' ({:?}) says its drawing holds for windows that leave out this one",
self.label(),
self.id
);
self.own.window[axis as usize] = holds;
self.own[axis].window = holds;
}
pub fn text_data(&mut self) -> &mut TextData {
@@ -553,7 +550,7 @@ impl<W: ?Sized> DrawResult<'_, '_, W> {
}
pub fn len(self, axis: Axis) -> LayoutLen {
self.size().axis(axis)
self.size()[axis]
}
}
@@ -606,16 +603,18 @@ impl Painter<'_> {
) -> LayoutHolds {
let mut result = LayoutHolds::ANY;
for axis in AXES {
let n = axis as usize;
let declared = declared[axis as usize];
let holds = holds[axis];
let result = &mut result[axis];
// Every read became pixels against the window, so a range on
// it is already in this widget's terms.
result.window[n] = holds.window[n];
let at = *place.axis(axis);
result.window = holds.window;
let at = place[axis];
let reaches = at.stated_rel_base().is_none()
&& !at.is_sized()
&& declared[n].is_none_or(|len| len.rel != Rel::ZERO);
result.rel_base[n] = holds.rel_base[n].and(reaches.then(|| self.rel_base.axis(axis)));
match (at.within_span(), declared[n].is_some()) {
&& declared.is_none_or(|len| len.rel != Rel::ZERO);
result.rel_base = holds.rel_base.and(reaches.then(|| self.rel_base[axis]));
match (at.within_span(), declared.is_some()) {
// Its box is a part of this widget's own box, in that box's
// own lengths, so what it holds for maps back through that
// part into a range on this widget's box. A length it pinned
@@ -625,10 +624,10 @@ impl Painter<'_> {
// widget's own length.
(Some(span), false) => {
let part_len = span.len();
result.region[n] = holds.region[n].through(part_len);
result.region_len[n] = holds.region_len[n].map(|pinned| match part_len.rel {
result.region = holds.region.through(part_len);
result.region_len = holds.region_len.map(|pinned| match part_len.rel {
Rel::ONE => pinned - Len::from_parts(Rel::ZERO, part_len.px),
_ => self.region.axis(axis).len(),
_ => self.region[axis].len(),
});
}
// Its box is a length this widget decided, from its own
@@ -636,8 +635,7 @@ impl Painter<'_> {
// widget's box reaches it, so what it holds for is a range
// on the window and none of it on that box.
_ => {
result.window[n] =
result.window[n].and(holds.region[n].through(region.axis(axis).len()));
result.window = result.window.and(holds.region.through(region[axis].len()));
}
}
}
@@ -645,35 +643,40 @@ impl Painter<'_> {
}
}
/// What a widget declares a length of its box to be. `leftover` is not one: a
/// share of what is left over is only a length to the widget dividing one,
/// so it passes up in the size instead.
pub(crate) fn declared_lens(widgets: &Widgets, id: WidgetId) -> [Option<LayoutLen>; 2] {
let rules = widgets.size_rules(id);
let widget = widgets.get_dyn(id);
AXES.map(|axis| {
rules.axis(axis).declared().or_else(|| {
// A hint still narrows the box where no rule does, which is how a
// widget with a natural pixel size -- an image, a gap -- gets that
// size rather than the whole offer. That is the offer's business
// rather than a declaration's, and this falls away once a widget
// occupies its reported size inside the box it was offered.
widget
.and_then(|widget| widget.size_hint(axis))
.filter(|len| len.leftover == Weight::ZERO)
impl Widgets {
/// What a widget declares a length of its box to be. `leftover` is not
/// one: a share of what is left over is only a length to the widget
/// dividing one, so it passes up in the size instead.
pub(crate) fn declared_lens(&self, id: WidgetId) -> [Option<LayoutLen>; 2] {
let rules = self.size_rules(id);
let widget = self.get_dyn(id);
AXES.map(|axis| {
rules[axis].declared().or_else(|| {
// A hint still narrows the box where no rule does, which is
// how a widget with a natural pixel size -- an image, a gap
// -- gets that size rather than the whole offer. That is the
// offer's business rather than a declaration's, and this
// falls away once a widget occupies its reported size inside
// the box it was offered.
widget
.and_then(|widget| widget.size_hint(axis))
.filter(|len| len.leftover == Weight::ZERO)
})
})
})
}
}
/// Whether what a widget reported along an axis is the whole of the box it
/// is in rather than a part to be placed inside it. A share fills, because a
/// share is a length only to whoever divides one, and whoever did is the one
/// that handed down this box. A declared axis does too: the rule already gave
/// the region its length, and the rule's length is what the widget reports
/// there. And an axis the parent decided from the answer is
/// the answer already.
pub(crate) fn fills(reported: LayoutLen, declared: Option<LayoutLen>, decided: bool) -> bool {
reported.leftover != Weight::ZERO || declared.is_some() || decided
impl LayoutLen {
/// Whether what a widget reported along an axis is the whole of the box
/// it is in rather than a part to be placed inside it. A share fills,
/// because a share is a length only to whoever divides one, and whoever
/// did is the one that handed down this box. A declared axis does too:
/// the rule already gave the region its length, and the rule's length is
/// what the widget reports there. And an axis the parent decided from
/// the answer is the answer already.
pub(crate) fn fills(self, declared: Option<LayoutLen>, decided: bool) -> bool {
self.leftover != Weight::ZERO || declared.is_some() || decided
}
}
/// Where a widget's drawing goes inside the part its parent gave it: what
@@ -685,63 +688,59 @@ pub(crate) fn fills(reported: LayoutLen, declared: Option<LayoutLen>, decided: b
/// That is what makes a fraction the same fraction wherever the part it is
/// placed in sits and however long it is -- the fraction is resolved once,
/// here, against the rel base it was reported of.
pub(crate) fn placement(
region: UiRegion,
size: Size,
declared: [Option<LayoutLen>; 2],
place: PlaceDesc,
align: RegionAlign,
) -> UiRegion {
let mut placed = region;
for axis in AXES {
let n = axis as usize;
let reported = size.axis(axis);
if fills(reported, declared[n], place.axis(axis).does_fill()) {
continue;
impl PlaceDesc {
pub(crate) fn placement(
self,
region: UiRegion,
size: Size,
declared: [Option<LayoutLen>; 2],
align: RegionAlign,
) -> UiRegion {
let mut placed = region;
for axis in AXES {
let n = axis as usize;
let reported = size[axis];
if reported.fills(declared[n], self[axis].does_fill()) {
continue;
}
placed[axis] = placed[axis].place(reported.without_leftover(), align[axis]);
}
let len = Len::from_parts(reported.rel, reported.px);
let span = placed.axis_mut(axis);
span.start += (span.len() - len).scale(align.axis(axis).rel());
span.end = span.start + len;
placed
}
placed
}
/// The rel base length and the box a child is asked in, in the coordinates the
/// widget asking draws in.
///
/// `own` is that widget's own box, and `place` what of it the child is
/// given, including any rel base it states -- a row's slot, or padding's rel
/// base less its pixels. That is a window length, like every other length
/// here, since a slot of a row is not a fraction of anything the row can
/// name. The child's declaration is a fraction of whichever reached it, and
/// is the only one that also places the box: a box the caller decided is
/// what `place` names.
pub(crate) fn rel_base_and_region(
own: UiRegion,
parent_rel_base: UiVec2,
place: PlaceDesc,
declared: [Option<LayoutLen>; 2],
align: RegionAlign,
) -> (UiVec2, UiRegion) {
let given = place.of(own, align);
let mut rel_base = parent_rel_base;
let mut region = given;
for axis in AXES {
let n = axis as usize;
let base = place
.axis(axis)
.stated_rel_base()
.unwrap_or_else(|| parent_rel_base.axis(axis));
let len = declared[n]
.map(|len| Len::from_parts(len.rel, len.px).within_len(base))
.unwrap_or(base);
*rel_base.axis_mut(axis) = len;
if declared[n].is_some() {
let slot = given.axis(axis);
let start = slot.start + (slot.len() - len).scale(align.axis(axis).rel());
*region.axis_mut(axis) = UiSpan::new(start, start + len);
/// The rel base length and the box a child is asked in, in the coordinates the
/// widget asking draws in.
///
/// `own` is that widget's own box, and `place` what of it the child is
/// given, including any rel base it states -- a row's slot, or padding's rel
/// base less its pixels. That is a window length, like every other length
/// here, since a slot of a row is not a fraction of anything the row can
/// name. The child's declaration is a fraction of whichever reached it, and
/// is the only one that also places the box: a box the caller decided is
/// what `place` names.
pub(crate) fn rel_base_and_region(
self,
own: UiRegion,
parent_rel_base: UiVec2,
declared: [Option<LayoutLen>; 2],
align: RegionAlign,
) -> (UiVec2, UiRegion) {
let given = self.of(own, align);
let mut rel_base = parent_rel_base;
let mut region = given;
for axis in AXES {
let n = axis as usize;
let base = self[axis]
.stated_rel_base()
.unwrap_or_else(|| parent_rel_base[axis]);
let len = declared[n]
.map(|len| len.without_leftover().within_len(base))
.unwrap_or(base);
rel_base[axis] = len;
if declared[n].is_some() {
region[axis] = given[axis].place(len, align[axis]);
}
}
(rel_base, region)
}
(rel_base, region)
}