From aa1a85b31613696c1509f09fcef246e73ff7c298 Mon Sep 17 00:00:00 2001 From: iris-ai <4+iris-ai@noreply.localhost> Date: Fri, 18 Sep 2026 19:53:48 -0400 Subject: [PATCH] Refine the frame-as-length step: one window unit, region nodes as translations Co-Authored-By: Claude Fable 5.1 --- docs/HANDOFF.md | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md index 6e63e77..cf1e1e8 100644 --- a/docs/HANDOFF.md +++ b/docs/HANDOFF.md @@ -250,10 +250,20 @@ second draw back. ### 2. Make the frame a length and the box a region -The frame stops being a coordinate system. Coordinates start at a region -node (or the root) and every box under it is a region in that node's -coordinates; a widget's frame is a *length* of the node's box, per axis, and -is only what fractions resolve against: +The frame stops being a coordinate system. **There is one coordinate unit, +the window**: every box in the tree is a region in window units, a widget's +frame is a *length* in the same units and is only what fractions resolve +against, and a region node's move entry is a *translation* -- a region `rel +1` long anchored at the node's box start -- rather than a box. The last +point is forced, not chosen: a node whose unit was its frame would need its +box expressed in frame units, which is a division of two lengths and not a +`rel + px` length (a `.region_node()` child of a pad in a span room is the +case). Pending Bryan's yes, 2026-09-18: it changes one cost. A node that +moves, or scrolls, is one entry write whatever narrowed it (today only a +narrowed node gets that; a transparent one is re-expressed), and a node +whose box changes *length* at the same window size is re-expressed as a +subtree (today a narrowed node is one write). The shader's `resolve_move` +composes a `rel 1` entry as a translation already and does not change. ```rust pub struct Painter<'a> { @@ -264,9 +274,12 @@ pub struct Painter<'a> { pub(super) frame: UiVec2, /// Where this widget's drawing goes, in the node's coordinates. pub(super) extent: UiRegion, - /// The node's box in pixels. The frame in pixels is `frame.to_px(node_px)` - /// and the box's is `extent.size().to_px(node_px)`. - pub(super) node_px: PxVec2, + /// The window in pixels: the frame in pixels is `frame.to_px(window)` + /// and the box's is `extent.size().to_px(window)`. Every `Holds` range + /// is in these pixels, so `in_parent` combines them without `through`, + /// and a widget's own frame reads convert once: `Holds::at(frame_px) + /// .through(frame)`. + pub(super) window: PxVec2, ... } @@ -314,15 +327,15 @@ What this changes in the core, and why it is not merely more code: - `Painter::resolve` becomes `region.within(&self.extent)`; the second `within` through the frame goes. - `in_parent` no longer maps a child's frame holds through the frame length - per level: every widget under one node holds for the node's pixel box, so - the holds `and` directly, and only a region node maps through its box. + per level: every widget holds for the window's pixels, so the holds `and` + directly, at region nodes too. - `Part::of` gains one `within_len` per `From` end. `Of` and `All` are as they were. - `DrawInfo::frame`/`frame_abs`, `ActiveData::frame`/`frame_abs`, `local` in `Placing`, `recompose_subtree` and `asked_px` all change meaning: - `frame` is a `UiVec2` length, `frame_abs` goes, a node's move entry is the - node's frame length anchored at its box start (so `rel` inside means the - frame), and `asked_px` walks to the node rather than the root. + `frame` is a `UiVec2` length, `frame_abs` goes, a node's move entry is a + `rel 1` region at the node's box start, and `asked_px` becomes the window + size. - `Stack` needs a part that is "a box of this length, placed by the child's alignment" (`Part::Sized(Len)`, the same thing a declared length does), since `narrow` no longer makes the box.