Take a span child's length from its hint, and ask it once in its slot

A share child was drawn in the measuring room and again in its slot, and
one record holding two questions made every local change under it defer
to the span. Where a rule or a hint gives the length along the span, the
first ask answers nothing the rule does not, so the child is asked once,
in its slot; the widgets that always report the whole of their box now
say so. A hint with a fraction resolves against the frame and pins it.

The dump rig prints every cold layout so a change to it shows in a diff.
This commit is contained in:
iris-ai committed 2026-09-19 02:09:41 -04:00
1 parent a888717ee9
commit f6242aa33c
7 files changed
+120 -33

No files matched your search

+4
View File
@@ -15,4 +15,8 @@ impl Widget for Masked {
// draw, and the framework would place the drawing it clipped away.
Size::LEFTOVER
}
fn size_hint(&self, _: Axis) -> Option<LayoutLen> {
Some(LayoutLen::LEFTOVER)
}
}
+4
View File
@@ -72,6 +72,10 @@ impl Widget for Scroll {
// is.
Size::LEFTOVER
}
fn size_hint(&self, _: Axis) -> Option<LayoutLen> {
Some(LayoutLen::LEFTOVER)
}
}
impl Scroll {
+38 -26
View File
@@ -22,26 +22,38 @@ impl Widget for Span {
// whole of the row: a span is what contains its children there, and
// nothing divides that axis.
let across = Place::Within(Part::All);
// A length for every child before their final slots are chosen. The
// frame 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 it is asked in is the room left from the cursor,
// because a text has to wrap at the width actually there. This is
// the one ask a fixed child gets: its slot is its answer, and the
// drawing is moved there once the shares are known.
// A length for every child before their final slots are chosen: from
// a hint where one says, and from drawing otherwise. The frame 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.
// This is the one ask a drawn fixed child gets: its slot is its
// answer, and the drawing is moved there once the shares are known.
// A hinted child is asked once, in its slot.
let mut cursor = Len::rel_min();
let mut sizes = Vec::with_capacity(self.children.len());
let mut lens = Vec::with_capacity(self.children.len());
let mut measured = Vec::with_capacity(self.children.len());
for child in &self.children {
let room = Place::Within(Part::From(along(cursor, far)));
let size = painter
.widget_at(child, [None; 2], axis.pair(room, across))
.size();
let len = size.axis(axis);
let size = match painter.size_hint(child, axis) {
Some(len) => {
measured.push(None);
len
}
None => {
let room = Place::Within(Part::From(along(cursor, far)));
let size = painter
.widget_at(child, [None; 2], axis.pair(room, across))
.size();
measured.push(Some(size));
size.axis(axis)
}
};
let len = size;
cursor.px += len.px + self.gap;
cursor.rel += len.rel;
sizes.push(size);
lens.push(len);
}
let lens: Vec<LayoutLen> = sizes.iter().map(|size| size.axis(axis)).collect();
let gaps = self
.gap
@@ -92,8 +104,8 @@ impl Widget for Span {
let mut taken = Weight::ZERO;
let mut start = Len::rel_min();
let mut ortho = LayoutLen::ZERO;
for (child, size) in self.children.iter().zip(&sizes) {
let len = size.axis(axis);
for ((child, len), measured) in self.children.iter().zip(&lens).zip(&measured) {
let len = *len;
// 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.
@@ -115,20 +127,20 @@ impl Widget for Span {
// answer inside again. A share is decided here and nowhere
// else: its slot narrows its frame, and the child is asked in
// it, since a text wraps at the width it is actually given. A
// fixed child's slot is its own answer, so its drawing is put
// there as it is.
// 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 place = axis.pair(Place::Fill(Part::From(slot)), across);
let used = match len.leftover > Weight::ZERO && shares {
true => {
let mut narrow = [None; 2];
narrow[axis as usize] = Some(slot.len());
painter.widget_at(child, narrow, place).len(!axis)
}
false => {
let mut narrow = [None; 2];
if len.leftover > Weight::ZERO && shares {
narrow[axis as usize] = Some(slot.len());
}
let used = match (measured, narrow[axis as usize]) {
(Some(size), None) => {
painter.place_at(child, place);
size.axis(!axis)
}
_ => painter.widget_at(child, narrow, place).len(!axis),
};
if shrinks {
// Choosing between a fixed and a relative length from the
+9
View File
@@ -49,6 +49,15 @@ impl Widget for Stack {
}
size
}
/// Without a sizing child a stack is whatever box it is given, which it
/// can say without drawing anything.
fn size_hint(&self, _: Axis) -> Option<LayoutLen> {
match self.size {
StackSize::Default => Some(LayoutLen::LEFTOVER),
StackSize::Child(_) => None,
}
}
}
#[derive(Default, Debug)]