From 940265538137b23e14f08538d499eeb20ed505ec Mon Sep 17 00:00:00 2001 From: Lemmy Date: Fri, 19 Dec 2025 19:09:52 -0500 Subject: [PATCH] PopupContextMenu: fixed popup positionning on wlroots and hyprland --- Modules/Bar/Widgets/ActiveWindow.qml | 3 +- Modules/Bar/Widgets/AudioVisualizer.qml | 3 +- Modules/Bar/Widgets/Battery.qml | 3 +- Modules/Bar/Widgets/Bluetooth.qml | 3 +- Modules/Bar/Widgets/Brightness.qml | 3 +- Modules/Bar/Widgets/Clock.qml | 3 +- Modules/Bar/Widgets/ControlCenter.qml | 3 +- Modules/Bar/Widgets/KeyboardLayout.qml | 3 +- Modules/Bar/Widgets/LockKeys.qml | 3 +- Modules/Bar/Widgets/MediaMini.qml | 3 +- Modules/Bar/Widgets/Microphone.qml | 3 +- Modules/Bar/Widgets/NotificationHistory.qml | 3 +- Modules/Bar/Widgets/SessionMenu.qml | 3 +- Modules/Bar/Widgets/SystemMonitor.qml | 3 +- Modules/Bar/Widgets/Taskbar.qml | 2 +- Modules/Bar/Widgets/VPN.qml | 3 +- Modules/Bar/Widgets/Volume.qml | 3 +- Modules/Bar/Widgets/WallpaperSelector.qml | 3 +- Modules/Bar/Widgets/WiFi.qml | 3 +- Modules/Bar/Widgets/Workspace.qml | 3 +- Services/UI/BarService.qml | 50 ---------- Widgets/NContextMenu.qml | 3 + Widgets/NPopupContextMenu.qml | 104 ++++++++++++++++---- 23 files changed, 108 insertions(+), 108 deletions(-) diff --git a/Modules/Bar/Widgets/ActiveWindow.qml b/Modules/Bar/Widgets/ActiveWindow.qml index ac4937bed..d96622527 100644 --- a/Modules/Bar/Widgets/ActiveWindow.qml +++ b/Modules/Bar/Widgets/ActiveWindow.qml @@ -450,8 +450,7 @@ Item { var popupMenuWindow = PanelService.getPopupMenuWindow(screen); if (popupMenuWindow) { popupMenuWindow.showContextMenu(contextMenu); - const pos = BarService.getContextMenuPosition(root, contextMenu.implicitWidth, contextMenu.implicitHeight); - contextMenu.openAtItem(root, pos.x, pos.y); + contextMenu.openAtItem(root, screen); } } } diff --git a/Modules/Bar/Widgets/AudioVisualizer.qml b/Modules/Bar/Widgets/AudioVisualizer.qml index 8f04e7314..5fae49930 100644 --- a/Modules/Bar/Widgets/AudioVisualizer.qml +++ b/Modules/Bar/Widgets/AudioVisualizer.qml @@ -154,8 +154,7 @@ Item { var popupMenuWindow = PanelService.getPopupMenuWindow(screen); if (popupMenuWindow) { popupMenuWindow.showContextMenu(contextMenu); - const pos = BarService.getContextMenuPosition(root, contextMenu.implicitWidth, contextMenu.implicitHeight); - contextMenu.openAtItem(root, pos.x, pos.y); + contextMenu.openAtItem(root, screen); } } else { const types = ["linear", "mirrored", "wave"]; diff --git a/Modules/Bar/Widgets/Battery.qml b/Modules/Bar/Widgets/Battery.qml index e0689c3ad..8ed7f1007 100644 --- a/Modules/Bar/Widgets/Battery.qml +++ b/Modules/Bar/Widgets/Battery.qml @@ -248,8 +248,7 @@ Item { var popupMenuWindow = PanelService.getPopupMenuWindow(screen); if (popupMenuWindow) { popupMenuWindow.showContextMenu(contextMenu); - const pos = BarService.getContextMenuPosition(pill, contextMenu.implicitWidth, contextMenu.implicitHeight); - contextMenu.openAtItem(pill, pos.x, pos.y); + contextMenu.openAtItem(pill, screen); } } } diff --git a/Modules/Bar/Widgets/Bluetooth.qml b/Modules/Bar/Widgets/Bluetooth.qml index 610f8767b..645a7a511 100644 --- a/Modules/Bar/Widgets/Bluetooth.qml +++ b/Modules/Bar/Widgets/Bluetooth.qml @@ -93,8 +93,7 @@ Item { var popupMenuWindow = PanelService.getPopupMenuWindow(screen); if (popupMenuWindow) { popupMenuWindow.showContextMenu(contextMenu); - const pos = BarService.getContextMenuPosition(pill, contextMenu.implicitWidth, contextMenu.implicitHeight); - contextMenu.openAtItem(pill, pos.x, pos.y); + contextMenu.openAtItem(pill, screen); } } tooltipText: { diff --git a/Modules/Bar/Widgets/Brightness.qml b/Modules/Bar/Widgets/Brightness.qml index 160d02c6d..819b634d0 100644 --- a/Modules/Bar/Widgets/Brightness.qml +++ b/Modules/Bar/Widgets/Brightness.qml @@ -151,8 +151,7 @@ Item { var popupMenuWindow = PanelService.getPopupMenuWindow(screen); if (popupMenuWindow) { popupMenuWindow.showContextMenu(contextMenu); - const pos = BarService.getContextMenuPosition(pill, contextMenu.implicitWidth, contextMenu.implicitHeight); - contextMenu.openAtItem(pill, pos.x, pos.y); + contextMenu.openAtItem(pill, screen); } } } diff --git a/Modules/Bar/Widgets/Clock.qml b/Modules/Bar/Widgets/Clock.qml index f318357da..37cb15baf 100644 --- a/Modules/Bar/Widgets/Clock.qml +++ b/Modules/Bar/Widgets/Clock.qml @@ -169,8 +169,7 @@ Rectangle { var popupMenuWindow = PanelService.getPopupMenuWindow(screen); if (popupMenuWindow) { popupMenuWindow.showContextMenu(contextMenu); - const pos = BarService.getContextMenuPosition(root, contextMenu.implicitWidth, contextMenu.implicitHeight); - contextMenu.openAtItem(root, pos.x, pos.y); + contextMenu.openAtItem(root, screen); } } else { PanelService.getPanel("clockPanel", screen)?.toggle(this); diff --git a/Modules/Bar/Widgets/ControlCenter.qml b/Modules/Bar/Widgets/ControlCenter.qml index 432fb9d74..d0e7de2d3 100644 --- a/Modules/Bar/Widgets/ControlCenter.qml +++ b/Modules/Bar/Widgets/ControlCenter.qml @@ -149,8 +149,7 @@ NIconButton { var popupMenuWindow = PanelService.getPopupMenuWindow(screen); if (popupMenuWindow) { popupMenuWindow.showContextMenu(contextMenu); - const pos = BarService.getContextMenuPosition(root, contextMenu.implicitWidth, contextMenu.implicitHeight); - contextMenu.openAtItem(root, pos.x, pos.y); + contextMenu.openAtItem(root, screen); } } onMiddleClicked: PanelService.getPanel("launcherPanel", screen)?.toggle() diff --git a/Modules/Bar/Widgets/KeyboardLayout.qml b/Modules/Bar/Widgets/KeyboardLayout.qml index 981985c4d..43189b3e3 100644 --- a/Modules/Bar/Widgets/KeyboardLayout.qml +++ b/Modules/Bar/Widgets/KeyboardLayout.qml @@ -83,8 +83,7 @@ Item { var popupMenuWindow = PanelService.getPopupMenuWindow(screen); if (popupMenuWindow) { popupMenuWindow.showContextMenu(contextMenu); - const pos = BarService.getContextMenuPosition(pill, contextMenu.implicitWidth, contextMenu.implicitHeight); - contextMenu.openAtItem(pill, pos.x, pos.y); + contextMenu.openAtItem(pill, screen); } } } diff --git a/Modules/Bar/Widgets/LockKeys.qml b/Modules/Bar/Widgets/LockKeys.qml index 23bd3db1d..361ab186d 100644 --- a/Modules/Bar/Widgets/LockKeys.qml +++ b/Modules/Bar/Widgets/LockKeys.qml @@ -83,8 +83,7 @@ Rectangle { var popupMenuWindow = PanelService.getPopupMenuWindow(screen); if (popupMenuWindow) { popupMenuWindow.showContextMenu(contextMenu); - const pos = BarService.getContextMenuPosition(root, contextMenu.implicitWidth, contextMenu.implicitHeight); - contextMenu.openAtItem(root, pos.x, pos.y); + contextMenu.openAtItem(root, screen); } } } diff --git a/Modules/Bar/Widgets/MediaMini.qml b/Modules/Bar/Widgets/MediaMini.qml index fe7d658ba..42e33c5d5 100644 --- a/Modules/Bar/Widgets/MediaMini.qml +++ b/Modules/Bar/Widgets/MediaMini.qml @@ -366,8 +366,7 @@ Item { var popupWindow = PanelService.getPopupMenuWindow(screen); if (popupWindow) { popupWindow.showContextMenu(contextMenu); - const pos = BarService.getContextMenuPosition(container, contextMenu.implicitWidth, contextMenu.implicitHeight); - contextMenu.openAtItem(container, pos.x, pos.y); + contextMenu.openAtItem(container, screen); } } else if (mouse.button === Qt.MiddleButton && hasPlayer && MediaService.canGoPrevious) { MediaService.previous(); diff --git a/Modules/Bar/Widgets/Microphone.qml b/Modules/Bar/Widgets/Microphone.qml index 3b22ed0e0..bd622711d 100644 --- a/Modules/Bar/Widgets/Microphone.qml +++ b/Modules/Bar/Widgets/Microphone.qml @@ -170,8 +170,7 @@ Item { var popupMenuWindow = PanelService.getPopupMenuWindow(screen); if (popupMenuWindow) { popupMenuWindow.showContextMenu(contextMenu); - const pos = BarService.getContextMenuPosition(pill, contextMenu.implicitWidth, contextMenu.implicitHeight); - contextMenu.openAtItem(pill, pos.x, pos.y); + contextMenu.openAtItem(pill, screen); } } onMiddleClicked: root.openExternalMixer() diff --git a/Modules/Bar/Widgets/NotificationHistory.qml b/Modules/Bar/Widgets/NotificationHistory.qml index c72914c96..2a6e65094 100644 --- a/Modules/Bar/Widgets/NotificationHistory.qml +++ b/Modules/Bar/Widgets/NotificationHistory.qml @@ -106,8 +106,7 @@ NIconButton { var popupMenuWindow = PanelService.getPopupMenuWindow(screen); if (popupMenuWindow) { popupMenuWindow.showContextMenu(contextMenu); - const pos = BarService.getContextMenuPosition(root, contextMenu.implicitWidth, contextMenu.implicitHeight); - contextMenu.openAtItem(root, pos.x, pos.y); + contextMenu.openAtItem(root, screen); } } diff --git a/Modules/Bar/Widgets/SessionMenu.qml b/Modules/Bar/Widgets/SessionMenu.qml index f208f0052..d1e779995 100644 --- a/Modules/Bar/Widgets/SessionMenu.qml +++ b/Modules/Bar/Widgets/SessionMenu.qml @@ -89,8 +89,7 @@ NIconButton { var popupMenuWindow = PanelService.getPopupMenuWindow(screen); if (popupMenuWindow) { popupMenuWindow.showContextMenu(contextMenu); - const pos = BarService.getContextMenuPosition(root, contextMenu.implicitWidth, contextMenu.implicitHeight); - contextMenu.openAtItem(root, pos.x, pos.y); + contextMenu.openAtItem(root, screen); } } } diff --git a/Modules/Bar/Widgets/SystemMonitor.qml b/Modules/Bar/Widgets/SystemMonitor.qml index 6a7cc200f..e5ddf1123 100644 --- a/Modules/Bar/Widgets/SystemMonitor.qml +++ b/Modules/Bar/Widgets/SystemMonitor.qml @@ -151,8 +151,7 @@ Rectangle { var popupMenuWindow = PanelService.getPopupMenuWindow(screen); if (popupMenuWindow) { popupMenuWindow.showContextMenu(contextMenu); - const pos = BarService.getContextMenuPosition(root, contextMenu.implicitWidth, contextMenu.implicitHeight); - contextMenu.openAtItem(root, pos.x, pos.y); + contextMenu.openAtItem(root, screen); } } } diff --git a/Modules/Bar/Widgets/Taskbar.qml b/Modules/Bar/Widgets/Taskbar.qml index a9c388e49..4cb7ffbba 100644 --- a/Modules/Bar/Widgets/Taskbar.qml +++ b/Modules/Bar/Widgets/Taskbar.qml @@ -687,7 +687,7 @@ Rectangle { menuY = globalPos.y + (item.height / 2) - (contextMenu.implicitHeight / 2); } popupMenuWindow.showContextMenu(contextMenu); - contextMenu.openAtItem(root, menuX, menuY); + contextMenu.openAtItem(root, screen); } } } diff --git a/Modules/Bar/Widgets/VPN.qml b/Modules/Bar/Widgets/VPN.qml index 261846f25..9865131fa 100644 --- a/Modules/Bar/Widgets/VPN.qml +++ b/Modules/Bar/Widgets/VPN.qml @@ -125,8 +125,7 @@ Item { var popupMenuWindow = PanelService.getPopupMenuWindow(screen); if (popupMenuWindow) { popupMenuWindow.showContextMenu(contextMenu); - const pos = BarService.getContextMenuPosition(pill, contextMenu.implicitWidth, contextMenu.implicitHeight); - contextMenu.openAtItem(pill, pos.x, pos.y); + contextMenu.openAtItem(pill, screen); } } tooltipText: { diff --git a/Modules/Bar/Widgets/Volume.qml b/Modules/Bar/Widgets/Volume.qml index bddd2ef27..4ba671d5a 100644 --- a/Modules/Bar/Widgets/Volume.qml +++ b/Modules/Bar/Widgets/Volume.qml @@ -154,8 +154,7 @@ Item { var popupMenuWindow = PanelService.getPopupMenuWindow(screen); if (popupMenuWindow) { popupMenuWindow.showContextMenu(contextMenu); - const pos = BarService.getContextMenuPosition(pill, contextMenu.implicitWidth, contextMenu.implicitHeight); - contextMenu.openAtItem(pill, pos.x, pos.y); + contextMenu.openAtItem(pill, screen); } } onMiddleClicked: root.openExternalMixer() diff --git a/Modules/Bar/Widgets/WallpaperSelector.qml b/Modules/Bar/Widgets/WallpaperSelector.qml index 360514250..8a0156d67 100644 --- a/Modules/Bar/Widgets/WallpaperSelector.qml +++ b/Modules/Bar/Widgets/WallpaperSelector.qml @@ -61,8 +61,7 @@ NIconButton { var popupMenuWindow = PanelService.getPopupMenuWindow(screen); if (popupMenuWindow) { popupMenuWindow.showContextMenu(contextMenu); - const pos = BarService.getContextMenuPosition(root, contextMenu.implicitWidth, contextMenu.implicitHeight); - contextMenu.openAtItem(root, pos.x, pos.y); + contextMenu.openAtItem(root, screen); } } } diff --git a/Modules/Bar/Widgets/WiFi.qml b/Modules/Bar/Widgets/WiFi.qml index 60b70f25a..7cfd4317d 100644 --- a/Modules/Bar/Widgets/WiFi.qml +++ b/Modules/Bar/Widgets/WiFi.qml @@ -115,8 +115,7 @@ Item { var popupMenuWindow = PanelService.getPopupMenuWindow(screen); if (popupMenuWindow) { popupMenuWindow.showContextMenu(contextMenu); - const pos = BarService.getContextMenuPosition(pill, contextMenu.implicitWidth, contextMenu.implicitHeight); - contextMenu.openAtItem(pill, pos.x, pos.y); + contextMenu.openAtItem(pill, screen); } } tooltipText: { diff --git a/Modules/Bar/Widgets/Workspace.qml b/Modules/Bar/Widgets/Workspace.qml index cfce187a8..b2589fd16 100644 --- a/Modules/Bar/Widgets/Workspace.qml +++ b/Modules/Bar/Widgets/Workspace.qml @@ -333,8 +333,7 @@ Item { var popupMenuWindow = PanelService.getPopupMenuWindow(screen); if (popupMenuWindow) { popupMenuWindow.showContextMenu(contextMenu); - const pos = BarService.getContextMenuPosition(workspaceBackground, contextMenu.implicitWidth, contextMenu.implicitHeight); - contextMenu.openAtItem(workspaceBackground, pos.x, pos.y); + contextMenu.openAtItem(workspaceBackground, screen); } } } diff --git a/Services/UI/BarService.qml b/Services/UI/BarService.qml index a7128b12e..17ca52ca4 100644 --- a/Services/UI/BarService.qml +++ b/Services/UI/BarService.qml @@ -286,56 +286,6 @@ Singleton { } } - // Calculate context menu position based on bar position - // Parameters: - // anchorItem: The widget item to anchor the menu to - // menuWidth: Width of the context menu (optional, defaults to 180) - // menuHeight: Height of the context menu (optional, defaults to 100) - // Returns: { x: number, y: number } - // Note: Anchor position is top-left corner, so we calculate from center - function getContextMenuPosition(anchorItem, menuWidth, menuHeight) { - if (!anchorItem) { - Logger.w("BarService", "getContextMenuPosition: anchorItem is null"); - return { - "x": 0, - "y": 0 - }; - } - - const mWidth = menuWidth || 180; - const mHeight = menuHeight || 100; - const barPosition = Settings.data.bar.position; - let menuX = 0; - let menuY = 0; - - // Calculate center-based positioning for consistent spacing - const anchorCenterX = anchorItem.width / 2; - const anchorCenterY = anchorItem.height / 2; - - if (barPosition === "left") { - // For left bar: position menu to the right of anchor, vertically centered - menuX = anchorItem.width + Style.marginM; - menuY = anchorCenterY - (mHeight / 2); - } else if (barPosition === "right") { - // For right bar: position menu to the left of anchor, vertically centered - menuX = -mWidth - Style.marginM; - menuY = anchorCenterY - (mHeight / 2); - } else if (barPosition === "top") { - // For top bar: position menu below bar, horizontally centered - menuX = anchorCenterX - (mWidth / 2); - menuY = Style.barHeight; - } else { - // For bottom bar: position menu above, horizontally centered - menuX = anchorCenterX - (mWidth / 2); - menuY = -mHeight - Style.marginM; - } - - return { - "x": menuX, - "y": menuY - }; - } - // Open widget settings dialog for a bar widget // Parameters: // screen: The screen to show the dialog on diff --git a/Widgets/NContextMenu.qml b/Widgets/NContextMenu.qml index 7e85423b9..ecc258e3e 100644 --- a/Widgets/NContextMenu.qml +++ b/Widgets/NContextMenu.qml @@ -3,6 +3,9 @@ import QtQuick.Controls import QtQuick.Layouts import qs.Commons +/* +This component could probably be deleted and replaced by NPopupContextMenu +*/ Popup { id: root diff --git a/Widgets/NPopupContextMenu.qml b/Widgets/NPopupContextMenu.qml index 61c031823..5592944da 100644 --- a/Widgets/NPopupContextMenu.qml +++ b/Widgets/NPopupContextMenu.qml @@ -6,20 +6,22 @@ import qs.Commons // Simple context menu PopupWindow (similar to TrayMenu) // Designed to be rendered inside a PopupMenuWindow for click-outside-to-close +// Automatically positions itself to respect screen boundaries PopupWindow { id: root property alias model: repeater.model - property real itemHeight: 28 // Match TrayMenu + 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 + property ShellScreen screen: null property real calculatedWidth: 180 + readonly property string barPosition: Settings.data.bar.position + signal triggered(string action, var item) implicitWidth: calculatedWidth @@ -76,8 +78,82 @@ PopupWindow { } anchor.item: anchorItem - anchor.rect.x: anchorX - anchor.rect.y: anchorY + + anchor.rect.x: { + if (anchorItem && screen) { + const anchorGlobalPos = anchorItem.mapToItem(null, 0, 0); + + // For right bar: position menu to the left of anchor + if (root.barPosition === "right") { + let baseX = -implicitWidth - Style.marginM; + return baseX; + } + + // For left bar: position menu to the right of anchor + if (root.barPosition === "left") { + let baseX = anchorItem.width + Style.marginM; + return baseX; + } + + // For top/bottom bar: center horizontally on anchor + const anchorCenterX = anchorItem.width / 2; + let baseX = anchorCenterX - (implicitWidth / 2); + + // Calculate menu position on screen + const menuScreenX = anchorGlobalPos.x + baseX; + const menuRight = menuScreenX + implicitWidth; + + // Adjust if menu would clip on the right + if (menuRight > screen.width - Style.marginM) { + const overflow = menuRight - (screen.width - Style.marginM); + return baseX - overflow; + } + // Adjust if menu would clip on the left + if (menuScreenX < Style.marginM) { + return baseX + (Style.marginM - menuScreenX); + } + return baseX; + } + return 0; + } + anchor.rect.y: { + if (anchorItem && screen) { + const anchorCenterY = anchorItem.height / 2; + + // Calculate base Y position based on bar orientation + let baseY; + if (root.barPosition === "bottom") { + // For bottom bar: position menu above the bar + baseY = -(implicitHeight + Style.marginM); + } else if (root.barPosition === "top") { + // For top bar: position menu below bar + baseY = Style.barHeight + Style.marginM; + } else { + // For left/right bar: vertically center on anchor + baseY = anchorCenterY - (implicitHeight / 2); + } + + // Calculate menu position on screen + const anchorGlobalPos = anchorItem.mapToItem(null, 0, 0); + const menuScreenY = anchorGlobalPos.y + baseY; + + const menuBottom = menuScreenY + implicitHeight; + + // Adjust if menu would clip at bottom + if (menuBottom > screen.height - Style.marginM) { + const overflow = menuBottom - (screen.height - Style.marginM); + return baseY - overflow; + } + + return baseY; + } + + // Fallback if no screen + if (root.barPosition === "bottom") { + return -implicitHeight - Style.marginM; + } + return Style.barHeight; + } Component.onCompleted: { Qt.callLater(calculateWidth); @@ -208,8 +284,9 @@ PopupWindow { } } - // Helper function to open at specific position relative to anchor item - function openAt(x, y, item) { + // Helper function to open context menu anchored to an item + // Position is calculated automatically based on bar position and screen boundaries + function openAtItem(item, itemScreen) { if (!item) { Logger.w("NPopupContextMenu", "anchorItem is undefined, won't show menu."); return; @@ -218,19 +295,8 @@ PopupWindow { calculateWidth(); anchorItem = item; - anchorX = x; - anchorY = y; + screen = itemScreen || null; visible = true; - - Qt.callLater(() => { - if (root.anchor) { - root.anchor.updateAnchor(); - } - }); - } - - function openAtItem(item, mouseX, mouseY) { - openAt(mouseX || 0, mouseY || 0, item); } function close() {