From 762bae37cb008ac2aa536dac01dc52538e2e4601 Mon Sep 17 00:00:00 2001 From: Zacharias-Brohn Date: Tue, 28 Oct 2025 00:46:21 +0100 Subject: [PATCH 1/6] I guess it works lmao --- Bar.qml | 1 + Modules/TrayItem.qml | 37 +++++++++--------- Modules/TrayMenu.qml | 87 ++++++++++++++++++++++++++++++++++++++++++ Modules/TrayWidget.qml | 4 ++ 4 files changed, 112 insertions(+), 17 deletions(-) create mode 100644 Modules/TrayMenu.qml diff --git a/Bar.qml b/Bar.qml index ce0cd79..bbdb1ae 100644 --- a/Bar.qml +++ b/Bar.qml @@ -16,6 +16,7 @@ Scope { PanelWindow { id: bar required property var modelData + property bool trayMenuVisible: false screen: modelData anchors { diff --git a/Modules/TrayItem.qml b/Modules/TrayItem.qml index 406bd06..35f5825 100644 --- a/Modules/TrayItem.qml +++ b/Modules/TrayItem.qml @@ -7,6 +7,7 @@ import Caelestia import Quickshell import Quickshell.Widgets import Quickshell.Services.SystemTray +import qs.Modules MouseArea { id: root @@ -16,17 +17,9 @@ MouseArea { implicitWidth: 22 implicitHeight: 22 + hoverEnabled: true acceptedButtons: Qt.LeftButton | Qt.RightButton - onClicked: event => { - switch (event.button) { - case Qt.LeftButton: root.item.activate(); break; - case Qt.RightButton: - if (root.item.hasMenu) { - menuAnchor.open(); - } - break; - } - } + IconImage { id: icon @@ -51,13 +44,23 @@ MouseArea { id: analyser sourceItem: icon } + + TrayMenu { + id: trayMenu + menu: root.item.menu + anchor.item: root + anchor.edges: Edges.Bottom + anchor.margins { + left: -270 + } + } + } - QsMenuAnchor { - id: menuAnchor - menu: root.item.menu - anchor.item: root - anchor.edges: Edges.Bottom | Edges.Left - anchor.margins.top: 23 - anchor.adjustment: PopupAdjustment.SlideX + onClicked: { + if ( mouse.button === Qt.LeftButton ) { + root.item.activate(); + } else if ( mouse.button === Qt.RightButton ) { + trayMenu.visible = !trayMenu.visible; + } } } diff --git a/Modules/TrayMenu.qml b/Modules/TrayMenu.qml new file mode 100644 index 0000000..f7ff246 --- /dev/null +++ b/Modules/TrayMenu.qml @@ -0,0 +1,87 @@ +pragma ComponentBehavior: Bound + +import Quickshell +import QtQuick +import QtQuick.Layouts +import Quickshell.Hyprland + +PopupWindow { + id: root + required property QsMenuHandle menu + property int height: entryCount * ( entryHeight + 3 ) + property int entryHeight: 30 + property int entryCount: 0 + implicitWidth: 300 + implicitHeight: height + color: "transparent" + + QsMenuOpener { + id: menuOpener + menu: root.menu + } + + HyprlandFocusGrab { + id: grab + windows: [ root ] + onCleared: { + root.visible = false; + } + } + + onVisibleChanged: { + grab.active = root.visible; + } + Rectangle { + anchors.fill: parent + color: "#90000000" + radius: 8 + border.color: "#10FFFFFF" + ColumnLayout { + id: columnLayout + anchors.fill: parent + anchors.margins: 5 + spacing: 2 + Repeater { + id: repeater + model: menuOpener.children + Rectangle { + id: menuItem + width: root.implicitWidth + Layout.maximumWidth: parent.width + Layout.fillHeight: true + height: root.entryHeight + color: mouseArea.containsMouse && !modelData.isSeparator ? "#15FFFFFF" : "transparent" + radius: 4 + visible: modelData.isSeparator ? false : true + required property QsMenuEntry modelData + + Component.onCompleted: { + if ( !modelData.isSeparator ) { + root.entryCount += 1; + } + } + + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + preventStealing: true + propagateComposedEvents: true + acceptedButtons: Qt.LeftButton + onClicked: { + menuItem.modelData.triggered(); + root.visible = false; + } + } + Text { + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: 10 + text: menuItem.modelData.text + color: "white" + } + } + } + } + } +} diff --git a/Modules/TrayWidget.qml b/Modules/TrayWidget.qml index 23122aa..13f4a52 100644 --- a/Modules/TrayWidget.qml +++ b/Modules/TrayWidget.qml @@ -1,9 +1,12 @@ +pragma ComponentBehavior: Bound + import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Services.SystemTray Rectangle { + id: root required property PanelWindow bar implicitHeight: parent.height implicitWidth: rowL.implicitWidth + 20 @@ -14,6 +17,7 @@ Rectangle { id: rowL anchors.centerIn: parent Repeater { + id: repeater model: SystemTray.items TrayItem { required property SystemTrayItem modelData From 1b12ff6e7f5da2b31b70f4771f345ede1b8ac568 Mon Sep 17 00:00:00 2001 From: Zacharias-Brohn Date: Tue, 28 Oct 2025 16:40:28 +0100 Subject: [PATCH 2/6] tray menu --- Modules/SubMenu.qml | 91 ++++++++++++++++++++++++++++++++++++++++++++ Modules/TrayMenu.qml | 21 +++++++++- Wallpaper.qml | 29 ++++++++++++++ shell.qml | 1 + 4 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 Modules/SubMenu.qml create mode 100644 Wallpaper.qml diff --git a/Modules/SubMenu.qml b/Modules/SubMenu.qml new file mode 100644 index 0000000..3f3d014 --- /dev/null +++ b/Modules/SubMenu.qml @@ -0,0 +1,91 @@ +pragma ComponentBehavior: Bound + +import Quickshell +import QtQuick +import QtQuick.Layouts +import Quickshell.Hyprland + +PopupWindow { + id: root + required property QsMenuHandle menu + property int height: entryCount * ( entryHeight + 3 ) + property int entryHeight: 30 + property int entryCount: 0 + implicitWidth: 300 + implicitHeight: height + color: "transparent" + + QsMenuOpener { + id: menuOpener + menu: root.menu + } + + HyprlandFocusGrab { + id: grab + windows: [ root ] + onCleared: { + root.visible = false; + } + } + + onVisibleChanged: { + grab.active = root.visible; + } + Rectangle { + id: menuRect + anchors.fill: parent + color: "#90000000" + radius: 8 + border.color: "#10FFFFFF" + ColumnLayout { + id: columnLayout + anchors.fill: parent + anchors.margins: 5 + spacing: 2 + Repeater { + id: repeater + model: menuOpener.children + Rectangle { + id: menuItem + width: root.implicitWidth + Layout.maximumWidth: parent.width + Layout.fillHeight: true + height: root.entryHeight + color: mouseArea.containsMouse && !modelData.isSeparator ? "#15FFFFFF" : "transparent" + radius: 4 + visible: modelData.isSeparator ? false : true + required property QsMenuEntry modelData + + Component.onCompleted: { + if ( !modelData.isSeparator ) { + root.entryCount += 1; + } + } + + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + preventStealing: true + propagateComposedEvents: true + acceptedButtons: Qt.LeftButton + onClicked: { + if ( !menuItem.modelData.hasChildren ) { + menuItem.modelData.triggered(); + root.visible = false; + } + } + } + + Text { + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: 10 + text: menuItem.modelData.text + color: "white" + } + } + } + } + } +} diff --git a/Modules/TrayMenu.qml b/Modules/TrayMenu.qml index f7ff246..4b7feef 100644 --- a/Modules/TrayMenu.qml +++ b/Modules/TrayMenu.qml @@ -32,6 +32,7 @@ PopupWindow { grab.active = root.visible; } Rectangle { + id: menuRect anchors.fill: parent color: "#90000000" radius: 8 @@ -69,10 +70,22 @@ PopupWindow { propagateComposedEvents: true acceptedButtons: Qt.LeftButton onClicked: { - menuItem.modelData.triggered(); - root.visible = false; + if ( !menuItem.modelData.hasChildren ) { + menuItem.modelData.triggered(); + root.visible = false; + } else { + subMenuComponent.createObject( null, { + menu: menuItem.modelData, + anchor: { + item: menuItem, + edges: Edges.Right + }, + visible: true + }) + } } } + Text { anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left @@ -84,4 +97,8 @@ PopupWindow { } } } + Component { + id: subMenuComponent + SubMenu {} + } } diff --git a/Wallpaper.qml b/Wallpaper.qml new file mode 100644 index 0000000..7968e41 --- /dev/null +++ b/Wallpaper.qml @@ -0,0 +1,29 @@ +import Quickshell +import QtQuick +import Quickshell.Wayland + +Scope { + Variants { + model: Quickshell.screens + PanelWindow { + id: root + required property var modelData + screen: modelData + WlrLayershell.exclusionMode: ExclusionMode.Ignore + WlrLayershell.layer: WlrLayer.Bottom + + anchors { + top: true + left: true + right: true + bottom: true + } + Image { + id: wallpaperImage + anchors.fill: parent + source: "/mnt/IronWolf/SDImages/SWWW_Wals/ComfyUI_00037_.png" + fillMode: Image.PreserveAspectFit + } + } + } +} diff --git a/shell.qml b/shell.qml index 9c9d2f2..bece751 100644 --- a/shell.qml +++ b/shell.qml @@ -3,4 +3,5 @@ import Quickshell Scope { Bar {} + Wallpaper {} } From 7b5ee719c47b29277bb0fc5e3bc9e9df5e7240dd Mon Sep 17 00:00:00 2001 From: Zacharias-Brohn Date: Tue, 28 Oct 2025 19:20:27 +0100 Subject: [PATCH 3/6] fixed destroying menu on focus loss --- Modules/TrayItem.qml | 9 +++++++++ Modules/TrayMenu.qml | 20 ++++++++++---------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/Modules/TrayItem.qml b/Modules/TrayItem.qml index 35f5825..c1d3e8e 100644 --- a/Modules/TrayItem.qml +++ b/Modules/TrayItem.qml @@ -6,6 +6,7 @@ import QtQuick.Effects import Caelestia import Quickshell import Quickshell.Widgets +import Quickshell.Hyprland import Quickshell.Services.SystemTray import qs.Modules @@ -53,6 +54,13 @@ MouseArea { anchor.margins { left: -270 } + HyprlandFocusGrab { + id: grab + windows: [ trayMenu ] + onCleared: { + trayMenu.visible = false; + } + } } } @@ -61,6 +69,7 @@ MouseArea { root.item.activate(); } else if ( mouse.button === Qt.RightButton ) { trayMenu.visible = !trayMenu.visible; + grab.active = true; } } } diff --git a/Modules/TrayMenu.qml b/Modules/TrayMenu.qml index 4b7feef..1576915 100644 --- a/Modules/TrayMenu.qml +++ b/Modules/TrayMenu.qml @@ -20,17 +20,17 @@ PopupWindow { menu: root.menu } - HyprlandFocusGrab { - id: grab - windows: [ root ] - onCleared: { - root.visible = false; - } - } + // HyprlandFocusGrab { + // id: grab + // windows: [ root ] + // onCleared: { + // root.visible = false; + // } + // } - onVisibleChanged: { - grab.active = root.visible; - } + // onVisibleChanged: { + // grab.active = root.visible; + // } Rectangle { id: menuRect anchors.fill: parent From f2736ad9d3fcd30b0937b46a1f2991e4b6f8c6d9 Mon Sep 17 00:00:00 2001 From: Zacharias-Brohn Date: Tue, 28 Oct 2025 19:21:10 +0100 Subject: [PATCH 4/6] fixed destroying menu on focus loss --- Modules/TrayMenu.qml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/Modules/TrayMenu.qml b/Modules/TrayMenu.qml index 1576915..1cafa47 100644 --- a/Modules/TrayMenu.qml +++ b/Modules/TrayMenu.qml @@ -20,17 +20,6 @@ PopupWindow { menu: root.menu } - // HyprlandFocusGrab { - // id: grab - // windows: [ root ] - // onCleared: { - // root.visible = false; - // } - // } - - // onVisibleChanged: { - // grab.active = root.visible; - // } Rectangle { id: menuRect anchors.fill: parent From 7fd2e8d1df1b4daa78cb5ac67b9a7383840f762d Mon Sep 17 00:00:00 2001 From: Zacharias-Brohn Date: Thu, 30 Oct 2025 00:42:59 +0100 Subject: [PATCH 5/6] icons, width and centering --- Modules/TrayItem.qml | 7 +++-- Modules/TrayMenu.qml | 68 +++++++++++++++++++++++++++++++++++--------- shell.qml | 2 +- 3 files changed, 60 insertions(+), 17 deletions(-) diff --git a/Modules/TrayItem.qml b/Modules/TrayItem.qml index c1d3e8e..73c884b 100644 --- a/Modules/TrayItem.qml +++ b/Modules/TrayItem.qml @@ -44,6 +44,7 @@ MouseArea { ImageAnalyser { id: analyser sourceItem: icon + rescaleSize: 20 } TrayMenu { @@ -51,8 +52,10 @@ MouseArea { menu: root.item.menu anchor.item: root anchor.edges: Edges.Bottom - anchor.margins { - left: -270 + onVisibleChanged: { + if ( grab.active && !visible ) { + grab.active = false; + } } HyprlandFocusGrab { id: grab diff --git a/Modules/TrayMenu.qml b/Modules/TrayMenu.qml index 1cafa47..a8f90f9 100644 --- a/Modules/TrayMenu.qml +++ b/Modules/TrayMenu.qml @@ -3,17 +3,27 @@ pragma ComponentBehavior: Bound import Quickshell import QtQuick import QtQuick.Layouts +import Qt5Compat.GraphicalEffects import Quickshell.Hyprland PopupWindow { id: root required property QsMenuHandle menu - property int height: entryCount * ( entryHeight + 3 ) + property int height: { + let count = 0; + for (let i = 0; i < repeater.count; i++) { + if (!repeater.itemAt(i).modelData.isSeparator) count++; + } + return count * (entryHeight + 3); + } property int entryHeight: 30 - property int entryCount: 0 - implicitWidth: 300 + property int maxWidth + implicitWidth: maxWidth + 20 implicitHeight: height color: "transparent" + anchor.margins { + left: -implicitWidth + } QsMenuOpener { id: menuOpener @@ -36,18 +46,28 @@ PopupWindow { model: menuOpener.children Rectangle { id: menuItem + property bool containsMouseAndEnabled: mouseArea.containsMouse && menuItem.modelData.enabled + property bool containsMouseAndNotEnabled: mouseArea.containsMouse && !menuItem.modelData.enabled width: root.implicitWidth Layout.maximumWidth: parent.width Layout.fillHeight: true height: root.entryHeight - color: mouseArea.containsMouse && !modelData.isSeparator ? "#15FFFFFF" : "transparent" + color: modelData.isSeparator ? "transparent" : containsMouseAndEnabled ? "#15FFFFFF" : containsMouseAndNotEnabled ? "#08FFFFFF" : "transparent" radius: 4 visible: modelData.isSeparator ? false : true required property QsMenuEntry modelData + TextMetrics { + id: textMetrics + font: menuText.font + text: menuItem.modelData.text + } + Component.onCompleted: { - if ( !modelData.isSeparator ) { - root.entryCount += 1; + // Measure text width to determine maximumWidth + var textWidth = textMetrics.width + 20 + (iconImage.width > 0 ? iconImage.width + 10 : 0); + if ( textWidth > root.maxWidth ) { + root.maxWidth = textWidth } } @@ -60,8 +80,10 @@ PopupWindow { acceptedButtons: Qt.LeftButton onClicked: { if ( !menuItem.modelData.hasChildren ) { - menuItem.modelData.triggered(); - root.visible = false; + if ( menuItem.modelData.enabled ) { + menuItem.modelData.triggered(); + root.visible = false; + } } else { subMenuComponent.createObject( null, { menu: menuItem.modelData, @@ -75,12 +97,30 @@ PopupWindow { } } - Text { - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - anchors.leftMargin: 10 - text: menuItem.modelData.text - color: "white" + RowLayout { + anchors.fill: parent + Text { + id: menuText + Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + Layout.leftMargin: 10 + text: menuItem.modelData.text + color: menuItem.modelData.enabled ? "white" : "gray" + } + Image { + id: iconImage + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + Layout.rightMargin: 10 + Layout.maximumWidth: 20 + Layout.maximumHeight: 20 + source: menuItem.modelData.icon + sourceSize.width: width + sourceSize.height: height + fillMode: Image.PreserveAspectFit + layer.enabled: true + layer.effect: ColorOverlay { + color: menuItem.modelData.enabled ? "white" : "gray" + } + } } } } diff --git a/shell.qml b/shell.qml index bece751..6e48a28 100644 --- a/shell.qml +++ b/shell.qml @@ -3,5 +3,5 @@ import Quickshell Scope { Bar {} - Wallpaper {} + // Wallpaper {} } From 01a0fa8bae7ed355fec0b36eca5489deb4c20115 Mon Sep 17 00:00:00 2001 From: Zacharias-Brohn Date: Sun, 2 Nov 2025 22:26:00 +0100 Subject: [PATCH 6/6] stuff --- Modules/TrayItem.qml | 5 ++++- Modules/TrayMenu.qml | 13 ++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Modules/TrayItem.qml b/Modules/TrayItem.qml index 73c884b..c6350da 100644 --- a/Modules/TrayItem.qml +++ b/Modules/TrayItem.qml @@ -14,6 +14,7 @@ MouseArea { id: root required property SystemTrayItem item + property var menuHandle implicitWidth: 22 implicitHeight: 22 @@ -41,6 +42,7 @@ MouseArea { mipmap: false asynchronous: true + ImageAnalyser { id: analyser sourceItem: icon @@ -57,6 +59,7 @@ MouseArea { grab.active = false; } } + HyprlandFocusGrab { id: grab windows: [ trayMenu ] @@ -65,8 +68,8 @@ MouseArea { } } } - } + onClicked: { if ( mouse.button === Qt.LeftButton ) { root.item.activate(); diff --git a/Modules/TrayMenu.qml b/Modules/TrayMenu.qml index a8f90f9..207f979 100644 --- a/Modules/TrayMenu.qml +++ b/Modules/TrayMenu.qml @@ -8,6 +8,8 @@ import Quickshell.Hyprland PopupWindow { id: root + + signal menuActionTriggered() required property QsMenuHandle menu property int height: { let count = 0; @@ -17,8 +19,8 @@ PopupWindow { return count * (entryHeight + 3); } property int entryHeight: 30 - property int maxWidth - implicitWidth: maxWidth + 20 + property int maxWidth: 100 + implicitWidth: Math.max(100, maxWidth + 20) implicitHeight: height color: "transparent" anchor.margins { @@ -65,8 +67,8 @@ PopupWindow { Component.onCompleted: { // Measure text width to determine maximumWidth - var textWidth = textMetrics.width + 20 + (iconImage.width > 0 ? iconImage.width + 10 : 0); - if ( textWidth > root.maxWidth ) { + var textWidth = textMetrics.width + 20 + (iconImage.source ? iconImage.width + 10 : 0); + if ( textWidth > 0 && textWidth > root.maxWidth ) { root.maxWidth = textWidth } } @@ -76,12 +78,13 @@ PopupWindow { anchors.fill: parent hoverEnabled: true preventStealing: true - propagateComposedEvents: true +propagateComposedEvents: true acceptedButtons: Qt.LeftButton onClicked: { if ( !menuItem.modelData.hasChildren ) { if ( menuItem.modelData.enabled ) { menuItem.modelData.triggered(); + root.menuActionTriggered(); root.visible = false; } } else {