summaryrefslogtreecommitdiff
path: root/accounts/gkleen@sif/shell/quickshell/NotificationDisplay.qml
diff options
context:
space:
mode:
authorGregor Kleen <gkleen@yggdrasil.li>2025-09-12 22:01:51 +0200
committerGregor Kleen <gkleen@yggdrasil.li>2025-09-12 22:01:51 +0200
commit666464567055a2e4ba9f6bb310e901cdc27977f7 (patch)
tree45e626dc591803925880230a3e06d568e6a5fa48 /accounts/gkleen@sif/shell/quickshell/NotificationDisplay.qml
parent1ff0e9ecbef79e1b3592cd4a68ce3e90c8536bdb (diff)
downloadnixos-666464567055a2e4ba9f6bb310e901cdc27977f7.tar
nixos-666464567055a2e4ba9f6bb310e901cdc27977f7.tar.gz
nixos-666464567055a2e4ba9f6bb310e901cdc27977f7.tar.bz2
nixos-666464567055a2e4ba9f6bb310e901cdc27977f7.tar.xz
nixos-666464567055a2e4ba9f6bb310e901cdc27977f7.zip
...
Diffstat (limited to 'accounts/gkleen@sif/shell/quickshell/NotificationDisplay.qml')
-rw-r--r--accounts/gkleen@sif/shell/quickshell/NotificationDisplay.qml302
1 files changed, 302 insertions, 0 deletions
diff --git a/accounts/gkleen@sif/shell/quickshell/NotificationDisplay.qml b/accounts/gkleen@sif/shell/quickshell/NotificationDisplay.qml
new file mode 100644
index 00000000..589c36e5
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/NotificationDisplay.qml
@@ -0,0 +1,302 @@
1import QtQml
2import QtQml.Models
3import QtQuick
4import Quickshell
5import Quickshell.Widgets
6import Quickshell.Wayland
7import qs.Services
8import QtQuick.Layouts
9import Quickshell.Services.Notifications
10
11Scope {
12 readonly property ShellScreen activeScreen: Array.from(Quickshell.screens).find(screen => screen.name === Array.from(NiriService.workspaces).find(ws => ws.is_focused)?.output) ?? null
13
14 Instantiator {
15 id: notifsRepeater
16
17 model: ScriptModel {
18 values: NotificationManager.displayInhibited ? [] : [...NotificationManager.groups]
19 }
20
21 delegate: PanelWindow {
22 id: notifWindow
23
24 required property var modelData
25 required property var index
26
27 property int activeIx: modelData.length - 1
28 onModelDataChanged: {
29 notifWindow.activeIx = modelData.length - 1;
30 }
31
32 property color textColor: {
33 if (notifWindow.modelData?.[notifWindow.activeIx]?.urgency == NotificationUrgency.Low)
34 return "#ff999999";
35 return "white";
36 }
37 property color backgroundColor: {
38 if (notifWindow.modelData?.[notifWindow.activeIx]?.urgency == NotificationUrgency.Critical)
39 return "#dd900000";
40 return "black";
41 }
42
43 anchors {
44 right: true
45 top: true
46 }
47
48 readonly property real spaceAbove: {
49 var res = 0;
50 for (let i = 0; i < notifWindow.index; i++) {
51 res += notifsRepeater.objectAt(i).height + 8;
52 }
53 return res;
54 }
55
56 margins {
57 right: 26 + 8
58 top: 8 + spaceAbove
59 }
60
61 color: "transparent"
62
63 implicitHeight: Math.max(notifCount.visible ? notifCount.contentHeight : 0, notifSummary.contentHeight) + (notifBody.visible ? 8 + notifBody.contentHeight : 0) + (notifActions.visible ? 8 + notifActions.height : 0) + 16
64 implicitWidth: 400
65
66 WrapperMouseArea {
67 enabled: true
68
69 anchors.fill: parent
70
71 cursorShape: Qt.PointingHandCursor
72
73 onClicked: {
74 for (const notif of notifWindow.modelData)
75 notif.dismiss();
76 }
77
78 property real angleRem: 0
79 property real sensitivity: 1 / 120
80 onWheel: event => {
81 angleRem += event.angleDelta.y;
82 const d = Math.round(angleRem * sensitivity);
83 angleRem -= d / sensitivity;
84 notifWindow.activeIx = ((notifWindow.modelData?.length ?? 1) + notifWindow.activeIx - d) % (notifWindow.modelData?.length ?? 1);
85 }
86
87 Rectangle {
88 color: notifWindow.backgroundColor
89 anchors.fill: parent
90 border {
91 color: Qt.hsla(195/360, 1, 0.45, 1)
92 width: 2
93 }
94
95 GridLayout {
96 id: notifLayout
97
98 width: 400 - 16
99 anchors.fill: parent
100 anchors.margins: 8
101 columnSpacing: 8
102 rowSpacing: 8
103
104 columns: notifImage.visible ? 3 : 2
105 rows: {
106 var res = 1;
107 if (notifBody.visible)
108 res += 1;
109 if (notifActions.visible)
110 res += 1;
111 return res;
112 }
113
114 Text {
115 id: notifCount
116
117 visible: notifWindow.modelData?.length > 1 ?? false
118 text: `${notifWindow.activeIx + 1}/${notifWindow.modelData?.length ?? ""}`
119
120 font.pointSize: 10
121 font.family: "Fira Sans"
122 font.bold: true
123 font.features: { "tnum": 1 }
124 color: notifWindow.textColor
125 maximumLineCount: 1
126
127 Layout.fillWidth: false
128 Layout.row: 0
129 Layout.column: notifImage.visible ? 1 : 0
130 }
131
132 Text {
133 id: notifSummary
134
135 text: notifWindow.modelData?.[notifWindow.activeIx]?.summary ?? ""
136
137 font.pointSize: 10
138 font.family: "Fira Sans"
139 font.italic: true
140 color: notifWindow.textColor
141 maximumLineCount: 1
142 elide: Text.ElideRight
143
144 Layout.fillWidth: true
145 Layout.row: 0
146 Layout.column: (notifCount.visible ? 1 : 0) + (notifImage.visible ? 1 : 0)
147 Layout.columnSpan: notifCount.visible ? 1 : 2
148 }
149
150 Image {
151 id: notifImage
152
153 visible: (notifWindow.modelData?.[notifWindow.activeIx]?.image || notifWindow.modelData?.[notifWindow.activeIx]?.appIcon) ?? false
154
155 onStatusChanged: {
156 if (notifImage.status == Image.Error)
157 notifImage.visible = false;
158 }
159
160 source: (notifWindow.modelData?.[notifWindow.activeIx]?.image || notifWindow.modelData?.[notifWindow.activeIx]?.appIcon) ?? ""
161 fillMode: Image.PreserveAspectFit
162 asynchronous: true
163 smooth: true
164 mipmap: true
165
166 Layout.maximumWidth: 50
167 Layout.column: 0
168 Layout.row: 0
169 Layout.fillHeight: true
170 Layout.rowSpan: notifBody.visible ? 2 : 1
171 }
172
173 Text {
174 id: notifBody
175
176 visible: notifWindow.modelData?.[notifWindow.activeIx]?.body ?? false
177 text: notifWindow.modelData?.[notifWindow.activeIx]?.body ?? ""
178 textFormat: Text.RichText
179
180 font.pointSize: 10
181 font.family: "Fira Sans"
182 color: notifWindow.textColor
183
184 Layout.fillWidth: true
185 Layout.row: 1
186 Layout.column: notifImage.visible ? 1 : 0
187 Layout.columnSpan: notifCount.visible ? 2 : 1
188 }
189
190 RowLayout {
191 id: notifActions
192
193 visible: notifWindow.modelData?.[notifWindow.activeIx]?.actions.length > 0 ?? false
194
195 spacing: 8
196 uniformCellSizes: true
197
198 width: 400 - 16
199 Layout.row: notifBody.visible ? 2 : 1
200 Layout.column: 0
201 Layout.columnSpan: notifImage.visible ? 3 : 2
202
203 Repeater {
204 model: ScriptModel {
205 values: notifWindow.modelData?.[notifWindow.activeIx]?.actions
206 }
207
208 delegate: WrapperMouseArea {
209 id: actionMouseArea
210
211 required property var modelData
212
213 height: actionLabelWrapper.implicitHeight
214 Layout.fillWidth: true
215 Layout.horizontalStretchFactor: 1
216
217 hoverEnabled: true
218 cursorShape: Qt.PointingHandCursor
219
220 onClicked: actionMouseArea.modelData?.invoke()
221
222 Rectangle {
223 anchors.fill: parent
224
225 color: actionMouseArea.containsMouse ? "#20ffffff" : "transparent"
226
227 border {
228 width: 2
229 color: "#20ffffff"
230 }
231
232 WrapperItem {
233 id: actionLabelWrapper
234
235 margin: 8
236 anchors.centerIn: parent
237
238 RowLayout {
239 id: actionLabelLayout
240
241 spacing: 8
242
243 IconImage {
244 id: actionIcon
245
246 visible: notifWindow.modelData?.[notifWindow.activeIx]?.hasActionIcons
247
248 onStatusChanged: {
249 if (actionIcon.status == Image.Error)
250 actionIcon.visible = false;
251 }
252
253 implicitSize: 16
254 source: {
255 if (!actionIcon.visible)
256 return "";
257
258 let icon = actionMouseArea.modelData?.identifier ?? ""
259 if (icon.includes("?path=")) {
260 const split = icon.split("?path=")
261 if (split.length !== 2)
262 return icon
263 const name = split[0]
264 const path = split[1]
265 const fileName = name.substring(
266 name.lastIndexOf("/") + 1)
267 return `file://${path}/${fileName}`
268 }
269 return icon
270 }
271 asynchronous: true
272 smooth: true
273 mipmap: true
274
275 Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
276 }
277
278 Text {
279 id: actionLabel
280
281 visible: actionMouseArea.modelData?.text ?? false
282
283 text: actionMouseArea.modelData?.text ?? ""
284
285 font.pointSize: 10
286 font.family: "Fira Sans"
287 color: notifWindow.textColor
288 maximumLineCount: 1
289 elide: Text.ElideRight
290 }
291 }
292 }
293 }
294 }
295 }
296 }
297 }
298 }
299 }
300 }
301 }
302}