diff options
Diffstat (limited to 'accounts/gkleen@sif/shell/quickshell/NotificationDisplay.qml')
-rw-r--r-- | accounts/gkleen@sif/shell/quickshell/NotificationDisplay.qml | 332 |
1 files changed, 332 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..03406687 --- /dev/null +++ b/accounts/gkleen@sif/shell/quickshell/NotificationDisplay.qml | |||
@@ -0,0 +1,332 @@ | |||
1 | import QtQml | ||
2 | import QtQml.Models | ||
3 | import QtQuick | ||
4 | import Quickshell | ||
5 | import Quickshell.Widgets | ||
6 | import Quickshell.Wayland | ||
7 | import qs.Services | ||
8 | import QtQuick.Layouts | ||
9 | import Quickshell.Services.Notifications | ||
10 | |||
11 | Scope { | ||
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.active ? [] : 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 | (_ => {})(notifsRepeater.objectAt(i).modelData); | ||
52 | res += notifsRepeater.objectAt(i).height + 8; | ||
53 | } | ||
54 | return res; | ||
55 | } | ||
56 | |||
57 | margins { | ||
58 | right: 26 + 8 | ||
59 | top: 8 + spaceAbove | ||
60 | } | ||
61 | |||
62 | color: "transparent" | ||
63 | |||
64 | implicitHeight: Math.max(notifCount.visible ? notifCount.contentHeight : 0, notifSummary.contentHeight) + (notifBody.visible ? 8 + notifBody.contentHeight : 0) + (notifActions.visible ? 8 + notifActions.height : 0) + (notifTime.visible ? 8 + notifTime.contentHeight : 0) + 16 | ||
65 | implicitWidth: 400 | ||
66 | |||
67 | WrapperMouseArea { | ||
68 | enabled: true | ||
69 | |||
70 | anchors.fill: parent | ||
71 | |||
72 | cursorShape: Qt.PointingHandCursor | ||
73 | |||
74 | onClicked: { | ||
75 | for (const notif of notifWindow.modelData) | ||
76 | notif.dismiss(); | ||
77 | } | ||
78 | |||
79 | property real angleRem: 0 | ||
80 | property real sensitivity: 1 / 120 | ||
81 | onWheel: event => { | ||
82 | angleRem += event.angleDelta.y; | ||
83 | const d = Math.round(angleRem * sensitivity); | ||
84 | angleRem -= d / sensitivity; | ||
85 | notifWindow.activeIx = ((notifWindow.modelData?.length ?? 1) + notifWindow.activeIx - d) % (notifWindow.modelData?.length ?? 1); | ||
86 | } | ||
87 | |||
88 | Rectangle { | ||
89 | color: notifWindow.backgroundColor | ||
90 | anchors.fill: parent | ||
91 | border { | ||
92 | color: Qt.hsla(195/360, 1, 0.45, 1) | ||
93 | width: 2 | ||
94 | } | ||
95 | |||
96 | GridLayout { | ||
97 | id: notifLayout | ||
98 | |||
99 | width: 400 - 16 | ||
100 | anchors.fill: parent | ||
101 | anchors.margins: 8 | ||
102 | columnSpacing: 8 | ||
103 | rowSpacing: 8 | ||
104 | |||
105 | columns: notifImage.visible ? 3 : 2 | ||
106 | rows: { | ||
107 | var res = 1; | ||
108 | if (notifBody.visible) | ||
109 | res += 1; | ||
110 | if (notifActions.visible) | ||
111 | res += 1; | ||
112 | if (notifTime.visible) | ||
113 | res += 1; | ||
114 | return res; | ||
115 | } | ||
116 | |||
117 | Text { | ||
118 | id: notifCount | ||
119 | |||
120 | visible: notifWindow.modelData?.length > 1 ?? false | ||
121 | text: `${notifWindow.activeIx + 1}/${notifWindow.modelData?.length ?? ""}` | ||
122 | |||
123 | font.pointSize: 10 | ||
124 | font.family: "Fira Sans" | ||
125 | font.bold: true | ||
126 | font.features: { "tnum": 1 } | ||
127 | color: notifWindow.textColor | ||
128 | maximumLineCount: 1 | ||
129 | |||
130 | Layout.fillWidth: false | ||
131 | Layout.row: 0 | ||
132 | Layout.column: notifImage.visible ? 1 : 0 | ||
133 | } | ||
134 | |||
135 | Text { | ||
136 | id: notifSummary | ||
137 | |||
138 | text: notifWindow.modelData?.[notifWindow.activeIx]?.summary ?? "" | ||
139 | |||
140 | font.pointSize: 10 | ||
141 | font.family: "Fira Sans" | ||
142 | font.italic: true | ||
143 | color: notifWindow.textColor | ||
144 | maximumLineCount: 1 | ||
145 | elide: Text.ElideRight | ||
146 | |||
147 | Layout.fillWidth: true | ||
148 | Layout.row: 0 | ||
149 | Layout.column: (notifCount.visible ? 1 : 0) + (notifImage.visible ? 1 : 0) | ||
150 | Layout.columnSpan: notifCount.visible ? 1 : 2 | ||
151 | } | ||
152 | |||
153 | Image { | ||
154 | id: notifImage | ||
155 | |||
156 | visible: (notifWindow.modelData?.[notifWindow.activeIx]?.image || notifWindow.modelData?.[notifWindow.activeIx]?.appIcon) ?? false | ||
157 | |||
158 | onStatusChanged: { | ||
159 | if (notifImage.status == Image.Error) | ||
160 | notifImage.visible = false; | ||
161 | } | ||
162 | |||
163 | source: (notifWindow.modelData?.[notifWindow.activeIx]?.image || notifWindow.modelData?.[notifWindow.activeIx]?.appIcon) ?? "" | ||
164 | fillMode: Image.PreserveAspectFit | ||
165 | asynchronous: true | ||
166 | smooth: true | ||
167 | mipmap: true | ||
168 | |||
169 | Layout.maximumWidth: 50 | ||
170 | Layout.column: 0 | ||
171 | Layout.row: 0 | ||
172 | Layout.fillHeight: true | ||
173 | Layout.rowSpan: 1 + (notifBody.visible ? 1 : 0) + (notifTime.visible ? 1 : 0) | ||
174 | } | ||
175 | |||
176 | Text { | ||
177 | id: notifBody | ||
178 | |||
179 | visible: notifWindow.modelData?.[notifWindow.activeIx]?.body ?? false | ||
180 | text: notifWindow.modelData?.[notifWindow.activeIx]?.body ?? "" | ||
181 | textFormat: Text.RichText | ||
182 | wrapMode: Text.Wrap | ||
183 | |||
184 | font.pointSize: 10 | ||
185 | font.family: "Fira Sans" | ||
186 | color: notifWindow.textColor | ||
187 | |||
188 | Layout.fillWidth: true | ||
189 | Layout.row: 1 | ||
190 | Layout.column: notifImage.visible ? 1 : 0 | ||
191 | Layout.columnSpan: notifCount.visible ? 2 : 1 | ||
192 | } | ||
193 | |||
194 | Text { | ||
195 | id: notifTime | ||
196 | |||
197 | Connections { | ||
198 | target: NotificationManager.clock | ||
199 | function onDateChanged() { | ||
200 | notifTime.text = NotificationManager.formatTime(notifWindow.modelData?.[notifWindow.activeIx]?.receivedTime); | ||
201 | } | ||
202 | } | ||
203 | |||
204 | visible: notifTime.text && notifTime.text !== "now" | ||
205 | text: NotificationManager.formatTime(notifWindow.modelData?.[notifWindow.activeIx]?.receivedTime) | ||
206 | |||
207 | font.pointSize: 8 | ||
208 | font.family: "Fira Sans" | ||
209 | font.italic: true | ||
210 | color: "#555" | ||
211 | maximumLineCount: 1 | ||
212 | horizontalAlignment: Text.AlignRight | ||
213 | |||
214 | Layout.fillWidth: true | ||
215 | Layout.row: notifBody.visible ? 2 : 1 | ||
216 | Layout.column: notifImage.visible ? 1 : 0 | ||
217 | Layout.columnSpan: 2 | ||
218 | } | ||
219 | |||
220 | RowLayout { | ||
221 | id: notifActions | ||
222 | |||
223 | visible: notifWindow.modelData?.[notifWindow.activeIx]?.actions.length > 0 ?? false | ||
224 | |||
225 | spacing: 8 | ||
226 | uniformCellSizes: true | ||
227 | |||
228 | width: 400 - 16 | ||
229 | Layout.row: 1 + (notifBody.visible ? 1 : 0) + (notifTime.visible ? 1 : 0) | ||
230 | Layout.column: 0 | ||
231 | Layout.columnSpan: 2 + (notifImage.visible ? 1 : 0) | ||
232 | |||
233 | Repeater { | ||
234 | model: ScriptModel { | ||
235 | values: notifWindow.modelData?.[notifWindow.activeIx]?.actions | ||
236 | } | ||
237 | |||
238 | delegate: WrapperMouseArea { | ||
239 | id: actionMouseArea | ||
240 | |||
241 | required property var modelData | ||
242 | |||
243 | height: actionLabelWrapper.implicitHeight | ||
244 | Layout.fillWidth: true | ||
245 | Layout.horizontalStretchFactor: 1 | ||
246 | |||
247 | hoverEnabled: true | ||
248 | cursorShape: Qt.PointingHandCursor | ||
249 | |||
250 | onClicked: actionMouseArea.modelData?.invoke() | ||
251 | |||
252 | Rectangle { | ||
253 | anchors.fill: parent | ||
254 | |||
255 | color: actionMouseArea.containsMouse ? "#20ffffff" : "transparent" | ||
256 | |||
257 | border { | ||
258 | width: 2 | ||
259 | color: "#20ffffff" | ||
260 | } | ||
261 | |||
262 | WrapperItem { | ||
263 | id: actionLabelWrapper | ||
264 | |||
265 | margin: 8 | ||
266 | anchors.centerIn: parent | ||
267 | |||
268 | RowLayout { | ||
269 | id: actionLabelLayout | ||
270 | |||
271 | spacing: 8 | ||
272 | |||
273 | IconImage { | ||
274 | id: actionIcon | ||
275 | |||
276 | visible: notifWindow.modelData?.[notifWindow.activeIx]?.hasActionIcons | ||
277 | |||
278 | onStatusChanged: { | ||
279 | if (actionIcon.status == Image.Error) | ||
280 | actionIcon.visible = false; | ||
281 | } | ||
282 | |||
283 | implicitSize: 16 | ||
284 | source: { | ||
285 | if (!actionIcon.visible) | ||
286 | return ""; | ||
287 | |||
288 | let icon = actionMouseArea.modelData?.identifier ?? "" | ||
289 | if (icon.includes("?path=")) { | ||
290 | const split = icon.split("?path=") | ||
291 | if (split.length !== 2) | ||
292 | return icon | ||
293 | const name = split[0] | ||
294 | const path = split[1] | ||
295 | const fileName = name.substring( | ||
296 | name.lastIndexOf("/") + 1) | ||
297 | return `file://${path}/${fileName}` | ||
298 | } | ||
299 | return icon | ||
300 | } | ||
301 | asynchronous: true | ||
302 | smooth: true | ||
303 | mipmap: true | ||
304 | |||
305 | Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter | ||
306 | } | ||
307 | |||
308 | Text { | ||
309 | id: actionLabel | ||
310 | |||
311 | visible: actionMouseArea.modelData?.text ?? false | ||
312 | |||
313 | text: actionMouseArea.modelData?.text ?? "" | ||
314 | |||
315 | font.pointSize: 10 | ||
316 | font.family: "Fira Sans" | ||
317 | color: notifWindow.textColor | ||
318 | maximumLineCount: 1 | ||
319 | elide: Text.ElideRight | ||
320 | } | ||
321 | } | ||
322 | } | ||
323 | } | ||
324 | } | ||
325 | } | ||
326 | } | ||
327 | } | ||
328 | } | ||
329 | } | ||
330 | } | ||
331 | } | ||
332 | } | ||