noctalia-shell/Widgets/NotificationManager.qml
2025-07-11 14:14:28 +02:00

178 lines
5.2 KiB
QML

import QtQuick
import QtQuick.Controls
import Quickshell
import qs.Settings
PanelWindow {
id: window
width: 350
implicitHeight: notificationColumn.implicitHeight + 20
color: "transparent"
visible: false
screen: Quickshell.primaryScreen
focusable: false
anchors.top: true
anchors.right: true
margins.top: -20 // keep as you want
margins.right: 6
property var notifications: []
property int maxVisible: 5
property int spacing: 10
function addNotification(notification) {
var notifObj = {
id: notification.id,
appName: notification.appName || "Notification",
summary: notification.summary || "",
body: notification.body || "",
rawNotification: notification
};
notifications.unshift(notifObj);
if (notifications.length > maxVisible) {
notifications = notifications.slice(0, maxVisible);
}
visible = true;
notificationsChanged();
}
function dismissNotification(id) {
notifications = notifications.filter(n => n.id !== id);
if (notifications.length === 0) {
visible = false;
}
notificationsChanged();
}
Column {
id: notificationColumn
anchors.right: parent.right
spacing: window.spacing
width: parent.width
clip: false // prevent clipping during animation
Repeater {
model: notifications
delegate: Rectangle {
id: notificationDelegate
width: parent.width
height: contentColumn.height + 20
color: Theme.backgroundPrimary
radius: 20
opacity: 1
Column {
id: contentColumn
width: parent.width - 20
anchors.centerIn: parent
spacing: 5
Text {
text: modelData.appName
width: parent.width
color: "white"
font.bold: true
font.pixelSize: 14
elide: Text.ElideRight
}
Text {
text: modelData.summary
width: parent.width
color: "#eeeeee"
font.pixelSize: 13
wrapMode: Text.Wrap
visible: text !== ""
}
Text {
text: modelData.body
width: parent.width
color: "#cccccc"
font.pixelSize: 12
wrapMode: Text.Wrap
visible: text !== ""
}
}
Timer {
interval: 4000
running: true
onTriggered: {
dismissAnimation.start();
if (modelData.rawNotification) {
modelData.rawNotification.expire();
}
}
}
MouseArea {
anchors.fill: parent
onClicked: {
dismissAnimation.start();
if (modelData.rawNotification) {
modelData.rawNotification.dismiss();
}
}
}
ParallelAnimation {
id: dismissAnimation
NumberAnimation {
target: notificationDelegate
property: "opacity"
to: 0
duration: 300
}
NumberAnimation {
target: notificationDelegate
property: "height"
to: 0
duration: 300
}
onFinished: window.dismissNotification(modelData.id)
}
Component.onCompleted: {
opacity = 0;
height = 0;
appearAnimation.start();
}
ParallelAnimation {
id: appearAnimation
NumberAnimation {
target: notificationDelegate
property: "opacity"
to: 1
duration: 300
}
NumberAnimation {
target: notificationDelegate
property: "height"
to: contentColumn.height + 20
duration: 300
}
}
}
}
}
onNotificationsChanged: {
height = notificationColumn.implicitHeight + 20
}
Connections {
target: Quickshell
function onScreensChanged() {
if (window.screen) {
x = window.screen.width - width - 20
// y stays as it is (margins.top = -20)
}
}
}
}