diff options
| author | 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> | 2025-09-20 15:47:44 +1000 |
|---|---|---|
| committer | 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> | 2025-09-20 15:47:44 +1000 |
| commit | 89d46c1b2fd1a35a3229511f745205ac4956bf9e (patch) | |
| tree | aa5c7faed9c627b6933cdc533b540eb695af9dd9 /modules | |
| parent | sidebar/notifs: sort groups by latest notif (diff) | |
| download | caelestia-shell-89d46c1b2fd1a35a3229511f745205ac4956bf9e.tar.gz caelestia-shell-89d46c1b2fd1a35a3229511f745205ac4956bf9e.tar.bz2 caelestia-shell-89d46c1b2fd1a35a3229511f745205ac4956bf9e.zip | |
sidebar/notifs: better anims + misc fixes
Diffstat (limited to 'modules')
| -rw-r--r-- | modules/lock/NotifDock.qml | 5 | ||||
| -rw-r--r-- | modules/lock/NotifGroup.qml | 4 | ||||
| -rw-r--r-- | modules/notifications/Content.qml | 2 | ||||
| -rw-r--r-- | modules/sidebar/NotifDock.qml | 134 | ||||
| -rw-r--r-- | modules/sidebar/NotifDockList.qml | 150 | ||||
| -rw-r--r-- | modules/sidebar/NotifGroup.qml | 19 | ||||
| -rw-r--r-- | modules/sidebar/NotifGroupList.qml | 22 |
7 files changed, 207 insertions, 129 deletions
diff --git a/modules/lock/NotifDock.qml b/modules/lock/NotifDock.qml index ac40319..7551e68 100644 --- a/modules/lock/NotifDock.qml +++ b/modules/lock/NotifDock.qml @@ -84,7 +84,10 @@ ColumnLayout { clip: true model: ScriptModel { - values: [...new Set(Notifs.list.map(notif => notif.appName))].reverse() + values: { + const list = Notifs.notClosed.map(n => [n.appName, null]); + return [...new Map(list).keys()]; + } } delegate: NotifGroup {} diff --git a/modules/lock/NotifGroup.qml b/modules/lock/NotifGroup.qml index 15342d4..2a08c26 100644 --- a/modules/lock/NotifGroup.qml +++ b/modules/lock/NotifGroup.qml @@ -16,7 +16,7 @@ StyledRect { required property string modelData - readonly property list<var> notifs: Notifs.list.filter(notif => notif.appName === modelData).reverse() + readonly property list<var> notifs: Notifs.list.filter(notif => notif.appName === modelData) 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" @@ -227,7 +227,7 @@ StyledRect { ParallelAnimation { running: notif.modelData.closed - onFinished: notif.modelData.lock(notif) + onFinished: notif.modelData.unlock(notif) Anim { target: notif diff --git a/modules/notifications/Content.qml b/modules/notifications/Content.qml index 019e922..2d4590e 100644 --- a/modules/notifications/Content.qml +++ b/modules/notifications/Content.qml @@ -55,7 +55,7 @@ Item { id: list model: ScriptModel { - values: Notifs.popups.filter(n => !n.closed).reverse() + values: Notifs.popups.filter(n => !n.closed) } anchors.fill: parent diff --git a/modules/sidebar/NotifDock.qml b/modules/sidebar/NotifDock.qml index 0a6eeaa..e3267c3 100644 --- a/modules/sidebar/NotifDock.qml +++ b/modules/sidebar/NotifDock.qml @@ -89,133 +89,23 @@ Item { } } - StyledListView { + StyledFlickable { id: view anchors.fill: parent - spacing: Appearance.spacing.small - - model: ScriptModel { - values: { - const list = Notifs.list.filter(n => !n.closed).sort((a, b) => b.time - a.time).map(n => [n.appName, null]); - return [...new Map(list).keys()]; - } - } + flickableDirection: Flickable.VerticalFlick + contentWidth: width + contentHeight: notifList.implicitHeight StyledScrollBar.vertical: StyledScrollBar { flickable: view } - delegate: MouseArea { - id: notif - - required property int index - required property string modelData - - property int startY - - function closeAll(): void { - for (const n of Notifs.list.filter(n => !n.closed && n.appName === modelData)) - n.close(); - } - - implicitWidth: root.width - implicitHeight: notifInner.implicitHeight - - hoverEnabled: true - cursorShape: pressed ? Qt.ClosedHandCursor : undefined - acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton - preventStealing: true + NotifDockList { + id: notifList - drag.target: this - drag.axis: Drag.XAxis - - onPressed: event => { - if (event.button === Qt.LeftButton) - startY = event.y; - else if (event.button === Qt.RightButton) - notifInner.toggleExpand(); - else if (event.button === Qt.MiddleButton) - closeAll(); - } - onPositionChanged: event => { - if (pressed) { - const diffY = event.y - startY; - if (Math.abs(diffY) > Config.notifs.expandThreshold) - notifInner.toggleExpand(diffY > 0); - } - } - onReleased: event => { - if (Math.abs(x) < width * Config.notifs.clearThreshold) - x = 0; - else - closeAll(); - } - - NotifGroup { - id: notifInner - - modelData: notif.modelData - props: root.props - } - - Behavior on x { - Anim { - duration: Appearance.anim.durations.expressiveDefaultSpatial - easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial - } - } - } - - add: Transition { - Anim { - property: "opacity" - from: 0 - to: 1 - } - Anim { - property: "scale" - from: 0 - to: 1 - duration: Appearance.anim.durations.expressiveDefaultSpatial - easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial - } - } - - remove: Transition { - Anim { - property: "opacity" - to: 0 - } - Anim { - property: "scale" - to: 0.6 - } - } - - move: Transition { - Anim { - properties: "opacity,scale" - to: 1 - } - Anim { - property: "y" - duration: Appearance.anim.durations.expressiveDefaultSpatial - easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial - } - } - - displaced: Transition { - Anim { - properties: "opacity,scale" - to: 1 - } - Anim { - property: "y" - duration: Appearance.anim.durations.expressiveDefaultSpatial - easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial - } + props: root.props } } } @@ -226,8 +116,14 @@ Item { repeat: true interval: 50 onTriggered: { - if (root.notifCount > 0) - Notifs.list.find(n => !n.closed).close(); + let next = null; + for (let i = 0; i < notifList.repeater.count; i++) { + next = notifList.repeater.itemAt(i); + if (!next?.closed) + break; + } + if (next) + next.closeAll(); else stop(); } diff --git a/modules/sidebar/NotifDockList.qml b/modules/sidebar/NotifDockList.qml new file mode 100644 index 0000000..0fd3464 --- /dev/null +++ b/modules/sidebar/NotifDockList.qml @@ -0,0 +1,150 @@ +pragma ComponentBehavior: Bound + +import qs.components +import qs.services +import qs.config +import Quickshell +import QtQuick + +Item { + id: root + + required property Props props + + readonly property alias repeater: repeater + readonly property int spacing: Appearance.spacing.small + property bool flag + + anchors.left: parent.left + anchors.right: parent.right + implicitHeight: { + const item = repeater.itemAt(repeater.count - 1); + return item ? item.y + item.implicitHeight : 0; + } + + Repeater { + id: repeater + + model: ScriptModel { + values: { + const list = Notifs.list.map(n => [n.appName, null]); + return [...new Map(list).keys()]; + } + onValuesChanged: root.flagChanged() + } + + MouseArea { + id: notif + + required property int index + required property string modelData + + readonly property bool closed: notifInner.notifCount === 0 + readonly property alias nonAnimHeight: notifInner.nonAnimHeight + property int startY + + function closeAll(): void { + for (const n of Notifs.notClosed.filter(n => n.appName === modelData)) + n.close(); + } + + y: { + root.flag; // Force update + let y = 0; + for (let i = 0; i < index; i++) { + const item = repeater.itemAt(i); + if (!item.closed) + y += item.nonAnimHeight + root.spacing; + } + return y; + } + + implicitWidth: root.width + implicitHeight: notifInner.implicitHeight + + hoverEnabled: true + cursorShape: pressed ? Qt.ClosedHandCursor : undefined + acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton + preventStealing: true + + drag.target: this + drag.axis: Drag.XAxis + + onPressed: event => { + startY = event.y; + if (event.button === Qt.RightButton) + notifInner.toggleExpand(!notifInner.expanded); + else if (event.button === Qt.MiddleButton) + closeAll(); + } + onPositionChanged: event => { + if (pressed) { + const diffY = event.y - startY; + if (Math.abs(diffY) > Config.notifs.expandThreshold) + notifInner.toggleExpand(diffY > 0); + } + } + onReleased: event => { + if (Math.abs(x) < width * Config.notifs.clearThreshold) + x = 0; + else + closeAll(); + } + + ParallelAnimation { + running: true + + Anim { + target: notif + property: "opacity" + from: 0 + to: 1 + } + Anim { + target: notif + property: "scale" + from: 0 + to: 1 + duration: Appearance.anim.durations.expressiveDefaultSpatial + easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial + } + } + + ParallelAnimation { + running: notif.closed + + Anim { + target: notif + property: "opacity" + to: 0 + } + Anim { + target: notif + property: "scale" + to: 0.6 + } + } + + NotifGroup { + id: notifInner + + modelData: notif.modelData + props: root.props + } + + Behavior on x { + Anim { + duration: Appearance.anim.durations.expressiveDefaultSpatial + easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial + } + } + + Behavior on y { + Anim { + duration: Appearance.anim.durations.expressiveDefaultSpatial + easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial + } + } + } + } +} diff --git a/modules/sidebar/NotifGroup.qml b/modules/sidebar/NotifGroup.qml index a87deb6..f0b168d 100644 --- a/modules/sidebar/NotifGroup.qml +++ b/modules/sidebar/NotifGroup.qml @@ -16,11 +16,17 @@ StyledRect { required property string modelData required property Props props - readonly property list<var> notifs: Notifs.list.filter(notif => notif.appName === modelData).reverse() + readonly property list<var> notifs: Notifs.list.filter(n => n.appName === modelData) + readonly property int notifCount: notifs.reduce((acc, n) => n.closed ? acc : acc + 1, 0) 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 int urgency: notifs.some(n => n.urgency === NotificationUrgency.Critical) ? NotificationUrgency.Critical : notifs.some(n => n.urgency === NotificationUrgency.Normal) ? NotificationUrgency.Normal : NotificationUrgency.Low + readonly property int nonAnimHeight: { + const headerHeight = header.implicitHeight + (root.expanded ? Math.round(Appearance.spacing.small / 2) : 0); + const columnHeight = headerHeight + notifList.nonAnimHeight + column.Layout.topMargin + column.Layout.bottomMargin; + return Math.round(Math.max(Config.notifs.sizes.image, columnHeight) + Appearance.padding.normal * 2); + } readonly property bool expanded: props.expandedNotifs.includes(modelData) function toggleExpand(expand: bool): void { @@ -32,6 +38,11 @@ StyledRect { } } + Component.onDestruction: { + if (notifCount === 0 && expanded) + props.expandedNotifs.splice(props.expandedNotifs.indexOf(modelData), 1); + } + anchors.left: parent?.left anchors.right: parent?.right implicitHeight: content.implicitHeight + Appearance.padding.normal * 2 @@ -133,6 +144,8 @@ StyledRect { spacing: 0 RowLayout { + id: header + Layout.bottomMargin: root.expanded ? Math.round(Appearance.spacing.small / 2) : 0 Layout.fillWidth: true spacing: Appearance.spacing.smaller @@ -178,7 +191,7 @@ StyledRect { Layout.leftMargin: Appearance.padding.small / 2 animate: true - text: root.notifs.reduce((acc, n) => n.closed ? acc : acc + 1, 0) + text: root.notifCount color: root.urgency === NotificationUrgency.Critical ? Colours.palette.m3onError : Colours.palette.m3onSurface font.pointSize: Appearance.font.size.small } @@ -198,6 +211,8 @@ StyledRect { } NotifGroupList { + id: notifList + props: root.props notifs: root.notifs expanded: root.expanded diff --git a/modules/sidebar/NotifGroupList.qml b/modules/sidebar/NotifGroupList.qml index 3c45b0b..162c343 100644 --- a/modules/sidebar/NotifGroupList.qml +++ b/modules/sidebar/NotifGroupList.qml @@ -14,16 +14,23 @@ Item { required property list<var> notifs required property bool expanded + readonly property real nonAnimHeight: { + let h = -root.spacing; + for (let i = 0; i < repeater.count; i++) { + const item = repeater.itemAt(i); + if (!item.modelData.closed) + h += item.nonAnimHeight + root.spacing; + } + return h; + } + readonly property int spacing: Math.round(Appearance.spacing.small / 2) property bool flag signal requestToggleExpand(expand: bool) Layout.fillWidth: true - implicitHeight: { - const item = repeater.itemAt(repeater.count - 1); - return item ? item.y + item.implicitHeight : 0; - } + implicitHeight: nonAnimHeight Repeater { id: repeater @@ -145,4 +152,11 @@ Item { } } } + + Behavior on implicitHeight { + Anim { + duration: Appearance.anim.durations.expressiveDefaultSpatial + easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial + } + } } |