Update region-node handoff

This commit is contained in:
iris-ai committed 2026-09-15 18:03:37 -04:00
1 parent 7b8b43a7d2
commit 8db0969d9d
1 file changed
+52 -25
+52 -25
View File
@@ -8,7 +8,7 @@ log.
Canonical Iris `main` is **`ca2b4b2`** (#17, the headless rig). **#18 Canonical Iris `main` is **`ca2b4b2`** (#17, the headless rig). **#18
`split/18-position-chain`** is open in `/home/bob/repos/iris-pr18`; its local `split/18-position-chain`** is open in `/home/bob/repos/iris-pr18`; its local
head is **`f437495`**, forty-six commits, pushed to the fork. No PR reviews head is **`71c9c39`**, forty-seven commits, pushed to the fork. No PR reviews
were present when checked on 2026-09-15; the verification summary is posted on were present when checked on 2026-09-15; the verification summary is posted on
the PR. the PR.
@@ -30,8 +30,18 @@ add defensive work to recover optimizations from a false declaration.
the largest fixed orthogonal length is reported, while any relative or the largest fixed orthogonal length is reported, while any relative or
`leftover` child makes the span report `leftover`. `Full` reports exactly `leftover` child makes the span report `leftover`. `Full` reports exactly
`Len::rel(1.0)`. It does not read the children's orthogonal sizes, but their `Len::rel(1.0)`. It does not read the children's orthogonal sizes, but their
`Holds` ranges still propagate through placement, so a resize repositions them `Holds` ranges still propagate through final-box drawing, so a resize
without redrawing when their own contracts permit it. repositions them without redrawing when their own contracts permit it.
`71c9c39` replaces the public `Painter::place` distinction with an opt-in
widget property. `.region_node()` gives a widget one independently movable
retained region; `Widgets::set_region_node` can change that choice at runtime
and causes one structural redraw. Widgets without the property remain at the
default shallow chain depth: Iris recursively remaps their retained primitive
and mask regions when they move. `.scrollable()` enables a region node on its
content once as its convenient default; raw `Scroll::new` respects the
caller's choice, and the property can be disabled later without breaking
scrolling. `Span` and `Align` do not add nodes to their children.
Do not change `Children` to select the pixel-longest arbitrary `Len` at the Do not change `Children` to select the pixel-longest arbitrary `Len` at the
span's current width. A fixed child and a relative child can create multiple span's current width. A fixed child and a relative child can create multiple
@@ -52,19 +62,23 @@ rewrite:
- A widget retains the layer it was entered on, not the last child layer its - A widget retains the layer it was entered on, not the last child layer its
painter visited. The old value drifted on local redraw and put a redrawn tab painter visited. The old value drifted on local redraw and put a redrawn tab
background above its retained text. background above its retained text.
- `Span`'s leftover/no-leftover split is a strict layout decision, not a
rounding tolerance. Its `Holds` range must use the same exact divided
boundary as drawing; a tolerant endpoint retained zero-height children at
the boundary in generated seed 16.
`core/src/ui/holds.rs`, the retained tests, and the generated cold-layout oracle `core/src/ui/holds.rs`, the retained tests, and the generated cold-layout oracle
pin those rules. Seeds 10 and 86 are now in the ordinary generated set. pin those rules. Seeds 10 and 86 are now in the ordinary generated set.
## Verification at `f437495` ## Verification at `71c9c39`
- `cargo fmt --all --check` - `cargo fmt --all --check`
- `cargo clippy --workspace --all-targets --all-features -- -D warnings` - `cargo clippy --workspace --all-targets --all-features -- -D warnings`
- `cargo test --workspace`: 77 passed, 8 ignored - `cargo test --workspace --all-features`: 84 passed, 10 ignored
- `cargo test --release --test generated -- --ignored - `cargo test --release --test generated -- --ignored
a_long_run_of_seeds_agrees`: 100 seeds passed in 66.81 s a_long_run_of_seeds_agrees --exact`: 100 seeds passed in 64.77 s
- The `tabs` reference at 900x1200 remained byte-identical to `ca2b4b2` after - The `tabs` reference at 900x1200 remained byte-identical to `f437495`: zero
retaining `Children` as the default. differing pixels out of 1,080,000 after the region-node rewrite.
- At preceding head `29c7881`, reference renders against - At preceding head `29c7881`, reference renders against
`/home/bob/repos/iris-main-cmp` at `ca2b4b2` covered: `/home/bob/repos/iris-main-cmp` at `ca2b4b2` covered:
`tabs`, `view`, `minimal`, and `text` at 1920x1200; `tabs` cold at `tabs`, `view`, `minimal`, and `text` at 1920x1200; `tabs` cold at
@@ -72,7 +86,10 @@ pin those rules. Seeds 10 and 86 are now in the ordinary generated set.
before and after replay. Every comparison had zero differing pixels. The before and after replay. Every comparison had zero differing pixels. The
live-resize image is also byte-identical to the cold 900x1200 image. live-resize image is also byte-identical to the cold 900x1200 image.
The final pre-submit review was run in four passes. The earlier retained-layout The final pre-submit review was run in four passes. The region-node review
caught an index-reuse hazard when removing a node; its move entry now remains
alive until every descendant has migrated. The generated oracle then exposed
the exact `Span` threshold described above. The earlier retained-layout
review caught the layer defect above, corrected validity-range inversion for review caught the layer defect above, corrected validity-range inversion for
negative relative extents, and removed an impossible-state `unwrap` from negative relative extents, and removed an impossible-state `unwrap` from
`Scroll`. The orthogonal-sizing review caught both the circular longest-child `Scroll`. The orthogonal-sizing review caught both the circular longest-child
@@ -98,19 +115,27 @@ tighter figures.
## Retained-layout invariants ## Retained-layout invariants
- A slot holds a whole `UiRegion` in the coordinates of the slot it names. - A region node holds a whole `UiRegion` in its parent node's coordinates.
`UiRegion::FULL` is the identity. Slots are opt-in: `Painter::place` gives a `UiRegion::FULL` is the identity. Widgets opt in with `.region_node()` or
child a slot; `widget` and `widget_within` share the nearest ancestor's. `Widgets::set_region_node`; ordinary widgets share the nearest ancestor
- A widget's `ActiveData::region` is where it was placed in its parent's slot. node. Region nodes therefore add chain depth only where moving a whole
A placed widget draws in `FULL`; its box lives in its slot. This keeps chains subtree through one entry is useful.
at positioning-container depth rather than full widget-tree depth. - A widget's `ActiveData::region` is its box in its parent node. A region-node
widget draws in `FULL`; its box lives in its node. Moving an ordinary
retained subtree instead remaps its primitive, mask, and active regions.
Remapping stops at a descendant region node after rewriting that one entry.
- Changing `region_node` redraws the subtree once to rebuild the coordinate
boundary. The property belongs to widget identity, which is safe because a
widget has one parent. `.scrollable()` sets it once; raw `Scroll::new` does
not, and `Scroll` never reasserts it while drawing.
- The first box a parent asks about is the offer. A later box chosen from the - The first box a parent asks about is the offer. A later box chosen from the
child's answer is a placement, not another independent answer. Dirty widgets child's answer is the final box, not another independent answer. Dirty
are re-asked at the offer and only then placed again. widgets are re-asked at the offer and only then drawn in the final box.
- A retained drawing can be reused only when its `Holds` interval contains the - A retained drawing can be reused only when its `Holds` interval contains the
new pixel box on both axes, its parent slot is unchanged, and the widget is new pixel box on both axes, its parent node is unchanged, its region-node
clean. A moved unslotted widget must draw again because it has no slot to choice matches the retained structure, and the widget is clean. A valid
rewrite. ordinary subtree may move without redrawing because its regions are
recursively remapped.
- `Painter` records size-dependency edges only when a parent reads a child's - `Painter` records size-dependency edges only when a parent reads a child's
size or hint. An undrawn measured child remains recorded so a later change size or hint. An undrawn measured child remains recorded so a later change
reaches the parent that decided whether to draw it. reaches the parent that decided whether to draw it.
@@ -126,8 +151,10 @@ tighter figures.
remains valid from its longest produced line through the width at which it remains valid from its longest produced line through the width at which it
was made, and `TextView` reports that interval through `Painter::holds`. was made, and `TextView` reports that interval through `Painter::holds`.
- `Span`'s decision to distribute `leftover` is a pixel question. Its validity - `Span`'s decision to distribute `leftover` is a pixel question. Its validity
interval is split at the length where fixed parts fill the box; pure interval is split exactly at the length where fixed parts fill the box;
`leftover` children are undrawn on the no-space side. pure `leftover` children are undrawn on the no-space side. Exact/open ranges
are for hard widget decisions; the ordinary tolerant ranges remain for
accumulated coordinate rounding.
- `Scroll` reports its content's first measured size. Its drawing can survive - `Scroll` reports its content's first measured size. Its drawing can survive
container-length changes only over the interval in which clamping and its container-length changes only over the interval in which clamping and its
current offset do not change. current offset do not change.
@@ -186,9 +213,9 @@ instruction totals.
## Next ## Next
Let PR #18 review. The next small LAYOUT.md §2 item is `LazySpan`; Let PR #18 review. The next small LAYOUT.md §2 item is `LazySpan`. Region nodes
`set_child_offset` is no longer separate, because placing a child is now cover the independently movable-subtree use case; do not restore a
`Painter::place`. separate child-placement API.
After #18 lands, the next structural design is built-in alignment and size. After #18 lands, the next structural design is built-in alignment and size.
Today a declared size and the `Size` returned by `draw` are two sources of Today a declared size and the `Size` returned by `draw` are two sources of