Make the frame a length of the window and the box a region

There is one coordinate unit, the window. Every box in the tree is a
region in window units and a widget's frame is a length in the same
units, which is only what fractions resolve against, so the box need not
be the frame and padding can take from both without either becoming the
other. A region node's entry is a translation -- a rel 1 region anchored
where its box starts -- rather than a box, so nothing composes a frame
back up a chain and a node that moves is one entry write.

Padding is then an inset of both: its pixels come off the frame, so
rel(1.0) under it fills the padded widget rather than overflowing it,
and off the box, so what is drawn sits inside. A length a container
decides for a child's frame is a length of the window like everything
else here -- a row's slot, padding's frame less its pixels, or the box a
stack's sizing child decided, which arrives as Part::Sized -- because a
slot of a row is not a fraction of anything the row can name, the same
reason a node entry is a translation. A declaration is a fraction of
whichever of those reached it, and is the only one that also places the
box.

Frame validity is a pin beside the box's, not a range: a range of window
pixels cannot say which frame an answer is a fraction of, since two
frames are different lengths at the same window size. A widget pins its
frame by reading it or by being answered with it under a fractional
rule, and the pin composes up wherever a length of this frame is what
reached the child.

Also here, because the diagnosis needed them: the shrinker reports the
shrunk tree's own divergence with each level's frame, ask, box and size
warm against cold, and there is a size-resize case -- a change and then a
resize, the order that shows an answer kept as a fraction of the wrong
length, which every other case compares at the window it was made at.

Three defects the reports found, each pinned: a rule changed over two
pads relocated the column under them instead of dividing it again (seed
59, depth 5, resize-size), a share inside padding had the padding taken
off twice, and a root resolved its own rule twice.

fmt and clippy clean with and without layout-diagnostics, 121 suite, 20
core, 11 generated, the 400-seed depth-5 shrinker over all sixteen cases.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
iris-aiandClaude Opus 5 committed 2026-09-19 00:14:46 -04:00
1 parent 0ef87ebfcf
commit 1512d8418b
15 files changed
+680 -444

No files matched your search

+23 -28
View File
@@ -1,6 +1,6 @@
use crate::{
LayerId, LayoutHolds, LayoutLen, Len, MaskIdx, MoveIdx, Place, RegionAlign, RetainedPrimitive,
Size, TextureHandle, UiRegion, WidgetId,
Size, TextureHandle, UiRegion, UiVec2, WidgetId,
};
/// What is kept of a widget its parent has asked about. `drawn` says whether
@@ -9,22 +9,15 @@ use crate::{
#[derive(Debug)]
pub struct ActiveData {
pub id: WidgetId,
/// Its frame in `parent_move`'s coordinates: what a fraction it declares
/// or reports is a fraction of, composed. Everything it draws sits inside
/// this by way of `extent`.
pub frame_abs: UiRegion,
/// Where its drawing goes, in the frame's own coordinates.
/// Where its drawing goes, in its region node's coordinates.
pub extent: UiRegion,
/// That frame in its parent's frame coordinates, before composition:
/// forwarded whole by a transparent container, narrowed by a declared
/// length. Its length is the same on every ask, which is what
/// a local redraw relies on to ask its parent's own question again.
pub frame: UiRegion,
/// The length its frame was narrowed to on each axis, as a length of
/// its parent's frame: a declared rule, or a box its parent decided for
/// it. `None` forwards the parent's frame whole. Kept as a length rather
/// than a position so that every placement puts the frame back in the
/// part it is given.
/// What a fraction declared or reported under this widget is a fraction
/// of, as a length of the window.
pub frame: UiVec2,
/// A frame its parent decided for it on each axis -- a row's slot, or
/// padding's frame less its pixels -- as a length of the window. `None`
/// forwards the parent's frame. What it declared is kept separately in
/// `declared` and is a fraction of whichever of the two reached it.
pub narrow: [Option<Len>; 2],
/// Where its drawing was put, as a part of its parent's box, and where
/// it was asked. The two differ where a container asks in one place and
@@ -33,9 +26,9 @@ pub struct ActiveData {
/// so a box that moved re-places every child by re-adding that start.
pub place: [Place; 2],
pub offer_place: [Place; 2],
/// The box it was asked in, in its frame's coordinates: the box its
/// drawing was made in and the one its contract is about. Its drawing
/// is placed elsewhere by re-expression, never by asking again there.
/// The box it was asked in, in the parent's region-node coordinates: the
/// box its drawing was made in and the one its contract is about. Its
/// drawing is placed elsewhere by re-expression, never by asking again.
pub offer_part: UiRegion,
/// The measured answer and its dependencies. A hint-only dependency or
/// a widget first encountered during placement has no measurement yet.
@@ -45,9 +38,10 @@ pub struct ActiveData {
/// rests on the first answer and its drawing on the last, so only the
/// parent can ask either again.
pub re_asked: bool,
/// What the widget said it used of its frame, the last time it drew.
/// What the widget reported, in window-unit lengths.
pub size: Size,
/// The frame and extent reads that this drawing holds for.
/// The window and extent reads that this drawing holds for, and the
/// frame and box it pinned.
pub holds: LayoutHolds,
pub drawn: bool,
pub parent: Option<WidgetId>,
@@ -73,7 +67,8 @@ pub struct ActiveData {
/// Its alignment when it was last drawn, which a change to the property
/// is found against.
pub own_align: RegionAlign,
/// The movable region whose coordinates `frame_abs` uses.
/// The movable region whose coordinates `extent` uses when this widget
/// does not own a region node.
pub parent_move: MoveIdx,
/// The mask its drawing is clipped to: one it set itself, or the one it
/// inherited from whoever drew it.
@@ -95,12 +90,12 @@ impl ActiveData {
self.answer.map(|(size, _)| size)
}
/// Whether what it answered still stands for a frame of these pixel
/// lengths. The answer was given in the box its parent first asked
/// about, which is what it is checked against -- `holds` on the record
/// is about the box the answer then chose.
pub fn answers_at(&self, px: crate::PxVec2, part: UiRegion) -> bool {
/// Whether what it answered still stands in this window, for the frame
/// and the box it was asked in. The answer was given in the box its
/// parent first asked about, which is what it is checked against --
/// `holds` on the record is about the box the answer then chose.
pub fn answers_at(&self, window: crate::PxVec2, part: UiRegion) -> bool {
self.answer
.is_some_and(|(_, holds)| holds.contains(px, part))
.is_some_and(|(_, holds)| holds.contains(window, self.frame, part))
}
}