Files
noctalia-shell/Widgets/NPopupContextMenu.qml
T
2025-11-17 20:35:45 -05:00

202 lines
5.3 KiB
QML

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import qs.Commons
// Simple context menu PopupWindow (similar to TrayMenu)
// Designed to be rendered inside a PopupMenuWindow for click-outside-to-close
PopupWindow {
id: root
property alias model: repeater.model
property real itemHeight: 28 // Match TrayMenu
property real itemPadding: Style.marginM
property int verticalPolicy: ScrollBar.AsNeeded
property int horizontalPolicy: ScrollBar.AsNeeded
property var anchorItem: null
property real anchorX: 0
property real anchorY: 0
signal triggered(string action)
implicitWidth: 180
implicitHeight: Math.min(600, flickable.contentHeight + (Style.marginS * 2))
visible: false
color: Color.transparent
anchor.item: anchorItem
anchor.rect.x: anchorX
anchor.rect.y: anchorY
// Handle Escape key to close menu
Item {
anchors.fill: parent
focus: true
Keys.onEscapePressed: root.close()
}
// Background
Rectangle {
id: menuBackground
anchors.fill: parent
color: Color.mSurface
border.color: Color.mOutline
border.width: Style.borderS
radius: Style.radiusM
// Fade-in animation
opacity: root.visible ? 1.0 : 0.0
Behavior on opacity {
NumberAnimation {
duration: Style.animationNormal
easing.type: Easing.OutQuad
}
}
}
// Content - Use Flickable + ColumnLayout like TrayMenu for consistency
Flickable {
id: flickable
anchors.fill: parent
anchors.margins: Style.marginS
contentHeight: columnLayout.implicitHeight
interactive: true
// Fade-in animation
opacity: root.visible ? 1.0 : 0.0
Behavior on opacity {
NumberAnimation {
duration: Style.animationNormal
easing.type: Easing.OutQuad
}
}
ColumnLayout {
id: columnLayout
width: flickable.width
spacing: 0
Repeater {
id: repeater
delegate: Rectangle {
id: menuItem
required property var modelData
required property int index
Layout.preferredWidth: parent.width
Layout.preferredHeight: modelData.visible !== false ? root.itemHeight : 0
visible: modelData.visible !== false
color: Color.transparent
Rectangle {
id: innerRect
anchors.fill: parent
color: mouseArea.containsMouse ? Color.mHover : Color.transparent
radius: Style.radiusS
opacity: modelData.enabled !== false ? 1.0 : 0.5
Behavior on color {
ColorAnimation {
duration: Style.animationFast
}
}
RowLayout {
anchors.fill: parent
anchors.leftMargin: Style.marginM
anchors.rightMargin: Style.marginM
spacing: Style.marginS
// Optional icon
NIcon {
visible: modelData.icon !== undefined
icon: modelData.icon || ""
pointSize: Style.fontSizeS
applyUiScale: false
color: mouseArea.containsMouse ? Color.mOnHover : Color.mOnSurface
verticalAlignment: Text.AlignVCenter
Behavior on color {
ColorAnimation {
duration: Style.animationFast
}
}
}
NText {
text: modelData.label || modelData.text || ""
pointSize: Style.fontSizeS
color: mouseArea.containsMouse ? Color.mOnHover : Color.mOnSurface
verticalAlignment: Text.AlignVCenter
Layout.fillWidth: true
Behavior on color {
ColorAnimation {
duration: Style.animationFast
}
}
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
enabled: (modelData.enabled !== false) && root.visible
cursorShape: Qt.PointingHandCursor
onClicked: {
if (menuItem.modelData.enabled !== false) {
root.triggered(menuItem.modelData.action || menuItem.modelData.key || menuItem.index.toString());
// Don't call root.close() here - let the parent PopupMenuWindow handle closing
}
}
}
}
}
}
}
}
// Helper function to open at specific position relative to anchor item
function openAt(x, y, item) {
if (!item) {
Logger.w("NPopupContextMenu", "anchorItem is undefined, won't show menu.");
return;
}
anchorItem = item;
anchorX = x;
anchorY = y;
visible = true;
// Force update after showing
Qt.callLater(() => {
if (root.anchor) {
root.anchor.updateAnchor();
}
});
}
// Helper function to open at item (compatible with NContextMenu API)
function openAtItem(item, mouseX, mouseY) {
openAt(mouseX || 0, mouseY || 0, item);
}
// Helper function to close menu (compatible with PopupMenuWindow)
function close() {
visible = false;
}
// Alias for backward compatibility
function closeMenu() {
close();
}
}