From 6ce7c7d55dfac0ee3b268cb6e33c6679caae2a02 Mon Sep 17 00:00:00 2001 From: quadbyte Date: Sun, 10 Aug 2025 21:59:33 -0400 Subject: [PATCH] Tray: basic tray, no dropdown menu yet --- Modules/Bar/Bar.qml | 4 ++ Modules/Bar/Tray.qml | 122 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 Modules/Bar/Tray.qml diff --git a/Modules/Bar/Bar.qml b/Modules/Bar/Bar.qml index f0f1b89..ba608fc 100644 --- a/Modules/Bar/Bar.qml +++ b/Modules/Bar/Bar.qml @@ -76,6 +76,10 @@ Variants { anchors.verticalCenter: bar.verticalCenter spacing: Style.marginSmall * scaling + Tray { + anchors.verticalCenter: parent.verticalCenter + } + Battery { anchors.verticalCenter: parent.verticalCenter } diff --git a/Modules/Bar/Tray.qml b/Modules/Bar/Tray.qml new file mode 100644 index 0000000..74dff62 --- /dev/null +++ b/Modules/Bar/Tray.qml @@ -0,0 +1,122 @@ +import QtQuick +import QtQuick.Effects +import QtQuick.Layouts +import QtQuick.Controls +import Quickshell +import Quickshell.Services.SystemTray +import Quickshell.Widgets +import qs.Services +import qs.Widgets + +Row { + readonly property real scaling: Scaling.scale(screen) + property bool containsMouse: false + property var systemTray: SystemTray + + spacing: 8 + Layout.alignment: Qt.AlignVCenter + + Repeater { + model: systemTray.items + delegate: Item { + width: 24 * scaling + height: 24 * scaling + + visible: modelData + property bool isHovered: trayMouseArea.containsMouse + + // No animations - static display + Rectangle { + anchors.centerIn: parent + width: 16 * scaling + height: 16 * scaling + radius: 6 + color: "transparent" + clip: true + + IconImage { + id: trayIcon + anchors.centerIn: parent + width: 16 * scaling + height: 16 * scaling + smooth: false + asynchronous: true + backer.fillMode: Image.PreserveAspectFit + source: { + let icon = modelData?.icon || "" + if (!icon) { + return "" + } + + // Process icon path + if (icon.includes("?path=")) { + const chunks = icon.split("?path=") + const name = chunks[0] + const path = chunks[1] + const fileName = name.substring(name.lastIndexOf("/") + 1) + return `file://${path}/${fileName}` + } + return icon + } + opacity: status === Image.Ready ? 1 : 0 + } + } + + MouseArea { + id: trayMouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton + onClicked: mouse => { + if (!modelData) + return + + if (mouse.button === Qt.LeftButton) { + // Close any open menu first + if (trayMenu && trayMenu.visible) { + trayMenu.hideMenu() + } + + if (!modelData.onlyMenu) { + modelData.activate() + } + } else if (mouse.button === Qt.MiddleButton) { + // Close any open menu first + if (trayMenu && trayMenu.visible) { + trayMenu.hideMenu() + } + + modelData.secondaryActivate && modelData.secondaryActivate() + } else if (mouse.button === Qt.RightButton) { + trayTooltip.tooltipVisible = false + // If menu is already visible, close it + if (trayMenu && trayMenu.visible) { + trayMenu.hideMenu() + return + } + + if (modelData.hasMenu && modelData.menu && trayMenu) { + // Anchor the menu to the tray icon item (parent) and position it below the icon + const menuX = (width / 2) - (trayMenu.width / 2) + const menuY = height + 20 * scaling + trayMenu.menu = modelData.menu + trayMenu.showAt(parent, menuX, menuY) + } else { + + console.log("Tray: no menu available for", modelData.id, "or trayMenu not set") + } + } + } + onEntered: trayTooltip.show() + onExited: trayTooltip.hide() + } + + NTooltip { + id: trayTooltip + target: trayIcon + text: modelData.tooltipTitle || modelData.name || modelData.id || "Tray Item" + } + } + } +}