quickshell: custom QML tray context menus

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
rope 2026-05-26 10:37:50 +01:00
parent 63bd64ec56
commit e02e1f41c1

View file

@ -566,6 +566,7 @@ in
import Quickshell
import Quickshell.Hyprland
import Quickshell.Services.SystemTray
import Quickshell.Widgets
import QtQuick
import QtQuick.Layouts
@ -655,6 +656,7 @@ in
}
}
// Tray icon popup
PopupWindow {
id: trayPopup
anchor.item: trayToggle
@ -696,13 +698,13 @@ in
}
MouseArea {
id: trayMouse
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: (event) => {
if (event.button === Qt.RightButton && modelData.hasMenu) {
let mapped = trayMouse.mapToItem(null, event.x, event.y);
modelData.display(trayPopup, mapped.x, mapped.y);
contextMenu.trayItem = modelData;
menuOpener.menu = modelData.menu;
contextMenu.visible = true;
} else {
modelData.activate();
}
@ -713,6 +715,117 @@ in
}
}
}
// Custom-rendered context menu
PopupWindow {
id: contextMenu
property var trayItem: null
anchor.item: trayToggle
anchor.edges: Edges.Bottom | Edges.Right
anchor.gravity: Edges.Bottom | Edges.Left
anchor.adjustment: PopupAdjustment.Slide
visible: false
color: "transparent"
implicitWidth: menuColumn.width + 2
implicitHeight: menuColumn.height + 2
QsMenuOpener {
id: menuOpener
}
Rectangle {
id: menuColumn
width: menuItems.width + 16
height: menuItems.height + 12
color: "#${c.base00}"
border.color: "#${c.base03}"
border.width: 1
radius: 8
Column {
id: menuItems
anchors.centerIn: parent
width: 200
Repeater {
model: menuOpener.children
Loader {
required property var modelData
width: 200
sourceComponent: modelData.isSeparator ? separatorComp : menuItemComp
}
}
}
}
// Close when clicking outside
onVisibleChanged: {
if (!visible) {
menuOpener.menu = null;
}
}
}
// Menu item delegate
component MenuItemDelegate: Rectangle {
required property var modelData
width: 200
height: modelData.isSeparator ? 1 : 28
color: itemMouse.containsMouse && modelData.enabled ? "#${c.base02}" : "transparent"
radius: 4
RowLayout {
anchors.fill: parent
anchors.leftMargin: 10
anchors.rightMargin: 10
spacing: 8
Text {
Layout.fillWidth: true
text: modelData.text ?? ""
color: modelData.enabled ? "#${c.base05}" : "#${c.base03}"
font.family: "FiraMono Nerd Font"
font.pixelSize: 12
elide: Text.ElideRight
}
Text {
visible: modelData.buttonType !== QsMenuButtonType.None
text: modelData.checkState === Qt.Checked ? "\u2713" : ""
color: "#${c.base0D}"
font.family: "FiraMono Nerd Font"
font.pixelSize: 12
}
}
MouseArea {
id: itemMouse
anchors.fill: parent
hoverEnabled: true
enabled: modelData.enabled
onClicked: {
modelData.triggered();
contextMenu.visible = false;
}
}
}
// Separator delegate
component SeparatorDelegate: Rectangle {
width: 200
height: 9
color: "transparent"
Rectangle {
anchors.centerIn: parent
width: parent.width - 20
height: 1
color: "#${c.base03}"
}
}
Component { id: menuItemComp; MenuItemDelegate {} }
Component { id: separatorComp; SeparatorDelegate {} }
}
}
}