chore: headful baseline probe for #569 (do not merge) - #655
Closed
kdroidFilter wants to merge 7 commits into
Closed
Conversation
#569) `nativePopupLayers` made every Compose `Popup` a real OS window, but the *decision* of where to put it stayed window-rooted, in two stacked ways. The layers each built a work-area-sized `WindowInfo` so a popup could lay out and flip against the display — and then `setContent` replayed the owner window's composition locals over it, so `Popup.skiko.kt` read the window's `containerSize` and clipped every popup back inside the window. The intended design had never taken effect. The layers now re-provide `LocalWindowInfo` inside the replayed locals. With that in force the box is screen-sized but still rooted at the window's content origin, so a `DropdownMenu` in a window near the bottom of the display did not flip up — Compose saw a whole work area of room below the anchor — and walked off the screen. Each layer now clamps its native frame into the work area of the display it lands on, at the single point where it pushes that frame: `popupScreenClampOffset`, fed the owner's content origin on screen plus every display's work area. Only the native frame moves; `boundsInWindow` stays what Compose believes, which is what hit-testing and the surface content are expressed in. Re-clamped on every push, so an open popup survives an owner drag, across monitors included. Dialogs go through the same layers but must not follow the display: `Dialog.skiko.kt` places at `containerSize.center`, so a window-owned dialog centred on the screen would sit visibly off-centre and drift as the window moved. Layers report the window size for dialogs and the work area for popups, discriminated on `scrimColor` — only `Dialog.skiko.kt` writes it, from `DialogAppearanceController.properties` during `DialogLayout`'s composition, before `layer.Content { }` reads the container. macOS needs the NSView's own origin on screen (a native title bar sits between it and the window frame), hence `nativeGetContentRect`. Wayland reports no geometry and is left unclamped: a popup there is a `wl_subsurface` placed relative to the parent, with no global position. Also exposes `nativePopupLayers` on `JewelDecoratedWindow`, which had no such parameter at all — Jewel apps could not opt in. Jewel needs nothing further: `DefaultPopupRenderer` delegates to `androidx.compose.ui.window.Popup`, so its combo boxes, menus and tooltips flow through the fixed layers. Tests: 18 unit cases on the clamp geometry, and 13 headful cases driving real windows parked at real work-area edges — including one that reads the popup HWND's rect back through Win32 and asserts it matches the reported frame to the pixel, and two that pin the dialog contract. A new "Popups" tab in nucleus-demo parks the window at any corner and opens menus anchored at each window edge.
A Compose Dialog opened through nativePopupLayers had no scrim, clipped its shadow and its appearance animation at the layout edge, slid diagonally towards the display centre while scaling in, and stayed put when the window was resized. - Scrims: the owner window paints every layer's scrim after its content and each layer paints the scrims of the layers above it (PopupScrimRegistry, TaoSceneBundle.renderOverlay). A scrim change marks the owner scene visually dirty, so the Windows clean-frame present skip no longer eats the fade. - Draw margin: the native surface extends 32 dp past boundsInWindow so shadows and the 10 dp slide-in are not clipped. Compose 1.12 renders a scene as one RenderNode drawable with unbounded bounds, so the R-tree cull-rect measurement upstream uses reports the whole canvas; a constant margin replaces it. The screen clamp is decided on the content rect; the interactive region stays the content (Windows content rect, macOS region hit-test, Linux press filter). - Dialog scene size: a dialog's root Layout fills the layer scene's constraints and carries the appearance GraphicsLayer, so its scale pivots on the scene centre. Dialog layers now run their inner scene at the owner window size, popups keep the work area. - Resize: the dialog container size is read from the owner's snapshot-backed WindowInfo, so the dialog re-centres when the window is resized. DialogAppearanceHeadfulCases films both layer modes with java.awt.Robot and compares slide-in, scrim ramp and settle time; PopupFrameRecord gains the content frame next to the inflated native frame.
The appearance film now also records the hide animation and counts grabs that repeat the previous frame during either animation — dropped frames show up as a stall count the native layer must not exceed. The owner window carries forty rows of text so its per-frame present costs something. The first-visible check is one-sided: the native surface legitimately shows its first frame before the owner's next present.
…t fades out (#569) Dialog.skiko.kt's disappearance swaps the layer's content for an empty Layout that replays the recorded picture, and Compose then reports a zero-size boundsInWindow at the window centre for the whole fade-out. An in-scene layer draws into the window canvas and does not care; the native surface followed the bounds and shrank to a 32 dp square around a point, so a closing dialog collapsed and vanished instead of fading out. Each layer now sizes and places its surface on the last non-empty bounds. The appearance film gains the Material 3 AlertDialog nucleus-demo opens, a warm-up before filming, a duration-based comparison, and the smallest height the dialog spanned while fading out.
…face
The Windows and Linux context menu flyouts drew inside the window's render
target whenever the window ran without nativePopupLayers, so a menu opened
near an edge was clipped by the window like any in-scene popup. An
OS-looking menu has to leave the window like the menus it imitates, and the
application's choice for its other popups must not decide that.
nativePopupLayers is a whole-scene switch (platform vs canvas layers), so a
per-popup opt-in needs the seam Compose 1.12 actually uses: Popup picks its
layer through LocalComposeSceneContext. A friend-package Java accessor
reaches that internal local without reflection; NativePopupLayers { } then
provides, for its subtree only, the window scene's own context with
createLayer routed to the window's native popup layer factory — the same
factory attach() uses when nativePopupLayers is on. The context menu
representation wraps the Windows and Linux flyouts in it; macOS stays on
NSMenu.
…tates Three things the menu got wrong on a Linux desktop, found by driving a real right click against a nested GNOME Shell and reading back both screenshots and the app's own trace (scripts/context-menu-wayland-e2e.py, with the fixture it drives in nucleus-application's tests). A menu opened near the bottom of the screen was cut off. On native Wayland a client cannot know where its own window is, so it cannot keep a popup on screen by itself — the X11 clamp of #569 has nothing to work with there. The popup layer now maps as an xdg_popup instead of a wl_subsurface and lets the compositor place it: it flips above the pointer when there is no room below and slides along an edge, which is what GTK's own menus do. The tao patch carries the anchor point, the surface size and the shadow margins in one request, because GDK builds the positioner from the window's geometry as it stands at map time — a popup still sized 1x1 there asks the compositor to constrain a 1x1 rectangle and is never flipped. One popup per parent takes that path (an xdg_popup must be its parent's topmost popup); a dialog keeps the subsurface, since it belongs to its window rather than to the display. A second right click only closed the menu instead of moving it. The press that dismisses a popup is delivered to the scene in the same turn as the dismissal, so Compose's contextMenuOpenDetector — disabled while the menu is open — was still disabled when the press arrived, and the press did nothing. The host now recomposes and re-lays-out the scene between the two, so the detector is listening again by the time it sees the press. The menu also appeared a beat late: the layer painted its first frame only on the owner window's next redraw, though that first render is what measures the content and puts the popup on screen at all. It renders as soon as its GPU side is up. Measured against the fixture's trace, press to first present is now 40 ms steady, 108 ms for the first menu of a session.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Temporary: runs
tao-headfulon the #569 branch as it stood before the review fixes, so the failures there can be told apart from regressions. Delete after reading.