quickshell: open tray menus at full size, not empty-then-grown
The DBus menu populates asynchronously and Steam's is slow, so the panel was sized from an empty body: menuItems.width is fixed, making the width final immediately while fullHeight was only padding. The panel shot out sideways, then lurched taller when the items arrived — two motions instead of one. contextMenu now loads the menu first and opens once it has items, falling back to a 500ms timeout so a menu that never populates cannot strand the click. Hovering a tray icon also prefetches its menu, which covers the wait in practice since you cannot click without hovering. Clicking the open menu's own icon still closes it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
0a6a23aa8a
commit
60e39613d6
1 changed files with 77 additions and 26 deletions
|
|
@ -1849,23 +1849,24 @@ in
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
acceptedButtons: Qt.NoButton
|
acceptedButtons: Qt.NoButton
|
||||||
onEntered: {
|
onEntered: {
|
||||||
if (bar.activeDropdown) {
|
if (!modelData.hasMenu) return;
|
||||||
if (modelData.hasMenu && !(bar.activeDropdown === contextMenu && contextMenu.trayItem === modelData)) {
|
// 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) {
|
if (bar.activeDropdown === contextMenu) {
|
||||||
// Same dropdown, just switch content
|
// Same dropdown, just switch content
|
||||||
let pos = parent.mapToItem(bar.contentItem, 0, parent.height / 2);
|
|
||||||
contextMenu.dropdownY = pos.y;
|
contextMenu.dropdownY = pos.y;
|
||||||
contextMenu.trayItem = modelData;
|
contextMenu.trayItem = modelData;
|
||||||
menuOpener.menu = modelData.menu;
|
menuOpener.menu = modelData.menu;
|
||||||
} else {
|
} else {
|
||||||
bar.toggleDropdown(contextMenu, function() {
|
contextMenu.openFor(modelData, pos.y);
|
||||||
let pos = parent.mapToItem(bar.contentItem, 0, parent.height / 2);
|
|
||||||
contextMenu.dropdownY = pos.y;
|
|
||||||
contextMenu.trayItem = modelData;
|
|
||||||
menuOpener.menu = modelData.menu;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1873,15 +1874,14 @@ in
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||||
onClicked: (event) => {
|
onClicked: (event) => {
|
||||||
if (modelData.hasMenu) {
|
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 {
|
|
||||||
modelData.activate();
|
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
|
fullHeight: menuItems.height + 2 * Theme.panelGap
|
||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (!visible) menuOpener.menu = null;
|
if (!visible) {
|
||||||
|
menuOpener.menu = null;
|
||||||
|
pendingItem = null;
|
||||||
|
_menuWait.stop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QsMenuOpener {
|
QsMenuOpener {
|
||||||
id: menuOpener
|
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 {
|
Card {
|
||||||
id: menuItems
|
id: menuItems
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue