From 3e02c3f36a3cacedc3281f060a6f70051bb10388 Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Tue, 11 Aug 2026 11:29:04 +0200 Subject: [PATCH] fixed tray + media popouts --- quickshell/bar/MediaPill.qml | 2 ++ quickshell/components/MaskedArt.qml | 11 ++++++++-- quickshell/components/Popout.qml | 32 +++++++++++++++++++++++++++++ quickshell/popouts/MediaPopout.qml | 5 +++++ quickshell/popouts/TrayMenuList.qml | 25 +++++++++++++++------- 5 files changed, 66 insertions(+), 9 deletions(-) diff --git a/quickshell/bar/MediaPill.qml b/quickshell/bar/MediaPill.qml index 969b584..6a2ff02 100644 --- a/quickshell/bar/MediaPill.qml +++ b/quickshell/bar/MediaPill.qml @@ -52,6 +52,7 @@ BarPill { anchors.fill: parent visible: false layer.enabled: true + layer.smooth: true preferredRendererType: Shape.CurveRenderer ShapePath { fillColor: "white" @@ -71,6 +72,7 @@ BarPill { asynchronous: true visible: status === Image.Ready layer.enabled: true + layer.smooth: true layer.effect: MaskedArt { maskItem: artMask } } diff --git a/quickshell/components/MaskedArt.qml b/quickshell/components/MaskedArt.qml index ad71156..0732dc9 100644 --- a/quickshell/components/MaskedArt.qml +++ b/quickshell/components/MaskedArt.qml @@ -8,7 +8,14 @@ MultiEffect { maskEnabled: maskItem !== null maskSource: maskItem - // Hard edge — the chopped corners should read as cuts, not fades. + + // The chopped corners should read as cuts, not fades — but a spread of 0 + // is a hard `alpha < 0.5 ? 0 : 1` step, which throws away the one thing + // that makes the cut look drawn rather than pixelated: the mask Shape's own + // coverage antialiasing. Every partially covered pixel along a diagonal got + // rounded to all-or-nothing and the art came out with a visible staircase + // down both leaning edges. A narrow spread lets that single-pixel ramp + // through and nothing wider — still a cut, just not a jagged one. maskThresholdMin: 0.5 - maskSpreadAtMin: 0.0 + maskSpreadAtMin: 0.3 } diff --git a/quickshell/components/Popout.qml b/quickshell/components/Popout.qml index 99c73e7..1dd9140 100644 --- a/quickshell/components/Popout.qml +++ b/quickshell/components/Popout.qml @@ -1,5 +1,6 @@ import QtQuick import Quickshell +import Quickshell.Hyprland import "root:/config" // Hover-driven panel that hangs off a bar item. Opens on a short dwell, stays @@ -70,6 +71,31 @@ PopupWindow { onTriggered: root.open() } + // A pinned popout ignores hover by design, which left it with exactly one + // way out: another click on the same bar item that opened it. Moving the + // pointer away did nothing, so a panel opened by a click — the notification + // history, most often — simply stayed on screen. Click anywhere else and + // the compositor drops the grab, which unpins it and hands it back to the + // hover rules. + // + // The grab can only take hold once the surface is mapped; asked for in the + // same tick as `shown`, Hyprland clears it immediately. + Timer { + id: grabDelay + interval: 60 + running: root.pinned && root.shown + } + + HyprlandFocusGrab { + active: root.pinned && root.shown && !grabDelay.running + windows: [root] + + // A click on the trigger itself also clears the grab, but that click is + // the pill's own toggle — unpinning here as well would cancel it out and + // the panel would never close from its own button. + onCleared: if (!root.triggerHovered) root.pinned = false + } + Timer { id: hideTimer interval: root.hideDelay @@ -109,8 +135,14 @@ PopupWindow { // The handler has to live on the content item, not the window: a // HoverHandler parented to the PopupWindow itself latches on and the // panel never learns the pointer left. + // + // Disabled while the popout is down, which clears `hovered`. An unmapped + // surface gets no leave event, so a handler that was hovered when the + // window went away stayed hovered — `wantsOpen` never went false again + // and the panel came back up and refused to close. HoverHandler { id: panelHover + enabled: root.shown } } diff --git a/quickshell/popouts/MediaPopout.qml b/quickshell/popouts/MediaPopout.qml index ca6b32b..4ab0ca2 100644 --- a/quickshell/popouts/MediaPopout.qml +++ b/quickshell/popouts/MediaPopout.qml @@ -46,6 +46,10 @@ Popout { anchors.fill: parent visible: false layer.enabled: true + // Without this the mask texture is point-sampled, so the + // antialiased pixels along the cuts get snapped back to hard + // steps before MultiEffect ever sees them — see MaskedArt. + layer.smooth: true preferredRendererType: Shape.CurveRenderer ShapePath { fillColor: "white" @@ -65,6 +69,7 @@ Popout { cache: true visible: status === Image.Ready layer.enabled: true + layer.smooth: true layer.effect: MaskedArt { maskItem: artMask } } diff --git a/quickshell/popouts/TrayMenuList.qml b/quickshell/popouts/TrayMenuList.qml index 6bfbcf2..83f73a3 100644 --- a/quickshell/popouts/TrayMenuList.qml +++ b/quickshell/popouts/TrayMenuList.qml @@ -226,6 +226,24 @@ Column { NumberAnimation { duration: Theme.durFast; easing.type: Easing.OutExpo } } + // The guide rule down the side of an expanded submenu. + // + // Same trap the separators hit, from the other direction: the + // house lean is a fraction of *height*, and this rule is as tall + // as the submenu. A Wi-Fi list of thirty networks made it 600px + // tall, which sheared a 1px hairline into a 150px crimson wedge + // lying across every entry in the list. The rise is pinned in + // pixels, and it is declared before the entries so it can never + // paint over them again. + Skew { + x: Theme.padS + width: 1 + height: parent.height + lean: height > 0 ? 5 / height : 0 + visible: row.expanded + color: Theme.alpha(Theme.accent, 0.8) + } + // Loaded by URL rather than as an inline component: QML rejects // a type that instantiates itself, even lazily, and a menu tree // is the one place recursion is the natural shape. @@ -249,13 +267,6 @@ Column { } } - Skew { - x: Theme.padS - width: 1 - height: parent.height - visible: row.expanded - color: Theme.alpha(Theme.accent, 0.8) - } } } }