summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
author2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-06-06 22:38:15 +1000
committer2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-06-06 22:38:15 +1000
commit17fb60f216e3f6979a44144677a7d1d8bb9e9fe4 (patch)
treeca79775a5cc31d58ae2f024dc5c905b0c86adc89 /modules
parentpopouts: transition between popout content (diff)
downloadcaelestia-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.qml15
-rw-r--r--modules/bar/components/Tray.qml4
-rw-r--r--modules/bar/popouts/Content.qml22
-rw-r--r--modules/bar/popouts/TrayMenu.qml235
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 {}
+ }
+}