import qs.config import qs.custom import qs.services import Quickshell import Quickshell.Widgets import QtQuick Item { id: root required property PersistentProperties uiState required property Item panels readonly property int padding: 15 readonly property var monitor: Hypr.monitorFor(QsWindow.window.screen) readonly property var notifs: Notifs.list.filter(n => n.popup === monitor) anchors.top: parent.top anchors.bottom: parent.bottom anchors.right: parent.right implicitWidth: implicitHeight > 0 ? Config.notifs.width + padding * 2 : 0 implicitHeight: { const count = list.count; if (count === 0) return 0; let height = (count - 1) * 10 + padding * 2; for (let i = 0; i < count; i++) height += list.itemAtIndex(i)?.nonAnimHeight ?? 0; if (uiState.osd) { const h = panels.osd.y - Config.border.rounding * 2; if (height > h) height = h; } return Math.min((QsWindow.window?.screen?.height ?? 0) - Config.bar.height - Config.border.thickness - padding, height); } ClippingWrapperRectangle { anchors.fill: parent anchors.margins: root.padding color: "transparent" radius: 17 CustomListView { id: list model: ScriptModel { values: root.notifs } anchors.fill: parent orientation: Qt.Vertical spacing: 0 cacheBuffer: QsWindow.window?.screen.height ?? 0 delegate: Item { id: wrapper required property Notifs.Notif modelData required property int index readonly property alias nonAnimHeight: notif.nonAnimHeight property int idx onIndexChanged: { if (index !== -1) idx = index; } implicitWidth: notif.implicitWidth implicitHeight: notif.implicitHeight + (idx === 0 ? 0 : 10) 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 } } ClippingRectangle { anchors.top: parent.top anchors.topMargin: wrapper.idx === 0 ? 0 : 10 color: "transparent" radius: notif.radius implicitWidth: notif.implicitWidth implicitHeight: notif.implicitHeight Notification { id: notif notif: wrapper.modelData } } } move: Transition { NotifAnim { property: "y" } } displaced: Transition { NotifAnim { property: "y" } } ExtraIndicator { anchors.top: parent.top extra: { const count = list.count; if (count === 0) return 0; const scrollY = list.contentY; let height = 0; for (let i = 0; i < count; i++) { height += (list.itemAtIndex(i)?.nonAnimHeight ?? 0) + 10; if (height - 10 >= scrollY) return i; } return count; } } ExtraIndicator { anchors.bottom: parent.bottom extra: { const count = list.count; if (count === 0) return 0; const scrollY = list.contentHeight - (list.contentY + list.height); let height = 0; for (let i = count - 1; i >= 0; i--) { height += (list.itemAtIndex(i)?.nonAnimHeight ?? 0) + 10; if (height - 10 >= scrollY) return count - i - 1; } return 0; } } } } Behavior on implicitHeight { NotifAnim {} } component NotifAnim: Anim { duration: Config.anim.durations.expressiveDefaultSpatial easing.bezierCurve: Config.anim.curves.expressiveDefaultSpatial } }