|
| 1 | +# Layout and Scene Contract |
| 2 | + |
| 3 | +Updated: 2026-08-01 |
| 4 | + |
| 5 | +The first self-drawn renderer slice consumes the existing `NativeElement` |
| 6 | +tree. It does not define a calculator-specific visual tree or copy component |
| 7 | +state into a renderer model. |
| 8 | + |
| 9 | +```rust |
| 10 | +use a3s_gui::drawing::{scene_from_layout, LayoutSceneOptions}; |
| 11 | +use a3s_gui::layout::layout_native_tree; |
| 12 | +use a3s_gui::Size; |
| 13 | + |
| 14 | +let layout = layout_native_tree(&native, Size::new(410.0, 620.0))?; |
| 15 | +layout.require_supported()?; |
| 16 | +let scene = scene_from_layout(&layout, LayoutSceneOptions::default())?; |
| 17 | +# Ok::<(), a3s_gui::GuiError>(()) |
| 18 | +``` |
| 19 | + |
| 20 | +Layout is available in semantic-only builds. Graphics scene extraction is |
| 21 | +available only with the `graphics` feature, while rendering remains behind |
| 22 | +`software-reference` or `gpu`. |
| 23 | + |
| 24 | +## Versioned records |
| 25 | + |
| 26 | +`LayoutSnapshot` schema version 1 contains: |
| 27 | + |
| 28 | +- logical surface size and boxes quantized to 1/64 logical point |
| 29 | +- flat `LayoutNodeRecord` values with role, parent identity, border/content |
| 30 | + boxes, inherited clip, z-index, paint order, hit eligibility, and box paint |
| 31 | +- separate `LayoutHitRegion` values keyed to the same semantic elements |
| 32 | +- structured warnings and errors with element and field attribution |
| 33 | +- deterministic serialization, fingerprinting, and node-level layout diffs |
| 34 | + |
| 35 | +`LayoutElementId` is a path of byte-length-prefixed sibling keys. Reordering a |
| 36 | +sibling does not change its identity, and `/` or other punctuation in a key |
| 37 | +cannot make two paths collide. The Graphics adapter derives each `DrawId` from |
| 38 | +that path plus a stable paint slot. |
| 39 | + |
| 40 | +The snapshot contains no labels, values, passwords, native handles, GPU |
| 41 | +objects, or product state. |
| 42 | + |
| 43 | +## Implemented M3 slice |
| 44 | + |
| 45 | +The current deterministic box path implements the subset needed by the shared |
| 46 | +calculator: |
| 47 | + |
| 48 | +- vertical block flow and no-wrap horizontal/vertical flex flow |
| 49 | +- explicit, percentage, minimum, and maximum size with border/content sizing |
| 50 | +- physical and horizontal-writing logical padding, margin, inset, and border |
| 51 | +- row/column gap, source order, integer order, main/cross-axis alignment, and |
| 52 | + stretch for auto cross sizes |
| 53 | +- relative, absolute, and fixed positioning |
| 54 | +- overflow rectangle clipping, visibility, opacity, pointer eligibility, |
| 55 | + simple sibling z-order, and cumulative descendant opacity |
| 56 | +- solid color backgrounds, per-edge solid borders, circular corner radii, and |
| 57 | + opaque rounded background/border composition |
| 58 | + |
| 59 | +The portable Tailwind contract historically treats a positive border width as |
| 60 | +solid when no explicit border style is present. Explicit `none` and `hidden` |
| 61 | +still suppress the border. The layout path preserves that behavior so it |
| 62 | +matches the existing native backends without depending on browser Preflight. |
| 63 | + |
| 64 | +Full flex growth and shrinkage, wrapping, grid/table layout, vertical writing, |
| 65 | +baseline alignment, elliptical radii, CSS expression evaluation, inherited or |
| 66 | +functional colors, and complete stacking contexts are not claimed by this |
| 67 | +slice. |
| 68 | + |
| 69 | +## Projection diagnostics |
| 70 | + |
| 71 | +Every effective `PortableStyle` field is checked against the executable |
| 72 | +[renderer field inventory](renderer-field-inventory.md). |
| 73 | + |
| 74 | +- A later-milestone field produces a warning. Text and control roles therefore |
| 75 | + retain their M3 boxes while their visible content remains an explicit M4 |
| 76 | + item. |
| 77 | +- An M3 field or value that this slice cannot project produces an error. |
| 78 | +- A property retained in `PortableStyle::unsupported` produces an error. |
| 79 | +- `scene_from_layout` rejects any snapshot containing an error before it emits |
| 80 | + a draw command. |
| 81 | + |
| 82 | +This lets inspection tools display partial boxes without allowing a required |
| 83 | +calculator style to disappear silently from an accepted scene fixture. |
| 84 | + |
| 85 | +## Retention and paint ordering |
| 86 | + |
| 87 | +Repeated snapshots have identical fingerprints. `LayoutSnapshot::diff` |
| 88 | +reports stable-key additions, removals, changed records, dirty bounds, and |
| 89 | +surface rebuilds. Graphics performs the subsequent primitive diff and retained |
| 90 | +damage calculation. |
| 91 | + |
| 92 | +A parent background is emitted before its descendants. Siblings are placed by |
| 93 | +z-index, flex order, and source order, and each subtree remains contiguous. |
| 94 | +This is sufficient for the calculator and simple overlays; complete CSS |
| 95 | +stacking-context behavior remains M3 work. |
| 96 | + |
| 97 | +Overflow clips apply to descendants and are intersected through the tree. |
| 98 | +Fixed-position descendants use the viewport rather than inheriting an ordinary |
| 99 | +ancestor clip. Hit regions use the same quantized identity and visible bounds |
| 100 | +but remain separate from paint commands. |
| 101 | + |
| 102 | +## Calculator evidence |
| 103 | + |
| 104 | +`tests/calculator_layout_scene.rs` compiles the existing shared calculator RSX, |
| 105 | +lowers it through `RsxCompilerBridge`, wraps the real 410 by 620 window native |
| 106 | +tree, and then uses this generic path. The fixture pins: |
| 107 | + |
| 108 | +- layout fingerprint `16529597026056060935` |
| 109 | +- Graphics scene fingerprint `2100550662756266801` |
| 110 | +- exact repeated software output and retained no-damage behavior |
| 111 | +- background, transparent exterior, ordinary-key, and equals-key pixels |
| 112 | + |
| 113 | +On the 2026-08-01 local Direct3D 12 run, GPU readback differed from the software |
| 114 | +reference at 940 of 254,200 pixels (0.370%), all around rasterized edges, with a |
| 115 | +maximum channel delta of 91. The reviewed non-text gate allows at most 0.5% of |
| 116 | +pixels and a maximum channel delta of 96 while requiring the listed solid |
| 117 | +pixels to match exactly. |
| 118 | + |
| 119 | +An unavailable adapter skips the local GPU test and is not cross-platform |
| 120 | +evidence. Metal and Vulkan runs, real window presentation, text, input, IME, |
| 121 | +and accessibility remain separate roadmap gates. |
0 commit comments