nosignal-shell/modules/notifications/NotificationToasts.qml

186 lines
5.1 KiB
QML

pragma ComponentBehavior: Bound
import qs.components
import qs.config
import qs.services
import Quickshell
import Quickshell.Widgets
import QtQuick
Item {
id: root
required property Item panels
readonly property int spacing: Appearance.spacing.small
readonly property int maxToasts: 5
readonly property bool listVisible: panels.notifications.content.shouldShow
property bool flag
property var activeToasts: new Set()
anchors.top: parent.top
anchors.right: parent.right
anchors.margins: Appearance.padding.normal
implicitWidth: Config.notifs.sizes.width
implicitHeight: {
if (listVisible)
return 0;
let height = -spacing;
for (let i = 0; i < repeater.count; i++) {
const item = repeater.itemAt(i) as ToastWrapper;
if (item && !item.modelData.closed && !item.previewHidden)
height += item.implicitHeight + spacing;
}
return height;
}
opacity: listVisible ? 0 : 1
visible: opacity > 0
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
}
}
Repeater {
id: repeater
model: ScriptModel {
values: {
const toasts = [];
let visibleCount = 0;
for (const notif of Notifs.list) {
if (notif.showAsToast) {
root.activeToasts.add(notif);
}
if (notif.closed) {
root.activeToasts.delete(notif);
}
}
for (const notif of Notifs.list) {
if (root.activeToasts.has(notif)) {
toasts.push(notif);
if (notif.showAsToast && !notif.closed) {
visibleCount++;
if (visibleCount > root.maxToasts)
break;
}
}
}
return toasts;
}
onValuesChanged: root.flagChanged()
}
ToastWrapper {}
}
component ToastWrapper: MouseArea {
id: toast
required property int index
required property Notifs.Notif modelData
readonly property bool previewHidden: {
let extraHidden = 0;
for (let i = 0; i < index; i++) {
const item = repeater.itemAt(i);
if (item && item.modelData.closed)
extraHidden++;
}
return index >= root.maxToasts + extraHidden;
}
opacity: modelData.closed || previewHidden || !modelData.showAsToast ? 0 : 1
scale: modelData.closed || previewHidden || !modelData.showAsToast ? 0.7 : 1
anchors.topMargin: {
root.flag;
let margin = 0;
for (let i = 0; i < index; i++) {
const item = repeater.itemAt(i) as ToastWrapper;
if (item && !item.modelData.closed && !item.previewHidden)
margin += item.implicitHeight + root.spacing;
}
return margin;
}
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
implicitHeight: toastInner.implicitHeight
acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
onClicked: {
modelData.showAsToast = false;
modelData.close();
}
Component.onCompleted: modelData.lock(this)
onPreviewHiddenChanged: {
if (initAnim.running && previewHidden)
initAnim.stop();
}
Anim {
id: initAnim
Component.onCompleted: running = !toast.previewHidden
target: toast
properties: "opacity,scale"
from: 0
to: 1
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
ParallelAnimation {
running: toast.modelData.closed || (!toast.modelData.showAsToast && !toast.modelData.closed)
onStarted: toast.anchors.topMargin = toast.anchors.topMargin
onFinished: {
if (toast.modelData.closed)
toast.modelData.unlock(toast);
}
Anim {
target: toast
property: "opacity"
to: 0
}
Anim {
target: toast
property: "scale"
to: 0.7
}
}
NotificationToast {
id: toastInner
modelData: toast.modelData
}
Behavior on opacity {
Anim {}
}
Behavior on scale {
Anim {}
}
Behavior on anchors.topMargin {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
}
}