diff --git a/settings/quickshell.nix b/settings/quickshell.nix index d4c1a9a..1bf6562 100644 --- a/settings/quickshell.nix +++ b/settings/quickshell.nix @@ -1849,23 +1849,24 @@ in hoverEnabled: true acceptedButtons: Qt.NoButton onEntered: { - if (bar.activeDropdown) { - if (modelData.hasMenu && !(bar.activeDropdown === contextMenu && contextMenu.trayItem === modelData)) { - if (bar.activeDropdown === contextMenu) { - // Same dropdown, just switch content - let pos = parent.mapToItem(bar.contentItem, 0, parent.height / 2); - contextMenu.dropdownY = pos.y; - contextMenu.trayItem = modelData; - menuOpener.menu = modelData.menu; - } else { - bar.toggleDropdown(contextMenu, function() { - let pos = parent.mapToItem(bar.contentItem, 0, parent.height / 2); - contextMenu.dropdownY = pos.y; - contextMenu.trayItem = modelData; - menuOpener.menu = modelData.menu; - }); - } - } + if (!modelData.hasMenu) return; + // Prefetch: you cannot click without + // hovering first, so start the DBus + // round-trip now and the menu is + // usually loaded before the click. + if (!bar.activeDropdown && menuOpener.menu !== modelData.menu) { + menuOpener.menu = modelData.menu; + } + if (!bar.activeDropdown) return; + if (bar.activeDropdown === contextMenu && contextMenu.trayItem === modelData) return; + let pos = parent.mapToItem(bar.contentItem, 0, parent.height / 2); + if (bar.activeDropdown === contextMenu) { + // Same dropdown, just switch content + contextMenu.dropdownY = pos.y; + contextMenu.trayItem = modelData; + menuOpener.menu = modelData.menu; + } else { + contextMenu.openFor(modelData, pos.y); } } } @@ -1873,15 +1874,14 @@ in anchors.fill: parent acceptedButtons: Qt.LeftButton | Qt.RightButton onClicked: (event) => { - if (modelData.hasMenu) { - bar.toggleDropdown(contextMenu, function() { - let pos = parent.mapToItem(bar.contentItem, 0, parent.height / 2); - contextMenu.dropdownY = pos.y; - contextMenu.trayItem = modelData; - menuOpener.menu = modelData.menu; - }); - } else { + if (!modelData.hasMenu) { modelData.activate(); + } else if (bar.activeDropdown === contextMenu && contextMenu.trayItem === modelData) { + // Clicking the open menu's own icon still closes it + contextMenu.animateClose(); + } else { + let pos = parent.mapToItem(bar.contentItem, 0, parent.height / 2); + contextMenu.openFor(modelData, pos.y); } } } @@ -2103,13 +2103,64 @@ in fullHeight: menuItems.height + 2 * Theme.panelGap onVisibleChanged: { - if (!visible) menuOpener.menu = null; + if (!visible) { + menuOpener.menu = null; + pendingItem = null; + _menuWait.stop(); + } } QsMenuOpener { id: menuOpener } + // The DBus menu populates asynchronously and Steam's is + // slow. Opening on click sized the panel from an EMPTY + // body: menuItems.width is fixed, so the width was already + // final while fullHeight was just padding — the panel shot + // out sideways, then lurched taller when the items landed. + // Two motions instead of one. Load first, open once there + // is something to show. + property var pendingItem: null + readonly property bool menuReady: + menuOpener.children && menuOpener.children.values.length > 0 + + function openFor(item, y) { + dropdownY = y; + trayItem = item; + menuOpener.menu = item.menu; + if (menuReady) { + pendingItem = null; + bar.toggleDropdown(contextMenu, null); + } else { + pendingItem = item; + _menuWait.restart(); + } + } + + function openPending() { + if (!pendingItem) return; + pendingItem = null; + _menuWait.stop(); + if (bar.activeDropdown !== contextMenu) + bar.toggleDropdown(contextMenu, null); + } + + // Never strand a click if the menu never populates. + Timer { + id: _menuWait + interval: 500 + onTriggered: contextMenu.openPending() + } + + Connections { + target: menuOpener.children + function onValuesChanged() { + if (contextMenu.pendingItem && contextMenu.menuReady) + contextMenu.openPending(); + } + } + Card { id: menuItems anchors.centerIn: parent