extract shared PopupPanel for animated tray/power menus
This commit is contained in:
parent
77ce83462d
commit
b8ec39f2c9
6 changed files with 249 additions and 302 deletions
|
|
@ -1,204 +1,137 @@
|
|||
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 {
|
||||
M.PopupPanel {
|
||||
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()
|
||||
QsMenuOpener {
|
||||
id: opener
|
||||
menu: menuWindow._currentHandle
|
||||
}
|
||||
|
||||
// Menu panel
|
||||
// Back button (submenus only)
|
||||
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
|
||||
}
|
||||
visible: menuWindow._handleStack.length > 0
|
||||
width: menuWindow.panelWidth
|
||||
height: visible ? 28 : 0
|
||||
|
||||
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
|
||||
anchors.leftMargin: 4
|
||||
anchors.rightMargin: 4
|
||||
color: backArea.containsMouse ? M.Theme.base02 : "transparent"
|
||||
radius: M.Theme.radius
|
||||
}
|
||||
|
||||
Column {
|
||||
id: menuCol
|
||||
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
|
||||
}
|
||||
|
||||
width: 220
|
||||
topPadding: 4
|
||||
bottomPadding: 4
|
||||
spacing: 2
|
||||
MouseArea {
|
||||
id: backArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
const stack = menuWindow._handleStack.slice();
|
||||
menuWindow._currentHandle = stack.pop();
|
||||
menuWindow._handleStack = stack;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QsMenuOpener {
|
||||
id: opener
|
||||
menu: menuWindow._currentHandle
|
||||
Repeater {
|
||||
model: opener.children
|
||||
|
||||
delegate: Item {
|
||||
id: entryItem
|
||||
|
||||
required property QsMenuEntry modelData
|
||||
|
||||
width: menuWindow.panelWidth
|
||||
height: modelData.isSeparator ? 9 : 28
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: opener.children
|
||||
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
|
||||
}
|
||||
|
||||
delegate: Item {
|
||||
id: entryItem
|
||||
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
|
||||
}
|
||||
|
||||
required property QsMenuEntry modelData
|
||||
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
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
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.dismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue