import QtQuick import Quickshell import Quickshell.Wayland import "." as M // Per-icon context menu popup window. // Covers the screen on the Overlay layer so clicking anywhere outside // the menu panel dismisses it. Created on demand by Tray.qml delegates. PanelWindow { id: menuWindow required property var handle required property var screen required property real anchorX signal menuClosed() // Current menu level — swapped when entering/leaving submenus property var _currentHandle: handle property var _handleStack: [] visible: true color: "transparent" WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.exclusiveZone: 0 WlrLayershell.namespace: "nova-traymenu" anchors.top: true anchors.left: true anchors.right: true anchors.bottom: true // Click outside the menu panel → dismiss MouseArea { anchors.fill: parent onClicked: menuWindow.menuClosed() } // Menu panel Item { id: panel x: Math.max(0, Math.min( Math.round(menuWindow.anchorX - menuCol.width / 2), menuWindow.width - menuCol.width )) y: 0 width: menuCol.width height: menuCol.height // Eat clicks inside the panel MouseArea { anchors.fill: parent } Rectangle { anchors.fill: parent color: M.Theme.base01 opacity: Math.max(M.Theme.barOpacity, 0.85) topLeftRadius: 0 topRightRadius: 0 bottomLeftRadius: M.Theme.radius bottomRightRadius: M.Theme.radius } Column { id: menuCol width: 220 topPadding: 4 bottomPadding: 4 spacing: 2 QsMenuOpener { id: opener menu: menuWindow._currentHandle } // Back button (submenus only) Item { visible: menuWindow._handleStack.length > 0 width: menuCol.width height: visible ? 28 : 0 Rectangle { anchors.fill: parent anchors.leftMargin: 4 anchors.rightMargin: 4 color: backArea.containsMouse ? M.Theme.base02 : "transparent" radius: M.Theme.radius } Text { anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left anchors.leftMargin: 12 text: "\u2039 Back" color: M.Theme.base05 font.pixelSize: M.Theme.fontSize font.family: M.Theme.fontFamily } MouseArea { id: backArea anchors.fill: parent hoverEnabled: true onClicked: { const stack = menuWindow._handleStack.slice(); menuWindow._currentHandle = stack.pop(); menuWindow._handleStack = stack; } } } Repeater { model: opener.children delegate: Item { id: entryItem required property QsMenuEntry modelData width: menuCol.width height: modelData.isSeparator ? 9 : 28 // Separator Rectangle { visible: entryItem.modelData.isSeparator anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left anchors.right: parent.right anchors.leftMargin: 8 anchors.rightMargin: 8 height: 1 color: M.Theme.base03 } // Hover highlight Rectangle { visible: !entryItem.modelData.isSeparator anchors.fill: parent anchors.leftMargin: 4 anchors.rightMargin: 4 color: rowArea.containsMouse && entryItem.modelData.enabled ? M.Theme.base02 : "transparent" radius: M.Theme.radius } // Icon Image { id: entryIcon visible: !entryItem.modelData.isSeparator && entryItem.modelData.icon !== "" anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left anchors.leftMargin: 12 width: M.Theme.fontSize height: M.Theme.fontSize source: entryItem.modelData.icon fillMode: Image.PreserveAspectFit } // Label Text { visible: !entryItem.modelData.isSeparator anchors.verticalCenter: parent.verticalCenter anchors.left: entryIcon.visible ? entryIcon.right : parent.left anchors.leftMargin: entryIcon.visible ? 6 : 12 anchors.right: entryChevron.visible ? entryChevron.left : parent.right anchors.rightMargin: entryChevron.visible ? 4 : 12 text: entryItem.modelData.text color: entryItem.modelData.enabled ? M.Theme.base05 : M.Theme.base03 font.pixelSize: M.Theme.fontSize font.family: M.Theme.fontFamily elide: Text.ElideRight } // Submenu chevron Text { id: entryChevron visible: !entryItem.modelData.isSeparator && entryItem.modelData.hasChildren anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right anchors.rightMargin: 12 text: "\u203A" color: entryItem.modelData.enabled ? M.Theme.base05 : M.Theme.base03 font.pixelSize: M.Theme.fontSize font.family: M.Theme.fontFamily } MouseArea { id: rowArea anchors.fill: parent hoverEnabled: true enabled: !entryItem.modelData.isSeparator && entryItem.modelData.enabled onClicked: { if (entryItem.modelData.hasChildren) { menuWindow._handleStack = menuWindow._handleStack.concat([menuWindow._currentHandle]); menuWindow._currentHandle = entryItem.modelData; } else { entryItem.modelData.triggered(); menuWindow.menuClosed(); } } } } } } } }