summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-09-19 22:43:50 +1000
committerGitHub <noreply@github.com>2025-09-19 22:43:50 +1000
commit9dfb195913a0062a7d493d0128ebd50d86b5e493 (patch)
tree7508ff58826b60c2855a4cd6b8f21dde71a37db3
parentsidebar/notifs: add mouse actions (diff)
downloadcaelestia-shell-9dfb195913a0062a7d493d0128ebd50d86b5e493.tar.gz
caelestia-shell-9dfb195913a0062a7d493d0128ebd50d86b5e493.tar.bz2
caelestia-shell-9dfb195913a0062a7d493d0128ebd50d86b5e493.zip
sidebar/notifs: add actions (#648)
* add actions (not done, transfer between machines) * notifs: fix persistence * notifs: persist actions (appearance only) * sidebar/notifs: add actions * sidebar/notifs: add copy action Also actions fill width * sidebar/notifs: better actions Fade at edges when scrollable * sidebar/notifs: fix urgency colours & icon * sidebar/notifs: remove unnecessary clipping * sidebar/notifs: fix artifacts with actions
-rw-r--r--modules/sidebar/Notif.qml6
-rw-r--r--modules/sidebar/NotifActionList.qml198
-rw-r--r--modules/sidebar/NotifDock.qml1
-rw-r--r--modules/sidebar/NotifGroup.qml26
-rw-r--r--services/Notifs.qml19
5 files changed, 232 insertions, 18 deletions
diff --git a/modules/sidebar/Notif.qml b/modules/sidebar/Notif.qml
index 3aecc59..6d930b9 100644
--- a/modules/sidebar/Notif.qml
+++ b/modules/sidebar/Notif.qml
@@ -106,7 +106,7 @@ StyledRect {
anchors.topMargin: Appearance.spacing.small / 2
sourceComponent: ColumnLayout {
- spacing: Math.floor(Appearance.spacing.small / 2)
+ spacing: Appearance.spacing.smaller
StyledText {
Layout.fillWidth: true
@@ -115,6 +115,10 @@ StyledRect {
color: root.modelData.urgency === "critical" ? Colours.palette.m3secondary : Colours.palette.m3outline
wrapMode: Text.WordWrap
}
+
+ NotifActionList {
+ notif: root.modelData
+ }
}
}
diff --git a/modules/sidebar/NotifActionList.qml b/modules/sidebar/NotifActionList.qml
new file mode 100644
index 0000000..a8f75cc
--- /dev/null
+++ b/modules/sidebar/NotifActionList.qml
@@ -0,0 +1,198 @@
+pragma ComponentBehavior: Bound
+
+import qs.components
+import qs.components.containers
+import qs.components.effects
+import qs.services
+import qs.config
+import Quickshell
+import Quickshell.Widgets
+import QtQuick
+import QtQuick.Layouts
+
+Item {
+ id: root
+
+ required property Notifs.Notif notif
+
+ Layout.fillWidth: true
+ implicitHeight: flickable.contentHeight
+
+ layer.enabled: true
+ layer.smooth: true
+ layer.effect: OpacityMask {
+ maskSource: gradientMask
+ }
+
+ Item {
+ id: gradientMask
+
+ anchors.fill: parent
+ layer.enabled: true
+ visible: false
+
+ Rectangle {
+ anchors.fill: parent
+
+ gradient: Gradient {
+ orientation: Gradient.Horizontal
+
+ GradientStop {
+ position: 0
+ color: Qt.rgba(0, 0, 0, 0)
+ }
+ GradientStop {
+ position: 0.1
+ color: Qt.rgba(0, 0, 0, 1)
+ }
+ GradientStop {
+ position: 0.9
+ color: Qt.rgba(0, 0, 0, 1)
+ }
+ GradientStop {
+ position: 1
+ color: Qt.rgba(0, 0, 0, 0)
+ }
+ }
+ }
+
+ Rectangle {
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ anchors.left: parent.left
+
+ implicitWidth: parent.width / 2
+ opacity: flickable.contentX > 0 ? 0 : 1
+
+ Behavior on opacity {
+ Anim {}
+ }
+ }
+
+ Rectangle {
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ anchors.right: parent.right
+
+ implicitWidth: parent.width / 2
+ opacity: flickable.contentX < flickable.contentWidth - parent.width ? 0 : 1
+
+ Behavior on opacity {
+ Anim {}
+ }
+ }
+ }
+
+ StyledFlickable {
+ id: flickable
+
+ anchors.fill: parent
+ contentWidth: Math.max(width, actionList.implicitWidth)
+ contentHeight: actionList.implicitHeight
+
+ RowLayout {
+ id: actionList
+
+ anchors.fill: parent
+ spacing: Appearance.spacing.small
+
+ Repeater {
+ model: [
+ {
+ isClose: true
+ },
+ ...root.notif.actions,
+ {
+ isCopy: true
+ }
+ ]
+
+ StyledRect {
+ id: action
+
+ required property var modelData
+
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ implicitWidth: actionInner.implicitWidth + Appearance.padding.normal * 2
+ implicitHeight: actionInner.implicitHeight + Appearance.padding.small * 2
+
+ Layout.preferredWidth: implicitWidth + (actionStateLayer.pressed ? Appearance.padding.large : 0)
+ radius: actionStateLayer.pressed ? Appearance.rounding.small / 2 : Appearance.rounding.small
+ color: Colours.layer(Colours.palette.m3surfaceContainerHighest, 4)
+
+ Timer {
+ id: copyTimer
+
+ interval: 3000
+ onTriggered: actionInner.item.text = "content_copy"
+ }
+
+ StateLayer {
+ id: actionStateLayer
+
+ function onClicked(): void {
+ if (action.modelData.isClose) {
+ root.notif.close();
+ } else if (action.modelData.isCopy) {
+ Quickshell.clipboardText = root.notif.body;
+ actionInner.item.text = "inventory";
+ copyTimer.start();
+ } else if (action.modelData.invoke) {
+ action.modelData.invoke();
+ } else if (!root.notif.resident) {
+ root.notif.close();
+ }
+ }
+ }
+
+ Loader {
+ id: actionInner
+
+ anchors.centerIn: parent
+ sourceComponent: action.modelData.isClose || action.modelData.isCopy ? iconBtn : root.notif.hasActionIcons ? iconComp : textComp
+ }
+
+ Component {
+ id: iconBtn
+
+ MaterialIcon {
+ animate: action.modelData.isCopy ?? false
+ text: action.modelData.isCopy ? "content_copy" : "close"
+ }
+ }
+
+ Component {
+ id: iconComp
+
+ IconImage {
+ source: Quickshell.iconPath(action.modelData.identifier)
+ }
+ }
+
+ Component {
+ id: textComp
+
+ StyledText {
+ text: action.modelData.text
+ }
+ }
+
+ Behavior on Layout.preferredWidth {
+ Anim {
+ duration: Appearance.anim.durations.expressiveFastSpatial
+ easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
+ }
+ }
+
+ Behavior on radius {
+ Anim {
+ duration: Appearance.anim.durations.expressiveFastSpatial
+ easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/modules/sidebar/NotifDock.qml b/modules/sidebar/NotifDock.qml
index 7ddee30..c34229e 100644
--- a/modules/sidebar/NotifDock.qml
+++ b/modules/sidebar/NotifDock.qml
@@ -92,7 +92,6 @@ Item {
anchors.fill: parent
spacing: Appearance.spacing.small
- clip: true
model: ScriptModel {
values: [...new Set(Notifs.list.filter(n => !n.closed).map(n => n.appName))].reverse()
diff --git a/modules/sidebar/NotifGroup.qml b/modules/sidebar/NotifGroup.qml
index 83cffcd..a87deb6 100644
--- a/modules/sidebar/NotifGroup.qml
+++ b/modules/sidebar/NotifGroup.qml
@@ -6,7 +6,6 @@ import qs.services
import qs.config
import qs.utils
import Quickshell
-import Quickshell.Widgets
import Quickshell.Services.Notifications
import QtQuick
import QtQuick.Layouts
@@ -20,7 +19,7 @@ StyledRect {
readonly property list<var> notifs: Notifs.list.filter(notif => notif.appName === modelData).reverse()
readonly property string image: notifs.find(n => n.image.length > 0)?.image ?? ""
readonly property string appIcon: notifs.find(n => n.appIcon.length > 0)?.appIcon ?? ""
- readonly property string urgency: notifs.some(n => n.urgency === NotificationUrgency.Critical) ? "critical" : notifs.some(n => n.urgency === NotificationUrgency.Normal) ? "normal" : "low"
+ readonly property int urgency: notifs.some(n => n.urgency === NotificationUrgency.Critical) ? NotificationUrgency.Critical : notifs.some(n => n.urgency === NotificationUrgency.Normal) ? NotificationUrgency.Normal : NotificationUrgency.Low
readonly property bool expanded: props.expandedNotifs.includes(modelData)
@@ -37,9 +36,8 @@ StyledRect {
anchors.right: parent?.right
implicitHeight: content.implicitHeight + Appearance.padding.normal * 2
- clip: true
radius: Appearance.rounding.normal
- color: root.urgency === "critical" ? Colours.palette.m3secondaryContainer : Colours.layer(Colours.palette.m3surfaceContainer, 2)
+ color: Colours.layer(Colours.palette.m3surfaceContainer, 2)
RowLayout {
id: content
@@ -75,7 +73,7 @@ StyledRect {
ColouredIcon {
implicitSize: Math.round(Config.notifs.sizes.image * 0.6)
source: Quickshell.iconPath(root.appIcon)
- colour: root.urgency === "critical" ? Colours.palette.m3onError : root.urgency === "low" ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer
+ colour: root.urgency === NotificationUrgency.Critical ? Colours.palette.m3onError : root.urgency === NotificationUrgency.Low ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer
layer.enabled: root.appIcon.endsWith("symbolic")
}
}
@@ -85,14 +83,14 @@ StyledRect {
MaterialIcon {
text: Icons.getNotifIcon(root.notifs[0]?.summary, root.urgency)
- color: root.urgency === "critical" ? Colours.palette.m3onError : root.urgency === "low" ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer
+ color: root.urgency === NotificationUrgency.Critical ? Colours.palette.m3onError : root.urgency === NotificationUrgency.Low ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer
font.pointSize: Appearance.font.size.large
}
}
- ClippingRectangle {
+ StyledClippingRect {
anchors.fill: parent
- color: root.urgency === "critical" ? Colours.palette.m3error : root.urgency === "low" ? Colours.layer(Colours.palette.m3surfaceContainerHigh, 3) : Colours.palette.m3secondaryContainer
+ color: root.urgency === NotificationUrgency.Critical ? Colours.palette.m3error : root.urgency === NotificationUrgency.Low ? Colours.layer(Colours.palette.m3surfaceContainerHigh, 3) : Colours.palette.m3secondaryContainer
radius: Appearance.rounding.full
Loader {
@@ -112,14 +110,14 @@ StyledRect {
implicitWidth: Config.notifs.sizes.badge
implicitHeight: Config.notifs.sizes.badge
- color: root.urgency === "critical" ? Colours.palette.m3error : root.urgency === "low" ? Colours.palette.m3surfaceContainerHigh : Colours.palette.m3secondaryContainer
+ color: root.urgency === NotificationUrgency.Critical ? Colours.palette.m3error : root.urgency === NotificationUrgency.Low ? Colours.palette.m3surfaceContainerHigh : Colours.palette.m3secondaryContainer
radius: Appearance.rounding.full
ColouredIcon {
anchors.centerIn: parent
implicitSize: Math.round(Config.notifs.sizes.badge * 0.6)
source: Quickshell.iconPath(root.appIcon)
- colour: root.urgency === "critical" ? Colours.palette.m3onError : root.urgency === "low" ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer
+ colour: root.urgency === NotificationUrgency.Critical ? Colours.palette.m3onError : root.urgency === NotificationUrgency.Low ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer
layer.enabled: root.appIcon.endsWith("symbolic")
}
}
@@ -158,11 +156,11 @@ StyledRect {
implicitWidth: expandBtn.implicitWidth + Appearance.padding.smaller * 2
implicitHeight: groupCount.implicitHeight + Appearance.padding.small
- color: root.urgency === "critical" ? Colours.palette.m3error : Colours.layer(Colours.palette.m3surfaceContainerHigh, 3)
+ color: root.urgency === NotificationUrgency.Critical ? Colours.palette.m3error : Colours.layer(Colours.palette.m3surfaceContainerHigh, 3)
radius: Appearance.rounding.full
StateLayer {
- color: root.urgency === "critical" ? Colours.palette.m3onError : Colours.palette.m3onSurface
+ color: root.urgency === NotificationUrgency.Critical ? Colours.palette.m3onError : Colours.palette.m3onSurface
function onClicked(): void {
root.toggleExpand(!root.expanded);
@@ -181,7 +179,7 @@ StyledRect {
Layout.leftMargin: Appearance.padding.small / 2
animate: true
text: root.notifs.reduce((acc, n) => n.closed ? acc : acc + 1, 0)
- color: root.urgency === "critical" ? Colours.palette.m3onError : Colours.palette.m3onSurface
+ color: root.urgency === NotificationUrgency.Critical ? Colours.palette.m3onError : Colours.palette.m3onSurface
font.pointSize: Appearance.font.size.small
}
@@ -189,7 +187,7 @@ StyledRect {
Layout.rightMargin: -Appearance.padding.small / 2
animate: true
text: root.expanded ? "expand_less" : "expand_more"
- color: root.urgency === "critical" ? Colours.palette.m3onError : Colours.palette.m3onSurface
+ color: root.urgency === NotificationUrgency.Critical ? Colours.palette.m3onError : Colours.palette.m3onSurface
}
}
}
diff --git a/services/Notifs.qml b/services/Notifs.qml
index b651442..c5f0700 100644
--- a/services/Notifs.qml
+++ b/services/Notifs.qml
@@ -30,7 +30,10 @@ Singleton {
appName: n.appName,
image: n.image,
expireTimeout: n.expireTimeout,
- urgency: n.urgency
+ urgency: n.urgency,
+ resident: n.resident,
+ hasActionIcons: n.hasActionIcons,
+ actions: n.actions
}))));
}
@@ -73,6 +76,12 @@ Singleton {
root.list.push(notifComp.createObject(root, notif));
root.loaded = true;
}
+ onLoadFailed: err => {
+ if (err === FileViewError.FileNotFound) {
+ root.loaded = true;
+ setText("[]");
+ }
+ }
}
CustomShortcut {
@@ -138,7 +147,13 @@ Singleton {
property string image: notification?.image ?? ""
property real expireTimeout: notification?.expireTimeout ?? Config.notifs.defaultExpireTimeout
property int urgency: notification?.urgency ?? NotificationUrgency.Normal
- readonly property list<NotificationAction> actions: notification?.actions ?? []
+ property bool resident: notification?.resident ?? false
+ property bool hasActionIcons: notification?.hasActionIcons ?? false
+ property list<var> actions: notification?.actions.map(a => ({
+ identifier: a.identifier,
+ text: a.text,
+ invoke: () => a.invoke()
+ })) ?? []
readonly property Timer timer: Timer {
running: true