pragma ComponentBehavior: Bound import qs.config import qs.custom import qs.services import qs.util import Quickshell import Quickshell.Services.Notifications import QtQuick import QtQuick.Layouts import qs.modules.notifications as N ColumnLayout { id: root anchors.fill: parent spacing: 7 readonly property int notifCount: (Notifs.list && Notifs.list.length !== undefined) ? Notifs.list.length : ((Notifs.list && Notifs.list.count !== undefined) ? Notifs.list.count : 0) function notifAt(i) { if (!Notifs.list) return undefined; if (typeof Notifs.list.get === 'function') return Notifs.list.get(i); return Notifs.list[i]; } function scrollToTop(): void { if (notifScroll && notifScroll.contentItem && notifScroll.contentItem.contentY !== undefined) { notifScroll.contentItem.contentY = 0; } } RowLayout { Layout.alignment: Qt.AlignTop Layout.margins: 10 Layout.fillWidth: true spacing: 7 MaterialIcon { Layout.alignment: Qt.AlignVCenter id: icon text: { if (Notifs.dnd) return "notifications_off"; if (notifCount > 0) return "notifications_active"; return "notifications"; } fill: { if (Notifs.dnd) return 0; if (notifCount > 0) return 1; return 0; } color: Notifs.dnd ? Config.colors.error : Config.colors.notification font.pointSize: Config.font.size.larger animate: true Behavior on color { SequentialAnimation { PauseAnimation { duration: icon.animateDuration / 2 } PropertyAction {} } } } CustomText { Layout.alignment: Qt.AlignVCenter Layout.fillWidth: true text: notifCount > 0 ? qsTr("%1 notifications").arg(notifCount) : qsTr("No notifications") font.weight: 600 font.pointSize: Config.font.size.normal animate: true } CustomText { Layout.alignment: Qt.AlignVCenter text: qsTr("Do Not Disturb") } CustomSwitch { Layout.alignment: Qt.AlignVCenter Layout.leftMargin: 5 Layout.rightMargin: 5 bg: Config.colors.containerAlt accent: Color.mute(Config.colors.notification) checked: Notifs.dnd onToggled: Notifs.toggleDnd() } } Item { Layout.fillWidth: true Layout.fillHeight: true Layout.leftMargin: 10 Layout.rightMargin: 10 Layout.bottomMargin: 10 CustomListView { id: notifScroll anchors.fill: parent anchors.margins: 10 anchors.rightMargin: 5 spacing: 12 clip: true CustomScrollBar.vertical: CustomScrollBar { flickable: notifScroll } model: ScriptModel { values: [...Notifs.list].reverse() } delegate: Item { id: wrapper required property int index required property var modelData readonly property alias nonAnimHeight: notif.nonAnimHeight width: 405 height: notif.implicitHeight ListView.onRemove: removeAnim.start() SequentialAnimation { id: removeAnim PropertyAction { target: wrapper property: "ListView.delayRemove" value: true } PropertyAction { target: wrapper property: "enabled" value: false } PropertyAction { target: wrapper property: "implicitHeight" value: 0 } PropertyAction { target: wrapper property: "z" value: 1 } Anim { target: notif property: "x" to: (notif.x >= 0 ? Config.notifs.width : -Config.notifs.width) * 2 easing.bezierCurve: Config.anim.curves.emphasized } PropertyAction { target: wrapper property: "ListView.delayRemove" value: false } } N.Notification { id: notif width: parent.width notif: wrapper.modelData color: wrapper.modelData.urgency === NotificationUrgency.Critical ? Config.colors.errorBg : Config.colors.containerAlt inPopup: false } } add: Transition { Anim { property: "x" from: Config.notifs.width to: 0 easing.bezierCurve: Config.anim.curves.emphasizedDecel } } move: Transition { NotifAnim { property: "y" } } displaced: Transition { NotifAnim { property: "y" } } } } Item { Layout.alignment: Qt.AlignBottom Layout.fillWidth: true height: 0 CustomRect { anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.bottom anchors.bottomMargin: bottomMargin property real bottomMargin: notifCount > 0 ? 12 : -4 opacity: notifCount > 0 ? 1 : 0 visible: opacity > 0 implicitWidth: clearBtn.implicitWidth + 32 implicitHeight: clearBtn.implicitHeight + 20 radius: 25 color: Config.colors.inactive Behavior on opacity { Anim {} } Behavior on bottomMargin { Anim {} } StateLayer { anchors.fill: parent function onClicked(): void { for (let i = root.notifCount - 1; i >= 0; i--) { const n = root.notifAt(i); n?.notification?.dismiss(); } } } RowLayout { id: clearBtn anchors.centerIn: parent spacing: 7 MaterialIcon { id: clearIcon text: "clear_all" color: Config.colors.secondary } CustomText { text: qsTr("Clear all") color: Config.colors.secondary } } } } component NotifAnim: Anim { duration: Config.anim.durations.expressiveDefaultSpatial easing.bezierCurve: Config.anim.curves.expressiveDefaultSpatial } }