Say what a child gets of a container's box in that box's own lengths

Three things the measurements asked for, all about how much a box that came
from an answer costs.

**A part in the box's own coordinates.** Saying "less eleven pixels at the
end" in frame lengths from the box's start means reading how long the box is,
and a container whose box is its own answer then depends on its own answer:
`Pad` drew sixty-four times in one resize frame at seed 13, chasing its own
width. `Part::Of` says the same thing as a part of the box, which composes
without a length -- pixels are pixels wherever the box lands -- and what a
child under it holds for maps back through that part onto the container's
own box rather than onto the frame.

**One axis of the box at a time.** `extent_len` pinned both axes, so a span
dividing one of them held for one length of the other as well, and a resize
broke every span whose cross-axis answer moved.

**No lazy placement.** Leaving a child's answer to be placed at the end of
the parent's draw, rather than as the child answers, was meant to save a
recomposition. It costs one instead: the drawing is put in the part first
and in the answer's box after, and where it does not hold for both that is
two drawings rather than one. Seed 1 at depth 8 went from 391 widget draws
on a resize to 29 with it gone. The test that pinned three draws for a
numeric leaf in a span goes with it.

Seed 1 at depth 8, widget draws / distinct widgets / update, against #18's
head and against the commit this branch started from:

| phase   | e44dea3      | 34cafb6      | here          |
| ---     | ---          | ---          | ---           |
| cold    | 369/261/10.6 | 463/274/13.3 | 516/288/12.0  |
| repaint | 1            | 1            | 1             |
| many    | 157/95/0.33  | 263/108/0.59 | 187/119/0.52  |
| size    | 16/12/0.018  | 3/3          | 3/3/0.010     |
| scroll  | 2/0.002      | 1            | 1/0.004       |
| resize  | 13/13/0.019  | 22/15/0.032  | 24/76/0.090   |

Seed 13 at depth 8 is where the protocol still costs: `many` 1091 draws
against #18's 524, and `resize` 2215 against a frame #18 does not draw at
all. Both are the same shape -- an answer measured in one box and drawn in
another -- and the handoff says where that comes from.

Checked: fmt, clippy with -D warnings, 108 suite tests, 20 core tests, the
11 generated cases, and the shrinker at 400 trees of depth 5, which fails
seeds 2 (repaint) and 108 (reorder).
This commit is contained in:
iris-ai committed 2026-09-18 01:06:05 -04:00
1 parent 1956be3f3d
commit 0954770ceb
11 files changed
+134 -222

No files matched your search

+27 -23
View File
@@ -1,7 +1,7 @@
#[cfg(feature = "layout-diagnostics")]
use crate::layout_diagnostics::{self as diag, Counter};
use crate::{
Axis, Holds, LayoutHolds, LayoutLen, Len, Place, Px, PxVec2, RegionAlign, RenderedText,
Axis, Holds, LayoutHolds, LayoutLen, Len, Part, Place, Px, PxVec2, RegionAlign, RenderedText,
RetainedPrimitive, Size, StrongWidget, TextAttrs, TextBuffer, TextData, TextureHandle,
UiRegion, UiRenderState, UiRsc, UiSpan, UiVec2, Weight, WidgetId, Widgets,
render::{
@@ -151,7 +151,7 @@ impl<'a> Painter<'a> {
/// one child wants, and what every transparent container passes for the
/// frame.
pub fn widget<'s, W: ?Sized>(&'s mut self, id: &'s StrongWidget<W>) -> DrawResult<'s, 'a, W> {
self.widget_at(id, UiRegion::FULL, [Place::Within(None); 2])
self.widget_at(id, UiRegion::FULL, [Place::Within(Part::All); 2])
}
/// Draws a child, saying what its fractions are of and where its drawing
@@ -225,12 +225,12 @@ impl<'a> Painter<'a> {
place,
offer_place,
// The question its parent measured it by, asked again: the
// same box of the same widget, however this draw came about.
// same widget in the same place, however this draw came
// about.
offer: place == offer_place,
px,
},
None,
false,
self.rsc,
);
let compose = |holds| in_parent(holds, local, extent, place, declared);
@@ -353,13 +353,15 @@ impl<'a> Painter<'a> {
}
}
/// The symbolic length of this widget's own box, in the lengths of its
/// frame that it places its children in. Reading it pins the drawing to
/// that length -- and to nothing about where the box starts, which is
/// what lets a container move without being drawn again.
pub fn extent_len(&mut self) -> UiVec2 {
let len = self.extent.size();
self.extent_len = [Some(len.x), Some(len.y)];
/// The symbolic length of this widget's own box along one axis, in the
/// lengths of its frame that it places its children in. Reading it pins
/// the drawing to that length -- and to nothing about where the box
/// starts, which is what lets a container move without being drawn
/// again. One axis at a time, because a container that divides one axis
/// holds for any length of the other.
pub fn extent_len(&mut self, axis: Axis) -> Len {
let len = self.extent.axis(axis).len();
self.extent_len[axis as usize] = Some(len);
len
}
@@ -553,17 +555,22 @@ pub(crate) fn in_parent(
let n = axis as usize;
let frame_len = frame.axis(axis).len();
result.frame[n] = holds.frame[n].through(frame_len);
match (place[n].span(), declared[n]) {
// Its box is this widget's own, so what it holds for is a range
// on this widget's own box -- which is what lets that box move
// without a redraw. A length it pinned is this widget's length,
// and pins the same way: where the part is a span instead, that
// span is this widget's own to decide and nothing has to be
// said.
(None, None) => {
match (place[n].part(), declared[n]) {
// Its box is this widget's own, or a part of it in that box's
// own lengths: so what it holds for is a range on this widget's
// own box, which is what lets that box move without a redraw. A
// length it pinned is this widget's length wherever the part is
// the whole of it, and pins the same way.
(Part::All, None) => {
result.extent[n] = holds.extent[n];
result.extent_len[n] = holds.extent_len[n];
}
// 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.
(Part::Of(span), None) => {
result.extent[n] = holds.extent[n].through(span.len());
}
// Its box is a part of this widget's frame: a length of the
// frame is all that reaches it, so what it holds for is a range
// on the frame and none of it on this widget's own box.
@@ -663,10 +670,7 @@ pub(crate) fn placed_extent(
pub(crate) fn part_of(extent: UiRegion, place: [Place; 2]) -> UiRegion {
let mut part = extent;
for axis in AXES {
if let Some(span) = place[axis as usize].span() {
let start = extent.axis(axis).start;
*part.axis_mut(axis) = UiSpan::new(start + span.start, start + span.end);
}
*part.axis_mut(axis) = place[axis as usize].part().of(*extent.axis(axis));
}
part
}