From 494961f24861d2d771ced9927d0e897f0d95d03a Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Sat, 13 Jun 2026 21:00:21 +0900 Subject: ext-stage-dock: transparent strip, surface hugs cards, fix re-minimize after empty MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Builds on the kernel per-pixel-alpha + set_size-resize capabilities. - Transparent strip: body.dock background #1c1c1ee6 -> transparent, so the windows beneath show through everywhere the cards don't cover; cards keep their solid #2e2e32 panel. data-attr-src preview, Noto Sans, and the d1 slot-enter animation are intact. - Surface hugs the card stack: height = surface_height(count) = max(1, 2*pad + count*card + (count-1)*gap) (0->1px hidden, 1->140, 2->272, …), never the full output height, so the transparent area doesn't needlessly capture input. The empty dock is a positive 1px hidden placeholder (the substrate rejects 0 geometry); grows/shrinks via set_size on minimize/restore. - Fix: re-minimize after the dock empties was a no-op. do_restore relied on on_toplevel_focused re-firing, but a restored window was never seat-defocused, so focus() is a seat no-op and the event never fires — leaving focused_ stale, so the next Super+M's focused_ guard rejected it (a new toplevel mapping unstuck it). Fix: set focused_ = tl directly in restore. No kernel change. Tests: new policy cases (surface_height always positive; hug heights 0/140/272/536) and a glue minimize->restore->minimize 1->0->1 cycle with has_focused() probe. ext-stage-dock 2/2 green on build + build-asan (no sanitizer reports). Real-seat verified: transparent strip, dock visible, cards float, re-minimize works. Edits confined to packages/ext-stage-dock/. --- packages/ext-stage-dock/src/dock_layout.hpp | 14 ++++ packages/ext-stage-dock/src/extension.cpp | 109 ++++++++++++++++++++++++-- packages/ext-stage-dock/src/probe.hpp | 6 ++ packages/ext-stage-dock/tests/test_glue.cpp | 35 +++++++++ packages/ext-stage-dock/tests/test_policy.cpp | 50 ++++++++++++ 5 files changed, 206 insertions(+), 8 deletions(-) diff --git a/packages/ext-stage-dock/src/dock_layout.hpp b/packages/ext-stage-dock/src/dock_layout.hpp index 43ee910..b02d819 100644 --- a/packages/ext-stage-dock/src/dock_layout.hpp +++ b/packages/ext-stage-dock/src/dock_layout.hpp @@ -86,6 +86,20 @@ struct DockMetrics { return 2 * m.pad + count * m.slot_height + (count - 1) * m.gap; } +// The POSITIVE surface height that hugs `count` cards: content_height(count) +// clamped to a strictly-positive minimum. The ui substrate REJECTS a surface +// with non-positive geometry (create_surface/set_size return nullptr + log an +// error), so the empty dock (count 0 -> content_height 0) must still be created +// / resized at a positive size and merely hidden (set_visible(false)), never at +// height 0. Returns max(content_height(count), 1): >= 1 for every count >= 0, +// equal to content_height once there is at least one card. (Width never hits 0 +// in practice — dock_width is a fixed positive constant — but callers should +// likewise guard it; this helper covers the height, which is the count-driven +// dimension.) +[[nodiscard]] inline auto surface_height(const DockMetrics& m, int count) -> int { + return std::max(1, content_height(m, count)); +} + // The on-screen rect of slot `i` (0-based) within the dock content, given the // current vertical `scroll` offset (px scrolled DOWN; 0 = top). The slot's // content-space top is pad + i*(slot_height+gap); subtracting `scroll` yields diff --git a/packages/ext-stage-dock/src/extension.cpp b/packages/ext-stage-dock/src/extension.cpp index 82814e2..e5261e4 100644 --- a/packages/ext-stage-dock/src/extension.cpp +++ b/packages/ext-stage-dock/src/extension.cpp @@ -56,6 +56,21 @@ constexpr std::uint32_t kMinimizeMods = WLR_MODIFIER_LOGO; // Super/LOGO // dock sits fully revealed (no reveal animation); d1 animates dock_box(f). constexpr int kDockWidth = 240; +// Card-stack metrics, in px, that MIRROR the kDockRml RCSS so the surface rect +// can be sized (via dock_layout::content_height) to hug the rendered card stack +// rather than the full output height. dp == px (substrate dp-ratio is 1.0), so +// these are the RCSS dp values: +// kCardHeight — one div.slot's OUTER (border-box) height: padding 6dp*2 (=12) +// + img.preview 84dp + span.title (margin-top 4dp + ~one 13dp line). Rounded +// UP to 124 so RmlUi's exact line-box height never clips a card (over-sizing +// the transparent surface by a few px is harmless; clipping a card is not). +// kCardGap — the inter-card vertical space (div.slot margin-bottom: 8dp). +// kStripPad — the strip's inner top/bottom margin (body.dock padding: 8dp). +// content_height(count) = 2*kStripPad + count*kCardHeight + (count-1)*kCardGap. +constexpr int kCardHeight = 124; +constexpr int kCardGap = 8; +constexpr int kStripPad = 8; + // A minimized window's dock entry: the live Toplevel* borrow (valid until its // unmapped event), the frozen Preview (owns the imported texture; null when the // substrate has no GL path), and a copied title (the Toplevel's title() view is @@ -73,7 +88,21 @@ struct Slot { // rounded like the Stage-Manager reference. d1 ADDS the RCSS animation on top of // c2's static document — without touching the data model (same "slots" list, // same per-row preview/title/restore bindings). data-model "ui" (the substrate -// default). The img src is the Preview source_uri(), bound via +// default). +// +// TRANSPARENT STRIP (per-pixel alpha). The substrate composites this surface +// with per-pixel alpha (ui.hpp UiSurface §PER-PIXEL ALPHA): any pixel body.dock +// does not paint is fully transparent and the windows BELOW show through. So +// `body.dock` paints NO background (`background-color: transparent`) — only the +// `div.slot` CARDS paint (their `#2e2e32ff` panel), reading as cards floating +// over the window with the empty strip see-through. NOTE the substrate still +// consumes pointer/touch over the whole surface RECT regardless of visual +// transparency (slice-5 consumption model), so the surface is sized to HUG the +// card stack (see content_height in create/refresh) — the rest of the screen +// stays interactive. A real input-transparent strip needs a deferred +// UiSurfaceSpec flag (report change-req). +// +// The img src is the Preview source_uri(), bound via // `data-attr-src="row.preview"` — RmlUi interpolates {{ }} only in TEXT, so an // element attribute must use the data-attr- attribute-binding form // (verified against vendored RmlUi 6.2: data_binding/options samples). The @@ -110,7 +139,7 @@ constexpr const char* kDockRml = R"RML(