From d8199f792a4b4377cde187ac1c7010d50a75db6e Mon Sep 17 00:00:00 2001 From: Zacharias-Brohn Date: Mon, 17 Nov 2025 15:00:22 +0100 Subject: [PATCH] **animations** oops --- Bar.qml | 4 + Daemons/NotifServer.qml | 7 - Helpers/NotifCenterSpacing.qml | 16 ++ Modules/NotificationCenter.qml | 178 ++++++++++---------- Modules/TrackedNotification.qml | 287 ++++++++++++++++++-------------- shell.qml | 2 +- 6 files changed, 275 insertions(+), 219 deletions(-) create mode 100644 Helpers/NotifCenterSpacing.qml diff --git a/Bar.qml b/Bar.qml index c5b7c7a..5b7ed3b 100644 --- a/Bar.qml +++ b/Bar.qml @@ -17,6 +17,10 @@ Scope { screen: modelData property var root: Quickshell.shellDir + NotificationCenter { + bar: bar + } + Process { id: ncProcess command: ["sh", "-c", `qs -p ${bar.root}/shell.qml ipc call root showCenter`] diff --git a/Daemons/NotifServer.qml b/Daemons/NotifServer.qml index de9679a..2c9b419 100644 --- a/Daemons/NotifServer.qml +++ b/Daemons/NotifServer.qml @@ -237,13 +237,6 @@ Singleton { } } - Component { - id: notificationPopup - TrackedNotification { - centerX: NotificationCenter.posX - } - } - Component { id: notifComp diff --git a/Helpers/NotifCenterSpacing.qml b/Helpers/NotifCenterSpacing.qml new file mode 100644 index 0000000..e5b34ed --- /dev/null +++ b/Helpers/NotifCenterSpacing.qml @@ -0,0 +1,16 @@ +pragma Singleton + +import Quickshell +import Quickshell.Io + +Singleton { + id: root + + property alias centerX: notifCenterSpacing.centerX + + JsonAdapter { + id: notifCenterSpacing + + property int centerX + } +} diff --git a/Modules/NotificationCenter.qml b/Modules/NotificationCenter.qml index 0398ca3..81d1bc9 100644 --- a/Modules/NotificationCenter.qml +++ b/Modules/NotificationCenter.qml @@ -2,6 +2,7 @@ import Quickshell import Quickshell.Hyprland import Quickshell.Widgets import Quickshell.Io +import Quickshell.Wayland import QtQuick.Layouts import QtQuick.Controls.FluentWinUI3 import QtQuick.Effects @@ -20,9 +21,11 @@ PanelWindow { left: true bottom: true } + + WlrLayershell.layer: WlrLayer.Overlay + required property PanelWindow bar property bool centerShown: false property alias posX: backgroundRect.x - property alias doNotDisturb: dndSwitch.checked visible: false mask: Region { item: backgroundRect } @@ -61,10 +64,10 @@ PanelWindow { id: showAnimation target: backgroundRect property: "x" - from: Screen.width - to: Screen.width - backgroundRect.implicitWidth - 10 - duration: 300 - easing.type: Easing.OutCubic + to: root.bar.screen.width - backgroundRect.implicitWidth - 10 + from: root.bar.screen.width + duration: MaterialEasing.expressiveEffectsTime + easing.bezierCurve: MaterialEasing.expressiveEffects onStopped: { focusGrab.active = true; } @@ -74,10 +77,10 @@ PanelWindow { id: closeAnimation target: backgroundRect property: "x" - from: Screen.width - backgroundRect.implicitWidth - 10 - to: Screen.width - duration: 300 - easing.type: Easing.OutCubic + from: root.bar.screen.width - backgroundRect.implicitWidth - 10 + to: root.bar.screen.width + duration: MaterialEasing.expressiveEffectsTime + easing.bezierCurve: MaterialEasing.expressiveEffects } HyprlandFocusGrab { @@ -89,10 +92,16 @@ PanelWindow { } } + TrackedNotification { + centerShown: root.centerShown + bar: root.bar + } + Rectangle { id: backgroundRect y: 10 x: Screen.width + z: 1 implicitWidth: 400 implicitHeight: root.height - 20 color: Config.baseBgColor @@ -114,6 +123,9 @@ PanelWindow { focus: false activeFocusOnTab: false focusPolicy: Qt.NoFocus + onCheckedChanged: { + NotifServer.dnd = dndSwitch.checked; + } } RowLayout { Layout.alignment: Qt.AlignRight | Qt.AlignVCenter @@ -176,7 +188,7 @@ PanelWindow { move: Transition { NumberAnimation { - properties: "y,x"; + properties: "x"; duration: 200; easing.type: Easing.OutCubic } @@ -215,31 +227,45 @@ PanelWindow { Anim {} } - // add: Transition { - // id: addTrans - // SequentialAnimation { - // PauseAnimation { - // duration: ( addTrans.ViewTransition.index - addTrans.ViewTransition.targetIndexes[ 0 ]) * 50 - // } - // ParallelAnimation { - // NumberAnimation { - // properties: "y"; - // from: addTrans.ViewTransition.destination.y - (height / 2); - // to: addTrans.ViewTransition.destination.y; - // duration: 100; - // easing.type: Easing.OutCubic - // } - // NumberAnimation { - // properties: "opacity"; - // from: 0; - // to: 1; - // duration: 100; - // easing.type: Easing.OutCubic - // } - // } - // } - // } - // + Behavior on y { + Anim { + duration: MaterialEasing.expressiveEffectsTime + easing.bezierCurve: MaterialEasing.expressiveEffects + } + } + + add: Transition { + id: addTrans + SequentialAnimation { + PauseAnimation { + duration: ( addTrans.ViewTransition.index - addTrans.ViewTransition.targetIndexes[ 0 ]) * 50 + } + ParallelAnimation { + NumberAnimation { + properties: "y"; + from: addTrans.ViewTransition.destination.y - (height / 2); + to: addTrans.ViewTransition.destination.y; + duration: 100; + easing.type: Easing.OutCubic + } + NumberAnimation { + properties: "opacity"; + from: 0; + to: 1; + duration: 100; + easing.type: Easing.OutCubic + } + NumberAnimation { + properties: "scale"; + from: 0.7; + to: 1.0; + duration: 100 + easing.type: Easing.InOutQuad + } + } + } + } + move: Transition { id: moveTrans NumberAnimation { @@ -281,79 +307,35 @@ PanelWindow { anchors.fill: parent hoverEnabled: true onClicked: { - groupColumn.isExpanded = false; + groupColumn.shouldShow = false; } } } } - ListView { + Repeater { id: groupListView model: ScriptModel { id: groupModel values: groupColumn.isExpanded ? groupColumn.notifications : groupColumn.notifications.slice( 0, 1 ) } - width: parent.width - spacing: 10 - height: contentHeight - contentHeight: childrenRect.height - clip: false - - pixelAligned: true - boundsBehavior: Flickable.StopAtBounds - displayMarginBeginning: 0 - displayMarginEnd: 5000 - - Behavior on height { - Anim { - duration: 20; - } - } - - add: Transition { - id: add - NumberAnimation { - properties: "y,opacity"; - duration: 100 * ( add.ViewTransition.targetIndexes.length / ( add.ViewTransition.targetIndexes.length < 3 ? 1 : 3 )); - easing.bezierCurve: MaterialEasing.expressiveDefaultSpatial - } - } - - remove: Transition { - NumberAnimation { - properties: "opacity"; - from: 1; - to: 0; - duration: 300; - easing.bezierCurve: MaterialEasing.expressiveDefaultSpatial - } - } - - displaced: Transition { - NumberAnimation { - properties: "y"; - duration: 200; - easing.bezierCurve: MaterialEasing.expressiveDefaultSpatial - } - } - - delegate: Rectangle { + Rectangle { id: groupHeader required property int index required property NotifServer.Notif modelData property alias notifHeight: groupHeader.height - property bool previewHidden: !groupColumn.isExpanded && index > 0 + property bool previewHidden: groupColumn.shouldShow && index > 0 - width: groupListView.width + width: parent.width height: contentColumn.height + 20 color: Config.baseBgColor border.color: "#555555" border.width: 1 radius: 8 - opacity: 1 - scale: 1.0 + opacity: previewHidden ? 0 : 1.0 + scale: previewHidden ? 0.7 : 1.0 Component.onCompleted: modelData.lock(this); Component.onDestruction: modelData.unlock(this); @@ -366,11 +348,35 @@ PanelWindow { groupHeader.modelData.actions[0].invoke(); } } else { + groupColumn.shouldShow = true; groupColumn.isExpanded = true; } } } + ParallelAnimation { + id: collapseAnim + running: !groupColumn.shouldShow && index > 0 + + Anim { + target: groupHeader + property: "opacity" + duration: 100 + from: 1 + to: index > 0 ? 0 : 1.0 + } + Anim { + target: groupHeader + property: "scale" + duration: 100 + from: 1 + to: index > 0 ? 0.7 : 1.0 + } + onFinished: { + groupColumn.isExpanded = false; + } + } + ParallelAnimation { running: groupHeader.modelData.closed onFinished: groupHeader.modelData.unlock(groupHeader) diff --git a/Modules/TrackedNotification.qml b/Modules/TrackedNotification.qml index 64a6b81..86421d0 100644 --- a/Modules/TrackedNotification.qml +++ b/Modules/TrackedNotification.qml @@ -1,167 +1,204 @@ import Quickshell import Quickshell.Widgets import Quickshell.Wayland +import Quickshell.Hyprland import QtQuick.Layouts import QtQuick -import Quickshell.Services.Notifications import qs.Config import qs.Daemons +import qs.Helpers PanelWindow { id: root color: "transparent" + screen: root.bar.screen anchors { top: true right: true left: true bottom: true } - mask: Region { item: backgroundRect } + mask: Region { regions: root.notifRegions } exclusionMode: ExclusionMode.Ignore WlrLayershell.layer: WlrLayer.Overlay - required property Notification notif - required property int centerX - required property list notifIndex - property int index: notifIndex.indexOf(notif.id) - property alias y: backgroundRect.y - property alias notifHeight: backgroundRect.implicitHeight - signal notifDestroy() + property list notifRegions: [] + required property bool centerShown + required property PanelWindow bar + visible: Hyprland.monitorFor(screen).focused Component.onCompleted: { - openAnim.start(); - console.log(root.index); + console.log(NotifServer.list.filter( n => n.popup ).length + " notification popups loaded."); } - Timer { - id: timeout - interval: 5000 - onTriggered: { - closeAnim.start(); + ListView { + id: notifListView + model: ScriptModel { + values: NotifServer.list.filter( n => n.popup ) } - } + anchors.top: parent.top + anchors.bottom: parent.bottom + x: root.centerShown ? root.bar.width - width - 420 : root.bar.width - width - 20 + z: 0 + anchors.topMargin: 54 + width: 400 + spacing: 10 - NumberAnimation { - id: openAnim - target: backgroundRect - property: "x" - from: root.centerX - to: root.centerX - backgroundRect.implicitWidth - 20 - duration: 200 - easing.type: Easing.InOutQuad - onStopped: { timeout.start(); } - } - - NumberAnimation { - id: closeAnim - target: backgroundRect - property: "x" - from: root.centerX - backgroundRect.implicitWidth - 20 - to: root.centerX - duration: 200 - easing.type: Easing.InOutQuad - onStopped: { - root.destroy(); - root.notifDestroy(); - } - } - - Rectangle { - id: backgroundRect - implicitWidth: 400 - implicitHeight: contentLayout.childrenRect.height + 16 - x: root.centerX - implicitWidth - 20 - y: !root.notifList[ root.index - 1 ] ? 34 + 20 : root.notifList[ root.index - 1 ].y + root.notifList[ root.index - 1 ].notifHeight + 10 - color: Config.baseBgColor - border.color: "#555555" - radius: 8 - - Behavior on y { + Behavior on x { NumberAnimation { - duration: 200 + duration: MaterialEasing.expressiveEffectsTime + easing.bezierCurve: MaterialEasing.expressiveEffects + } + } + + displaced: Transition { + NumberAnimation { + property: "y" + duration: 100 easing.type: Easing.InOutQuad } } - Column { - id: contentLayout - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: 10 - spacing: 8 - RowLayout { - spacing: 12 - IconImage { - source: root.notif.image - Layout.preferredWidth: 48 - Layout.preferredHeight: 48 - Layout.alignment: Qt.AlignHCenter | Qt.AlignLeft - visible: root.notif.image !== "" + remove: Transition { + id: hideTransition + ParallelAnimation { + NumberAnimation { + property: "opacity" + from: 1 + to: 0 + duration: 200 + easing.type: Easing.InOutQuad } - ColumnLayout { - Layout.fillWidth: true - Layout.fillHeight: true - Layout.leftMargin: 0 - Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft - - Text { - text: root.notif.appName - color: "white" - font.bold: true - font.pointSize: 14 - elide: Text.ElideRight - wrapMode: Text.NoWrap - Layout.fillWidth: true - } - - Text { - text: root.notif.summary - color: "white" - font.pointSize: 12 - font.bold: true - elide: Text.ElideRight - wrapMode: Text.WordWrap - Layout.fillWidth: true - } - + NumberAnimation { + property: "x" + to: hideTransition.ViewTransition.destination.x + 200 + duration: 200 + easing.type: Easing.InOutQuad } } - Text { - text: root.notif.body - color: "#dddddd" - font.pointSize: 14 - elide: Text.ElideRight - wrapMode: Text.WordWrap - maximumLineCount: 4 - width: parent.width - } } - Rectangle { - anchors.right: parent.right - anchors.top: parent.top - anchors.rightMargin: 6 - anchors.topMargin: 6 - width: 18 - height: 18 - color: closeArea.containsMouse ? "#FF6077" : "transparent" - radius: 9 + add: Transition { + id: showTransition + ParallelAnimation { + NumberAnimation { + property: "opacity" + from: 0 + to: 1 + duration: 200 + easing.type: Easing.InOutQuad + } - Text { - anchors.centerIn: parent - text: "✕" - color: closeArea.containsMouse ? "white" : "#888888" - font.pointSize: 12 + NumberAnimation { + property: "x" + from: showTransition.ViewTransition.destination.x + 200 + to: showTransition.ViewTransition.destination.x + duration: 200 + easing.type: Easing.InOutQuad + } + } + } + + component NotifRegion: Region { } + + Component { + id: notifRegion + NotifRegion {} + } + + delegate: Rectangle { + id: backgroundRect + required property NotifServer.Notif modelData + implicitWidth: 400 + implicitHeight: contentLayout.childrenRect.height + 16 + color: Config.baseBgColor + border.color: "#555555" + radius: 8 + + Component.onCompleted: { + root.notifRegions.push( notifRegion.createObject(root, { item: backgroundRect })); } - MouseArea { - id: closeArea - anchors.fill: parent - hoverEnabled: true - onClicked: { - root.notif.dismiss(); - root.visible = false; + Column { + id: contentLayout + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: 10 + spacing: 8 + RowLayout { + spacing: 12 + IconImage { + source: backgroundRect.modelData.image + Layout.preferredWidth: 48 + Layout.preferredHeight: 48 + Layout.alignment: Qt.AlignHCenter | Qt.AlignLeft + visible: backgroundRect.modelData.image !== "" + } + + ColumnLayout { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.leftMargin: 0 + Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + + Text { + text: backgroundRect.modelData.appName + color: "white" + font.bold: true + font.pointSize: 14 + elide: Text.ElideRight + wrapMode: Text.NoWrap + Layout.fillWidth: true + } + + Text { + text: backgroundRect.modelData.summary + color: "white" + font.pointSize: 12 + font.bold: true + elide: Text.ElideRight + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + + } + } + Text { + text: backgroundRect.modelData.body + color: "#dddddd" + font.pointSize: 14 + elide: Text.ElideRight + wrapMode: Text.WordWrap + maximumLineCount: 4 + width: parent.width + } + } + + Rectangle { + anchors.right: parent.right + anchors.top: parent.top + anchors.rightMargin: 6 + anchors.topMargin: 6 + width: 18 + height: 18 + color: closeArea.containsMouse ? "#FF6077" : "transparent" + radius: 9 + + Text { + anchors.centerIn: parent + text: "✕" + color: closeArea.containsMouse ? "white" : "#888888" + font.pointSize: 12 + } + + MouseArea { + id: closeArea + anchors.fill: parent + hoverEnabled: true + onClicked: { + backgroundRect.modelData.close(); + } } } } diff --git a/shell.qml b/shell.qml index 6db3a87..918ada3 100644 --- a/shell.qml +++ b/shell.qml @@ -6,6 +6,6 @@ import qs.Modules Scope { Bar {} Wallpaper {} - NotificationCenter {} + // NotificationCenter {} Launcher {} }