diff --git a/settings/hyprland.nix b/settings/hyprland.nix index 0230e7b..4296bf9 100644 --- a/settings/hyprland.nix +++ b/settings/hyprland.nix @@ -714,7 +714,10 @@ in anchors.fill: parent acceptedButtons: Qt.LeftButton | Qt.RightButton onClicked: (event) => { + if (contextMenu.justDismissed) return; if (event.button === Qt.RightButton && modelData.hasMenu) { + let pos = parent.mapToItem(bar.contentItem, parent.width / 2, 0); + contextMenu.menuX = pos.x; contextMenu.trayItem = modelData; menuOpener.menu = modelData.menu; contextMenu.visible = true; @@ -732,32 +735,112 @@ in PopupWindow { id: contextMenu property var trayItem: null - anchor.item: trayArea - anchor.edges: Edges.Bottom | Edges.Right - anchor.gravity: Edges.Bottom | Edges.Left + property bool open: false + property bool justDismissed: false + 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 grabFocus: true visible: false color: "transparent" - implicitWidth: menuColumn.width + 2 - implicitHeight: menuColumn.height + 2 + implicitWidth: fullWidth + 16 + 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 { 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 { - id: menuColumn - width: menuItems.width + 16 - height: menuItems.height + 12 - color: "#${c.base00}" - border.color: "#${c.base03}" - border.width: 1 + id: menuContent + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + width: contextMenu.fullWidth + height: contextMenu.open ? contextMenu.fullHeight : 0 + color: "#D1${c.base00}" radius: 8 + topLeftRadius: 0 + topRightRadius: 0 + clip: true + + Behavior on height { + NumberAnimation { duration: 220; easing.type: Easing.OutCubic } + } Column { id: menuItems - anchors.centerIn: parent + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.topMargin: 6 width: 200 Repeater { @@ -771,7 +854,6 @@ in ? "#${c.base02}" : "transparent" radius: modelData.isSeparator ? 0 : 4 - // Separator line Rectangle { visible: modelData.isSeparator anchors.centerIn: parent @@ -780,7 +862,6 @@ in color: "#${c.base03}" } - // Menu item content RowLayout { visible: !modelData.isSeparator anchors.fill: parent @@ -820,12 +901,6 @@ in } } } - - onVisibleChanged: { - if (!visible) { - menuOpener.menu = null; - } - } } // Calendar popup