From 666464567055a2e4ba9f6bb310e901cdc27977f7 Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Fri, 12 Sep 2025 22:01:51 +0200 Subject: ... --- .../shell/quickshell/NotificationDisplay.qml | 302 +++++++++++++++++++++ 1 file changed, 302 insertions(+) create mode 100644 accounts/gkleen@sif/shell/quickshell/NotificationDisplay.qml (limited to 'accounts/gkleen@sif/shell/quickshell/NotificationDisplay.qml') 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 @@ +import QtQml +import QtQml.Models +import QtQuick +import Quickshell +import Quickshell.Widgets +import Quickshell.Wayland +import qs.Services +import QtQuick.Layouts +import Quickshell.Services.Notifications + +Scope { + readonly property ShellScreen activeScreen: Array.from(Quickshell.screens).find(screen => screen.name === Array.from(NiriService.workspaces).find(ws => ws.is_focused)?.output) ?? null + + Instantiator { + id: notifsRepeater + + model: ScriptModel { + values: NotificationManager.displayInhibited ? [] : [...NotificationManager.groups] + } + + delegate: PanelWindow { + id: notifWindow + + required property var modelData + required property var index + + property int activeIx: modelData.length - 1 + onModelDataChanged: { + notifWindow.activeIx = modelData.length - 1; + } + + property color textColor: { + if (notifWindow.modelData?.[notifWindow.activeIx]?.urgency == NotificationUrgency.Low) + return "#ff999999"; + return "white"; + } + property color backgroundColor: { + if (notifWindow.modelData?.[notifWindow.activeIx]?.urgency == NotificationUrgency.Critical) + return "#dd900000"; + return "black"; + } + + anchors { + right: true + top: true + } + + readonly property real spaceAbove: { + var res = 0; + for (let i = 0; i < notifWindow.index; i++) { + res += notifsRepeater.objectAt(i).height + 8; + } + return res; + } + + margins { + right: 26 + 8 + top: 8 + spaceAbove + } + + color: "transparent" + + implicitHeight: Math.max(notifCount.visible ? notifCount.contentHeight : 0, notifSummary.contentHeight) + (notifBody.visible ? 8 + notifBody.contentHeight : 0) + (notifActions.visible ? 8 + notifActions.height : 0) + 16 + implicitWidth: 400 + + WrapperMouseArea { + enabled: true + + anchors.fill: parent + + cursorShape: Qt.PointingHandCursor + + onClicked: { + for (const notif of notifWindow.modelData) + notif.dismiss(); + } + + property real angleRem: 0 + property real sensitivity: 1 / 120 + onWheel: event => { + angleRem += event.angleDelta.y; + const d = Math.round(angleRem * sensitivity); + angleRem -= d / sensitivity; + notifWindow.activeIx = ((notifWindow.modelData?.length ?? 1) + notifWindow.activeIx - d) % (notifWindow.modelData?.length ?? 1); + } + + Rectangle { + color: notifWindow.backgroundColor + anchors.fill: parent + border { + color: Qt.hsla(195/360, 1, 0.45, 1) + width: 2 + } + + GridLayout { + id: notifLayout + + width: 400 - 16 + anchors.fill: parent + anchors.margins: 8 + columnSpacing: 8 + rowSpacing: 8 + + columns: notifImage.visible ? 3 : 2 + rows: { + var res = 1; + if (notifBody.visible) + res += 1; + if (notifActions.visible) + res += 1; + return res; + } + + Text { + id: notifCount + + visible: notifWindow.modelData?.length > 1 ?? false + text: `${notifWindow.activeIx + 1}/${notifWindow.modelData?.length ?? ""}` + + font.pointSize: 10 + font.family: "Fira Sans" + font.bold: true + font.features: { "tnum": 1 } + color: notifWindow.textColor + maximumLineCount: 1 + + Layout.fillWidth: false + Layout.row: 0 + Layout.column: notifImage.visible ? 1 : 0 + } + + Text { + id: notifSummary + + text: notifWindow.modelData?.[notifWindow.activeIx]?.summary ?? "" + + font.pointSize: 10 + font.family: "Fira Sans" + font.italic: true + color: notifWindow.textColor + maximumLineCount: 1 + elide: Text.ElideRight + + Layout.fillWidth: true + Layout.row: 0 + Layout.column: (notifCount.visible ? 1 : 0) + (notifImage.visible ? 1 : 0) + Layout.columnSpan: notifCount.visible ? 1 : 2 + } + + Image { + id: notifImage + + visible: (notifWindow.modelData?.[notifWindow.activeIx]?.image || notifWindow.modelData?.[notifWindow.activeIx]?.appIcon) ?? false + + onStatusChanged: { + if (notifImage.status == Image.Error) + notifImage.visible = false; + } + + source: (notifWindow.modelData?.[notifWindow.activeIx]?.image || notifWindow.modelData?.[notifWindow.activeIx]?.appIcon) ?? "" + fillMode: Image.PreserveAspectFit + asynchronous: true + smooth: true + mipmap: true + + Layout.maximumWidth: 50 + Layout.column: 0 + Layout.row: 0 + Layout.fillHeight: true + Layout.rowSpan: notifBody.visible ? 2 : 1 + } + + Text { + id: notifBody + + visible: notifWindow.modelData?.[notifWindow.activeIx]?.body ?? false + text: notifWindow.modelData?.[notifWindow.activeIx]?.body ?? "" + textFormat: Text.RichText + + font.pointSize: 10 + font.family: "Fira Sans" + color: notifWindow.textColor + + Layout.fillWidth: true + Layout.row: 1 + Layout.column: notifImage.visible ? 1 : 0 + Layout.columnSpan: notifCount.visible ? 2 : 1 + } + + RowLayout { + id: notifActions + + visible: notifWindow.modelData?.[notifWindow.activeIx]?.actions.length > 0 ?? false + + spacing: 8 + uniformCellSizes: true + + width: 400 - 16 + Layout.row: notifBody.visible ? 2 : 1 + Layout.column: 0 + Layout.columnSpan: notifImage.visible ? 3 : 2 + + Repeater { + model: ScriptModel { + values: notifWindow.modelData?.[notifWindow.activeIx]?.actions + } + + delegate: WrapperMouseArea { + id: actionMouseArea + + required property var modelData + + height: actionLabelWrapper.implicitHeight + Layout.fillWidth: true + Layout.horizontalStretchFactor: 1 + + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + + onClicked: actionMouseArea.modelData?.invoke() + + Rectangle { + anchors.fill: parent + + color: actionMouseArea.containsMouse ? "#20ffffff" : "transparent" + + border { + width: 2 + color: "#20ffffff" + } + + WrapperItem { + id: actionLabelWrapper + + margin: 8 + anchors.centerIn: parent + + RowLayout { + id: actionLabelLayout + + spacing: 8 + + IconImage { + id: actionIcon + + visible: notifWindow.modelData?.[notifWindow.activeIx]?.hasActionIcons + + onStatusChanged: { + if (actionIcon.status == Image.Error) + actionIcon.visible = false; + } + + implicitSize: 16 + source: { + if (!actionIcon.visible) + return ""; + + let icon = actionMouseArea.modelData?.identifier ?? "" + if (icon.includes("?path=")) { + const split = icon.split("?path=") + if (split.length !== 2) + return icon + const name = split[0] + const path = split[1] + const fileName = name.substring( + name.lastIndexOf("/") + 1) + return `file://${path}/${fileName}` + } + return icon + } + asynchronous: true + smooth: true + mipmap: true + + Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter + } + + Text { + id: actionLabel + + visible: actionMouseArea.modelData?.text ?? false + + text: actionMouseArea.modelData?.text ?? "" + + font.pointSize: 10 + font.family: "Fira Sans" + color: notifWindow.textColor + maximumLineCount: 1 + elide: Text.ElideRight + } + } + } + } + } + } + } + } + } + } + } + } +} -- cgit v1.2.3