Compare commits

..
2 Commits
Author SHA1 Message Date
iris-ai cadfba05dd Keep only what a room drawing is still needed for
A span measuring a child in the room kept its whole `Size`, of which the
along axis is already in `lens` and only the across one is read again when
the drawing is placed. Keep that length alone, which also retires the
rebinding of the match's result and the one in the placing loop. The
placement comment already says what becomes of a drawing made in the room,
so the measuring pass no longer says it a second time.
2026-09-19 02:51:08 -04:00
iris-ai 38b3a81053 Pin a frame by the fraction the child declared
`size_hint` resolves a child's hint against the asking widget's frame and
pins that frame, so a later draw cannot reuse a resolution made against a
different one. It asked the *resolved* hint whether it still had a fraction,
which is false whenever the frame is itself pixels -- a slot of a row, or the
box a stack's sizing child decided -- and the pin was dropped there. Ask the
declared hint, which is what made this draw depend on the frame, and what
`ruled` in `render_state` already asks for a rule.

No generated tree distinguishes the two: the fuzzer grows no `rel` rules, and
a frame that changes almost always changes a box the other pins catch. Kept
for the reason the `frame_len` pin beside it is kept -- "these two
invalidations always coincide" is an assumption nothing states.
2026-09-19 02:51:08 -04:00
2 changed files with 31 additions and 40 deletions

No files matched your search

+23 -27
View File
@@ -267,36 +267,32 @@ 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(|| {
widgets
.get_dyn(id.id())
.and_then(|widget| widget.size_hint(axis))
})
.map(|hint| hint.within_len(self.frame.axis(axis)));
let hint = widgets.size_rules(id.id()).axis(axis).exact().or_else(|| {
widgets
.get_dyn(id.id())
.and_then(|widget| widget.size_hint(axis))
});
let frame = self.frame.axis(axis);
let resolved = hint.map(|hint| hint.within_len(frame));
#[cfg(feature = "layout-diagnostics")]
diag::hint_read(id.id(), self.id, axis, hint);
match hint {
Some(hint) => {
#[cfg(feature = "layout-diagnostics")]
diag::bump(Counter::HintHits);
self.depend_on(id);
// A fraction was just resolved against this frame, so what
// this draw does with it is a function of the frame's length.
if hint.rel != Rel::ZERO {
self.frame_own_len[axis as usize] = Some(self.frame.axis(axis));
}
Some(hint)
}
None => {
#[cfg(feature = "layout-diagnostics")]
diag::bump(Counter::HintMisses);
None
{
diag::hint_read(id.id(), self.id, axis, resolved);
diag::bump(match resolved {
Some(_) => Counter::HintHits,
None => Counter::HintMisses,
});
}
if let Some(hint) = hint {
self.depend_on(id);
// Resolving a fraction against this frame makes this draw a
// function of the frame's length. The fraction to ask about is
// the child's own: resolved against a frame of pixels, none is
// left to see it by.
if hint.rel != Rel::ZERO {
self.frame_own_len[axis as usize] = Some(frame);
}
}
resolved
}
fn depend_on<W: ?Sized>(&mut self, child: &StrongWidget<W>) {
+8 -13
View File
@@ -28,16 +28,13 @@ impl Widget for Span {
// 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 lens = Vec::with_capacity(self.children.len());
let mut measured = Vec::with_capacity(self.children.len());
let mut drawn_across = Vec::with_capacity(self.children.len());
for child in &self.children {
let size = match painter.size_hint(child, axis) {
let len = match painter.size_hint(child, axis) {
Some(len) => {
measured.push(None);
drawn_across.push(None);
len
}
None => {
@@ -45,11 +42,10 @@ impl Widget for Span {
let size = painter
.widget_at(child, [None; 2], axis.pair(room, across))
.size();
measured.push(Some(size));
drawn_across.push(Some(size.axis(!axis)));
size.axis(axis)
}
};
let len = size;
cursor.px += len.px + self.gap;
cursor.rel += len.rel;
lens.push(len);
@@ -104,8 +100,7 @@ impl Widget for Span {
let mut taken = Weight::ZERO;
let mut start = Len::rel_min();
let mut ortho = LayoutLen::ZERO;
for ((child, len), measured) in self.children.iter().zip(&lens).zip(&measured) {
let len = *len;
for ((child, &len), &across_len) in self.children.iter().zip(&lens).zip(&drawn_across) {
// 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.
@@ -135,10 +130,10 @@ impl Widget for Span {
if len.leftover > Weight::ZERO && shares {
narrow[axis as usize] = Some(slot.len());
}
let used = match (measured, narrow[axis as usize]) {
(Some(size), None) => {
let used = match (across_len, narrow[axis as usize]) {
(Some(across_len), None) => {
painter.place_at(child, place);
size.axis(!axis)
across_len
}
_ => painter.widget_at(child, narrow, place).len(!axis),
};