import QtQuick import QtQuick.Effects import Quickshell import Quickshell.Wayland import Quickshell.Services.Notifications import "." as M PanelWindow { id: root required property var screen visible: M.NotifService.popups.length > 0 color: "transparent" WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.exclusiveZone: 0 WlrLayershell.namespace: "nova-notif-popup" anchors.top: true anchors.right: true margins.top: 0 margins.right: 8 implicitWidth: 320 implicitHeight: popupCol.implicitHeight Column { id: popupCol width: parent.width spacing: 6 Repeater { model: M.NotifService.popups.slice(0, 4) delegate: Item { id: popupItem required property var modelData required property int index width: popupCol.width height: _targetHeight * _heightScale opacity: 0 x: 320 clip: true readonly property real _targetHeight: contentCol.height + 16 property real _heightScale: 0 Component.onCompleted: slideIn.start() ParallelAnimation { id: slideIn NumberAnimation { target: popupItem property: "opacity" to: 1 duration: 250 easing.type: Easing.OutCubic } NumberAnimation { target: popupItem property: "x" to: 0 duration: 350 easing.type: Easing.OutBack easing.overshoot: 0.5 } NumberAnimation { target: popupItem property: "_heightScale" to: 1 duration: 250 easing.type: Easing.OutCubic } } // Background Rectangle { anchors.fill: parent color: M.Theme.base01 opacity: Math.max(M.Theme.barOpacity, 0.9) radius: M.Theme.radius } // Urgency accent bar Rectangle { anchors.left: parent.left anchors.top: parent.top anchors.bottom: parent.bottom width: 3 radius: M.Theme.radius color: { const u = popupItem.modelData.urgency; return u === NotificationUrgency.Critical ? M.Theme.base08 : u === NotificationUrgency.Low ? M.Theme.base04 : M.Theme.base0D; } } // Glow on critical layer.enabled: popupItem.modelData.urgency === NotificationUrgency.Critical layer.effect: MultiEffect { shadowEnabled: true shadowColor: M.Theme.base08 shadowBlur: 0.6 shadowVerticalOffset: 0 shadowHorizontalOffset: 0 } Column { id: contentCol anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top anchors.margins: 8 anchors.leftMargin: 14 spacing: 2 // App name + time Row { width: parent.width Text { text: popupItem.modelData.appName || "Notification" color: M.Theme.base04 font.pixelSize: M.Theme.fontSize - 2 font.family: M.Theme.fontFamily width: parent.width - timeLabel.width elide: Text.ElideRight } Text { id: timeLabel text: { const diff = Math.floor((Date.now() - popupItem.modelData.time) / 1000); if (diff < 5) return "now"; if (diff < 60) return diff + "s"; return Math.floor(diff / 60) + "m"; } color: M.Theme.base03 font.pixelSize: M.Theme.fontSize - 2 font.family: M.Theme.fontFamily } } // Summary Text { width: parent.width text: popupItem.modelData.summary || "" color: M.Theme.base05 font.pixelSize: M.Theme.fontSize font.family: M.Theme.fontFamily font.bold: true elide: Text.ElideRight wrapMode: Text.WordWrap maximumLineCount: 2 } // Body Text { width: parent.width text: popupItem.modelData.body || "" color: M.Theme.base04 font.pixelSize: M.Theme.fontSize - 1 font.family: M.Theme.fontFamily elide: Text.ElideRight wrapMode: Text.WordWrap maximumLineCount: 3 visible: text !== "" } // Actions Row { spacing: 6 visible: popupItem.modelData.actions.length > 0 Repeater { model: popupItem.modelData.actions delegate: Rectangle { required property var modelData width: actionText.implicitWidth + 12 height: actionText.implicitHeight + 6 radius: M.Theme.radius color: actionArea.containsMouse ? M.Theme.base02 : M.Theme.base01 border.color: M.Theme.base03 border.width: 1 Text { id: actionText anchors.centerIn: parent text: parent.modelData.text color: M.Theme.base0D font.pixelSize: M.Theme.fontSize - 2 font.family: M.Theme.fontFamily } MouseArea { id: actionArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: { parent.modelData.invoke(); M.NotifService.dismiss(popupItem.modelData.id); } } } } } } property bool _fullDismiss: false function animateDismiss(full) { _fullDismiss = !!full; slideOut.start(); } ParallelAnimation { id: slideOut NumberAnimation { target: popupItem property: "opacity" to: 0 duration: 200 easing.type: Easing.InCubic } NumberAnimation { target: popupItem property: "x" to: 320 duration: 250 easing.type: Easing.InCubic } NumberAnimation { target: popupItem property: "_heightScale" to: 0 duration: 200 easing.type: Easing.InCubic } onFinished: popupItem._fullDismiss ? M.NotifService.dismiss(popupItem.modelData.id) : M.NotifService.dismissPopup(popupItem.modelData.id) } MouseArea { anchors.fill: parent z: -1 acceptedButtons: Qt.LeftButton | Qt.RightButton onClicked: mouse => popupItem.animateDismiss(mouse.button === Qt.RightButton) } } } } }