quickshell: per-icon tray menu cache, kill the switch flash
One shared QsMenuOpener meant tray-to-tray switching reassigned the handle and reloaded over DBus — the model went briefly empty, and no sizing gate downstream could hide that. Each tray icon now owns its opener, assigned when the icon appears, so every menu (Steam's slow one included) is fetched once at startup and held. Switching swaps one populated model for another in a single binding pass; contextMenu just repoints activeOpener. Hover prefetch deleted — nothing left to prefetch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
d866cae4f7
commit
5c71cc3a1f
1 changed files with 30 additions and 30 deletions
|
|
@ -1847,19 +1847,22 @@ in
|
|||
color: Theme.base05
|
||||
}
|
||||
|
||||
// The icon's own menu cache: assigned from the
|
||||
// moment the icon exists, so the DBus fetch
|
||||
// (slow for Steam) happens at startup, not
|
||||
// mid-animation. contextMenu just points at
|
||||
// this — switching tray icons never reloads.
|
||||
QsMenuOpener {
|
||||
id: itemOpener
|
||||
menu: modelData.menu
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.NoButton
|
||||
onEntered: {
|
||||
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);
|
||||
|
|
@ -1867,9 +1870,9 @@ in
|
|||
// Same dropdown, just switch content
|
||||
contextMenu.dropdownY = pos.y;
|
||||
contextMenu.trayItem = modelData;
|
||||
menuOpener.menu = modelData.menu;
|
||||
contextMenu.activeOpener = itemOpener;
|
||||
} else {
|
||||
contextMenu.openFor(modelData, pos.y);
|
||||
contextMenu.openFor(modelData, pos.y, itemOpener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1884,7 +1887,7 @@ in
|
|||
contextMenu.animateClose();
|
||||
} else {
|
||||
let pos = parent.mapToItem(bar.contentItem, 0, parent.height / 2);
|
||||
contextMenu.openFor(modelData, pos.y);
|
||||
contextMenu.openFor(modelData, pos.y, itemOpener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2130,8 +2133,8 @@ in
|
|||
// available the instant the data lands, so the height and
|
||||
// its content update in the same binding pass.
|
||||
readonly property real menuContentHeight: {
|
||||
if (!menuOpener.children) return 0;
|
||||
const items = menuOpener.children.values;
|
||||
if (!menuModel) return 0;
|
||||
const items = menuModel.values;
|
||||
let h = 0;
|
||||
for (let i = 0; i < items.length; i++)
|
||||
h += items[i].isSeparator ? menuSepHeight : menuRowHeight;
|
||||
|
|
@ -2148,26 +2151,23 @@ in
|
|||
fullHeight: (menuContentHeight > 0 ? menuContentHeight : lastMenuHeight)
|
||||
+ 2 * Theme.cardPad + 2 * Theme.panelGap
|
||||
|
||||
// Deliberately NOT cleared on close. Nulling the menu threw
|
||||
// away the loaded items, and the only thing that refills
|
||||
// them is a hover-enter — which never fires if the cursor
|
||||
// is still sitting on the icon you just clicked. Reopening
|
||||
// then re-fetched over DBus and the panel grew to its empty
|
||||
// height first, then jumped once the items landed. Keeping
|
||||
// the handle assigned makes reopening the same item free;
|
||||
// openFor() reassigns it when you pick a different one.
|
||||
// Which tray item's opener feeds the menu. Every tray icon
|
||||
// owns an always-assigned QsMenuOpener (see the tray
|
||||
// Repeater), fetched when the icon appears — so switching
|
||||
// tray→tray swaps one POPULATED model for another in a
|
||||
// single binding pass. The shared opener this replaces
|
||||
// reloaded over DBus on every reassignment, and no sizing
|
||||
// gate downstream could hide that the model itself went
|
||||
// briefly empty (that gap was the flash).
|
||||
property var activeOpener: null
|
||||
readonly property var menuModel: activeOpener ? activeOpener.children : null
|
||||
|
||||
QsMenuOpener {
|
||||
id: menuOpener
|
||||
}
|
||||
|
||||
// Opens immediately — never wait on the menu. Hovering the
|
||||
// icon has already started the DBus fetch (see the tray
|
||||
// MouseArea), so the size is normally settled by click.
|
||||
function openFor(item, y) {
|
||||
// Opens immediately — the item's opener has been loaded
|
||||
// since its icon appeared, so there is nothing to wait on.
|
||||
function openFor(item, y, opener) {
|
||||
dropdownY = y;
|
||||
trayItem = item;
|
||||
menuOpener.menu = item.menu;
|
||||
activeOpener = opener;
|
||||
bar.toggleDropdown(contextMenu, null);
|
||||
}
|
||||
|
||||
|
|
@ -2178,7 +2178,7 @@ in
|
|||
cardSpacing: 0
|
||||
|
||||
Repeater {
|
||||
model: menuOpener.children
|
||||
model: contextMenu.menuModel
|
||||
|
||||
Rectangle {
|
||||
required property var modelData
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue