diff --git a/Modules/DesktopWidgets/DraggableDesktopWidget.qml b/Modules/DesktopWidgets/DraggableDesktopWidget.qml index 135f04503..e29d9f484 100644 --- a/Modules/DesktopWidgets/DraggableDesktopWidget.qml +++ b/Modules/DesktopWidgets/DraggableDesktopWidget.qml @@ -2,8 +2,8 @@ import QtQuick import QtQuick.Effects import Quickshell import qs.Commons -import qs.Services.UI import qs.Services.Noctalia +import qs.Services.UI import qs.Widgets Item { @@ -190,6 +190,11 @@ Item { return; } + // Hide the dynamic context menu (popup window stays open for the dialog) + if (popupMenuWindow.hideDynamicMenu) { + popupMenuWindow.hideDynamicMenu(); + } + var component = Qt.createComponent(Quickshell.shellDir + "/Modules/Panels/Settings/DesktopWidgets/DesktopWidgetSettingsDialog.qml"); function instantiateAndOpen() { @@ -210,7 +215,6 @@ Item { popupMenuWindow.close(); dialog.destroy(); }); - popupMenuWindow.open(); dialog.open(); } else { Logger.e("DraggableDesktopWidget", "Failed to create widget settings dialog"); @@ -223,12 +227,12 @@ Item { Logger.e("DraggableDesktopWidget", "Error loading settings dialog component:", component.errorString()); } else { component.statusChanged.connect(() => { - if (component.status === Component.Ready) { - instantiateAndOpen(); - } else if (component.status === Component.Error) { - Logger.e("DraggableDesktopWidget", "Error loading settings dialog component:", component.errorString()); - } - }); + if (component.status === Component.Ready) { + instantiateAndOpen(); + } else if (component.status === Component.Error) { + Logger.e("DraggableDesktopWidget", "Error loading settings dialog component:", component.errorString()); + } + }); } } @@ -309,53 +313,46 @@ Item { z: 1 } - // Context menu for right-click - NPopupContextMenu { - id: contextMenu - visible: false - - property bool hasSettings: { - if (!widgetData || !widgetData.id) { - return false; - } + // Context menu model and handler - menu is created dynamically in PopupMenuWindow + property var contextMenuModel: { + var hasSettings = false; + if (widgetData && widgetData.id) { var widgetId = widgetData.id; if (DesktopWidgetRegistry.isPluginWidget(widgetId)) { var pluginId = widgetId.replace("plugin:", ""); var manifest = PluginRegistry.getPluginManifest(pluginId); - return manifest && manifest.entryPoints && manifest.entryPoints.settings; + hasSettings = manifest && manifest.entryPoints && manifest.entryPoints.settings; + } else { + hasSettings = DesktopWidgetRegistry.widgetSettingsMap[widgetId] !== undefined; } - return DesktopWidgetRegistry.widgetSettingsMap[widgetId] !== undefined; } - model: { - var items = []; - if (contextMenu.hasSettings) { - items.push({ - "label": I18n.tr("context-menu.widget-settings"), - "action": "widget-settings", - "icon": "settings" - }); - } + var items = []; + if (hasSettings) { items.push({ - "label": I18n.tr("context-menu.delete"), - "action": "delete", - "icon": "trash" + "label": I18n.tr("context-menu.widget-settings"), + "action": "widget-settings", + "icon": "settings" }); - return items; } + items.push({ + "label": I18n.tr("context-menu.delete"), + "action": "delete", + "icon": "trash" + }); + return items; + } - onTriggered: (action, item) => { - var popupMenuWindow = PanelService.getPopupMenuWindow(screen); - if (popupMenuWindow) { - popupMenuWindow.close(); - } - - if (action === "widget-settings") { - root.openWidgetSettings(); - } else if (action === "delete") { - root.removeWidget(); - } - } + function handleContextMenuAction(action) { + if (action === "widget-settings") { + // Don't close - openWidgetSettings will use the popup window for the dialog + root.openWidgetSettings(); + return true; // Signal that we're handling close ourselves + } else if (action === "delete") { + root.removeWidget(); + return false; // Let caller close the popup + } + return false; } // Drag MouseArea - handles dragging (left-click) @@ -450,10 +447,13 @@ Item { onPressed: mouse => { if (mouse.button === Qt.RightButton) { - var popupMenuWindow = PanelService.getPopupMenuWindow(screen); + var popupMenuWindow = PanelService.getPopupMenuWindow(root.screen); if (popupMenuWindow) { - popupMenuWindow.showContextMenu(contextMenu); - contextMenu.openAtItem(root, screen); + // Map click position to screen coordinates + var globalPos = root.mapToItem(null, mouse.x, mouse.y); + // Use dynamic context menu (created in PopupMenuWindow's Top layer) + // This ensures input events work correctly for desktop widgets (Bottom layer) + popupMenuWindow.showDynamicContextMenu(root.contextMenuModel, globalPos.x, globalPos.y, root.handleContextMenuAction); } } } @@ -486,7 +486,8 @@ Item { } Component.onCompleted: requestPaint() - onVisibleChanged: if (visible) requestPaint() + onVisibleChanged: if (visible) + requestPaint() MouseArea { anchors.fill: parent @@ -512,19 +513,13 @@ Item { // Calculate diagonal distance from opposite corner (bottom-right) var oppositeCornerX = root.x + root.width * root.widgetScale; var oppositeCornerY = root.y + root.height * root.widgetScale; - var initialDistance = Math.sqrt( - Math.pow(pressPos.x - oppositeCornerX, 2) + - Math.pow(pressPos.y - oppositeCornerY, 2) - ); - var currentDistance = Math.sqrt( - Math.pow(currentPos.x - oppositeCornerX, 2) + - Math.pow(currentPos.y - oppositeCornerY, 2) - ); - + var initialDistance = Math.sqrt(Math.pow(pressPos.x - oppositeCornerX, 2) + Math.pow(pressPos.y - oppositeCornerY, 2)); + var currentDistance = Math.sqrt(Math.pow(currentPos.x - oppositeCornerX, 2) + Math.pow(currentPos.y - oppositeCornerY, 2)); + if (initialDistance > 0) { var scaleRatio = currentDistance / initialDistance; var newScale = Math.max(minScale, Math.min(maxScale, internal.initialScale * scaleRatio)); - + if (!isNaN(newScale) && newScale > 0) { root.widgetScale = newScale; internal.lastScale = newScale; @@ -575,7 +570,8 @@ Item { } Component.onCompleted: requestPaint() - onVisibleChanged: if (visible) requestPaint() + onVisibleChanged: if (visible) + requestPaint() MouseArea { anchors.fill: parent @@ -601,19 +597,13 @@ Item { // Calculate diagonal distance from opposite corner (bottom-left) var oppositeCornerX = root.x; var oppositeCornerY = root.y + root.height * root.widgetScale; - var initialDistance = Math.sqrt( - Math.pow(pressPos.x - oppositeCornerX, 2) + - Math.pow(pressPos.y - oppositeCornerY, 2) - ); - var currentDistance = Math.sqrt( - Math.pow(currentPos.x - oppositeCornerX, 2) + - Math.pow(currentPos.y - oppositeCornerY, 2) - ); - + var initialDistance = Math.sqrt(Math.pow(pressPos.x - oppositeCornerX, 2) + Math.pow(pressPos.y - oppositeCornerY, 2)); + var currentDistance = Math.sqrt(Math.pow(currentPos.x - oppositeCornerX, 2) + Math.pow(currentPos.y - oppositeCornerY, 2)); + if (initialDistance > 0) { var scaleRatio = currentDistance / initialDistance; var newScale = Math.max(minScale, Math.min(maxScale, internal.initialScale * scaleRatio)); - + if (!isNaN(newScale) && newScale > 0) { root.widgetScale = newScale; internal.lastScale = newScale; @@ -664,12 +654,19 @@ Item { } Component.onCompleted: requestPaint() - onVisibleChanged: if (visible) requestPaint() + onVisibleChanged: if (visible) + requestPaint() Connections { target: root - function onWidthChanged() { if (bottomLeftHandle.visible) bottomLeftHandle.requestPaint() } - function onHeightChanged() { if (bottomLeftHandle.visible) bottomLeftHandle.requestPaint() } + function onWidthChanged() { + if (bottomLeftHandle.visible) + bottomLeftHandle.requestPaint(); + } + function onHeightChanged() { + if (bottomLeftHandle.visible) + bottomLeftHandle.requestPaint(); + } } MouseArea { @@ -696,19 +693,13 @@ Item { // Calculate diagonal distance from opposite corner (top-right) var oppositeCornerX = root.x + root.width * root.widgetScale; var oppositeCornerY = root.y; - var initialDistance = Math.sqrt( - Math.pow(pressPos.x - oppositeCornerX, 2) + - Math.pow(pressPos.y - oppositeCornerY, 2) - ); - var currentDistance = Math.sqrt( - Math.pow(currentPos.x - oppositeCornerX, 2) + - Math.pow(currentPos.y - oppositeCornerY, 2) - ); - + var initialDistance = Math.sqrt(Math.pow(pressPos.x - oppositeCornerX, 2) + Math.pow(pressPos.y - oppositeCornerY, 2)); + var currentDistance = Math.sqrt(Math.pow(currentPos.x - oppositeCornerX, 2) + Math.pow(currentPos.y - oppositeCornerY, 2)); + if (initialDistance > 0) { var scaleRatio = currentDistance / initialDistance; var newScale = Math.max(minScale, Math.min(maxScale, internal.initialScale * scaleRatio)); - + if (!isNaN(newScale) && newScale > 0) { root.widgetScale = newScale; internal.lastScale = newScale; @@ -759,12 +750,19 @@ Item { } Component.onCompleted: requestPaint() - onVisibleChanged: if (visible) requestPaint() + onVisibleChanged: if (visible) + requestPaint() Connections { target: root - function onWidthChanged() { if (bottomRightHandle.visible) bottomRightHandle.requestPaint() } - function onHeightChanged() { if (bottomRightHandle.visible) bottomRightHandle.requestPaint() } + function onWidthChanged() { + if (bottomRightHandle.visible) + bottomRightHandle.requestPaint(); + } + function onHeightChanged() { + if (bottomRightHandle.visible) + bottomRightHandle.requestPaint(); + } } MouseArea { @@ -791,19 +789,13 @@ Item { // Calculate diagonal distance from opposite corner (top-left) var oppositeCornerX = root.x; var oppositeCornerY = root.y; - var initialDistance = Math.sqrt( - Math.pow(pressPos.x - oppositeCornerX, 2) + - Math.pow(pressPos.y - oppositeCornerY, 2) - ); - var currentDistance = Math.sqrt( - Math.pow(currentPos.x - oppositeCornerX, 2) + - Math.pow(currentPos.y - oppositeCornerY, 2) - ); - + var initialDistance = Math.sqrt(Math.pow(pressPos.x - oppositeCornerX, 2) + Math.pow(pressPos.y - oppositeCornerY, 2)); + var currentDistance = Math.sqrt(Math.pow(currentPos.x - oppositeCornerX, 2) + Math.pow(currentPos.y - oppositeCornerY, 2)); + if (initialDistance > 0) { var scaleRatio = currentDistance / initialDistance; var newScale = Math.max(minScale, Math.min(maxScale, internal.initialScale * scaleRatio)); - + if (!isNaN(newScale) && newScale > 0) { root.widgetScale = newScale; internal.lastScale = newScale; diff --git a/Modules/MainScreen/PopupMenuWindow.qml b/Modules/MainScreen/PopupMenuWindow.qml index ce29d0231..b6255e634 100644 --- a/Modules/MainScreen/PopupMenuWindow.qml +++ b/Modules/MainScreen/PopupMenuWindow.qml @@ -3,6 +3,7 @@ import Quickshell import Quickshell.Wayland import qs.Commons import qs.Services.UI +import qs.Widgets // Generic full-screen popup window for menus and context menus // This is a top-level PanelWindow (sibling to MainScreen, not nested inside it) @@ -20,6 +21,9 @@ PanelWindow { // Expose the trayMenu Loader directly (for backward compatibility) readonly property alias trayMenuLoader: trayMenuLoader + // Dynamic context menu callback for items in other windows (e.g., desktop widgets) + property var dynamicMenuCallback: null + anchors.top: true anchors.left: true anchors.right: true @@ -55,6 +59,26 @@ PanelWindow { } } + // Dynamic context menu - created as child of this window (Top layer) so input works correctly + // Used for items in other windows like desktop widgets (bottom layer) + NPopupContextMenu { + id: dynamicMenu + visible: false + screen: root.screen + + onTriggered: (action, item) => { + if (root.dynamicMenuCallback) { + // Callback returns true if it will handle closing (e.g., opening a dialog) + var handled = root.dynamicMenuCallback(action); + if (!handled) { + root.close(); + } + } else { + root.close(); + } + } + } + function open() { visible = true; } @@ -67,6 +91,35 @@ PanelWindow { } } + // Show a dynamic context menu with model and callback at screen coordinates + // Used for items in other window layers (e.g., desktop widgets in bottom layer) + function showDynamicContextMenu(model, screenX, screenY, callback) { + dynamicMenu.model = model; + dynamicMenuCallback = callback; + + // Use the anchor point item for positioning at absolute coordinates + dynamicMenu.anchorItem = anchorPoint; + anchorPoint.x = screenX; + anchorPoint.y = screenY; + + contentItem = dynamicMenu; + dynamicMenu.visible = true; + open(); + } + + // Invisible anchor point for dynamic menu positioning + Item { + id: anchorPoint + width: 1 + height: 1 + } + + // Hide just the dynamic menu without closing the popup window + // Used when transitioning from context menu to a dialog + function hideDynamicMenu() { + dynamicMenu.visible = false; + } + function close() { visible = false; // Call close/hide method on current content @@ -77,6 +130,9 @@ PanelWindow { contentItem.close(); } } + // Hide dynamic menu + dynamicMenu.visible = false; + dynamicMenuCallback = null; // Restore TrayMenu as default content if (trayMenuLoader.item) { contentItem = trayMenuLoader.item;