diff --git a/.config/quickshell/modules/common/functions/color_utils.js b/.config/quickshell/modules/common/functions/color_utils.js
index 652b09ebb..c0ccfda9d 100644
--- a/.config/quickshell/modules/common/functions/color_utils.js
+++ b/.config/quickshell/modules/common/functions/color_utils.js
@@ -79,7 +79,7 @@ function mix(color1, color2, percentage) {
* @param {number} percentage - The amount to transparentize (0-1).
* @returns {Qt.rgba} The resulting color.
*/
-function transparentize(color, percentage) {
+function transparentize(color, percentage = 1) {
var c = Qt.color(color);
return Qt.rgba(c.r, c.g, c.b, c.a * (1 - percentage));
}
diff --git a/.config/quickshell/modules/common/widgets/DragManager.qml b/.config/quickshell/modules/common/widgets/DragManager.qml
index 109f18254..8876c18f2 100644
--- a/.config/quickshell/modules/common/widgets/DragManager.qml
+++ b/.config/quickshell/modules/common/widgets/DragManager.qml
@@ -30,7 +30,9 @@ MouseArea { // Flick to dismiss
onPressed: (mouse) => {
if (!root.interactive) {
- mouse.accepted = false;
+ if (mouse.button === Qt.LeftButton) {
+ mouse.accepted = false;
+ }
return;
}
if (mouse.button === Qt.LeftButton) {
@@ -40,7 +42,6 @@ MouseArea { // Flick to dismiss
}
onReleased: (mouse) => {
if (!root.interactive) {
- mouse.accepted = false;
return;
}
dragging = false
@@ -51,7 +52,6 @@ MouseArea { // Flick to dismiss
}
onPositionChanged: (mouse) => {
if (!root.interactive) {
- mouse.accepted = false;
return;
}
if (mouse.buttons & Qt.LeftButton) {
@@ -64,7 +64,6 @@ MouseArea { // Flick to dismiss
}
onCanceled: (mouse) => {
if (!root.interactive) {
- mouse.accepted = false;
return;
}
released(mouse);
diff --git a/.config/quickshell/modules/common/widgets/NotificationAppIcon.qml b/.config/quickshell/modules/common/widgets/NotificationAppIcon.qml
new file mode 100644
index 000000000..5a218d8f9
--- /dev/null
+++ b/.config/quickshell/modules/common/widgets/NotificationAppIcon.qml
@@ -0,0 +1,104 @@
+import "root:/modules/common"
+import "./notification_utils.js" as NotificationUtils
+import Qt5Compat.GraphicalEffects
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Quickshell
+import Quickshell.Widgets
+import Quickshell.Services.Notifications
+
+Rectangle { // App icon
+ id: root
+ property var appIcon: ""
+ property var summary: ""
+ property var urgency: NotificationUrgency.Normal
+ property var image: ""
+ property real size: 45
+ property real materialIconScale: 0.57
+ property real appIconScale: 0.7
+ property real smallAppIconScale: 0.49
+ property real materialIconSize: size * materialIconScale
+ property real appIconSize: size * appIconScale
+ property real smallAppIconSize: size * smallAppIconScale
+
+ implicitWidth: size
+ implicitHeight: size
+ radius: Appearance.rounding.full
+ color: Appearance.m3colors.m3secondaryContainer
+ Loader {
+ id: materialSymbolLoader
+ active: root.appIcon == ""
+ anchors.fill: parent
+ sourceComponent: MaterialSymbol {
+ text: {
+ const defaultIcon = NotificationUtils.findSuitableMaterialSymbol("")
+ const guessedIcon = NotificationUtils.findSuitableMaterialSymbol(root.summary)
+ return (root.urgency == NotificationUrgency.Critical && guessedIcon === defaultIcon) ?
+ "release_alert" : guessedIcon
+ }
+ anchors.fill: parent
+ color: (root.urgency == NotificationUrgency.Critical) ?
+ ColorUtils.mix(Appearance.m3colors.m3onSecondary, Appearance.m3colors.m3onSecondaryContainer, 0.1) :
+ Appearance.m3colors.m3onSecondaryContainer
+ iconSize: root.materialIconSize
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ }
+ }
+ Loader {
+ id: appIconLoader
+ active: root.image == "" && root.appIcon != ""
+ anchors.centerIn: parent
+ sourceComponent: IconImage {
+ id: appIconImage
+ implicitSize: root.appIconSize
+ asynchronous: true
+ source: Quickshell.iconPath(root.appIcon, "image-missing")
+ }
+ }
+ Loader {
+ id: notifImageLoader
+ active: root.image != ""
+ anchors.fill: parent
+ sourceComponent: Item {
+ anchors.fill: parent
+ Image {
+ id: notifImage
+ anchors.fill: parent
+ readonly property int size: parent.width
+
+ source: root.image
+ fillMode: Image.PreserveAspectCrop
+ cache: false
+ antialiasing: true
+ asynchronous: true
+
+ width: size
+ height: size
+ sourceSize.width: size
+ sourceSize.height: size
+
+ layer.enabled: true
+ layer.effect: OpacityMask {
+ maskSource: Rectangle {
+ width: notifImage.size
+ height: notifImage.size
+ radius: Appearance.rounding.full
+ }
+ }
+ }
+ Loader {
+ id: notifImageAppIconLoader
+ active: root.appIcon != ""
+ anchors.bottom: parent.bottom
+ anchors.right: parent.right
+ sourceComponent: IconImage {
+ implicitSize: root.smallAppIconSize
+ asynchronous: true
+ source: Quickshell.iconPath(root.appIcon, "image-missing")
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/.config/quickshell/modules/common/widgets/NotificationGroup.qml b/.config/quickshell/modules/common/widgets/NotificationGroup.qml
new file mode 100644
index 000000000..7b1381dd3
--- /dev/null
+++ b/.config/quickshell/modules/common/widgets/NotificationGroup.qml
@@ -0,0 +1,232 @@
+import "root:/modules/common"
+import "root:/services"
+import "root:/modules/common/functions/string_utils.js" as StringUtils
+import "root:/modules/common/functions/color_utils.js" as ColorUtils
+import "./notification_utils.js" as NotificationUtils
+import Qt5Compat.GraphicalEffects
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Effects
+import QtQuick.Layouts
+import Quickshell
+import Quickshell.Io
+import Quickshell.Widgets
+import Quickshell.Hyprland
+import Quickshell.Services.Notifications
+
+/**
+ * A group of notifications from the same app.
+ * Similar to Android's notifications
+ */
+Item { // Notification group area
+ id: root
+ property var notificationGroup
+ property var notifications: notificationGroup.notifications
+ property int notificationCount: notifications.length
+ property bool multipleNotifications: notificationCount > 1
+ property bool expanded: false
+ property bool popup: false
+ property real padding: 10
+ implicitHeight: background.implicitHeight
+
+ property real dragConfirmThreshold: 70 // Drag further to discard notification
+ property real dismissOvershoot: 20 // Account for gaps and bouncy animations
+ property var qmlParent: root.parent.parent // There's something between this and the parent ListView
+ property var parentDragIndex: qmlParent.dragIndex
+ property var parentDragDistance: qmlParent.dragDistance
+ property var dragIndexDiff: Math.abs(parentDragIndex - index)
+ property real xOffset: dragIndexDiff == 0 ? Math.max(0, parentDragDistance) :
+ parentDragDistance > dragConfirmThreshold ? 0 :
+ dragIndexDiff == 1 ? Math.max(0, parentDragDistance * 0.3) :
+ dragIndexDiff == 2 ? Math.max(0, parentDragDistance * 0.1) : 0
+
+ function destroyWithAnimation() {
+ root.qmlParent.resetDrag()
+ background.anchors.leftMargin = background.anchors.leftMargin; // Break binding
+ destroyAnimation.running = true;
+ }
+
+ SequentialAnimation { // Drag finish animation
+ id: destroyAnimation
+ running: false
+
+ NumberAnimation {
+ target: background.anchors
+ property: "leftMargin"
+ to: root.width + root.dismissOvershoot
+ duration: Appearance.animation.elementMove.duration
+ easing.type: Appearance.animation.elementMove.type
+ easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
+ }
+ onFinished: () => {
+ root.notifications.forEach((notif) => {
+ Notifications.discardNotification(notif.id);
+ });
+ }
+ }
+
+ function toggleExpanded() {
+ if (expanded) implicitHeightAnim.enabled = true;
+ else implicitHeightAnim.enabled = false;
+ root.expanded = !root.expanded;
+ }
+
+ DragManager { // Drag manager
+ id: dragManager
+ anchors.fill: parent
+ interactive: !expanded
+ automaticallyReset: false
+ acceptedButtons: Qt.LeftButton | Qt.RightButton
+
+ onClicked: (mouse) => {
+ if (mouse.button === Qt.RightButton) {
+ root.toggleExpanded();
+ }
+ }
+
+ onDraggingChanged: () => {
+ if (dragging) {
+ root.qmlParent.dragIndex = root.index ?? root.parent.children.indexOf(root);
+ }
+ }
+
+ onDragDiffXChanged: () => {
+ root.qmlParent.dragDistance = dragDiffX;
+ }
+
+ onDragReleased: (diffX, diffY) => {
+ if (diffX > root.dragConfirmThreshold)
+ root.destroyWithAnimation();
+ else
+ dragManager.resetDrag();
+ }
+ }
+
+
+
+ Rectangle { // Background of the notification
+ id: background
+ anchors.left: parent.left
+ width: parent.width
+ color: Appearance.m3colors.m3surfaceContainer
+ radius: Appearance.rounding.normal
+ anchors.leftMargin: root.xOffset
+
+ Behavior on anchors.leftMargin {
+ enabled: !dragManager.dragging
+ NumberAnimation {
+ duration: Appearance.animation.elementMove.duration
+ easing.type: Appearance.animation.elementMove.type
+ easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial
+ }
+ }
+
+ clip: true
+ implicitHeight: expanded ?
+ row.implicitHeight + padding * 2 :
+ Math.min(80, row.implicitHeight + padding * 2)
+
+ Behavior on implicitHeight {
+ id: implicitHeightAnim
+ animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
+ }
+
+ layer.enabled: true
+ layer.effect: MultiEffect {
+ source: background
+ anchors.fill: background
+ shadowEnabled: popup
+ shadowColor: Appearance.colors.colShadow
+ shadowVerticalOffset: 1
+ shadowBlur: 0.5
+ }
+
+ RowLayout { // Left column for icon, right column for content
+ id: row
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.margins: root.padding
+ spacing: 10
+
+ NotificationAppIcon { // Icons
+ Layout.alignment: Qt.AlignTop
+ Layout.fillWidth: false
+
+ appIcon: notificationGroup.appIcon
+ summary: notificationGroup.notifications[root.notificationCount - 1].summary
+ }
+
+ ColumnLayout { // Content
+ Layout.fillWidth: true
+ spacing: expanded ? 5 : 0
+ Behavior on spacing {
+ animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
+ }
+
+ RowLayout { // App name (or summary when there's only 1 notif) and time
+ id: topRow
+ spacing: 0
+ property real fontSize: Appearance.font.pixelSize.smaller
+ property bool showAppName: root.multipleNotifications
+
+ StyledText {
+ id: appName
+ text: topRow.showAppName ?
+ notificationGroup.appName :
+ notificationGroup.notifications[0].summary
+ font.pixelSize: topRow.showAppName ?
+ topRow.fontSize :
+ Appearance.font.pixelSize.small
+ color: topRow.showAppName ?
+ Appearance.colors.colSubtext :
+ Appearance.colors.colOnLayer2
+ }
+ StyledText {
+ id: timeText
+ text: " • " + NotificationUtils.getFriendlyNotifTimeString(notificationGroup.time)
+ font.pixelSize: topRow.fontSize
+ color: Appearance.colors.colSubtext
+ Layout.alignment: Qt.AlignRight
+ Layout.fillWidth: true
+ }
+ Item { Layout.fillWidth: true }
+ NotificationGroupExpandButton {
+ count: root.notificationCount
+ expanded: root.expanded
+ fontSize: topRow.fontSize
+ onClicked: { root.toggleExpanded() }
+ }
+ }
+
+ StyledListView { // Notification body (expanded)
+ id: notificationsColumn
+ implicitHeight: contentHeight
+ Layout.fillWidth: true
+ spacing: expanded ? 5 : 3
+ // clip: true
+ interactive: false
+ Behavior on spacing {
+ animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
+ }
+ model: ScriptModel {
+ // values: root.expanded ? root.notifications : root.notifications.slice(0, 2)
+ values: root.notifications.slice().reverse()
+ }
+ delegate: NotificationItem {
+ required property int index
+ required property var modelData
+ notificationObject: modelData
+ expanded: root.expanded
+ onlyNotification: (root.notificationCount === 1)
+ opacity: (!root.expanded && index == 1 && root.notificationCount > 2) ? 0.5 : 1
+ visible: root.expanded || (index < 2)
+ anchors.left: parent?.left
+ anchors.right: parent?.right
+ }
+ }
+
+ }
+ }
+ }
+}
diff --git a/.config/quickshell/modules/common/widgets/NotificationGroupExpandButton.qml b/.config/quickshell/modules/common/widgets/NotificationGroupExpandButton.qml
new file mode 100644
index 000000000..5de02a98e
--- /dev/null
+++ b/.config/quickshell/modules/common/widgets/NotificationGroupExpandButton.qml
@@ -0,0 +1,51 @@
+import "root:/modules/common"
+import "root:/services"
+import "root:/modules/common/functions/color_utils.js" as ColorUtils
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Effects
+import QtQuick.Layouts
+import Quickshell
+import Quickshell.Services.Notifications
+
+RippleButton { // Expand button
+ id: root
+ required property int count
+ required property bool expanded
+ property real fontSize: Appearance.font.pixelSize.small
+ implicitHeight: fontSize + 4 * 2
+ implicitWidth: Math.max(contentItem.implicitWidth + 5 * 2, 30)
+ Layout.alignment: Qt.AlignVCenter
+ Layout.fillHeight: false
+
+ buttonRadius: Appearance.rounding.full
+ colBackground: ColorUtils.mix(Appearance.colors.colLayer2, Appearance.colors.colLayer2Hover, 0.5)
+ colBackgroundHover: Appearance.colors.colLayer2Hover
+ colRipple: Appearance.colors.colLayer2Active
+
+ contentItem: Item {
+ anchors.centerIn: parent
+ implicitWidth: contentRow.implicitWidth
+ RowLayout {
+ id: contentRow
+ anchors.centerIn: parent
+ spacing: 3
+ StyledText {
+ Layout.leftMargin: 4
+ visible: root.count > 1
+ text: root.count
+ font.pixelSize: root.fontSize
+ }
+ MaterialSymbol {
+ text: "keyboard_arrow_down"
+ iconSize: Appearance.font.pixelSize.normal
+ color: Appearance.colors.colOnLayer2
+ rotation: expanded ? 180 : 0
+ Behavior on rotation {
+ animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
+ }
+ }
+ }
+ }
+
+}
diff --git a/.config/quickshell/modules/common/widgets/NotificationItem.qml b/.config/quickshell/modules/common/widgets/NotificationItem.qml
new file mode 100644
index 000000000..ada5a95ff
--- /dev/null
+++ b/.config/quickshell/modules/common/widgets/NotificationItem.qml
@@ -0,0 +1,268 @@
+import "root:/modules/common"
+import "root:/services"
+import "root:/modules/common/functions/string_utils.js" as StringUtils
+import "root:/modules/common/functions/color_utils.js" as ColorUtils
+import "./notification_utils.js" as NotificationUtils
+import Qt5Compat.GraphicalEffects
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Effects
+import QtQuick.Layouts
+import Quickshell
+import Quickshell.Io
+import Quickshell.Widgets
+import Quickshell.Hyprland
+import Quickshell.Services.Notifications
+
+Item { // Notification item area
+ id: root
+ property var notificationObject
+ property bool expanded: false
+ property bool onlyNotification: false
+ property real fontSize: Appearance.font.pixelSize.small
+ property real padding: 8
+
+ property real dragConfirmThreshold: 70 // Drag further to discard notification
+ property real dismissOvershoot: 20 // Account for gaps and bouncy animations
+ property var qmlParent: root?.parent?.parent // There's something between this and the parent ListView
+ property var parentDragIndex: qmlParent?.dragIndex ?? -1
+ property var parentDragDistance: qmlParent?.dragDistance ?? 0
+ property var dragIndexDiff: Math.abs(parentDragIndex - index)
+ property real xOffset: dragIndexDiff == 0 ? Math.max(0, parentDragDistance) :
+ parentDragDistance > dragConfirmThreshold ? 0 :
+ dragIndexDiff == 1 ? Math.max(0, parentDragDistance * 0.3) :
+ dragIndexDiff == 2 ? Math.max(0, parentDragDistance * 0.1) : 0
+
+ implicitHeight: background.implicitHeight
+
+ function destroyWithAnimation() {
+ root.qmlParent.resetDrag()
+ background.anchors.leftMargin = background.anchors.leftMargin; // Break binding
+ destroyAnimation.running = true;
+ }
+
+ SequentialAnimation { // Drag finish animation
+ id: destroyAnimation
+ running: false
+
+ NumberAnimation {
+ target: background.anchors
+ property: "leftMargin"
+ to: root.width + root.dismissOvershoot
+ duration: Appearance.animation.elementMove.duration
+ easing.type: Appearance.animation.elementMove.type
+ easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
+ }
+ onFinished: () => {
+ Notifications.discardNotification(notificationObject.id);
+ }
+ }
+
+ DragManager { // Drag manager
+ id: dragManager
+ anchors.fill: parent
+ interactive: expanded
+ automaticallyReset: false
+ acceptedButtons: Qt.LeftButton
+
+ onPressAndHold: (mouse) => {
+ if (mouse.button === Qt.LeftButton) {
+ Hyprland.dispatch(`exec wl-copy '${StringUtils.shellSingleQuoteEscape(notificationObject.body)}'`)
+ notificationSummaryText.text = String.format(qsTr("{0} (copied)"), notificationObject.summary)
+ }
+ }
+ onDraggingChanged: () => {
+ if (dragging) {
+ root.qmlParent.dragIndex = root.index ?? root.parent.children.indexOf(root);
+ }
+ }
+
+ onDragDiffXChanged: () => {
+ root.qmlParent.dragDistance = dragDiffX;
+ }
+
+ onDragReleased: (diffX, diffY) => {
+ if (diffX > root.dragConfirmThreshold)
+ root.destroyWithAnimation();
+ else
+ dragManager.resetDrag();
+ }
+ }
+
+ Rectangle { // Background of notification item
+ id: background
+ width: parent.width
+ anchors.left: parent.left
+ radius: Appearance.rounding.small
+ anchors.leftMargin: root.xOffset
+
+ Behavior on anchors.leftMargin {
+ enabled: !dragManager.dragging
+ NumberAnimation {
+ duration: Appearance.animation.elementMove.duration
+ easing.type: Appearance.animation.elementMove.type
+ easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial
+ }
+ }
+
+ color: expanded ?
+ (notificationObject.urgency == NotificationUrgency.Critical) ?
+ ColorUtils.mix(Appearance.m3colors.m3secondaryContainer, Appearance.colors.colLayer2, 0.35) :
+ (Appearance.m3colors.m3surfaceContainerHigh) :
+ ColorUtils.transparentize(Appearance.m3colors.m3surfaceContainerHighest)
+
+ implicitHeight: expanded ? (contentColumn.implicitHeight + padding * 2) : summaryRow.implicitHeight
+ Behavior on implicitHeight {
+ animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
+ }
+
+ ColumnLayout { // Content column
+ id: contentColumn
+ anchors.fill: parent
+ anchors.margins: expanded ? root.padding : 0
+ spacing: 3
+
+ Behavior on anchors.margins {
+ animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
+ }
+
+ RowLayout { // Summary row
+ id: summaryRow
+ visible: !root.onlyNotification || !root.expanded
+ Layout.fillWidth: true
+ implicitHeight: summaryText.implicitHeight
+ // Layout.fillWidth: true
+ StyledText {
+ id: summaryText
+ visible: !root.onlyNotification
+ font.pixelSize: root.fontSize
+ color: Appearance.colors.colOnLayer2
+ elide: Text.ElideRight
+ text: root.notificationObject.summary || ""
+ }
+ StyledText {
+ opacity: !root.expanded ? 1 : 0
+ visible: opacity > 0
+ Behavior on opacity {
+ animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
+ }
+ Layout.fillWidth: true
+ font.pixelSize: root.fontSize
+ color: Appearance.colors.colSubtext
+ elide: Text.ElideRight
+ textFormat: Text.StyledText
+ text: notificationObject.body.replace(/
0
+
+ StyledText { // Notification body (expanded)
+ id: notificationBodyText
+ Behavior on opacity {
+ animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
+ }
+ Layout.fillWidth: true
+ font.pixelSize: root.fontSize
+ color: Appearance.colors.colSubtext
+ wrapMode: Text.Wrap
+ elide: Text.ElideRight
+ textFormat: Text.RichText
+ text: `` +
+ `${notificationObject.body.replace(/\n/g, "
")}`
+ }
+
+ Flickable { // Notification actions
+ id: actionsFlickable
+ Layout.fillWidth: true
+ implicitHeight: actionRowLayout.implicitHeight
+ contentWidth: actionRowLayout.implicitWidth
+ clip: true
+
+ Behavior on opacity {
+ animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
+ }
+ Behavior on height {
+ animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
+ }
+ Behavior on implicitHeight {
+ animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
+ }
+
+ RowLayout {
+ id: actionRowLayout
+ Layout.alignment: Qt.AlignBottom
+
+ Repeater {
+ id: actionRepeater
+ model: notificationObject.actions
+ NotificationActionButton {
+ Layout.fillWidth: true
+ buttonText: modelData.text
+ urgency: notificationObject.urgency
+ onClicked: {
+ Notifications.attemptInvokeAction(notificationObject.id, modelData.identifier);
+ }
+ }
+ }
+
+ NotificationActionButton {
+ Layout.fillWidth: true
+ urgency: notificationObject.urgency
+ implicitWidth: (notificationObject.actions.length == 0) ? ((actionsFlickable.width - actionRowLayout.spacing) / 2) :
+ (contentItem.implicitWidth + leftPadding + rightPadding)
+
+ onClicked: {
+ Hyprland.dispatch(`exec wl-copy '${StringUtils.shellSingleQuoteEscape(notificationObject.body)}'`)
+ copyIcon.text = "inventory"
+ copyIconTimer.restart()
+ }
+
+ Timer {
+ id: copyIconTimer
+ interval: 1500
+ repeat: false
+ onTriggered: {
+ copyIcon.text = "content_copy"
+ }
+ }
+
+ contentItem: MaterialSymbol {
+ id: copyIcon
+ iconSize: Appearance.font.pixelSize.large
+ horizontalAlignment: Text.AlignHCenter
+ color: (notificationObject.urgency == NotificationUrgency.Critical) ?
+ Appearance.m3colors.m3onSurfaceVariant : Appearance.m3colors.m3onSurface
+ text: "content_copy"
+ }
+ }
+
+ NotificationActionButton {
+ Layout.fillWidth: true
+ buttonText: qsTr("Close")
+ urgency: notificationObject.urgency
+ implicitWidth: (notificationObject.actions.length == 0) ? ((actionsFlickable.width - actionRowLayout.spacing) / 2) :
+ (contentItem.implicitWidth + leftPadding + rightPadding)
+
+ onClicked: {
+ root.destroyWithAnimation()
+ }
+
+ contentItem: MaterialSymbol {
+ iconSize: Appearance.font.pixelSize.large
+ horizontalAlignment: Text.AlignHCenter
+ color: (notificationObject.urgency == NotificationUrgency.Critical) ?
+ Appearance.m3colors.m3onSurfaceVariant : Appearance.m3colors.m3onSurface
+ text: "close"
+ }
+ }
+
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/.config/quickshell/modules/common/widgets/NotificationListView.qml b/.config/quickshell/modules/common/widgets/NotificationListView.qml
new file mode 100644
index 000000000..087e4a403
--- /dev/null
+++ b/.config/quickshell/modules/common/widgets/NotificationListView.qml
@@ -0,0 +1,31 @@
+import "root:/"
+import "root:/modules/common/"
+import "root:/modules/common/widgets"
+import "root:/services"
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Quickshell
+import Quickshell.Wayland
+import Quickshell.Hyprland
+
+StyledListView { // Scrollable window
+ id: root
+ property bool popup: false
+
+ spacing: 3
+
+ model: ScriptModel {
+ values: root.popup ? Notifications.popupAppNameList : Notifications.appNameList
+ }
+ delegate: NotificationGroup {
+ required property int index
+ required property var modelData
+ popup: root.popup
+ anchors.left: parent?.left
+ anchors.right: parent?.right
+ notificationGroup: popup ?
+ Notifications.popupGroupsByAppName[modelData] :
+ Notifications.groupsByAppName[modelData]
+ }
+}
\ No newline at end of file
diff --git a/.config/quickshell/modules/common/widgets/NotificationWidget.qml b/.config/quickshell/modules/common/widgets/NotificationWidget.qml
deleted file mode 100644
index c970fd235..000000000
--- a/.config/quickshell/modules/common/widgets/NotificationWidget.qml
+++ /dev/null
@@ -1,577 +0,0 @@
-import "root:/modules/common"
-import "root:/services"
-import "root:/modules/common/functions/string_utils.js" as StringUtils
-import "root:/modules/common/functions/color_utils.js" as ColorUtils
-import Qt5Compat.GraphicalEffects
-import QtQuick
-import QtQuick.Controls
-import QtQuick.Effects
-import QtQuick.Layouts
-import Quickshell
-import Quickshell.Io
-import Quickshell.Widgets
-import Quickshell.Hyprland
-import Quickshell.Services.Notifications
-import "./notification_utils.js" as NotificationUtils
-
-Item {
- id: root
- property var notificationObject
- property bool popup: false
- property bool expanded: false
- property bool enableAnimation: true
- property int notificationListSpacing: 5
- property int defaultTimeoutValue: 5000
-
- property var notificationXAnimation: Appearance.animation.elementMoveEnter
-
- Layout.fillWidth: true
- clip: !popup
-
- implicitHeight: notificationColumnLayout.implicitHeight + notificationListSpacing
-
- Component.onCompleted: {
- if (popup) timeoutTimer.start()
- }
-
- Timer {
- id: timeoutTimer
- interval: notificationObject.expireTimeout ?? root.defaultTimeoutValue
- repeat: false
- onTriggered: {
- root.notificationXAnimation = Appearance.animation.elementMoveExit
- Notifications.timeoutNotification(notificationObject.id);
- }
- }
-
- function destroyWithAnimation(delay = 0) {
- destroyTimer0.interval = delay
- destroyTimer0.start()
- }
-
- function toggleExpanded() {
- root.enableAnimation = true
- notificationRowWrapper.anchors.bottom = undefined
- root.expanded = !root.expanded
- }
-
- Timer {
- id: destroyTimer0
- interval: 0
- repeat: false
- onTriggered: {
- notificationRowWrapper.anchors.left = undefined
- notificationRowWrapper.anchors.right = undefined
- notificationRowWrapper.anchors.fill = undefined
- notificationBackground.anchors.left = undefined
- notificationBackground.anchors.right = undefined
- notificationBackground.anchors.fill = undefined
- notificationRowWrapper.x = width + (Appearance.sizes.hyprlandGapsOut + Appearance.sizes.elevationMargin) * 2 // Account for shadow
- notificationBackground.x = width + (Appearance.sizes.hyprlandGapsOut + Appearance.sizes.elevationMargin) * 2 // Account for shadow
- destroyTimer1.start()
- }
- }
-
- Timer {
- id: destroyTimer1
- interval: notificationXAnimation.duration
- repeat: false
- onTriggered: {
- notificationRowWrapper.anchors.top = undefined
- notificationRowWrapper.anchors.bottom = root.bottom
- Notifications.discardNotification(notificationObject.id);
- }
- }
-
- MouseArea {
- // Middle click to close
- anchors.fill: parent
- acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
- onClicked: (mouse) => {
- if (mouse.button == Qt.MiddleButton)
- root.destroyWithAnimation()
- else if (mouse.button == Qt.RightButton)
- root.toggleExpanded()
- }
-
- // Flick right to dismiss/discard
- property real startX: 0
- property real dragStartThreshold: 10
- property real dragConfirmThreshold: 70
- property bool dragStarted: false
-
- onPressed: (mouse) => {
- if (mouse.button === Qt.LeftButton) {
- startX = mouse.x
- }
- }
- onPressAndHold: (mouse) => {
- if (mouse.button === Qt.LeftButton) {
- Hyprland.dispatch(`exec wl-copy '${StringUtils.shellSingleQuoteEscape(notificationObject.body)}'`)
- notificationSummaryText.text = String.format(qsTr("{0} (copied)"), notificationObject.summary)
- }
- }
- onDragStartedChanged: () => {
- // Prevent drag focus being shifted to parent flickable
- if (root.parent.parent.parent.interactive !== undefined) root.parent.parent.parent.interactive = !dragStarted
- root.enableAnimation = !dragStarted
- }
- onReleased: (mouse) => {
- dragStarted = false
- if (mouse.button === Qt.LeftButton) {
- if (notificationRowWrapper.x > dragConfirmThreshold) {
- root.notificationXAnimation = Appearance.animation.elementMoveEnter
- root.destroyWithAnimation()
- } else {
- // Animate back if not far enough
- root.notificationXAnimation = Appearance.animation.elementMoveFast
- notificationRowWrapper.x = 0
- notificationBackground.x = 0
- }
- }
- }
- onCanceled: (mouse) => {
- dragStarted = false
- if (notificationRowWrapper.x > dragConfirmThreshold) {
- root.notificationXAnimation = Appearance.animation.elementMoveEnter
- root.destroyWithAnimation()
- } else {
- // Animate back if not far enough
- root.notificationXAnimation = Appearance.animation.elementMoveFast
- notificationRowWrapper.x = 0
- notificationBackground.x = 0
- }
- }
-
- onPositionChanged: (mouse) => {
- if (mouse.buttons & Qt.LeftButton) {
- let dx = mouse.x - startX
- if (dragStarted || dx > dragStartThreshold) {
- dragStarted = true
- notificationRowWrapper.anchors.left = undefined
- notificationRowWrapper.anchors.right = undefined
- notificationRowWrapper.anchors.fill = undefined
- notificationBackground.anchors.left = undefined
- notificationBackground.anchors.right = undefined
- notificationBackground.anchors.fill = undefined
- notificationRowWrapper.x = Math.max(0, dx)
- notificationBackground.x = Math.max(0, dx)
- }
- }
- }
- }
-
- // Background
- Item {
- id: notificationBackgroundWrapper
-
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.bottom: parent.bottom
- anchors.topMargin: notificationListSpacing
- implicitHeight: notificationColumnLayout.implicitHeight + notificationListSpacing
-
- Rectangle {
- id: notificationBackground
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.bottom: parent.bottom
- // anchors.top: parent.top
- implicitHeight: notificationColumnLayout.implicitHeight
-
- color: (notificationObject.urgency == NotificationUrgency.Critical) ?
- ColorUtils.mix(Appearance.m3colors.m3secondaryContainer, Appearance.colors.colLayer2, 0.35) : Appearance.colors.colLayer2
- radius: Appearance.rounding.normal
-
- layer.enabled: true
- layer.effect: MultiEffect {
- source: notificationBackground
- anchors.fill: notificationBackground
- shadowEnabled: popup
- shadowColor: Appearance.colors.colShadow
- shadowVerticalOffset: 1
- shadowBlur: 0.5
- }
-
- Behavior on x {
- enabled: enableAnimation
- NumberAnimation {
- duration: root.notificationXAnimation.duration
- easing.type: root.notificationXAnimation.type
- easing.bezierCurve: root.notificationXAnimation.bezierCurve
- }
- }
- }
- }
-
-
- Item {
- id: notificationRowWrapper
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.bottom: parent.bottom
- // anchors.top: parent.top
- implicitHeight: notificationColumnLayout.implicitHeight + notificationListSpacing
-
- Behavior on x {
- enabled: enableAnimation
- NumberAnimation {
- duration: root.notificationXAnimation.duration
- easing.type: root.notificationXAnimation.type
- easing.bezierCurve: root.notificationXAnimation.bezierCurve
- }
- }
-
- ColumnLayout {
- id: notificationColumnLayout
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.bottom: parent.bottom
- spacing: 0
- Item {
- Layout.fillWidth: true
- implicitHeight: notificationRowLayout.implicitHeight
- Behavior on implicitHeight {
- enabled: enableAnimation
- NumberAnimation {
- duration: Appearance.animation.elementMoveFast.duration
- easing.type: Appearance.animation.elementMoveFast.type
- easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
- }
- }
-
- RowLayout {
- id: notificationRowLayout
- anchors.top: parent.top
- anchors.left: parent.left
- anchors.right: parent.right
-
- Rectangle { // App icon
- id: iconRectangle
- implicitWidth: 47
- implicitHeight: 47
- Layout.leftMargin: 10
- Layout.topMargin: 10
- Layout.bottomMargin: 10
- Layout.alignment: Qt.AlignTop
- Layout.fillWidth: false
- radius: Appearance.rounding.full
- color: Appearance.m3colors.m3secondaryContainer
- Loader {
- id: materialSymbolLoader
- active: notificationObject.appIcon == ""
- anchors.fill: parent
- sourceComponent: MaterialSymbol {
- text: {
- const defaultIcon = NotificationUtils.findSuitableMaterialSymbol("")
- const guessedIcon = NotificationUtils.findSuitableMaterialSymbol(notificationObject.summary)
- return (notificationObject.urgency == NotificationUrgency.Critical && guessedIcon === defaultIcon) ?
- "release_alert" : guessedIcon
- }
- anchors.fill: parent
- color: (notificationObject.urgency == NotificationUrgency.Critical) ?
- ColorUtils.mix(Appearance.m3colors.m3onSecondary, Appearance.m3colors.m3onSecondaryContainer, 0.1) :
- Appearance.m3colors.m3onSecondaryContainer
- iconSize: 27
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
- }
- }
- Loader {
- id: appIconLoader
- active: notificationObject.image == "" && notificationObject.appIcon != ""
- anchors.centerIn: parent
- sourceComponent: IconImage {
- implicitSize: 33
- asynchronous: true
- source: Quickshell.iconPath(notificationObject.appIcon, "image-missing")
- }
- }
- Loader {
- id: notifImageLoader
- active: notificationObject.image != ""
- anchors.fill: parent
- sourceComponent: Item {
- anchors.fill: parent
- Image {
- id: notifImage
- anchors.fill: parent
- readonly property int size: parent.width
-
- source: notificationObject?.image
- fillMode: Image.PreserveAspectCrop
- cache: false
- antialiasing: true
- asynchronous: true
-
- width: size
- height: size
- sourceSize.width: size
- sourceSize.height: size
-
- layer.enabled: true
- layer.effect: OpacityMask {
- maskSource: Rectangle {
- width: notifImage.size
- height: notifImage.size
- radius: Appearance.rounding.full
- }
- }
- }
- Loader {
- id: notifImageAppIconLoader
- active: notificationObject.appIcon != ""
- anchors.bottom: parent.bottom
- anchors.right: parent.right
- sourceComponent: IconImage {
- implicitSize: 23
- asynchronous: true
- source: Quickshell.iconPath(notificationObject.appIcon, "image-missing")
- }
- }
- }
- }
- }
-
- ColumnLayout { // Notification content
- spacing: 0
- Layout.fillWidth: true
-
- RowLayout { // Row of summary, time and expand button
- Layout.topMargin: 10
- Layout.leftMargin: 10
- Layout.rightMargin: 10
- Layout.fillWidth: true
-
- StyledText { // Summary
- id: notificationSummaryText
- Layout.fillWidth: true
- horizontalAlignment: Text.AlignLeft
- verticalAlignment: Text.AlignBottom
- font.pixelSize: Appearance.font.pixelSize.normal
- color: Appearance.colors.colOnLayer2
- text: notificationObject.summary
- wrapMode: expanded ? Text.Wrap : Text.NoWrap
- elide: Text.ElideRight
- }
-
- CircularProgress {
- id: notificationProgress
- visible: popup
- Layout.alignment: Qt.AlignVCenter
- lineWidth: 2
- value: popup ? 1 : 0
- size: 20
- animationDuration: notificationObject.expireTimeout ?? root.defaultTimeoutValue
- easingType: Easing.Linear
-
- Component.onCompleted: {
- value = 0
- }
- }
-
- StyledText { // Time
- id: notificationTimeText
- Layout.fillWidth: false
- Layout.alignment: Qt.AlignTop
- Layout.topMargin: 3
- wrapMode: Text.Wrap
- horizontalAlignment: Text.AlignLeft
- font.pixelSize: Appearance.font.pixelSize.smaller
- color: Appearance.m3colors.m3outline
- text: NotificationUtils.getFriendlyNotifTimeString(notificationObject.time)
-
- Connections {
- target: DateTime
- function onTimeChanged() {
- notificationTimeText.text = NotificationUtils.getFriendlyNotifTimeString(notificationObject.time)
- }
- }
- }
-
- RippleButton { // Expand button
- Layout.alignment: Qt.AlignTop
- id: expandButton
- implicitWidth: 22
- implicitHeight: 22
-
- buttonRadius: Appearance.rounding.full
- colBackgroundHover: Appearance.colors.colLayer2Hover
- colRipple: Appearance.colors.colLayer2Active
-
- onClicked: {
- root.toggleExpanded()
- }
-
- contentItem: MaterialSymbol {
- anchors.centerIn: parent
- text: "keyboard_arrow_down"
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
- iconSize: Appearance.font.pixelSize.normal
- color: Appearance.colors.colOnLayer2
- rotation: expanded ? 180 : 0
- Behavior on rotation {
- NumberAnimation {
- duration: Appearance.animation.elementMoveFast.duration
- easing.type: Appearance.animation.elementMoveFast.type
- easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
- }
- }
- }
-
- }
- }
-
- StyledText { // Notification body
- id: notificationBodyText
- Layout.fillWidth: true
- Layout.leftMargin: 10
- Layout.rightMargin: 10
- Layout.bottomMargin: 10
- clip: true
-
- wrapMode: expanded ? Text.Wrap : Text.NoWrap
- elide: Text.ElideRight
- font.pixelSize: Appearance.font.pixelSize.small
- horizontalAlignment: Text.AlignLeft
- color: Appearance.m3colors.m3outline
- textFormat: expanded ? Text.RichText : Text.StyledText
- text: expanded
- ? `` +
- `${notificationObject.body.replace(/\n/g, "
")}`
- : notificationObject.body.replace(/
{
- Qt.openUrlExternally(link)
- Hyprland.dispatch("global quickshell:sidebarRightClose")
- }
- MouseArea {
- anchors.fill: parent
- acceptedButtons: Qt.NoButton // Only for hover
- hoverEnabled: true
- cursorShape: parent.hoveredLink !== "" ? Qt.PointingHandCursor : Qt.ArrowCursor
- }
- }
- }
- }
- }
-
- // Actions
- Flickable {
- id: actionsFlickable
- Layout.fillWidth: true
- // Layout.topMargin: -5
- Layout.leftMargin: 10
- Layout.rightMargin: 10
- Layout.bottomMargin: expanded ? 10 : 0
- implicitHeight: expanded ? actionRowLayout.implicitHeight : 0
- height: expanded ? actionRowLayout.implicitHeight : 0
- contentWidth: actionRowLayout.implicitWidth
-
- clip: true
- layer.enabled: true
- layer.effect: OpacityMask {
- maskSource: Rectangle {
- width: actionsFlickable.width
- height: actionsFlickable.height
- radius: Appearance.rounding.small
- }
- }
-
- opacity: expanded ? 1 : 0
- visible: opacity > 0
- Behavior on opacity {
- NumberAnimation {
- duration: Appearance.animation.elementMoveFast.duration
- easing.type: Appearance.animation.elementMoveFast.type
- easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
- }
- }
- Behavior on height {
- NumberAnimation {
- duration: Appearance.animation.elementMoveFast.duration
- easing.type: Appearance.animation.elementMoveFast.type
- easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
- }
- }
- Behavior on implicitHeight {
- NumberAnimation {
- duration: Appearance.animation.elementMoveFast.duration
- easing.type: Appearance.animation.elementMoveFast.type
- easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
- }
- }
-
- RowLayout {
- id: actionRowLayout
- Layout.alignment: Qt.AlignBottom
-
- Repeater {
- id: actionRepeater
- model: notificationObject.actions
- NotificationActionButton {
- Layout.fillWidth: true
- buttonText: modelData.text
- urgency: notificationObject.urgency
- onClicked: {
- Notifications.attemptInvokeAction(notificationObject.id, modelData.identifier);
- }
- }
- }
-
- NotificationActionButton {
- Layout.fillWidth: true
- urgency: notificationObject.urgency
- implicitWidth: (notificationObject.actions.length == 0) ? (actionsFlickable.width / 2) :
- (contentItem.implicitWidth + leftPadding + rightPadding)
-
- onClicked: {
- Hyprland.dispatch(`exec wl-copy '${StringUtils.shellSingleQuoteEscape(notificationObject.body)}'`)
- copyIcon.text = "inventory"
- copyIconTimer.restart()
- }
-
- Timer {
- id: copyIconTimer
- interval: 1500
- repeat: false
- onTriggered: {
- copyIcon.text = "content_copy"
- }
- }
-
- contentItem: MaterialSymbol {
- id: copyIcon
- iconSize: Appearance.font.pixelSize.large
- horizontalAlignment: Text.AlignHCenter
- color: (notificationObject.urgency == NotificationUrgency.Critical) ?
- Appearance.m3colors.m3onSurfaceVariant : Appearance.m3colors.m3onSurface
- text: "content_copy"
- }
- }
-
- NotificationActionButton {
- Layout.fillWidth: true
- buttonText: qsTr("Close")
- urgency: notificationObject.urgency
- implicitWidth: (notificationObject.actions.length == 0) ? (actionsFlickable.width / 2) :
- (contentItem.implicitWidth + leftPadding + rightPadding)
-
- onClicked: {
- root.destroyWithAnimation()
- }
-
- contentItem: MaterialSymbol {
- iconSize: Appearance.font.pixelSize.large
- horizontalAlignment: Text.AlignHCenter
- color: (notificationObject.urgency == NotificationUrgency.Critical) ?
- Appearance.m3colors.m3onSurfaceVariant : Appearance.m3colors.m3onSurface
- text: "close"
- }
- }
-
- }
- }
- }
- }
-}
diff --git a/.config/quickshell/modules/common/widgets/StyledListView.qml b/.config/quickshell/modules/common/widgets/StyledListView.qml
new file mode 100644
index 000000000..ed6aa9162
--- /dev/null
+++ b/.config/quickshell/modules/common/widgets/StyledListView.qml
@@ -0,0 +1,106 @@
+import "root:/"
+import "root:/modules/common/"
+import "root:/modules/common/widgets"
+import "root:/services"
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Quickshell
+import Quickshell.Wayland
+import Quickshell.Hyprland
+
+ListView { // Scrollable window
+ id: root
+ spacing: 5
+ property real removeOvershoot: 20 // Account for gaps and bouncy animations
+ property int dragIndex: -1
+ property real dragDistance: 0
+
+ function resetDrag() {
+ root.dragIndex = -1
+ root.dragDistance = 0
+ }
+
+ add: Transition {
+ animations: [
+ Appearance.animation.elementMove.numberAnimation.createObject(this, {
+ properties: "opacity,scale",
+ from: 0,
+ to: 1,
+ }),
+ ]
+ }
+
+ addDisplaced: Transition {
+ animations: [
+ Appearance.animation.elementMove.numberAnimation.createObject(this, {
+ property: "y",
+ }),
+ Appearance.animation.elementMove.numberAnimation.createObject(this, {
+ properties: "opacity,scale",
+ to: 1,
+ }),
+ ]
+ }
+
+ displaced: Transition {
+ animations: [
+ Appearance.animation.elementMove.numberAnimation.createObject(this, {
+ property: "y",
+ }),
+ Appearance.animation.elementMove.numberAnimation.createObject(this, {
+ properties: "opacity,scale",
+ to: 1,
+ }),
+ ]
+ }
+
+ move: Transition {
+ animations: [
+ Appearance.animation.elementMove.numberAnimation.createObject(this, {
+ property: "y",
+ }),
+ Appearance.animation.elementMove.numberAnimation.createObject(this, {
+ properties: "opacity,scale",
+ to: 1,
+ }),
+ ]
+ }
+ moveDisplaced: Transition {
+ animations: [
+ Appearance.animation.elementMove.numberAnimation.createObject(this, {
+ property: "y",
+ }),
+ Appearance.animation.elementMove.numberAnimation.createObject(this, {
+ properties: "opacity,scale",
+ to: 1,
+ }),
+ ]
+ }
+
+ remove: Transition {
+ animations: [
+ Appearance.animation.elementMove.numberAnimation.createObject(this, {
+ property: "x",
+ to: root.width + root.removeOvershoot,
+ }),
+ Appearance.animation.elementMove.numberAnimation.createObject(this, {
+ property: "opacity",
+ to: 0,
+ })
+ ]
+ }
+
+ // This is movement when something is removed, not removing animation!
+ removeDisplaced: Transition {
+ animations: [
+ Appearance.animation.elementMove.numberAnimation.createObject(this, {
+ property: "y",
+ }),
+ Appearance.animation.elementMove.numberAnimation.createObject(this, {
+ properties: "opacity,scale",
+ to: 1,
+ }),
+ ]
+ }
+}
diff --git a/.config/quickshell/modules/notificationPopup/NotificationPopup.qml b/.config/quickshell/modules/notificationPopup/NotificationPopup.qml
index e526a2769..122489d88 100644
--- a/.config/quickshell/modules/notificationPopup/NotificationPopup.qml
+++ b/.config/quickshell/modules/notificationPopup/NotificationPopup.qml
@@ -34,74 +34,14 @@ Scope {
color: "transparent"
implicitWidth: Appearance.sizes.notificationPopupWidth
- ListView { // Scrollable window
+ NotificationListView {
id: listview
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
+ anchors.topMargin: 5
implicitWidth: parent.width - Appearance.sizes.elevationMargin * 2
-
- add: Transition {
- animations: [
- Appearance.animation.elementMove.numberAnimation.createObject(this, {
- properties: "opacity,scale",
- from: 0,
- to: 1,
- }),
- ]
- }
-
- addDisplaced: Transition {
- animations: [
- Appearance.animation.elementMove.numberAnimation.createObject(this, {
- property: "y",
- }),
- Appearance.animation.elementMove.numberAnimation.createObject(this, {
- properties: "opacity,scale",
- to: 1,
- }),
- ]
- }
-
- displaced: Transition {
- animations: [
- Appearance.animation.elementMove.numberAnimation.createObject(this, {
- property: "y",
- }),
- ]
- }
- move: Transition {
- animations: [
- Appearance.animation.elementMove.numberAnimation.createObject(this, {
- property: "y",
- }),
- ]
- }
-
- remove: Transition {
- animations: [
- Appearance.animation.elementMove.numberAnimation.createObject(this, {
- property: "x",
- to: listview.width,
- }),
- Appearance.animation.elementMove.numberAnimation.createObject(this, {
- property: "opacity",
- to: 0,
- })
- ]
- }
-
- model: ScriptModel {
- values: Notifications.popupList.slice().reverse()
- }
- delegate: NotificationWidget {
- required property var modelData
- id: notificationWidget
- popup: true
- anchors.left: parent?.left
- anchors.right: parent?.right
- notificationObject: modelData
- }
+ popup: true
}
}
}
diff --git a/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml b/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml
index 0c6e7314b..7dd4e28c6 100644
--- a/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml
+++ b/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml
@@ -11,7 +11,7 @@ import Quickshell.Widgets
Item {
id: root
- ListView { // Scrollable window
+ NotificationListView { // Scrollable window
id: listview
anchors.left: parent.left
anchors.right: parent.right
@@ -28,68 +28,7 @@ Item {
}
}
- add: Transition {
- animations: [
- Appearance.animation.elementMove.numberAnimation.createObject(this, {
- properties: "opacity,scale",
- from: 0,
- to: 1,
- }),
- ]
- }
-
- addDisplaced: Transition {
- animations: [
- Appearance.animation.elementMove.numberAnimation.createObject(this, {
- property: "y",
- }),
- Appearance.animation.elementMove.numberAnimation.createObject(this, {
- properties: "opacity,scale",
- to: 1,
- }),
- ]
- }
-
- displaced: Transition {
- animations: [
- Appearance.animation.elementMove.numberAnimation.createObject(this, {
- property: "y",
- }),
- ]
- }
- move: Transition {
- animations: [
- Appearance.animation.elementMove.numberAnimation.createObject(this, {
- property: "y",
- }),
- ]
- }
-
- remove: Transition {
- animations: [
- Appearance.animation.elementMove.numberAnimation.createObject(this, {
- property: "x",
- to: listview.width,
- }),
- Appearance.animation.elementMove.numberAnimation.createObject(this, {
- property: "opacity",
- to: 0,
- })
- ]
- }
-
- model: ScriptModel {
- values: Notifications.list.slice().reverse()
- }
- delegate: NotificationWidget {
- required property var modelData
- id: notificationWidget
- // anchors.horizontalCenter: parent.horizontalCenter
- anchors.left: parent?.left
- anchors.right: parent?.right
- Layout.fillWidth: true
- notificationObject: modelData
- }
+ popup: false
}
// Placeholder when list is empty