pragma ComponentBehavior: Bound import Quickshell import Quickshell.Widgets import QtQuick import ZShell.Components import ZShell.Config import ZShell.Services import qs.Components import qs.Components.Notifs import qs.Daemons import qs.Helpers import qs.Services Item { id: root readonly property int padding: Tokens.padding.smaller required property Item panels required property ScreenState screenState anchors.bottom: parent.bottom anchors.right: parent.right anchors.top: parent.top implicitHeight: { if (list.count === 0) return 0; let height = list.layoutHeight; if (panels.popouts.hasCurrent && (panels.popouts.currentCenter + (panels.popouts.current?.width / 2)) > panels.notifications.x) return 0; if (screenState.dashboard && (panels.dashboard.x + panels.dashboard.width >= panels.notifications.x)) return 0; if (screenState.osd) { const h = panels.osd.y - Tokens.spacing.small * 2 - padding * 2; if (height > h) height = h; } return Math.min(((QsWindow.window as QsWindow)?.screen?.height ?? 0) - 1 * 2, height + padding * 2); } implicitWidth: Config.notifs.sizes.width + padding * 2 Behavior on implicitHeight { Anim {} } Item { anchors.fill: parent anchors.margins: root.padding clip: true KineticFlickable { id: view anchors.fill: parent contentHeight: list.implicitHeight contentWidth: width flickableDirection: Flickable.VerticalFlick LazyListView { id: list anchors.left: parent.left anchors.right: parent.right implicitHeight: contentHeight spacing: Tokens.spacing.medium readyDelay: 1 cacheBuffer: (QsWindow.window as QsWindow)?.screen.height ?? 0 removeDuration: Tokens.anim.durations.normal useCustomViewport: true viewport: Qt.rect(0, view.contentY, width, view.height) model: ScriptModel { values: { if (!Config.notifs.showInFullscreen) { if (Hypr.monitorFor(root.panels.screen).activeWorkspace?.toplevels.values.some(t => t.lastIpcObject.fullscreen > 1) ?? false) return []; } return [...NotifServer.popupGroups]; } } delegate: NotifWrapper {} onViewportAdjustNeeded: d => { if (contentYAnim.running) contentYAnim.complete(); contentYAnim.to = Math.max(0, view.contentY + d); contentYAnim.start(); } Anim { id: contentYAnim target: view property: "contentY" } } } } component Anim: NumberAnimation { duration: Tokens.anim.durations.expressiveDefaultSpatial easing: Tokens.anim.expressiveDefaultSpatial } component NotifWrapper: Item { id: wrapper required property NotificationGroup modelData property bool expanded: Config.notifs.openExpanded readonly property bool holding: area.containsMouse || area.pressed || card.rowPressed function forEachPopup(fn: var): void { for (const n of [...card.notifs]) fn(n); } function holdPopups(): void { forEachPopup(n => n.holdPopup()); } LazyListView.trackViewport: !expanded && card.nonAnimHeight < card.implicitHeight LazyListView.preferredHeight: LazyListView.removing ? 0 : card.nonAnimHeight LazyListView.visibleHeight: LazyListView.removing ? 0 : card.implicitHeight LazyListView.onRemovingChanged: { if (!wrapper.LazyListView.removing) return; slideOut.to = (area.x >= 0 ? root.implicitWidth : -root.implicitWidth) * 2; slideOut.start(); } implicitWidth: area.implicitWidth implicitHeight: card.implicitHeight enabled: !LazyListView.removing z: LazyListView.removing ? 1 : 0 onHoldingChanged: { if (holding) holdPopups(); else forEachPopup(n => n.releasePopup()); } Connections { function onNotifsChanged(): void { if (wrapper.holding) wrapper.holdPopups(); } target: wrapper.modelData } Anim { id: slideOut target: area property: "x" duration: Tokens.anim.durations.normal easing: Tokens.anim.emphasized } Item { anchors.top: parent.top implicitWidth: area.implicitWidth implicitHeight: card.implicitHeight clip: true NotifSwipeArea { id: area implicitWidth: root.implicitWidth - root.padding * 2 implicitHeight: card.implicitHeight x: wrapper.LazyListView.adding ? implicitWidth : 0 expanded: wrapper.expanded onRequestExpand: expand => wrapper.expanded = expand onSwiped: wrapper.forEachPopup(n => n.dismissPopup()) onDismissed: wrapper.forEachPopup(n => n.dismiss()) onClicked: event => { if (!Config.notifs.actionOnClick || event.button !== Qt.LeftButton || card.notifs.length !== 1) return; const actions = card.notifs[0].actions; if (actions.length === 1) actions[0].invoke(); } NotifGroupCard { id: card group: wrapper.modelData container: view expanded: wrapper.expanded onRequestExpand: expand => wrapper.expanded = expand onSwiped: notif => notif.dismissPopup() onLinkActivated: notif => notif.dismissPopup() } } } Behavior on y { enabled: wrapper.LazyListView.ready Anim {} } } }