// A tray item's DBus menu, drawn natively so it inherits the shell's theme. // // Submenus drill down in place rather than flying out sideways — a popover with // a back row, the way GNOME's menus behave, and far less fiddly than chasing a // cascade of popup windows with the mouse. import Quickshell import Quickshell.Services.SystemTray import QtQuick import qs.config import qs.widgets Popover { id: root required property SystemTrayItem trayItem // Popover's container is a plain Item and does not size itself from its // children, so the window dimensions come from the column's implicit size. // Rows take their *actual* width from the window in the other direction — // the two chains are independent, so there is no binding loop. implicitWidth: Math.max(body.implicitWidth + contentPadding * 2, 200) implicitHeight: body.implicitHeight + contentPadding * 2 // Stack of submenu handles; empty means we are at the top level. property var menuStack: [] onVisibleChanged: if (!visible) root.menuStack = [] QsMenuOpener { id: opener menu: root.menuStack.length > 0 ? root.menuStack[root.menuStack.length - 1] : root.trayItem.menu } Column { id: body width: parent.width spacing: 2 // Back row, only while inside a submenu. TrayMenuRow { width: parent.width visible: root.menuStack.length > 0 label: "\u{F0141} Back" // md-chevron_left onActivated: root.menuStack = root.menuStack.slice(0, -1) } Repeater { model: opener.children delegate: Loader { id: entryLoader required property var modelData width: body.width sourceComponent: entryLoader.modelData.isSeparator ? separator : row Component { id: separator Item { implicitHeight: 7 Rectangle { anchors.verticalCenter: parent.verticalCenter width: parent.width height: 1 color: Theme.alpha(Theme.fg, 0.1) } } } Component { id: row TrayMenuRow { // QsMenuEntry calls it `text`, not `label`. label: entryLoader.modelData.text icon: entryLoader.modelData.icon rowEnabled: entryLoader.modelData.enabled buttonType: entryLoader.modelData.buttonType checkState: entryLoader.modelData.checkState submenu: entryLoader.modelData.hasChildren onActivated: { if (entryLoader.modelData.hasChildren) { root.menuStack = root.menuStack.concat([entryLoader.modelData]); return; } entryLoader.modelData.triggered(); root.visible = false; } } } } } } }