From 71258b6d8014bd1ff6657309ccc8f86c3693bb0d Mon Sep 17 00:00:00 2001 From: Damocles Date: Mon, 13 Apr 2026 23:26:35 +0200 Subject: [PATCH] merge PopupPanel into HoverPanel with popupMode flag, delete PopupPanel --- modules/BluetoothMenu.qml | 5 +- modules/HoverPanel.qml | 161 ++++++++++++++++++++++++++------------ modules/NetworkMenu.qml | 5 +- modules/PopupPanel.qml | 111 -------------------------- modules/PowerMenu.qml | 7 +- modules/qmldir | 1 - 6 files changed, 121 insertions(+), 169 deletions(-) delete mode 100644 modules/PopupPanel.qml diff --git a/modules/BluetoothMenu.qml b/modules/BluetoothMenu.qml index 6d4d1e4..0c2cfdb 100644 --- a/modules/BluetoothMenu.qml +++ b/modules/BluetoothMenu.qml @@ -3,10 +3,11 @@ import Quickshell import Quickshell.Io import "." as M -M.PopupPanel { +M.HoverPanel { id: menuWindow - panelWidth: 250 + popupMode: true + contentWidth: 250 property var _devices: [] diff --git a/modules/HoverPanel.qml b/modules/HoverPanel.qml index ace6472..377a75d 100644 --- a/modules/HoverPanel.qml +++ b/modules/HoverPanel.qml @@ -3,19 +3,36 @@ import Quickshell import Quickshell.Wayland import "." as M -// Shared hover/OSD panel PanelWindow — slides down from the bar on hover or -// external trigger. Fullscreen transparent window so content can resize freely -// without triggering Wayland surface resize events. Parent module computes -// showPanel and reads panelHovered. +// Unified bar panel — fullscreen transparent window so content can resize +// freely without triggering Wayland surface resizes. +// +// Hover mode (popupMode: false, default): +// Parent drives visibility via showPanel. Panel auto-closes when showPanel +// drops, with a 50ms debounce. Reports panelHovered back to parent. +// Pass anchorItem for lazy position computation on each show. +// +// Popup mode (popupMode: true): +// Shows immediately on creation. Stays open until click-outside or +// dismiss() call. Emits dismissed() when closed — caller's LazyLoader +// sets active: false. Pass anchorX (screen-relative centre x). PanelWindow { id: root - required property bool showPanel - required property Item anchorItem + property bool popupMode: false + + // Hover mode + property bool showPanel: true + property Item anchorItem: null + property bool panelHovered: false + + // Popup mode + property real anchorX: -1 + signal dismissed + + // Shared required property color accentColor property string panelNamespace: "nova-panel" property real contentWidth: 220 - property bool panelHovered: false default property alias content: panelContent.children @@ -36,30 +53,46 @@ PanelWindow { margins.top: 0 function _updatePosition() { - const pt = anchorItem.mapToGlobal(anchorItem.width / 2, 0); const scr = screen; const sw = scr?.width ?? 1920; - panelContent.x = Math.max(0, Math.min(Math.round(pt.x - (scr?.x ?? 0) - contentWidth / 2), sw - contentWidth)); + let cx; + if (root.anchorItem) { + const pt = root.anchorItem.mapToGlobal(root.anchorItem.width / 2, 0); + cx = pt.x - (scr?.x ?? 0); + } else { + cx = root.anchorX; + } + panelContainer.x = Math.max(0, Math.min(Math.round(cx - root.contentWidth / 2), sw - root.contentWidth)); } + function _show() { + _updatePosition(); + _winVisible = true; + hideAnim.stop(); + showAnim.start(); + } + + function dismiss() { + showAnim.stop(); + hideAnim.start(); + } + + Component.onCompleted: if (popupMode) + _show() + Timer { id: _hideTimer interval: 50 - onTriggered: { - if (!root.showPanel) { - showAnim.stop(); - hideAnim.start(); - } - } + onTriggered: if (!root.showPanel) + root.dismiss() } onShowPanelChanged: { + if (root.popupMode) + return; if (showPanel) { _hideTimer.stop(); - _updatePosition(); - _winVisible = true; - hideAnim.stop(); - showAnim.start(); + _show(); } else { _hideTimer.restart(); } @@ -68,14 +101,14 @@ PanelWindow { ParallelAnimation { id: showAnim NumberAnimation { - target: panelContent + target: panelContainer property: "opacity" to: 1 duration: 120 easing.type: Easing.OutCubic } NumberAnimation { - target: panelContent + target: panelContainer property: "y" to: 0 duration: 150 @@ -86,53 +119,81 @@ PanelWindow { ParallelAnimation { id: hideAnim NumberAnimation { - target: panelContent + target: panelContainer property: "opacity" to: 0 duration: 150 easing.type: Easing.InCubic } NumberAnimation { - target: panelContent + target: panelContainer property: "y" - to: -panelContent.height + to: -panelContainer.height duration: 150 easing.type: Easing.InCubic } - onFinished: root._winVisible = false - } - - M.PopupBackground { - x: panelContent.x - y: panelContent.y - width: panelContent.width - height: panelContent.height - opacity: panelContent.opacity * Math.max(M.Theme.barOpacity, 0.85) - accentColor: root.accentColor - } - - Column { - id: panelContent - width: root.contentWidth - opacity: 0 - y: -height - - HoverHandler { - onHoveredChanged: root.panelHovered = hovered + onFinished: { + root._winVisible = false; + if (root.popupMode) + root.dismissed(); } } - // Border overlay — drawn on top of content so full-bleed items (e.g. album art) don't cover it - Rectangle { - x: panelContent.x - y: panelContent.y - width: panelContent.width + // Popup mode: click-outside dismiss (declared first = lowest z = below content) + MouseArea { + anchors.fill: parent + visible: root.popupMode + enabled: root.popupMode + onClicked: root.dismiss() + } + + M.PopupBackground { + x: panelContainer.x + y: panelContainer.y + width: panelContainer.width + height: panelContainer.height + opacity: panelContainer.opacity * Math.max(M.Theme.barOpacity, 0.85) + accentColor: root.accentColor + } + + Item { + id: panelContainer + x: 0 + y: -height + width: root.contentWidth height: panelContent.height + opacity: 0 + + // Popup mode: eat clicks on panel background so outer dismiss doesn't fire + MouseArea { + anchors.fill: parent + visible: root.popupMode + enabled: root.popupMode + } + + Column { + id: panelContent + width: root.contentWidth + + HoverHandler { + enabled: !root.popupMode + onHoveredChanged: if (!root.popupMode) + root.panelHovered = hovered + } + } + } + + // Border overlay — on top of content so full-bleed items don't cover it + Rectangle { + x: panelContainer.x + y: panelContainer.y + width: panelContainer.width + height: panelContainer.height color: "transparent" border.color: root.accentColor border.width: 1 bottomLeftRadius: M.Theme.radius bottomRightRadius: M.Theme.radius - opacity: panelContent.opacity + opacity: panelContainer.opacity } } diff --git a/modules/NetworkMenu.qml b/modules/NetworkMenu.qml index bc6a6d2..4f8e128 100644 --- a/modules/NetworkMenu.qml +++ b/modules/NetworkMenu.qml @@ -3,10 +3,11 @@ import Quickshell import Quickshell.Io import "." as M -M.PopupPanel { +M.HoverPanel { id: menuWindow - panelWidth: 250 + popupMode: true + contentWidth: 250 property var _networks: [] diff --git a/modules/PopupPanel.qml b/modules/PopupPanel.qml deleted file mode 100644 index ed8b5e6..0000000 --- a/modules/PopupPanel.qml +++ /dev/null @@ -1,111 +0,0 @@ -import QtQuick -import Quickshell -import Quickshell.Wayland -import "." as M - -// Shared flyout popup window — slides down from the bar, dismisses on -// click outside. Created on demand via Loader; animates in on creation, -// animates out then emits dismissed() for the Loader to deactivate. -PanelWindow { - id: root - - default property alias content: contentCol.children - required property var screen - required property real anchorX - property real panelWidth: 220 - property color accentColor: M.Theme.base05 - - signal dismissed - - visible: true - color: "transparent" - - WlrLayershell.layer: WlrLayer.Overlay - WlrLayershell.exclusiveZone: 0 - WlrLayershell.namespace: "nova-popup" - - anchors.top: true - anchors.left: true - anchors.right: true - anchors.bottom: true - - Component.onCompleted: showAnim.start() - - function dismiss() { - showAnim.stop(); - hideAnim.start(); - } - - // Click outside → dismiss - MouseArea { - anchors.fill: parent - onClicked: root.dismiss() - } - - Item { - id: panel - - x: Math.max(0, Math.min(Math.round(root.anchorX - contentCol.width / 2), root.width - contentCol.width)) - y: 0 - width: contentCol.width - height: contentCol.height - opacity: 0 - - // Eat clicks inside the panel - MouseArea { - anchors.fill: parent - } - - M.PopupBackground { - anchors.fill: parent - accentColor: root.accentColor - } - - Column { - id: contentCol - width: root.panelWidth - topPadding: 4 - bottomPadding: 4 - spacing: 2 - } - } - - ParallelAnimation { - id: showAnim - NumberAnimation { - target: panel - property: "opacity" - from: 0 - to: 1 - duration: 150 - easing.type: Easing.OutCubic - } - NumberAnimation { - target: panel - property: "y" - from: -panel.height - to: 0 - duration: 200 - easing.type: Easing.OutCubic - } - } - - ParallelAnimation { - id: hideAnim - NumberAnimation { - target: panel - property: "opacity" - to: 0 - duration: 150 - easing.type: Easing.InCubic - } - NumberAnimation { - target: panel - property: "y" - to: -panel.height - duration: 150 - easing.type: Easing.InCubic - } - onFinished: root.dismissed() - } -} diff --git a/modules/PowerMenu.qml b/modules/PowerMenu.qml index 53197ac..b93a5f6 100644 --- a/modules/PowerMenu.qml +++ b/modules/PowerMenu.qml @@ -2,10 +2,11 @@ import QtQuick import Quickshell import "." as M -M.PopupPanel { +M.HoverPanel { id: menuWindow - panelWidth: 180 + popupMode: true + contentWidth: 180 signal runCommand(var cmd) @@ -56,7 +57,7 @@ M.PopupPanel { required property var modelData required property int index - width: menuWindow.panelWidth + width: menuWindow.contentWidth height: 32 Rectangle { diff --git a/modules/qmldir b/modules/qmldir index c79fa03..076eec8 100644 --- a/modules/qmldir +++ b/modules/qmldir @@ -12,7 +12,6 @@ Clock 1.0 Clock.qml Volume 1.0 Volume.qml Tray 1.0 Tray.qml TrayMenu 1.0 TrayMenu.qml -PopupPanel 1.0 PopupPanel.qml PopupBackground 1.0 PopupBackground.qml HoverPanel 1.0 HoverPanel.qml PowerMenu 1.0 PowerMenu.qml