summaryrefslogtreecommitdiff
path: root/modules/notifications/NotificationToasts.qml
blob: 96fe817f6131247d697cda76add7d0b9ee209c04 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
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
            }
        }
    }
}