From c98690fa63d15698b2f30e8966c9373cfc5a2673 Mon Sep 17 00:00:00 2001 From: Kat Inskip Date: Sun, 7 Dec 2025 18:41:31 -0800 Subject: [PATCH 1/6] feat: notifications o: --- quickshell/Components/NotificationDisplay.qml | 296 ++++++++++++++++++ quickshell/Components/SystemTrayButton.qml | 5 +- quickshell/Components/SystemTrayWrapper.qml | 14 +- quickshell/DataSources/Notifications.qml | 38 +++ quickshell/Modules/Bar.qml | 1 + 5 files changed, 346 insertions(+), 8 deletions(-) create mode 100644 quickshell/Components/NotificationDisplay.qml create mode 100644 quickshell/DataSources/Notifications.qml diff --git a/quickshell/Components/NotificationDisplay.qml b/quickshell/Components/NotificationDisplay.qml new file mode 100644 index 00000000..fc34d0bb --- /dev/null +++ b/quickshell/Components/NotificationDisplay.qml @@ -0,0 +1,296 @@ +import Quickshell +import Quickshell.Widgets +import Quickshell.Io +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import "root:/DataSources" +import "root:/Helpers" +import Quickshell.Services.Notifications + +Item { + id: root + Layout.alignment: Qt.AlignVCenter; + implicitWidth: 25 + implicitHeight: parent.height + Rectangle { + anchors.centerIn: parent + id: rootContainer + color: "transparent" + width: 30 + height: 30 + radius: 50 + Text { + id: rootIcon + text: "" + color: Stylix.base05 + anchors.centerIn: parent + } + } + function updateDisplay() { + if (Notifications.list.length > 0) { + rootContainer.color = Stylix.base08 + rootIcon.color = Stylix.base00 + } else { + rootContainer.color = "transparent" + rootIcon.color = Stylix.base05 + } + } + Timer { + interval: 1000 + running: true + repeat: true + onTriggered: root.updateDisplay() + } + MouseArea { + id: ma + anchors.fill: parent + hoverEnabled: true + + onClicked: function(mouseEvent) { + var m = root.QsWindow.mapFromItem(ma, ma.width/2.0, ma.height/2.0); + var offset = notificationLoader.item.width / 2.0; + notificationLoader.item.clicky = m.x - offset; + notificationLoader.item.visible = !notificationLoader.item.visible + } + } + + LazyLoader { + id: notificationLoader + + loading: true + + PopupWindow { + property real clicky + id: wrapperPopup + visible: false + anchor.window: root.QsWindow.window + anchor.rect.y: parentWindow.height + anchor.rect.x: clicky + color: "transparent" + + implicitWidth: 400 + implicitHeight: 600 + + Rectangle { + anchors.fill: parent + color: Stylix.base01 + bottomLeftRadius: 5 + bottomRightRadius: 5 + ColumnLayout { + anchors.fill: parent + RowLayout { + Layout.alignment: Qt.AlignHCenter + spacing: 5 + Text { + Layout.preferredHeight: 26 + Layout.alignment: Qt.AlignVCenter + verticalAlignment: Text.AlignVCenter + text: "Notifications" + color: Stylix.base05 + font.pixelSize: 16 + } + Text { + Layout.preferredHeight: 26 + Layout.alignment: Qt.AlignVCenter + verticalAlignment: Text.AlignBottom + id: clear + text: "󱏧" + color: Stylix.base08 + font.pixelSize: 16 + ToolTip { + id: clearTooltip + visible: false + delay: 500 + timeout: 1000 + text: "Clear notifications" + } + MouseArea { + anchors.fill: parent + onClicked: { + if (Notifications.list.length >= 0) { + Notifications.clear() + root.updateDisplay() + } + } + } + + HoverHandler { + id: clearHover + onHoveredChanged: { + clearTooltip.visible = hovered + } + } + } + } + ListView { + id: notificationList + model: Notifications.list + spacing: 10 + ScrollBar.vertical: ScrollBar {} + Layout.alignment: Qt.AlignCenter + Layout.preferredWidth: parent.width + Layout.preferredHeight: parent.height + + delegate: Item { + required property Notification modelData + + height: 100 + width: 400//notificationList.width + + Rectangle { + id: indivNotif + anchors { + fill: parent + leftMargin: 5 + rightMargin: 5 + } + color: Stylix.base02 + ColumnLayout { + anchors { + fill: parent + leftMargin: 5 + rightMargin: 5 + } + RowLayout { + spacing: 5 + ClippingWrapperRectangle { + radius: 5 + Layout.minimumWidth: 0 + Layout.minimumHeight: 0 + Layout.maximumWidth: 60 + Layout.preferredWidth: 60 + Layout.preferredHeight: 60 + Layout.leftMargin: 5 + Layout.rightMargin: 5 + visible: modelData.image != "" + Image { + fillMode: Image.PreserveAspectCrop + Layout.minimumWidth: 0 + Layout.minimumHeight: 0 + Layout.preferredWidth: 60 + Layout.preferredHeight: 60 + source: modelData.image + } + } + ColumnLayout { + spacing: 5 + RowLayout { + spacing: 5 + IconImage { + function getIcon() { + console.log(modelData.appIcon) + if (modelData.appIcon != "") { + return Quickshell.iconPath(modelData.appIcon) + } else { + return iconForId(modelData.appName) + } + } + width: 24 + height: 24 + visible: modelData.appIcon != "" + source: Quickshell.iconPath(modelData.appIcon) + } + Text { + elide: Text.ElideRight + text: modelData.summary + color: Stylix.base05 + } + Text { + id: dismiss + text: "󱏩" + color: Stylix.base08 + font.pixelSize: 16 + + ToolTip { + id: dismissTooltip + visible: false + delay: 500 + timeout: 1000 + text: "Dismiss notification" + } + + HoverHandler { + id: dismissHover + onHoveredChanged: { + dismissTooltip.visible = hovered + } + } + + Layout.topMargin: 5 + Layout.rightMargin: 10 + + MouseArea { + anchors.fill: parent + onClicked: { + modelData.dismiss(); + if (Notifications.list.length <= 0) { + popup.visible = false; + } + } + } + } + } + Text { + font.pointSize: 10 + wrapMode: Text.WordWrap + elide: Text.ElideRight + text: modelData.body + color: Stylix.base05 + } + } + } + RowLayout { + Layout.minimumHeight: 0 + visible: modelData.actions != [] + spacing: 5 + Repeater { + model: modelData.actions + + Item { + required property NotificationAction actionData + + width: 400 + height: 30 + + anchors { + left: parent.left + leftMargin: 5 + top: parent.top + topMargin: 5 + } + + Rectangle { + anchors.fill: parent + color: Stylix.base02 + radius: 5 + + Text { + text: actionData.text + color: Stylix.base05 + font.pixelSize: 12 + + anchors { + left: parent.left + leftMargin: 10 + verticalCenter: parent.verticalCenter + } + } + + MouseArea { + anchors.fill: parent + onClicked: actionData.invoke() + } + } + } + } + } + } + } + } + } + } + } + } + } +} diff --git a/quickshell/Components/SystemTrayButton.qml b/quickshell/Components/SystemTrayButton.qml index 82a189fe..c1e03578 100644 --- a/quickshell/Components/SystemTrayButton.qml +++ b/quickshell/Components/SystemTrayButton.qml @@ -12,9 +12,6 @@ Item { width: parent.width height: 30 - property real text_point_size: 12 - property real length: width / text_point_size - Rectangle { anchors { fill: parent @@ -33,7 +30,7 @@ Item { horizontalAlignment: Text.AlignHCenter text: modelData?.text ?? "" color: Stylix.base05 - font.pointSize: text_point_size + font.pointSize: 12 elide: Text.ElideRight } diff --git a/quickshell/Components/SystemTrayWrapper.qml b/quickshell/Components/SystemTrayWrapper.qml index 3fa6801b..f5f78a83 100644 --- a/quickshell/Components/SystemTrayWrapper.qml +++ b/quickshell/Components/SystemTrayWrapper.qml @@ -8,10 +8,11 @@ Item { Layout.alignment: Qt.AlignVCenter; implicitWidth: 25 implicitHeight: parent.height + property list textStates: ["", ""] Text { id: texty anchors.centerIn: parent - text: "" + text: textStates[0] color: Stylix.base05 } MouseArea { @@ -23,7 +24,9 @@ Item { var m = root.QsWindow.mapFromItem(ma, ma.width/2.0, ma.height/2.0); var offset = wrapperPopup.width / 2.0; wrapperPopup.clicky = m.x - offset; - wrapperPopup.visible = !wrapperPopup.visible + wrapperPopup.visible = !wrapperPopup.visible; + + texty.text = root.textStates[wrapperPopup.visible ? 1 : 0]; } } PopupWindow { @@ -32,11 +35,14 @@ Item { anchor.window: root.QsWindow.window anchor.rect.y: parentWindow.height anchor.rect.x: clicky - width: systray.width + 10 - height: systray.height + 10 + implicitWidth: systray.width + 10 + implicitHeight: systray.height + 10 + color: "transparent" Rectangle { anchors.fill: parent color: Stylix.base01 + bottomLeftRadius: 5 + bottomRightRadius: 5 SystemTray { id: systray } diff --git a/quickshell/DataSources/Notifications.qml b/quickshell/DataSources/Notifications.qml new file mode 100644 index 00000000..323e5d1a --- /dev/null +++ b/quickshell/DataSources/Notifications.qml @@ -0,0 +1,38 @@ +pragma Singleton + +import Quickshell +import Quickshell.Io +import Quickshell.Services.Notifications + +Singleton { + id: root + + NotificationServer { + id: notificationServer + imageSupported: true + bodySupported: true + bodyMarkupSupported: false + bodyImagesSupported: false + actionsSupported: true + onNotification: (notification) => { + notification.tracked = true; + root.notification(notification); + } + } + function clear(): void { + for (const notification of notificationServer.trackedNotifications.values) { + notification.tracked = false; + } + } + + // TODO: use signal + property list list: notificationServer.trackedNotifications.values.filter(notification => notification.tracked) + signal notification(Notification notification) + + IpcHandler { + target: "notifications" + function clear() { + root.clear() + } + } +} diff --git a/quickshell/Modules/Bar.qml b/quickshell/Modules/Bar.qml index 74cfd7eb..02a19c89 100644 --- a/quickshell/Modules/Bar.qml +++ b/quickshell/Modules/Bar.qml @@ -70,6 +70,7 @@ Scope { spacing: 15 + NotificationDisplay {} SystemTrayWrapper {} Clock {} DistroIcon {} From 04732abbc89834437499f20950e198f98530fb5a Mon Sep 17 00:00:00 2001 From: Kat Inskip Date: Sun, 7 Dec 2025 18:58:18 -0800 Subject: [PATCH 2/6] fix: make clearing work --- quickshell/DataSources/Notifications.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/quickshell/DataSources/Notifications.qml b/quickshell/DataSources/Notifications.qml index 323e5d1a..e8507ad2 100644 --- a/quickshell/DataSources/Notifications.qml +++ b/quickshell/DataSources/Notifications.qml @@ -23,10 +23,11 @@ Singleton { for (const notification of notificationServer.trackedNotifications.values) { notification.tracked = false; } + list.length = 0; } // TODO: use signal - property list list: notificationServer.trackedNotifications.values.filter(notification => notification.tracked) + property list list: notificationServer.trackedNotifications.values.filter(notification => notification.tracked).reverse() signal notification(Notification notification) IpcHandler { From fb4da4296e7a3ce99aae114dcc310af5162280cb Mon Sep 17 00:00:00 2001 From: Kat Inskip Date: Sun, 7 Dec 2025 20:07:08 -0800 Subject: [PATCH 3/6] fix: notifications --- quickshell/Components/NotificationActions.qml | 59 +++++++ quickshell/Components/NotificationDisplay.qml | 162 +++--------------- quickshell/Components/NotificationHeader.qml | 67 ++++++++ quickshell/Components/NotificationImage.qml | 25 +++ 4 files changed, 177 insertions(+), 136 deletions(-) create mode 100644 quickshell/Components/NotificationActions.qml create mode 100644 quickshell/Components/NotificationHeader.qml create mode 100644 quickshell/Components/NotificationImage.qml diff --git a/quickshell/Components/NotificationActions.qml b/quickshell/Components/NotificationActions.qml new file mode 100644 index 00000000..5931d68e --- /dev/null +++ b/quickshell/Components/NotificationActions.qml @@ -0,0 +1,59 @@ +import Quickshell +import Quickshell.Widgets +import Quickshell.Io +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import "root:/DataSources" +import "root:/Helpers" +import Quickshell.Services.Notifications + + +RowLayout { + required property Notification modelData_ + + Layout.minimumHeight: 0 + Layout.preferredHeight: modelData_.actions != [] ? 30 : 0 + visible: modelData_.actions != [] + spacing: 5 + Repeater { + model: modelData_.actions + + Item { + required property NotificationAction actionData + + width: 100 + height: 30 + + anchors { + left: parent.left + leftMargin: 5 + top: parent.top + topMargin: 5 + } + + Rectangle { + anchors.fill: parent + color: Stylix.base02 + radius: 5 + + Text { + text: actionData.text + color: Stylix.base05 + font.pixelSize: 12 + + anchors { + left: parent.left + leftMargin: 10 + verticalCenter: parent.verticalCenter + } + } + + MouseArea { + anchors.fill: parent + onClicked: actionData.invoke() + } + } + } + } +} diff --git a/quickshell/Components/NotificationDisplay.qml b/quickshell/Components/NotificationDisplay.qml index fc34d0bb..3664ee56 100644 --- a/quickshell/Components/NotificationDisplay.qml +++ b/quickshell/Components/NotificationDisplay.qml @@ -65,7 +65,7 @@ Item { id: wrapperPopup visible: false anchor.window: root.QsWindow.window - anchor.rect.y: parentWindow.height + anchor.rect.y: parentWindow?.height ?? 0 anchor.rect.x: clicky color: "transparent" @@ -140,151 +140,41 @@ Item { Rectangle { id: indivNotif + color: Stylix.base02 + radius: 5 anchors { fill: parent leftMargin: 5 rightMargin: 5 } - color: Stylix.base02 - ColumnLayout { - anchors { - fill: parent - leftMargin: 5 - rightMargin: 5 + RowLayout { + anchors { + fill: parent + } + NotificationImage { + image: modelData.image } - RowLayout { - spacing: 5 - ClippingWrapperRectangle { - radius: 5 - Layout.minimumWidth: 0 - Layout.minimumHeight: 0 - Layout.maximumWidth: 60 - Layout.preferredWidth: 60 - Layout.preferredHeight: 60 - Layout.leftMargin: 5 - Layout.rightMargin: 5 - visible: modelData.image != "" - Image { - fillMode: Image.PreserveAspectCrop - Layout.minimumWidth: 0 - Layout.minimumHeight: 0 - Layout.preferredWidth: 60 - Layout.preferredHeight: 60 - source: modelData.image - } + ColumnLayout { + Layout.leftMargin: 5 + Layout.rightMargin: 5 + Layout.fillWidth: true + NotificationHeader { + modelData_: modelData } - ColumnLayout { - spacing: 5 - RowLayout { - spacing: 5 - IconImage { - function getIcon() { - console.log(modelData.appIcon) - if (modelData.appIcon != "") { - return Quickshell.iconPath(modelData.appIcon) - } else { - return iconForId(modelData.appName) - } - } - width: 24 - height: 24 - visible: modelData.appIcon != "" - source: Quickshell.iconPath(modelData.appIcon) - } - Text { - elide: Text.ElideRight - text: modelData.summary - color: Stylix.base05 - } - Text { - id: dismiss - text: "󱏩" - color: Stylix.base08 - font.pixelSize: 16 - - ToolTip { - id: dismissTooltip - visible: false - delay: 500 - timeout: 1000 - text: "Dismiss notification" - } - - HoverHandler { - id: dismissHover - onHoveredChanged: { - dismissTooltip.visible = hovered - } - } - - Layout.topMargin: 5 - Layout.rightMargin: 10 - - MouseArea { - anchors.fill: parent - onClicked: { - modelData.dismiss(); - if (Notifications.list.length <= 0) { - popup.visible = false; - } - } - } - } - } - Text { - font.pointSize: 10 - wrapMode: Text.WordWrap - elide: Text.ElideRight - text: modelData.body - color: Stylix.base05 - } + Text { + font.pointSize: 10 + wrapMode: Text.WordWrap + Layout.fillWidth: true + Layout.preferredWidth: modelData.image != "" ? indivNotif.width - 80 : indivNotif.width + Layout.maximumWidth: indivNotif.width + elide: Text.ElideRight + text: modelData.body + color: Stylix.base05 } } - RowLayout { - Layout.minimumHeight: 0 - visible: modelData.actions != [] - spacing: 5 - Repeater { - model: modelData.actions - - Item { - required property NotificationAction actionData - - width: 400 - height: 30 - - anchors { - left: parent.left - leftMargin: 5 - top: parent.top - topMargin: 5 - } - - Rectangle { - anchors.fill: parent - color: Stylix.base02 - radius: 5 - - Text { - text: actionData.text - color: Stylix.base05 - font.pixelSize: 12 - - anchors { - left: parent.left - leftMargin: 10 - verticalCenter: parent.verticalCenter - } - } - - MouseArea { - anchors.fill: parent - onClicked: actionData.invoke() - } - } - } + NotificationActions { + modelData_: modelData } - } } } } diff --git a/quickshell/Components/NotificationHeader.qml b/quickshell/Components/NotificationHeader.qml new file mode 100644 index 00000000..05a26441 --- /dev/null +++ b/quickshell/Components/NotificationHeader.qml @@ -0,0 +1,67 @@ +import Quickshell +import Quickshell.Widgets +import Quickshell.Io +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import "root:/DataSources" +import "root:/Helpers" +import Quickshell.Services.Notifications + + +RowLayout { + required property Notification modelData_ + IconImage { + function getIcon() { + console.log(modelData_.appIcon) + if (modelData_.appIcon != "") { + return Quickshell.iconPath(modelData_.appIcon) + } else { + return iconForId(modelData_.appName) + } + } + width: 24 + height: 24 + visible: modelData_.appIcon != "" + source: Quickshell.iconPath(modelData_.appIcon) + } + Text { + elide: Text.ElideRight + text: modelData_.summary + color: Stylix.base05 + } + Text { + id: dismiss + text: "󱏩" + color: Stylix.base08 + font.pixelSize: 16 + + ToolTip { + id: dismissTooltip + visible: false + delay: 500 + timeout: 1000 + text: "Dismiss notification" + } + + HoverHandler { + id: dismissHover + onHoveredChanged: { + dismissTooltip.visible = hovered + } + } + + Layout.topMargin: 5 + Layout.rightMargin: 10 + + MouseArea { + anchors.fill: parent + onClicked: { + modelData_.dismiss(); + if (Notifications.list.length <= 0) { + popup.visible = false; + } + } + } + } +} diff --git a/quickshell/Components/NotificationImage.qml b/quickshell/Components/NotificationImage.qml new file mode 100644 index 00000000..d6b983e2 --- /dev/null +++ b/quickshell/Components/NotificationImage.qml @@ -0,0 +1,25 @@ +import Quickshell +import Quickshell.Widgets +import Quickshell.Io +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import "root:/DataSources" +import "root:/Helpers" +import Quickshell.Services.Notifications + + +ClippingWrapperRectangle { + required property string image + radius: 5 + Layout.preferredWidth: visible ? 80 : 0 + Layout.preferredHeight: visible ? parent.height : 0 + visible: image != "" + color: Stylix.base00 + Image { + fillMode: Image.PreserveAspectFit + Layout.preferredWidth: 80 + Layout.preferredHeight: parent.height + source: image + } +} From 6698f9648c8c9148f31c76648c18880c616cf5c3 Mon Sep 17 00:00:00 2001 From: Kat Inskip Date: Sun, 7 Dec 2025 20:08:02 -0800 Subject: [PATCH 4/6] fix: reduce systray button margin --- quickshell/Components/SystemTrayButton.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/quickshell/Components/SystemTrayButton.qml b/quickshell/Components/SystemTrayButton.qml index c1e03578..e8bd517b 100644 --- a/quickshell/Components/SystemTrayButton.qml +++ b/quickshell/Components/SystemTrayButton.qml @@ -15,8 +15,8 @@ Item { Rectangle { anchors { fill: parent - leftMargin: 10 - rightMargin: 10 + leftMargin: 5 + rightMargin: 5 } color: Stylix.base01 From b15bb36daeab8dd3fe775d69df3537b36a6a5eb1 Mon Sep 17 00:00:00 2001 From: Kat Inskip Date: Sun, 7 Dec 2025 20:18:58 -0800 Subject: [PATCH 5/6] fix: clip notif --- quickshell/Components/NotificationDisplay.qml | 91 ++++++++++--------- quickshell/Components/NotificationHeader.qml | 1 + 2 files changed, 49 insertions(+), 43 deletions(-) diff --git a/quickshell/Components/NotificationDisplay.qml b/quickshell/Components/NotificationDisplay.qml index 3664ee56..11128e62 100644 --- a/quickshell/Components/NotificationDisplay.qml +++ b/quickshell/Components/NotificationDisplay.qml @@ -81,8 +81,9 @@ Item { anchors.fill: parent RowLayout { Layout.alignment: Qt.AlignHCenter - spacing: 5 + spacing: 10 Text { + font.bold: true Layout.preferredHeight: 26 Layout.alignment: Qt.AlignVCenter verticalAlignment: Text.AlignVCenter @@ -123,58 +124,62 @@ Item { } } } - ListView { - id: notificationList - model: Notifications.list - spacing: 10 - ScrollBar.vertical: ScrollBar {} - Layout.alignment: Qt.AlignCenter - Layout.preferredWidth: parent.width - Layout.preferredHeight: parent.height + ClippingRectangle { + color: "transparent" + Layout.alignment: Qt.AlignBottom + Layout.preferredWidth: parent.width + Layout.preferredHeight: parent.height - 24 + ListView { + anchors.fill: parent + id: notificationList + model: Notifications.list + spacing: 10 + ScrollBar.vertical: ScrollBar {} - delegate: Item { - required property Notification modelData + delegate: Item { + required property Notification modelData - height: 100 - width: 400//notificationList.width + height: 100 + width: 400 - Rectangle { - id: indivNotif - color: Stylix.base02 - radius: 5 - anchors { - fill: parent - leftMargin: 5 - rightMargin: 5 - } - RowLayout { - anchors { - fill: parent - } - NotificationImage { - image: modelData.image + Rectangle { + id: indivNotif + color: Stylix.base02 + radius: 5 + anchors { + fill: parent + leftMargin: 5 + rightMargin: 5 } - ColumnLayout { - Layout.leftMargin: 5 - Layout.rightMargin: 5 - Layout.fillWidth: true - NotificationHeader { - modelData_: modelData + RowLayout { + anchors { + fill: parent } - Text { - font.pointSize: 10 - wrapMode: Text.WordWrap + NotificationImage { + image: modelData.image + } + ColumnLayout { + Layout.leftMargin: 5 + Layout.rightMargin: 5 Layout.fillWidth: true - Layout.preferredWidth: modelData.image != "" ? indivNotif.width - 80 : indivNotif.width - Layout.maximumWidth: indivNotif.width - elide: Text.ElideRight - text: modelData.body - color: Stylix.base05 + NotificationHeader { + modelData_: modelData + } + Text { + font.pointSize: 10 + wrapMode: Text.WordWrap + Layout.fillWidth: true + Layout.preferredWidth: modelData.image != "" ? indivNotif.width - 80 : indivNotif.width + Layout.maximumWidth: indivNotif.width + elide: Text.ElideRight + text: modelData.body + color: Stylix.base05 + } } - } NotificationActions { modelData_: modelData } + } } } } diff --git a/quickshell/Components/NotificationHeader.qml b/quickshell/Components/NotificationHeader.qml index 05a26441..da61afbb 100644 --- a/quickshell/Components/NotificationHeader.qml +++ b/quickshell/Components/NotificationHeader.qml @@ -26,6 +26,7 @@ RowLayout { source: Quickshell.iconPath(modelData_.appIcon) } Text { + font.bold: true elide: Text.ElideRight text: modelData_.summary color: Stylix.base05 From 9ae22c832f27093b9d5c220d02924953ee9b6052 Mon Sep 17 00:00:00 2001 From: Kat Inskip Date: Sun, 7 Dec 2025 21:34:18 -0800 Subject: [PATCH 6/6] feat: multi-height delegates --- quickshell/Components/NotificationDisplay.qml | 191 ------------------ .../NotificationActions.qml | 22 +- .../NotificationDisplay.qml | 81 ++++++++ .../NotificationHeader.qml | 9 +- .../NotificationImage.qml | 2 +- .../NotificationSystem/NotificationItem.qml | 76 +++++++ .../NotificationSystem/NotificationWindow.qml | 58 ++++++ .../NotificationWindowHeader.qml | 54 +++++ quickshell/Modules/Bar.qml | 3 +- 9 files changed, 284 insertions(+), 212 deletions(-) delete mode 100644 quickshell/Components/NotificationDisplay.qml rename quickshell/Components/{ => NotificationSystem}/NotificationActions.qml (66%) create mode 100644 quickshell/Components/NotificationSystem/NotificationDisplay.qml rename quickshell/Components/{ => NotificationSystem}/NotificationHeader.qml (83%) rename quickshell/Components/{ => NotificationSystem}/NotificationImage.qml (93%) create mode 100644 quickshell/Components/NotificationSystem/NotificationItem.qml create mode 100644 quickshell/Components/NotificationSystem/NotificationWindow.qml create mode 100644 quickshell/Components/NotificationSystem/NotificationWindowHeader.qml diff --git a/quickshell/Components/NotificationDisplay.qml b/quickshell/Components/NotificationDisplay.qml deleted file mode 100644 index 11128e62..00000000 --- a/quickshell/Components/NotificationDisplay.qml +++ /dev/null @@ -1,191 +0,0 @@ -import Quickshell -import Quickshell.Widgets -import Quickshell.Io -import QtQuick -import QtQuick.Layouts -import QtQuick.Controls -import "root:/DataSources" -import "root:/Helpers" -import Quickshell.Services.Notifications - -Item { - id: root - Layout.alignment: Qt.AlignVCenter; - implicitWidth: 25 - implicitHeight: parent.height - Rectangle { - anchors.centerIn: parent - id: rootContainer - color: "transparent" - width: 30 - height: 30 - radius: 50 - Text { - id: rootIcon - text: "" - color: Stylix.base05 - anchors.centerIn: parent - } - } - function updateDisplay() { - if (Notifications.list.length > 0) { - rootContainer.color = Stylix.base08 - rootIcon.color = Stylix.base00 - } else { - rootContainer.color = "transparent" - rootIcon.color = Stylix.base05 - } - } - Timer { - interval: 1000 - running: true - repeat: true - onTriggered: root.updateDisplay() - } - MouseArea { - id: ma - anchors.fill: parent - hoverEnabled: true - - onClicked: function(mouseEvent) { - var m = root.QsWindow.mapFromItem(ma, ma.width/2.0, ma.height/2.0); - var offset = notificationLoader.item.width / 2.0; - notificationLoader.item.clicky = m.x - offset; - notificationLoader.item.visible = !notificationLoader.item.visible - } - } - - LazyLoader { - id: notificationLoader - - loading: true - - PopupWindow { - property real clicky - id: wrapperPopup - visible: false - anchor.window: root.QsWindow.window - anchor.rect.y: parentWindow?.height ?? 0 - anchor.rect.x: clicky - color: "transparent" - - implicitWidth: 400 - implicitHeight: 600 - - Rectangle { - anchors.fill: parent - color: Stylix.base01 - bottomLeftRadius: 5 - bottomRightRadius: 5 - ColumnLayout { - anchors.fill: parent - RowLayout { - Layout.alignment: Qt.AlignHCenter - spacing: 10 - Text { - font.bold: true - Layout.preferredHeight: 26 - Layout.alignment: Qt.AlignVCenter - verticalAlignment: Text.AlignVCenter - text: "Notifications" - color: Stylix.base05 - font.pixelSize: 16 - } - Text { - Layout.preferredHeight: 26 - Layout.alignment: Qt.AlignVCenter - verticalAlignment: Text.AlignBottom - id: clear - text: "󱏧" - color: Stylix.base08 - font.pixelSize: 16 - ToolTip { - id: clearTooltip - visible: false - delay: 500 - timeout: 1000 - text: "Clear notifications" - } - MouseArea { - anchors.fill: parent - onClicked: { - if (Notifications.list.length >= 0) { - Notifications.clear() - root.updateDisplay() - } - } - } - - HoverHandler { - id: clearHover - onHoveredChanged: { - clearTooltip.visible = hovered - } - } - } - } - ClippingRectangle { - color: "transparent" - Layout.alignment: Qt.AlignBottom - Layout.preferredWidth: parent.width - Layout.preferredHeight: parent.height - 24 - ListView { - anchors.fill: parent - id: notificationList - model: Notifications.list - spacing: 10 - ScrollBar.vertical: ScrollBar {} - - delegate: Item { - required property Notification modelData - - height: 100 - width: 400 - - Rectangle { - id: indivNotif - color: Stylix.base02 - radius: 5 - anchors { - fill: parent - leftMargin: 5 - rightMargin: 5 - } - RowLayout { - anchors { - fill: parent - } - NotificationImage { - image: modelData.image - } - ColumnLayout { - Layout.leftMargin: 5 - Layout.rightMargin: 5 - Layout.fillWidth: true - NotificationHeader { - modelData_: modelData - } - Text { - font.pointSize: 10 - wrapMode: Text.WordWrap - Layout.fillWidth: true - Layout.preferredWidth: modelData.image != "" ? indivNotif.width - 80 : indivNotif.width - Layout.maximumWidth: indivNotif.width - elide: Text.ElideRight - text: modelData.body - color: Stylix.base05 - } - } - NotificationActions { - modelData_: modelData - } - } - } - } - } - } - } - } - } - } -} diff --git a/quickshell/Components/NotificationActions.qml b/quickshell/Components/NotificationSystem/NotificationActions.qml similarity index 66% rename from quickshell/Components/NotificationActions.qml rename to quickshell/Components/NotificationSystem/NotificationActions.qml index 5931d68e..ddaba845 100644 --- a/quickshell/Components/NotificationActions.qml +++ b/quickshell/Components/NotificationSystem/NotificationActions.qml @@ -13,33 +13,27 @@ RowLayout { required property Notification modelData_ Layout.minimumHeight: 0 - Layout.preferredHeight: modelData_.actions != [] ? 30 : 0 - visible: modelData_.actions != [] + Layout.preferredHeight: modelData_.actions.length > 0 ? 30 : 0 + visible: modelData_.actions.length > 0 spacing: 5 Repeater { model: modelData_.actions - Item { - required property NotificationAction actionData + delegate: Item { + required property NotificationAction modelData width: 100 height: 30 - anchors { - left: parent.left - leftMargin: 5 - top: parent.top - topMargin: 5 - } - Rectangle { anchors.fill: parent - color: Stylix.base02 + color: Stylix.base00 radius: 5 Text { - text: actionData.text + text: modelData.text color: Stylix.base05 + anchors.centerIn: parent font.pixelSize: 12 anchors { @@ -51,7 +45,7 @@ RowLayout { MouseArea { anchors.fill: parent - onClicked: actionData.invoke() + onClicked: modelData.invoke() } } } diff --git a/quickshell/Components/NotificationSystem/NotificationDisplay.qml b/quickshell/Components/NotificationSystem/NotificationDisplay.qml new file mode 100644 index 00000000..f0cc0dae --- /dev/null +++ b/quickshell/Components/NotificationSystem/NotificationDisplay.qml @@ -0,0 +1,81 @@ +import Quickshell +import Quickshell.Widgets +import Quickshell.Io +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import "root:/DataSources" +import "root:/Helpers" +import Quickshell.Services.Notifications + +Item { + id: root + Layout.alignment: Qt.AlignVCenter; + implicitWidth: 30 + implicitHeight: parent.height + Rectangle { + anchors.centerIn: parent + id: rootContainer + color: "transparent" + width: 60 + height: 30 + radius: 50 + RowLayout { + spacing: 5 + anchors.centerIn: parent + Text { + verticalAlignment: Text.AlignVCenter + id: rootIcon + text: "" + color: Stylix.base05 + } + Text { + verticalAlignment: Text.AlignVCenter + id: count + text: "" + font.bold: true + color: Stylix.base05 + } + } + } + function updateDisplay() { + if (Notifications.list.length > 0) { + rootContainer.color = Stylix.base08 + rootIcon.color = Stylix.base00 + count.color = Stylix.base00 + count.text = Notifications.list.length + rootContainer.width = 60 + root.implicitWidth = 60 + count.visible = true + } else { + rootContainer.width = 30 + root.implicitWidth = 30 + rootContainer.color = "transparent" + rootIcon.color = Stylix.base05 + count.color = Stylix.base05 + count.visible = false + } + } + Timer { + interval: 1000 + running: true + repeat: true + onTriggered: root.updateDisplay() + } + MouseArea { + id: ma + anchors.fill: parent + hoverEnabled: true + + onClicked: function(mouseEvent) { + var m = root.QsWindow.mapFromItem(ma, ma.width/2.0, ma.height/2.0); + var offset = notificationLoader.item.width / 2.0; + notificationLoader.item.clicky = m.x - offset; + notificationLoader.item.visible = !notificationLoader.item.visible + } + } + + NotificationWindow { + id: notificationLoader + } +} diff --git a/quickshell/Components/NotificationHeader.qml b/quickshell/Components/NotificationSystem/NotificationHeader.qml similarity index 83% rename from quickshell/Components/NotificationHeader.qml rename to quickshell/Components/NotificationSystem/NotificationHeader.qml index da61afbb..aaf0c503 100644 --- a/quickshell/Components/NotificationHeader.qml +++ b/quickshell/Components/NotificationSystem/NotificationHeader.qml @@ -13,17 +13,16 @@ RowLayout { required property Notification modelData_ IconImage { function getIcon() { - console.log(modelData_.appIcon) if (modelData_.appIcon != "") { - return Quickshell.iconPath(modelData_.appIcon) + return Quickshell.iconPath(modelData_.appIcon.replace("file://", "")) } else { - return iconForId(modelData_.appName) + return ThemeIcons.iconForAppId(modelData_.appName) } } width: 24 height: 24 - visible: modelData_.appIcon != "" - source: Quickshell.iconPath(modelData_.appIcon) + visible: source != "" + source: getIcon() } Text { font.bold: true diff --git a/quickshell/Components/NotificationImage.qml b/quickshell/Components/NotificationSystem/NotificationImage.qml similarity index 93% rename from quickshell/Components/NotificationImage.qml rename to quickshell/Components/NotificationSystem/NotificationImage.qml index d6b983e2..64e03b9b 100644 --- a/quickshell/Components/NotificationImage.qml +++ b/quickshell/Components/NotificationSystem/NotificationImage.qml @@ -20,6 +20,6 @@ ClippingWrapperRectangle { fillMode: Image.PreserveAspectFit Layout.preferredWidth: 80 Layout.preferredHeight: parent.height - source: image + source: image.replace("file://", "") } } diff --git a/quickshell/Components/NotificationSystem/NotificationItem.qml b/quickshell/Components/NotificationSystem/NotificationItem.qml new file mode 100644 index 00000000..614c5a1e --- /dev/null +++ b/quickshell/Components/NotificationSystem/NotificationItem.qml @@ -0,0 +1,76 @@ +import Quickshell +import Quickshell.Widgets +import Quickshell.Io +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import "root:/DataSources" +import "root:/Helpers" +import Quickshell.Services.Notifications + +Item { + required property Notification modelData + + function calculateHeight() { + if (modelData.actions.length > 0) { + return 150 + } else if (modelData.image != "") { + return 100 + } else { + return 60 + } + } + + function calculateBodyHeight() { + if (modelData.image != "" || modelData.actions.length > 0) { + return 40 + } else { + return 20 + } + } + + height: calculateHeight() + width: 450 + + Rectangle { + id: indivNotif + color: Stylix.base02 + radius: 5 + anchors { + fill: parent + leftMargin: 5 + rightMargin: 5 + } + RowLayout { + anchors { + fill: parent + } + NotificationImage { + image: modelData.image + } + ColumnLayout { + Layout.alignment: Qt.AlignVCenter + Layout.leftMargin: 5 + Layout.rightMargin: 5 + Layout.fillWidth: true + NotificationHeader { + modelData_: modelData + } + Text { + font.pointSize: 10 + wrapMode: Text.WordWrap + Layout.fillWidth: true + Layout.preferredWidth: modelData.image != "" ? indivNotif.width - 80 : indivNotif.width + Layout.maximumHeight: calculateBodyHeight() + Layout.maximumWidth: indivNotif.width + elide: Text.ElideRight + text: modelData.body + color: Stylix.base05 + } + NotificationActions { + modelData_: modelData + } + } + } + } +} diff --git a/quickshell/Components/NotificationSystem/NotificationWindow.qml b/quickshell/Components/NotificationSystem/NotificationWindow.qml new file mode 100644 index 00000000..07d46417 --- /dev/null +++ b/quickshell/Components/NotificationSystem/NotificationWindow.qml @@ -0,0 +1,58 @@ +import Quickshell +import Quickshell.Widgets +import Quickshell.Io +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import "root:/DataSources" +import "root:/Helpers" +import Quickshell.Services.Notifications + +LazyLoader { + id: notificationLoader + + loading: true + + PopupWindow { + property real clicky + id: wrapperPopup + visible: false + anchor.window: root.QsWindow.window + anchor.rect.y: parentWindow?.height ?? 0 + anchor.rect.x: clicky + color: "transparent" + + implicitWidth: 450 + implicitHeight: 600 + + Rectangle { + anchors.fill: parent + color: Stylix.base01 + bottomLeftRadius: 5 + bottomRightRadius: 5 + ColumnLayout { + anchors.fill: parent + NotificationWindowHeader { + Layout.topMargin: 5 + Layout.bottomMargin: 5 + } + ClippingRectangle { + color: "transparent" + Layout.alignment: Qt.AlignBottom + Layout.preferredWidth: parent.width + Layout.preferredHeight: parent.height - 34 + ListView { + cacheBuffer: 30 + anchors.fill: parent + id: notificationList + model: Notifications.list + spacing: 10 + ScrollBar.vertical: ScrollBar {} + + delegate: NotificationItem {} + } + } + } + } + } +} diff --git a/quickshell/Components/NotificationSystem/NotificationWindowHeader.qml b/quickshell/Components/NotificationSystem/NotificationWindowHeader.qml new file mode 100644 index 00000000..3e002bb0 --- /dev/null +++ b/quickshell/Components/NotificationSystem/NotificationWindowHeader.qml @@ -0,0 +1,54 @@ +import Quickshell +import Quickshell.Widgets +import Quickshell.Io +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import "root:/DataSources" +import "root:/Helpers" +import Quickshell.Services.Notifications + +RowLayout { + Layout.alignment: Qt.AlignHCenter + spacing: 10 + Text { + font.bold: true + Layout.preferredHeight: 26 + Layout.alignment: Qt.AlignVCenter + verticalAlignment: Text.AlignVCenter + text: "Notifications" + color: Stylix.base05 + font.pixelSize: 16 + } + Text { + Layout.preferredHeight: 26 + Layout.alignment: Qt.AlignVCenter + verticalAlignment: Text.AlignBottom + id: clear + text: "󱏧" + color: Stylix.base08 + font.pixelSize: 16 + ToolTip { + id: clearTooltip + visible: false + delay: 500 + timeout: 1000 + text: "Clear notifications" + } + MouseArea { + anchors.fill: parent + onClicked: { + if (Notifications.list.length >= 0) { + Notifications.clear() + root.updateDisplay() + } + } + } + HoverHandler { + id: clearHover + onHoveredChanged: { + clearTooltip.visible = hovered + } + } + } +} diff --git a/quickshell/Modules/Bar.qml b/quickshell/Modules/Bar.qml index 02a19c89..c7a10577 100644 --- a/quickshell/Modules/Bar.qml +++ b/quickshell/Modules/Bar.qml @@ -5,6 +5,7 @@ import QtQuick.Layouts import QtQuick.Controls import "root:/DataSources" import "root:/Components" +import "root:/Components/NotificationSystem" Scope { id: root @@ -70,9 +71,9 @@ Scope { spacing: 15 - NotificationDisplay {} SystemTrayWrapper {} Clock {} + NotificationDisplay {} DistroIcon {} } }