Record what the shaping store cost and what it bought
Both gestures, since two widths in turn flatters a store and a drag does not, and the memory beside the time: the question that decided the shape of it was whether it is a fixed cost or one per text widget. Bring LAYOUT.md's §1 and §2 to what shipped. A widget returns its size rather than recording it, a slot carries a box rather than a translation, and the per-axis answer is `OnResize` rather than `is_size_independent` -- and the constraint the position chain replaced was an agent's, which the text now says. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2acfcf51aa
commit
d50e791130
2 files changed
+88
-68
No files matched your search
+30
-27
@@ -4,6 +4,11 @@ A widget draws once and records its size on the `Painter`. Reading a child
|
||||
`DrawResult::size()` records a retained size dependency; drawing the child
|
||||
without reading that result does not make the parent's size depend on it.
|
||||
|
||||
§1 and §2 have landed in Iris (#16 and #18) and the notes below have been
|
||||
brought to what shipped rather than what was proposed; §3 to §6 describe the
|
||||
same design as it stands. `docs/IRIS_EXTRACTION_HANDOFF.md` has the invariants
|
||||
the code now rests on and what is still to do.
|
||||
|
||||
## Design
|
||||
|
||||
### UI ownership and frame access
|
||||
@@ -28,17 +33,15 @@ draw hooks so dispatch never has to scan every active widget.
|
||||
|
||||
```rust
|
||||
pub trait Widget: Any {
|
||||
fn draw(&mut self, painter: &mut Painter);
|
||||
fn draw(&mut self, painter: &mut Painter) -> Size;
|
||||
|
||||
fn size_hint(&self, axis: Axis) -> Option<Len> { None }
|
||||
|
||||
fn is_size_independent(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn on_resize(&self, axis: Axis) -> OnResize { OnResize::Redraw }
|
||||
}
|
||||
```
|
||||
|
||||
Every implementation calls `painter.set_size(size)` exactly once. A child
|
||||
A widget returns what it used of the box it was given. A child
|
||||
draw returns a `DrawResult` that keeps the painter borrowed; calling `.size()`
|
||||
on that result reads the child's retained size and records that the current
|
||||
widget depends on it. Dropping the result without reading it draws the child
|
||||
@@ -76,11 +79,13 @@ rewriting the row whenever an ancestor moved and would restore the very
|
||||
O(subtree) work this design removes. Chain depth is bounded in both Rust and
|
||||
WGSL.
|
||||
|
||||
`Painter::set_child_offset` inserts a retained coordinate slot between a
|
||||
container and its direct children. `LazySpan` uses one so visible row boxes
|
||||
remain stable while scrolling changes a single shared translation. Ordinary
|
||||
window-relative positions remain `rel + abs`; move slots carry translation
|
||||
only, not general remapping.
|
||||
`Painter::place` draws a child whose box its parent decides and may decide
|
||||
again, and gives that child a slot of its own; `widget` and `widget_within` do
|
||||
not, and share the nearest ancestor's. A slot carries a whole **box**, not a
|
||||
translation: a pixel-space scale and offset would scale a child that has to
|
||||
keep its pixel length, and the `rel`/`abs` pair is what distinguishes the two.
|
||||
(That slots carry translation only was an agent's choice on 2026-09-04, never
|
||||
asked for, and #18 replaced it.)
|
||||
|
||||
`UiRenderState::resolved_region` performs the same chain walk on the CPU for
|
||||
hit-testing, accessibility, and public window-coordinate queries. Masks store
|
||||
@@ -120,19 +125,17 @@ dirty widgets first, with a changed returned size propagated one reader edge
|
||||
at a time. Re-reporting the current output size is a no-op.
|
||||
|
||||
**(b) A widget's `available` (its parent's offered region) can change
|
||||
without the widget's *content* changing — this is what
|
||||
`is_size_independent` (§1) answers.** When a container's own layout shifts
|
||||
(a sibling grew or shrank, changing this widget's offered box), a widget
|
||||
that returns `true` from `is_size_independent` is not redrawn: its
|
||||
primitives are unaffected by size, only by placement, so the parent
|
||||
either (i) issues a move (§2) if only position changed, or (ii) rewrites
|
||||
the primitive's `region` fields directly via `region_mut` if the box
|
||||
changed shape too (still O(primitives owned directly by this widget, not
|
||||
its subtree, since a size-independent widget by definition has no
|
||||
size-dependent descendants worth distinguishing — in practice this is
|
||||
always a leaf: `Rect`, `Image`, a fixed glyph). A widget that returns
|
||||
`false` (the default) is redrawn in full whenever `available` changes,
|
||||
which is correct always, just not free.
|
||||
without the widget's *content* changing — this is what `Widget::on_resize`
|
||||
answers, per axis.** When a container's own layout shifts (a sibling grew or
|
||||
shrank, changing this widget's offered box), a widget that says `Scale` on the
|
||||
axes that changed is not redrawn: everything it drew is a fraction of its own
|
||||
slot's box, so writing that one box moves and stretches all of it. `Span`,
|
||||
`Pad`, `Stack`, `Offset`, `Aligned`, `SetSize` and `LayerOffset` say `Scale`;
|
||||
`Scroll` and `MaxSize` read their box in pixels and cannot. `Redraw`, the
|
||||
default, is correct always and free never. `Translate` — an unchanged drawing
|
||||
placed somewhere else in a bigger box — is reserved: nothing reads it until a
|
||||
widget can say where in that box its drawing belongs, which is the alignment
|
||||
work.
|
||||
|
||||
**Size invalidation travels upward before drawing; drawing itself travels only
|
||||
downward.** Every active widget retains the direct children whose size it read
|
||||
@@ -180,7 +183,7 @@ length is known, flexible space is allocated and `Painter::place` moves
|
||||
each retained child into its final box. A child is redrawn only when that
|
||||
box changes the size it was drawn for.
|
||||
|
||||
Hints are optional and affect cost, never correctness. `Sized` can report
|
||||
Hints are optional and affect cost, never correctness. `SetSize` can report
|
||||
its declared axis without inspecting its child, which covers the important
|
||||
`.height(rest())` case. A debug assertion compares every hint with the
|
||||
eventual `draw` result. Widgets whose answer depends on shaping or on a
|
||||
@@ -201,11 +204,11 @@ buffer. That check is kept exactly as it is; it is the caching mechanism,
|
||||
and it already operates at (id, region) granularity, which subsumes "(id,
|
||||
available size)" once size *is* what a region change means.
|
||||
|
||||
`ActiveData::size` stores the value the widget recorded with
|
||||
`Painter::set_size`. This is what a parent placing the widget for a second
|
||||
`ActiveData::size` stores the value the widget's `draw` returned.
|
||||
This is what a parent placing the widget for a second
|
||||
frame without redrawing it reads instead of recomputing — it replaces
|
||||
`Cache.size`'s role of "answer a size question without a full draw" with
|
||||
"read the size of the last actual draw." `ActiveData::size_dependencies`
|
||||
"read the size of the last actual draw." `ActiveData::size_deps`
|
||||
stores the direct children whose `DrawResult::size()` or known length the
|
||||
widget observed during that same draw; the next draw replaces the list, so a
|
||||
dependency disappears as soon as the widget stops reading it. Both fields
|
||||
|
||||
Reference in new issue
Block a user