quickshell: apply dropdown pattern to tray context menu

Same morphing animation, concave corners, and dismiss behavior
as the calendar popup. Menu drops down from bar at icon position.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
rope 2026-05-26 15:37:54 +01:00
parent 3b7cc98fc9
commit 0aba95779a

View file

@ -714,7 +714,10 @@ in
anchors.fill: parent anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: (event) => { onClicked: (event) => {
if (contextMenu.justDismissed) return;
if (event.button === Qt.RightButton && modelData.hasMenu) { if (event.button === Qt.RightButton && modelData.hasMenu) {
let pos = parent.mapToItem(bar.contentItem, parent.width / 2, 0);
contextMenu.menuX = pos.x;
contextMenu.trayItem = modelData; contextMenu.trayItem = modelData;
menuOpener.menu = modelData.menu; menuOpener.menu = modelData.menu;
contextMenu.visible = true; contextMenu.visible = true;
@ -732,32 +735,112 @@ in
PopupWindow { PopupWindow {
id: contextMenu id: contextMenu
property var trayItem: null property var trayItem: null
anchor.item: trayArea property bool open: false
anchor.edges: Edges.Bottom | Edges.Right property bool justDismissed: false
anchor.gravity: Edges.Bottom | Edges.Left property real menuX: 0
property real fullWidth: menuItems.width + 16
property real fullHeight: menuItems.height + 12
anchor.window: bar
anchor.rect.x: menuX - (fullWidth + 16) / 2
anchor.rect.y: bar.height
anchor.edges: Edges.Top | Edges.Left
anchor.gravity: Edges.Bottom | Edges.Right
anchor.adjustment: PopupAdjustment.Slide anchor.adjustment: PopupAdjustment.Slide
grabFocus: true grabFocus: true
visible: false visible: false
color: "transparent" color: "transparent"
implicitWidth: menuColumn.width + 2 implicitWidth: fullWidth + 16
implicitHeight: menuColumn.height + 2 implicitHeight: fullHeight + 4
onVisibleChanged: {
if (visible) {
open = true;
} else {
open = false;
justDismissed = true;
menuDismissGuard.start();
menuOpener.menu = null;
}
}
Timer {
id: menuDismissGuard
interval: 100
onTriggered: contextMenu.justDismissed = false
}
QsMenuOpener { QsMenuOpener {
id: menuOpener id: menuOpener
} }
// Concave corner left
Item {
anchors.right: menuContent.left
anchors.top: parent.top
width: 8
height: Math.min(8, menuContent.height)
clip: true
visible: menuContent.height > 0
Canvas {
anchors.top: parent.top
width: 8; height: 8
onPaint: {
var ctx = getContext("2d");
ctx.clearRect(0, 0, 8, 8);
ctx.fillStyle = "#D1${c.base00}";
ctx.beginPath();
ctx.moveTo(0, 0); ctx.lineTo(8, 0); ctx.lineTo(8, 8);
ctx.arc(0, 8, 8, 0, -Math.PI / 2, true);
ctx.closePath(); ctx.fill();
}
}
}
// Concave corner right
Item {
anchors.left: menuContent.right
anchors.top: parent.top
width: 8
height: Math.min(8, menuContent.height)
clip: true
visible: menuContent.height > 0
Canvas {
anchors.top: parent.top
width: 8; height: 8
onPaint: {
var ctx = getContext("2d");
ctx.clearRect(0, 0, 8, 8);
ctx.fillStyle = "#D1${c.base00}";
ctx.beginPath();
ctx.moveTo(0, 0); ctx.lineTo(8, 0);
ctx.arc(8, 8, 8, -Math.PI / 2, Math.PI, true);
ctx.closePath(); ctx.fill();
}
}
}
Rectangle { Rectangle {
id: menuColumn id: menuContent
width: menuItems.width + 16 anchors.horizontalCenter: parent.horizontalCenter
height: menuItems.height + 12 anchors.top: parent.top
color: "#${c.base00}" width: contextMenu.fullWidth
border.color: "#${c.base03}" height: contextMenu.open ? contextMenu.fullHeight : 0
border.width: 1 color: "#D1${c.base00}"
radius: 8 radius: 8
topLeftRadius: 0
topRightRadius: 0
clip: true
Behavior on height {
NumberAnimation { duration: 220; easing.type: Easing.OutCubic }
}
Column { Column {
id: menuItems id: menuItems
anchors.centerIn: parent anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 6
width: 200 width: 200
Repeater { Repeater {
@ -771,7 +854,6 @@ in
? "#${c.base02}" : "transparent" ? "#${c.base02}" : "transparent"
radius: modelData.isSeparator ? 0 : 4 radius: modelData.isSeparator ? 0 : 4
// Separator line
Rectangle { Rectangle {
visible: modelData.isSeparator visible: modelData.isSeparator
anchors.centerIn: parent anchors.centerIn: parent
@ -780,7 +862,6 @@ in
color: "#${c.base03}" color: "#${c.base03}"
} }
// Menu item content
RowLayout { RowLayout {
visible: !modelData.isSeparator visible: !modelData.isSeparator
anchors.fill: parent anchors.fill: parent
@ -820,12 +901,6 @@ in
} }
} }
} }
onVisibleChanged: {
if (!visible) {
menuOpener.menu = null;
}
}
} }
// Calendar popup // Calendar popup