layout: edge-inset, overlay, and view-that-fits primitives - #70
Conversation
Adds three general-purpose container kinds so apps can compose pinned chrome, floating controls, and fit-or-fallback content without host-side hacks: - edge-inset (EdgeInset): pins children after the first to a screen edge via .safeAreaInset(edge:) while the first child scrolls beneath. New 'edge' prop (required; top|bottom|leading|trailing) picks the edge, new 'visible' bool animates the pinned region in/out, 'gap' maps to safeAreaInset spacing. Surface props style the pinned region (the node bypasses the container surface modifier); 'background' gains the 'bar' material name. - overlay (Overlay): children[0] is the base and sizes the view; later children float over it via .overlay(alignment:). New 'alignment' prop (9-point grid: top-leading..bottom-trailing) on each child picks its anchor, falling back to the overlay's own alignment (default center). Distinct from stack's popover anchors (above|below|left|right), which can't express inside-corner placement. - view-that-fits (ViewThatFits): renders ViewThatFits(in:), picking the first child that fits; reuses 'orientation' (horizontal|vertical, default horizontal) as the axis. Validation parity across OCaml/Swift/Dart/C++/C#: edge-inset requires 'edge', vocabularies are closed, 'alignment' is allowed on any non-root kind (position hint on overlay children; inert elsewhere). Flutter/Qt/WinUI approximate (Stack+Positioned, anchors, LUIGrid); web maps to grid-overlay CSS. OCaml constructors: Lui_elements.edge_inset, overlay, view_that_fits, plus align combinator.
|
I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".
|
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e6cbe8c12c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| case LUINodeKind.EdgeInset: | ||
| case LUINodeKind.Overlay: | ||
| case LUINodeKind.ViewThatFits: | ||
| // No WinUI pinned/overlay/fit primitive; children stack | ||
| // in a grid (overlay-like approximation). | ||
| return new LUIGrid(); |
There was a problem hiding this comment.
Route the new WinUI containers through child synchronization
Although these cases create an LUIGrid, the SyncKind switch in LUIElement.Sync.cs has no EdgeInset, Overlay, or ViewThatFits case, so neither SyncPlainContainer nor SyncStackLike ever inserts their children. On WinUI, all three newly exposed containers therefore render empty rather than providing the stated grid approximation.
Useful? React with 👍 / 👎.
| | EdgeInset -> "lui-edge-inset" | ||
| | Overlay -> "lui-overlay" | ||
| | ViewThatFits -> "lui-view-that-fits" |
There was a problem hiding this comment.
Implement DOM handling for the new layout properties
Registering these web node kinds is insufficient because Lui_web_props.apply_secondary_property has no cases for EdgeValue, Visible, or AlignmentValue and falls through to invalid_arg. Every edge_inset constructed through the public API necessarily emits its required edge property, so applying its patch aborts immediately; overlays using ~alignment or align fail the same way.
Useful? React with 👍 / 👎.
| (* Honored on [overlay] children (position hint) and on [overlay] itself | ||
| as the default; inert elsewhere, like [container-relative-frame]. *) | ||
| | AlignmentValue -> kind <> Root |
There was a problem hiding this comment.
Admit alignment through restrictive property matrices
The AlignmentValue arm does not actually make alignment valid on every non-root kind: property_supported handles restrictive kinds such as TimelineItem, Toolbar, BottomTab, and Accordion before calling common_property_supported. Consequently, valid-looking uses such as overlay [base; align top_trailing (timeline_item ...)]raise fromLui_runtime.set_prop` during emission, and the platform validators mirror the same restrictive matrices; alignment must be special-cased before that dispatch or included in each matrix.
Useful? React with 👍 / 👎.
| if (visible) | ||
| for (final pinned in children.skip(1)) | ||
| Positioned.fill( | ||
| child: Align(alignment: alignment, child: pinned), | ||
| ), |
There was a problem hiding this comment.
Stack Flutter edge-inset children instead of overlapping them
When an edge inset has two or more pinned children, each child is independently placed in a full-size Positioned.fill with the same alignment, so they occupy the same coordinates and obscure one another; the advertised gap is also unused. The pinned children need to be grouped in an edge-appropriate Row or Column so every child after the base remains visible.
Useful? React with 👍 / 👎.
| 'top-leading' => Alignment.topLeft, | ||
| 'top' => Alignment.topCenter, | ||
| 'top-trailing' => Alignment.topRight, | ||
| 'leading' => Alignment.centerLeft, | ||
| 'trailing' => Alignment.centerRight, |
There was a problem hiding this comment.
Use directional Flutter alignments for leading and trailing
In an RTL Flutter app, mapping the protocol's leading and trailing values to physical Alignment.*Left and Alignment.*Right places overlay content on the wrong side; the edge-inset mapping below makes the same physical-side assumption. Use AlignmentDirectional/start-end alignments so these values follow Directionality, as the Swift implementation's leading/trailing edges do.
Useful? React with 👍 / 👎.
| if name == "bar" { | ||
| // System chrome material (`.background(.bar)`), for edge-pinned | ||
| // bars that should pick up the platform bar treatment. | ||
| content.background(.bar, in: shape) |
There was a problem hiding this comment.
Define the new bar background on every backend
The public edge_inset API advertises ~background:"bar", but only this Swift branch recognizes that new name. The Flutter color resolver falls back to transparent, Qt's palette falls back to transparent, WinUI's theme resolver returns no brush, and the web emits the invalid CSS color bar, so the advertised pinned chrome has no background on every non-Apple backend; each backend needs a suitable bar-surface fallback.
Useful? React with 👍 / 👎.
| mount_children context node children; | ||
| node | ||
|
|
||
| let edge_inset ?key ?gap ?main ?cross ?grow ?columns ?padding ?padding_horizontal ?padding_vertical ?background ?foreground ?border_color ?border_width ?corner_radius ?width ?height ?min_width ?max_width ?min_height ?max_height ?container_relative_frame ?container_relative_frame_inset ?accessibility_identifier ?accessibility_identifier_signal ?foreground_signal ?background_signal ?style_class ?on_appear ~edge ?visible ?visible_signal (children : t list) : t = |
There was a problem hiding this comment.
Permit foreground styling on the new containers
All three new constructors expose ~foreground and ~foreground_signal, and edge-inset explicitly applies its surface modifier to the pinned region, but common_property_supported omits EdgeInset, Overlay, and ViewThatFits from the ForegroundValue whitelist. Passing either foreground option therefore raises from Lui_runtime.set_prop before a patch is emitted; the new kinds must be added consistently to the OCaml and host property validators.
Useful? React with 👍 / 👎.
| .lui-edge-inset > *, | ||
| .lui-overlay > *, | ||
| .lui-view-that-fits > * { | ||
| grid-area: 1 / 1; |
There was a problem hiding this comment.
Exclude web overlay children from intrinsic sizing
CSS Grid computes the shared track's intrinsic size from every item assigned to it, so a large second or later overlay child enlarges .lui-overlay even though the API promises that only the first child sizes the view. Overlay children need positioning or another mechanism that removes their intrinsic contribution while retaining the base child as the sizing element.
Useful? React with 👍 / 👎.
| onItemAdded: function(index, item) { | ||
| if (index === 0) { | ||
| item.anchors.fill = overlay | ||
| } else { | ||
| overlay.anchorOverlay(item) |
There was a problem hiding this comment.
Re-anchor Qt overlays when alignment properties change
The anchors are assigned only from Repeater.onItemAdded, so if a retained overlay or child receives a new alignment value during dynamic reconciliation, the delegate remains at its original position because no item is added and anchorOverlay is not rerun. Use bindings or property-change handlers that also clear obsolete anchors; LuiEdgeInset.qml has the same issue when its retained edge property changes.
Useful? React with 👍 / 👎.
Edge Inset page toggles the pinned region via 'visible' and shows top and bottom bars over scrolling lists; Overlay floats aligned children over a base card; View That Fits swaps expanded/compact variants on window resize.
|
Verified all three new container kinds end-to-end in the native macOS
Demo pages are committed in |
- WinUI: route the new kinds through SyncStackLike so children render - Web: handle edge/visible/alignment in apply_secondary_property and remove_property; overlay children go position:absolute so they no longer contribute intrinsic size to the grid - Protocol validators (OCaml/Swift/Qt/WinUI/Flutter): admit 'alignment' ahead of the restrictive kindProperties matrices, and whitelist the new kinds for 'foreground' - Flutter: group pinned edge-inset children in an edge-appropriate Row/Column, honor 'gap', and use AlignmentDirectional so leading/trailing follow RTL - background:'bar' gets a translucent surface fallback on Flutter, Qt, WinUI, and web - Qt: bind anchors declaratively so retained delegates re-anchor when alignment/edge changes
A pinned child that expands to its proposal (e.g. overlay's ZStack) made safeAreaInset consume the whole screen, collapsing the scrolling base content to zero height. Hug the pinned stack along the edge axis.
Summary
Adds three general-purpose layout containers so apps can compose pinned chrome, floating controls, and fit-or-fallback content declaratively (replaces host-side
journal-chrome-style extensions; apps keep supplying their own titles/buttons/payloads):edge-inset— pins children after the first to a screen edge via.safeAreaInset(edge:)whilechildren[0]scrolls beneath. Newedgeprop (required,top|bottom|leading|trailing) picks the edge; newvisiblebool toggles the pinned region in place so the safe-area insertion animates rather than snapping; existinggapmaps to the inset spacing. Surface props style the pinned region —LUIUnmodifiedNodePolicy.bypassesSurfacenow coversedgeInset, and the pinned stack appliesLUISurfaceModifieritself.backgroundgains thebarmaterial name (Material.bar) alongsideglass/glass-container.overlay—children[0]is the base and sizes the view;children[1:]float over it via.overlay(alignment:). Newalignmentprop (9-point gridtop-leading…bottom-trailing) on each overlay child picks its anchor, falling back to the overlay's ownalignment(defaultcenter). This is distinct fromstack's popover-sideanchorprops (above|below|left|right), which can't express inside-corner placement — so a new kind rather than extending stack.view-that-fits— rendersViewThatFits(in:), picking the first child that fits; reusesorientation(horizontal|vertical, default horizontal) as the axis.Validation
edgeis required onedge-inset(node_properties_supported/validateNodePropertieson all backends);edge,visible, andalignmentvocabularies are closed.alignmentis allowed on any non-root kind — it's a position hint honored onoverlaychildren (and the overlay itself as the default), inert elsewhere, same pattern ascontainer-relative-frame.Other backends
Flutter:
Stack+Positioned.fill(Align(...))approximations. Qt:LuiEdgeInset/LuiOverlay/LuiViewThatFitsQML (anchors-based). WinUI:LUIGridstack approximation. Web: grid-overlay CSS classes. No new events; no journal-specific naming or payload fields.Verified
opam exec -- dune build,dune runtest -j 4(30 tests incl. newedge/overlay/fit rulescase),make test-apple(136 tests incl. 3 new parity tests) all pass.Link to Devin session: https://app.devin.ai/sessions/052ab2a0c5a74f2ebb311241b9bf344f
Open in Devin Desktop: https://app.devin.ai/desktop/session/052ab2a0c5a74f2ebb311241b9bf344f?variant=devin
Requested by: @RCmerci