From 17fb60f216e3f6979a44144677a7d1d8bb9e9fe4 Mon Sep 17 00:00:00 2001 From: 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> Date: Fri, 6 Jun 2025 22:38:15 +1000 Subject: feat: tray item menus --- modules/bar/popouts/TrayMenu.qml | 235 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 modules/bar/popouts/TrayMenu.qml (limited to 'modules/bar/popouts/TrayMenu.qml') diff --git a/modules/bar/popouts/TrayMenu.qml b/modules/bar/popouts/TrayMenu.qml new file mode 100644 index 0000000..f7d0928 --- /dev/null +++ b/modules/bar/popouts/TrayMenu.qml @@ -0,0 +1,235 @@ +pragma ComponentBehavior: Bound + +import "root:/widgets" +import "root:/services" +import "root:/config" +import Quickshell +import Quickshell.Widgets +import QtQuick +import QtQuick.Controls + +StackView { + id: root + + required property QsMenuHandle trayItem + + implicitWidth: currentItem.implicitWidth + implicitHeight: currentItem.implicitHeight + + initialItem: SubMenu { + handle: root.trayItem + } + + pushEnter: Anim {} + pushExit: Anim {} + popEnter: Anim {} + popExit: Anim {} + + component Anim: Transition { + NumberAnimation { + duration: 0 // Appearance.anim.durations.normal + easing.type: Easing.BezierSpline + easing.bezierCurve: Appearance.anim.curves.standard + } + } + + component SubMenu: Column { + id: menu + + required property QsMenuHandle handle + property bool isSubMenu + property bool shown + + padding: Appearance.padding.smaller + spacing: Appearance.spacing.small + + opacity: shown ? 1 : 0 + scale: shown ? 1 : 0.8 + + Component.onCompleted: shown = true + StackView.onActivating: shown = true + StackView.onDeactivating: shown = false + StackView.onRemoved: destroy() + + Behavior on opacity { + NumberAnimation { + duration: Appearance.anim.durations.normal + easing.type: Easing.BezierSpline + easing.bezierCurve: Appearance.anim.curves.standard + } + } + + Behavior on scale { + NumberAnimation { + duration: Appearance.anim.durations.normal + easing.type: Easing.BezierSpline + easing.bezierCurve: Appearance.anim.curves.standard + } + } + + QsMenuOpener { + id: menuOpener + + menu: menu.handle + } + + Repeater { + model: menuOpener.children + + StyledRect { + id: item + + required property QsMenuEntry modelData + + implicitWidth: BarConfig.sizes.trayMenuWidth + implicitHeight: modelData.isSeparator ? 1 : children.implicitHeight + + radius: Appearance.rounding.full + color: modelData.isSeparator ? Colours.palette.m3outlineVariant : "transparent" + + Loader { + id: children + + anchors.left: parent.left + anchors.right: parent.right + + active: !item.modelData.isSeparator + asynchronous: true + + sourceComponent: Item { + implicitHeight: label.implicitHeight + + StateLayer { + anchors.margins: -Appearance.padding.small / 2 + anchors.leftMargin: -Appearance.padding.smaller + anchors.rightMargin: -Appearance.padding.smaller + + radius: item.radius + disabled: !item.modelData.enabled + + function onClicked(): void { + const entry = item.modelData; + if (entry.hasChildren) + root.push(subMenuComp.createObject(null, { + handle: entry, + isSubMenu: true + })); + else + item.modelData.triggered(); + } + } + + Loader { + id: icon + + anchors.left: parent.left + + active: item.modelData.icon !== "" + asynchronous: true + + sourceComponent: IconImage { + implicitSize: label.implicitHeight + + source: item.modelData.icon + } + } + + StyledText { + id: label + + anchors.left: icon.right + anchors.leftMargin: icon.active ? Appearance.spacing.smaller : 0 + + text: labelMetrics.elidedText + color: item.modelData.enabled ? Colours.palette.m3onSurface : Colours.palette.m3outline + } + + TextMetrics { + id: labelMetrics + + text: item.modelData.text + font.pointSize: label.font.pointSize + font.family: label.font.family + + elide: Text.ElideRight + elideWidth: BarConfig.sizes.trayMenuWidth - (icon.active ? icon.implicitWidth + label.anchors.leftMargin : 0) - (expand.active ? expand.implicitWidth + Appearance.spacing.normal : 0) + } + + Loader { + id: expand + + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + + active: item.modelData.hasChildren + asynchronous: true + + sourceComponent: MaterialIcon { + text: "chevron_right" + color: item.modelData.enabled ? Colours.palette.m3onSurface : Colours.palette.m3outline + } + } + } + } + } + } + + Loader { + active: menu.isSubMenu + asynchronous: true + + sourceComponent: Item { + implicitWidth: back.implicitWidth + implicitHeight: back.implicitHeight + Appearance.spacing.small / 2 + + Item { + anchors.bottom: parent.bottom + implicitWidth: back.implicitWidth + implicitHeight: back.implicitHeight + + StyledRect { + anchors.fill: parent + anchors.margins: -Appearance.padding.small / 2 + anchors.leftMargin: -Appearance.padding.smaller + anchors.rightMargin: -Appearance.padding.smaller * 2 + + radius: Appearance.rounding.full + color: Colours.palette.m3secondaryContainer + + StateLayer { + radius: parent.radius + + function onClicked(): void { + root.pop(); + } + } + } + + Row { + id: back + + anchors.verticalCenter: parent.verticalCenter + + MaterialIcon { + anchors.verticalCenter: parent.verticalCenter + text: "chevron_left" + color: Colours.palette.m3onSecondaryContainer + } + + StyledText { + anchors.verticalCenter: parent.verticalCenter + text: qsTr("Back") + color: Colours.palette.m3onSecondaryContainer + } + } + } + } + } + } + + Component { + id: subMenuComp + + SubMenu {} + } +} -- cgit v1.2.3-freya