Record rel_base and the container API, and sweep LAYOUT.md
`frame` is gone from the layout vocabulary in `docs/LAYOUT.md` too, since leaving the design document in the old words defeats the rename; the rendered-frame sense of the word is untouched. The `local == UiRegion::FULL` item is dropped from the handoff: that comment was #18's and is not on the review branch.
This commit is contained in:
1 parent
90400f50ac
commit
93a6830fcd
3 files changed
+70
-60
No files matched your search
+40
-24
@@ -6,11 +6,11 @@ in `docs/LAYOUT_LOG.md`.
|
||||
|
||||
## The Iris layout repair is submitted
|
||||
|
||||
**Iris PR #19** (`layout/one-ask`) replaces closed #18. The tip is `5642f20`:
|
||||
**Iris PR #19** (`layout/one-ask`) replaces closed #18. The tip is `aeb60e5`:
|
||||
`add6774` fixes collapsed-share placement, retained mask ownership and a
|
||||
redraw-on-reparent defect, `84dad21` removes repeated work in the test
|
||||
harness, and `5642f20` is the naming sweep below. The core design remains
|
||||
sound; round-to-nearest is still unchanged.
|
||||
harness, and `5642f20` and `aeb60e5` are the vocabulary and API work below.
|
||||
The core design remains sound; round-to-nearest is still unchanged.
|
||||
|
||||
Two checkouts share one Git storage. `/home/bob/repos/iris` is the active
|
||||
`layout/one-ask` worktree; `ai-app-2/iris` stays on `main` at the app's
|
||||
@@ -20,24 +20,42 @@ Each round was checked with format, workspace clippy under `-D warnings`, the
|
||||
workspace tests with and without `layout-diagnostics`, the release fast
|
||||
oracle, and the three prescribed seed scans — 400 at depth 5, 1,000 at depth
|
||||
6, 2,000 at depth 4. The cold dump covers 34,488 boxes: the fixes moved 650 of
|
||||
them, all from the collapsed-share correction, and the naming sweep moved
|
||||
none.
|
||||
them, all from the collapsed-share correction, and nothing since has moved
|
||||
one.
|
||||
|
||||
## The naming sweep is done
|
||||
## The vocabulary and the container API are settled
|
||||
|
||||
`5642f20` finished the rename decided on 2026-09-17, which `frame`'s return as
|
||||
a length had left half-applied. The box a parent asks a widget in is the
|
||||
**region**, and where its drawing ends up is its **placement**:
|
||||
`5642f20` finished the rename decided on 2026-09-17, which `frame`'s return
|
||||
as a length had left half-applied, and `aeb60e5` finished the word `frame`
|
||||
itself. Three names, and only two of them are boxes:
|
||||
|
||||
- `Painter::extent` → `region`, and its `extent_len()` → `region_len()`.
|
||||
- `ActiveData::{part, extent}` → `{region, placement}`, and with them
|
||||
`DrawInfo::part` and `Placing::extent`.
|
||||
- `LayoutHolds::{extent, extent_len}` → `{region, region_len}`, and
|
||||
`Counter::OutsideExtent` → `OutsideRegion`.
|
||||
- `placed_extent` → `placement`, `frame_and_extent` → `frame_and_region`,
|
||||
`part_of` → `region_of`.
|
||||
- `Painter`'s `window_own`, `extent_own`, `frame_own_len` and `extent_len`
|
||||
become the one `own: LayoutHolds` they were assembled into.
|
||||
- **region** -- the box a parent asks a widget in. It draws there, and a text
|
||||
wraps at it. Was `Painter::extent`, `ActiveData::part`, `DrawInfo::part`,
|
||||
`Placing::extent`, `LayoutHolds::extent`.
|
||||
- **placement** -- the box its drawing ends up in, the size it reported,
|
||||
once it has answered. Equal to the region wherever it fills one. Was
|
||||
`ActiveData::extent`; `placed_extent` is now `placement`.
|
||||
- **rel_base** -- not a box: the length a fraction it declares or reports is
|
||||
a fraction of. Was `frame`, which said the wrong shape.
|
||||
`Painter::frame_len()` is `Painter::rel_base()`, `LayoutHolds::frame_len`
|
||||
is `rel_base`, and `frame_and_extent` is `rel_base_and_region`.
|
||||
|
||||
`aeb60e5` also cut what a container has to say, since correctness and cost
|
||||
had been paid for and ergonomics had not:
|
||||
|
||||
- `Painter::widget_within(id, region)` takes a box in the widget's own
|
||||
coordinates and derives the child's rel base from it, so `Pad` and
|
||||
`Offset` are one call each. `Offset` had been reading `region_len`, which
|
||||
pinned its drawing to a box length an offset does not depend on.
|
||||
- `Painter::place_at` takes the rel base, returns the answer, and draws the
|
||||
child where there is no answer to re-express. `Span`'s second pass is one
|
||||
call, and its `drawn_across` bookkeeping is gone.
|
||||
- `Part::All` is a `Part::WHOLE` constant rather than a variant. It was
|
||||
exactly `Of(UiSpan::FULL)`; measured at 0.07% of instructions retired
|
||||
against 0.04% run-to-run noise on a 60-seed depth-5 shrink.
|
||||
|
||||
Cold layout is byte-identical to `84dad21` across all of it, over 400
|
||||
depth-5 trees.
|
||||
|
||||
## What is next, in order
|
||||
|
||||
@@ -50,9 +68,10 @@ a length had left half-applied. The box a parent asks a widget in is the
|
||||
`32f6ad8` has 45 commits not reachable from the review branch; shared UI
|
||||
ownership, richer masks, Android support, and app-side performance work
|
||||
must survive the integration. The app then absorbs the branch's
|
||||
vocabulary, all of it already upstream: `rest` → `leftover`, `SizeRule`
|
||||
beside widgets, alignment properties, the removal of `OrthoSize`, and the
|
||||
region/placement names above.
|
||||
vocabulary and container API, all of it already upstream: `rest` →
|
||||
`leftover`, `SizeRule` beside widgets, alignment properties, the removal
|
||||
of `OrthoSize`, the region/placement/rel_base names above, and
|
||||
`widget_within`/`place_at`.
|
||||
3. **Round-to-nearest**, CPU and shader together as one verified change.
|
||||
Bryan approved it on 2026-09-17 and neither half has landed; the
|
||||
derivation, the form to use and what to re-check are in `docs/LAYOUT.md`
|
||||
@@ -67,9 +86,6 @@ a length had left half-applied. The box a parent asks a widget in is the
|
||||
children and nothing states what a widget may assume about one.
|
||||
- `Fixed::div` by zero answers `MIN`/`MAX` while `ratio` answers `ZERO`; both
|
||||
are caller bugs under `debug_assert`, but the fallbacks differ.
|
||||
- The comment on the `local == UiRegion::FULL` shortcut in `widget_at` says
|
||||
composing through `FULL` "is not quite the identity in f32"; on the grid it
|
||||
is exact and the shortcut is performance only.
|
||||
- `docs/LAYOUT.md` §4, §5 and the density section name `Painter::place`,
|
||||
`Painter::region()`, `SetSize`, `desired_width`, `apply_rest`, `Len::dp`,
|
||||
`Aligned` and `MaxSize`, none of which exist. Do not restore
|
||||
|
||||
+29
-29
@@ -8,7 +8,7 @@ without reading that result does not make the parent's size depend on it.
|
||||
carries those commits whole plus the retained-layout repair built on them. §4 to §6 and the density section
|
||||
retain the rationale of the design but still name types that have since been
|
||||
replaced; they are not an API reference. `docs/HANDOFF.md` is where the work
|
||||
in flight stands. The sections from "Frames, decided boxes and padding"
|
||||
in flight stands. The sections from "Rel bases, decided boxes and padding"
|
||||
onwards are the settled design, the findings that outlived the working log,
|
||||
and the measurement method.
|
||||
|
||||
@@ -302,7 +302,7 @@ physical pixels, unchanged.
|
||||
|
||||
## Masks
|
||||
|
||||
This section and "UI ownership and frame access" describe the app's pinned
|
||||
This section and "UI ownership and rel base access" describe the app's pinned
|
||||
Iris implementation. The upstream #19 review branch still has single
|
||||
rectangular masks and separate `UiData`/`UiRenderState`; adopting it requires
|
||||
integrating the app-side capabilities as well as migrating layout calls.
|
||||
@@ -319,18 +319,18 @@ fragment stage cannot make. Rendering and hit-testing both traverse the full
|
||||
mask chain and use the same rounded-rectangle coverage; `iris/tests/mask_sdf.rs`
|
||||
checks the WGSL implementation against the CPU SDF.
|
||||
|
||||
## Frames, decided boxes and padding
|
||||
## Rel bases, decided boxes and padding
|
||||
|
||||
Containers that only divide room are transparent to fractions. A child frame
|
||||
Containers that only divide room are transparent to fractions. A child rel base
|
||||
is narrowed by a length its parent decided: a declared `px` or `rel` length,
|
||||
or the resolved slot of a `leftover` child. A box a widget reports for itself
|
||||
does not narrow its descendants' frame.
|
||||
does not narrow its descendants' rel base.
|
||||
|
||||
`Pad` is an inset (Bryan, 2026-09-18): it subtracts the padding from both
|
||||
the child's frame and the child's box, and reports the child's size plus the
|
||||
the child's rel base and the child's box, and reports the child's size plus the
|
||||
padding. A `rel(1.0)` child inside padding fills the pad without overflowing
|
||||
it; a wrapping text inside padding wraps at the box the pad was given less
|
||||
the padding. A span's frame never subtracts siblings, so a padded fixed
|
||||
the padding. A span's rel base never subtracts siblings, so a padded fixed
|
||||
child measured after a 24 px sibling in a 900 px row wraps at 844 while a
|
||||
`rel(1.0)` inside it is 868. There is no outset kind and no mixed kind.
|
||||
|
||||
@@ -349,13 +349,13 @@ asserted exactly that and was unsatisfiable.
|
||||
A container that can learn a child's length without drawing it does.
|
||||
`Painter::size_hint` answers from the child's rule, or from
|
||||
`Widget::size_hint` where it implements one, resolved against the asking
|
||||
widget's frame; `Scroll`, `Masked` and a `Stack` without a sizing child hint
|
||||
widget's rel base; `Scroll`, `Masked` and a `Stack` without a sizing child hint
|
||||
`LEFTOVER`, since each always reports the whole of its box. `Span` takes each
|
||||
child's length from its hint where there is one and asks that child exactly
|
||||
once, in the slot it decided; a child with no hint is drawn in the room left
|
||||
from the cursor, because a text has to wrap at the width actually there, and
|
||||
its drawing is moved to its slot with `place_at`. Reading a hint records a
|
||||
size dependency and pins the frame where the hint declared a fraction, the
|
||||
size dependency and pins the rel base where the hint declared a fraction, the
|
||||
same pin a rule that is a fraction takes.
|
||||
|
||||
**What that leaves, and the design that would remove it.** A widget that
|
||||
@@ -373,23 +373,23 @@ either answer moved, which also retires `re_asked`'s deferral. Do it only
|
||||
once an app screen shows the cost, and measure that screen first.
|
||||
|
||||
**There is one coordinate unit, the window** (`1512d84`, `23523ee`). Every box in the
|
||||
tree is a region in window units; a widget's frame is a *length* in the same
|
||||
tree is a region in window units; a widget's rel base is a *length* in the same
|
||||
units and is only what fractions resolve against, so a widget's box need not
|
||||
be its frame and padding can take from both. A region node's entry is a
|
||||
be its rel base and padding can take from both. A region node's entry is a
|
||||
translation -- a `rel 1` region anchored where its box starts -- so nothing
|
||||
composes a frame back up a chain.
|
||||
composes a rel base back up a chain.
|
||||
|
||||
A frame decided for a child is therefore a length of the window too: a row's
|
||||
slot, padding's frame less its pixels, or the box a stack's sizing child
|
||||
A rel base decided for a child is therefore a length of the window too: a row's
|
||||
slot, padding's rel base less its pixels, or the box a stack's sizing child
|
||||
decided (which arrives as `Part::Sized`). It is never a fraction of the
|
||||
parent's frame, because a slot of a row is not a fraction of anything the row
|
||||
parent's rel base, because a slot of a row is not a fraction of anything the row
|
||||
can name -- a division of two lengths, which a `rel + px` length cannot hold.
|
||||
A widget's own declaration *is* a fraction, of whichever of those reached it,
|
||||
and it is the only one of them that also places the box inside the part.
|
||||
|
||||
Validity has a pin for each: `LayoutHolds::region_len` for the box's symbolic
|
||||
length and `frame_len` for the frame's. A range of window pixels cannot say
|
||||
which frame an answer is a fraction of, since two frames are different
|
||||
length and `rel_base` for the rel base's. A range of window pixels cannot say
|
||||
which rel base an answer is a fraction of, since two rel bases are different
|
||||
lengths at the same window size. Resolving any of these lengths in pixels is
|
||||
a read of the window, and `Painter::to_px` is where that read is taken --
|
||||
pinning the window where the length has a fraction in it, and nothing where
|
||||
@@ -449,10 +449,10 @@ floats.
|
||||
|
||||
Every length in the tree is a length of the window, so it becomes pixels in
|
||||
one multiply wherever it is read: `Painter::px_size` and `px_len` for the
|
||||
box, `frame_len` for the frame. There is no chain to walk and no coordinate
|
||||
frame in the way, so a region node cannot break it and warm and cold reach
|
||||
every length by the same expression. (Before `1512d84` a frame was a length
|
||||
of its parent's frame and a local redraw walked back up the parent chain in
|
||||
box, `rel_base` for the rel base. There is no chain to walk and no coordinate
|
||||
rel base in the way, so a region node cannot break it and warm and cold reach
|
||||
every length by the same expression. (Before `1512d84` a rel base was a length
|
||||
of its parent's rel base and a local redraw walked back up the parent chain in
|
||||
`asked_px`; both are gone.)
|
||||
|
||||
- **`Holds::through` is the exact preimage of `px + floor(rel * box)`**:
|
||||
@@ -470,7 +470,7 @@ of its parent's frame and a local redraw walked back up the parent chain in
|
||||
- **`px` is not stored on `ActiveData`, deliberately.** A resize every
|
||||
widget's `Holds` admits redraws nothing, so a stored pixel length would
|
||||
be stale on every widget in the tree with nothing to say so. What is
|
||||
stored is the symbolic frame, which a redraw resolves against the window
|
||||
stored is the symbolic rel base, which a redraw resolves against the window
|
||||
it has.
|
||||
- **The window is not a move entry** (`5b78002`). A chain bottoms out in
|
||||
`MoveIdx::NONE`; the window is applied where a fraction becomes pixels,
|
||||
@@ -502,8 +502,8 @@ of its parent's frame and a local redraw walked back up the parent chain in
|
||||
parent may reuse an answer while redrawing the placed output. Translate the
|
||||
drawing contract back through its placement; do not intersect it into the
|
||||
answer contract.
|
||||
- A fraction resolves once against its frame. A report returns raw and is
|
||||
composed only where a parent narrowed that frame. A part's own pixel length
|
||||
- A fraction resolves once against its rel base. A report returns raw and is
|
||||
composed only where a parent narrowed that rel base. A part's own pixel length
|
||||
is added rather than scaled, so a pixel length remains that many pixels at
|
||||
every nesting depth.
|
||||
- An asked-but-undrawn size dependency belongs to the widget that asked. Keep
|
||||
@@ -557,7 +557,7 @@ must still agree exactly.
|
||||
parts from cold at every other window. There is still no case that changes
|
||||
a tree twice with a resize between, and none that resizes twice.
|
||||
- The fuzzer grows no `rel` rules and never re-parents a widget, so neither
|
||||
a fraction resolved against a frame nor a subtree changing hands is
|
||||
a fraction resolved against a rel base nor a subtree changing hands is
|
||||
covered by any generated tree. Both have cost a defect that only a
|
||||
hand-written expectation or a render caught.
|
||||
|
||||
@@ -568,8 +568,8 @@ must still agree exactly.
|
||||
existed only because a widget was drawn twice; one draw leaves it nothing
|
||||
to name. A plan that proposes bookkeeping for a distinction should first
|
||||
ask whether the distinction has to exist.
|
||||
- **A frame narrowed by a region does not move when its part moves.** A
|
||||
narrowed frame must be a *length*, put back into the part on every
|
||||
- **A rel base narrowed by a region does not move when its part moves.** A
|
||||
narrowed rel base must be a *length*, put back into the part on every
|
||||
placement, exactly as a declared rule already is.
|
||||
- **Choosing between a fixed and a relative child in pixels at the span's
|
||||
current width** admits multiple self-sizing fixed points; seed 13 settles
|
||||
@@ -584,8 +584,8 @@ must still agree exactly.
|
||||
phase. A symbolic pin survives a resize because every ask box is
|
||||
symbolically stable across one, so the pin only fails where an answer
|
||||
changed, which is a real relayout.
|
||||
- **Resolving a rule that is a fraction of the frame from the record instead
|
||||
of redrawing** recovers nothing: `reuse outside: a frame length` is 3 of
|
||||
- **Resolving a rule that is a fraction of the rel base from the record instead
|
||||
of redrawing** recovers nothing: `reuse outside: a rel base` is 3 of
|
||||
264 cold draws at seed 1 and 1 of 758 at seed 13.
|
||||
- **A `git bisect`** once named a commit that could not be the cause; read
|
||||
the tree rather than the bisect when that happens.
|
||||
|
||||
+1
-7
@@ -138,7 +138,7 @@ The descriptions above preserve the original failure at `cadfba0`.
|
||||
|
||||
The first three -- the unswept `extent`, the two same-typed boxes on
|
||||
`ActiveData`, and `Painter`'s four holds accumulators -- were done in
|
||||
`5642f20`. What is left:
|
||||
`5642f20`, and `Part::All` in `aeb60e5`. What is left:
|
||||
|
||||
- `draw_inner` returns `(Size, LayoutHolds, LayoutHolds)`, two same-typed
|
||||
values ordered by convention. A named pair makes the swap unwriteable.
|
||||
@@ -152,9 +152,3 @@ The first three -- the unswept `extent`, the two same-typed boxes on
|
||||
`under.iter_mut().find`, `depend_on`), so a span of *n* children is
|
||||
O(n^2) per draw. Not a problem at today's sizes; it is worth knowing
|
||||
before a long transcript list lands on it.
|
||||
- `Part::All` is exactly `Part::Of(UiSpan::FULL)`: `Of` composes through
|
||||
`within`, and `through(Len::FULL)` maps a range back to itself, which
|
||||
`the_whole_of_a_box_maps_back_to_itself` already asserts. It buys a
|
||||
separate arm in `in_parent` and in `Part::of`. Keeping it as sugar is
|
||||
defensible; the case analysis being wider than the geometry is the thing
|
||||
to decide about rather than to leave unstated.
|
||||
Reference in new issue
Block a user