diff options
| author | 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> | 2025-06-06 22:38:15 +1000 |
|---|---|---|
| committer | 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> | 2025-06-06 22:38:15 +1000 |
| commit | 17fb60f216e3f6979a44144677a7d1d8bb9e9fe4 (patch) | |
| tree | ca79775a5cc31d58ae2f024dc5c905b0c86adc89 /modules | |
| parent | popouts: transition between popout content (diff) | |
| download | caelestia-shell-17fb60f216e3f6979a44144677a7d1d8bb9e9fe4.tar.gz caelestia-shell-17fb60f216e3f6979a44144677a7d1d8bb9e9fe4.tar.bz2 caelestia-shell-17fb60f216e3f6979a44144677a7d1d8bb9e9fe4.zip | |
feat: tray item menus
Diffstat (limited to 'modules')
| -rw-r--r-- | modules/bar/Content.qml | 15 | ||||
| -rw-r--r-- | modules/bar/components/Tray.qml | 4 | ||||
| -rw-r--r-- | modules/bar/popouts/Content.qml | 22 | ||||
| -rw-r--r-- | modules/bar/popouts/TrayMenu.qml | 235 |
4 files changed, 276 insertions, 0 deletions
diff --git a/modules/bar/Content.qml b/modules/bar/Content.qml index ebd19cc..54879fa 100644 --- a/modules/bar/Content.qml +++ b/modules/bar/Content.qml @@ -15,16 +15,31 @@ StyledRect { const spacing = Appearance.spacing.small; const aw = activeWindow.child; const awy = activeWindow.y + aw.y; + + const ty = tray.y; + const th = tray.implicitHeight; + const trayItems = tray.items; + const n = statusIconsInner.network; const ny = statusIcons.y + statusIconsInner.y + n.y - spacing / 2; + const bls = statusIcons.y + statusIconsInner.y + statusIconsInner.bs - spacing / 2; const ble = statusIcons.y + statusIconsInner.y + statusIconsInner.be + spacing / 2; + const b = statusIconsInner.battery; const by = statusIcons.y + statusIconsInner.y + b.y - spacing / 2; + if (y >= awy && y <= awy + aw.implicitHeight) { Popouts.currentName = "activewindow"; Popouts.currentCenter = Qt.binding(() => activeWindow.y + aw.y + aw.implicitHeight / 2); Popouts.hasCurrent = true; + } else if (y > ty && y < ty + th) { + const index = Math.floor(((y - ty) / th) * trayItems.count); + const item = trayItems.itemAt(index); + + Popouts.currentName = `traymenu${index}`; + Popouts.currentCenter = Qt.binding(() => tray.y + item.y + item.implicitHeight / 2); + Popouts.hasCurrent = true; } else if (y >= ny && y <= ny + n.implicitHeight + spacing) { Popouts.currentName = "network"; Popouts.currentCenter = Qt.binding(() => statusIcons.y + statusIconsInner.y + n.y + n.implicitHeight / 2); diff --git a/modules/bar/components/Tray.qml b/modules/bar/components/Tray.qml index fc65492..6280a30 100644 --- a/modules/bar/components/Tray.qml +++ b/modules/bar/components/Tray.qml @@ -5,6 +5,8 @@ import QtQuick Item { id: root + readonly property Repeater items: items + clip: true visible: width > 0 && height > 0 // To avoid warnings about being visible with no size @@ -28,6 +30,8 @@ Item { } Repeater { + id: items + model: SystemTray.items TrayItem {} diff --git a/modules/bar/popouts/Content.qml b/modules/bar/popouts/Content.qml index 1f9da27..6f0ec70 100644 --- a/modules/bar/popouts/Content.qml +++ b/modules/bar/popouts/Content.qml @@ -1,6 +1,9 @@ +pragma ComponentBehavior: Bound + import "root:/services" import "root:/config" import Quickshell +import Quickshell.Services.SystemTray import QtQuick Item { @@ -40,6 +43,24 @@ Item { name: "battery" source: "Battery.qml" } + + Repeater { + model: ScriptModel { + values: [...SystemTray.items.values] + } + + Popout { + id: trayMenu + + required property SystemTrayItem modelData + required property int index + + name: `traymenu${index}` + sourceComponent: TrayMenu { + trayItem: trayMenu.modelData.menu + } + } + } } Behavior on implicitWidth { @@ -65,6 +86,7 @@ Item { opacity: 0 scale: 0.8 + active: false asynchronous: true states: State { 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 {} + } +} |