diff options
| -rw-r--r-- | config/NotifsConfig.qml | 15 | ||||
| -rw-r--r-- | modules/bar/components/TrayItem.qml | 12 | ||||
| -rw-r--r-- | modules/notifications/Background.qml | 4 | ||||
| -rw-r--r-- | modules/notifications/Content.qml | 12 | ||||
| -rw-r--r-- | modules/notifications/Notification.qml | 188 | ||||
| -rw-r--r-- | modules/notifications/Notifications.qml | 11 | ||||
| -rwxr-xr-x | run.fish | 4 | ||||
| -rw-r--r-- | services/Notifs.qml | 76 | ||||
| -rw-r--r-- | widgets/Colouriser.qml | 15 |
9 files changed, 313 insertions, 24 deletions
diff --git a/config/NotifsConfig.qml b/config/NotifsConfig.qml new file mode 100644 index 0000000..1b5a852 --- /dev/null +++ b/config/NotifsConfig.qml @@ -0,0 +1,15 @@ +pragma Singleton + +import Quickshell +import QtQuick + +Singleton { + readonly property int defaultExpireTimeout: 3000 + readonly property Sizes sizes: Sizes {} + + component Sizes: QtObject { + readonly property int width: 400 + readonly property int image: 50 + readonly property int badge: 20 + } +} diff --git a/modules/bar/components/TrayItem.qml b/modules/bar/components/TrayItem.qml index b7ffdec..5ce45ee 100644 --- a/modules/bar/components/TrayItem.qml +++ b/modules/bar/components/TrayItem.qml @@ -1,5 +1,6 @@ pragma ComponentBehavior: Bound +import "root:/widgets" import "root:/config" import Quickshell import Quickshell.Widgets @@ -53,18 +54,9 @@ MouseArea { active: BarConfig.tray.recolourIcons asynchronous: true - sourceComponent: MultiEffect { + sourceComponent: Colouriser { source: icon colorizationColor: root.colour - colorization: 1 - - Behavior on colorizationColor { - ColorAnimation { - duration: Appearance.anim.durations.normal - easing.type: Easing.BezierSpline - easing.bezierCurve: Appearance.anim.curves.standard - } - } } } } diff --git a/modules/notifications/Background.qml b/modules/notifications/Background.qml index e3289af..a0ed11e 100644 --- a/modules/notifications/Background.qml +++ b/modules/notifications/Background.qml @@ -49,6 +49,10 @@ Shape { radiusX: root.rounding radiusY: root.rounding } + PathLine { + relativeX: 1 + relativeY: 0 + } Behavior on fillColor { ColorAnimation { diff --git a/modules/notifications/Content.qml b/modules/notifications/Content.qml index 86cef8b..7fdd000 100644 --- a/modules/notifications/Content.qml +++ b/modules/notifications/Content.qml @@ -7,16 +7,18 @@ import QtQuick Column { id: root - // padding: Appearance.padding.large + padding: Appearance.padding.large anchors.bottom: parent.bottom anchors.right: parent.right spacing: Appearance.spacing.normal - StyledRect { - width: 300 - height: 100 - // color: Qt.rgba(255, 0, 0, 0.4) + Repeater { + model: ScriptModel { + values: [...Notifs.list] + } + + Notification {} } } diff --git a/modules/notifications/Notification.qml b/modules/notifications/Notification.qml new file mode 100644 index 0000000..a3fa171 --- /dev/null +++ b/modules/notifications/Notification.qml @@ -0,0 +1,188 @@ +pragma ComponentBehavior: Bound + +import "root:/widgets" +import "root:/services" +import "root:/config" +import Quickshell +import Quickshell.Widgets +import QtQuick +import QtQuick.Effects + +StyledRect { + id: root + + required property Notifs.Notif modelData + readonly property bool hasImage: modelData.image.length > 0 + readonly property bool hasAppIcon: modelData.appIcon.length > 0 + readonly property int imageSize: summary.height + bodyPreview.height + property bool expanded + + color: Colours.palette.m3surfaceContainer + radius: Appearance.rounding.normal + implicitWidth: NotifsConfig.sizes.width + + Item { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: Appearance.padding.normal + + implicitHeight: summary.height + bodyPreview.height + anchors.margins * 2 + + Loader { + id: image + + active: root.hasImage + asynchronous: true + + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: -parent.anchors.margins + width: root.hasImage || root.hasAppIcon ? root.imageSize : 0 + height: root.hasImage || root.hasAppIcon ? root.imageSize : 0 + visible: root.hasImage || root.hasAppIcon + + sourceComponent: ClippingRectangle { + radius: Appearance.rounding.full + width: root.imageSize + height: root.imageSize + + Image { + anchors.fill: parent + source: Qt.resolvedUrl(root.modelData.image) + fillMode: Image.PreserveAspectCrop + cache: false + asynchronous: true + } + } + + states: State { + name: "expanded" + when: root.expanded + + AnchorChanges { + anchors.verticalCenter: undefined + anchors.top: image.parent.top + } + } + } + + Loader { + id: appIcon + + active: root.hasAppIcon + asynchronous: true + + anchors.horizontalCenter: root.hasImage ? undefined : image.horizontalCenter + anchors.verticalCenter: root.hasImage ? undefined : image.verticalCenter + anchors.right: root.hasImage ? image.right : undefined + anchors.bottom: root.hasImage ? image.bottom : undefined + + sourceComponent: StyledRect { + radius: Appearance.rounding.full + color: Colours.palette.m3tertiaryContainer + implicitWidth: root.hasImage ? NotifsConfig.sizes.badge : root.imageSize + implicitHeight: root.hasImage ? NotifsConfig.sizes.badge : root.imageSize + + IconImage { + id: icon + + anchors.centerIn: parent + visible: false + implicitSize: Math.round(parent.width * 0.6) + source: Quickshell.iconPath(root.modelData.appIcon) + asynchronous: true + } + + Colouriser { + anchors.fill: icon + source: icon + colorizationColor: Colours.palette.m3onTertiaryContainer + } + } + + states: State { + name: "expanded" + when: !root.hasImage && root.expanded + + AnchorChanges { + anchors.verticalCenter: undefined + anchors.top: image.parent.top + } + } + } + + StyledText { + id: summary + + anchors.top: parent.top + anchors.left: image.right + anchors.leftMargin: Appearance.spacing.small + + text: root.modelData.summary + maximumLineCount: 1 + } + + StyledText { + id: timeSep + + anchors.top: parent.top + anchors.left: summary.right + anchors.leftMargin: Appearance.spacing.small + + text: "•" + color: Colours.palette.m3onSurfaceVariant + font.pointSize: Appearance.font.size.small + } + + StyledText { + id: time + + anchors.top: parent.top + anchors.left: timeSep.right + anchors.leftMargin: Appearance.spacing.small + + animate: true + horizontalAlignment: Text.AlignLeft + text: root.modelData.timeStr + color: Colours.palette.m3onSurfaceVariant + font.pointSize: Appearance.font.size.small + } + + StyledRect { + id: expandBtn + + anchors.right: parent.right + anchors.top: parent.top + + MaterialIcon { + animate: true + text: root.expanded ? "expand_less" : "expand_more" + font.pointSize: Appearance.font.size.smaller + } + } + + StyledText { + id: bodyPreview + + anchors.left: summary.left + anchors.right: expandBtn.left + anchors.top: summary.bottom + anchors.rightMargin: Appearance.spacing.small + + text: bodyPreviewMetrics.elidedText + color: Colours.palette.m3onSurfaceVariant + font.pointSize: Appearance.font.size.small + } + + TextMetrics { + id: bodyPreviewMetrics + + text: root.modelData.body + font.family: bodyPreview.font.family + font.pointSize: bodyPreview.font.pointSize + elide: Text.ElideRight + elideWidth: bodyPreview.width + } + } +} diff --git a/modules/notifications/Notifications.qml b/modules/notifications/Notifications.qml index 2c73802..fa01d33 100644 --- a/modules/notifications/Notifications.qml +++ b/modules/notifications/Notifications.qml @@ -12,14 +12,7 @@ Variants { id: root required property ShellScreen modelData - property bool osdVisible - - Timer { - // running: true - repeat: true - interval: 2000 - onTriggered: root.osdVisible = !root.osdVisible - } + property bool osdVisible: Notifs.list.length > 0 LazyLoader { loading: true @@ -38,7 +31,9 @@ Variants { anchors.top: true anchors.bottom: true + anchors.left: true anchors.right: true + width: wrapper.width height: wrapper.height @@ -2,10 +2,12 @@ set -l dbus 'quickshell.dbus.properties.warning = false' # System tray dbus property errors set -l text_input 'qt.qpa.wayland.textinput.warning = false' # Text input focus when open window +set -l notifs 'quickshell.service.notifications.warning = false' # Notification server warnings on reload +set -l sni 'quickshell.service.sni.host.warning = false' # StatusNotifierItem warnings on reload set -l hypr 'invalid nullptr parameter' # Error that always pops up on Hyprland set -l intercept '^qsintercept:.*(:[0-9]+){2}$' # Empty qsintercept lines set -l loop 'Binding loop detected' # Binding loops set -l process 'QProcess: Destroyed while process' # Long running processes on reload set -l async_loader 'items in the process of being created at engine destruction' # Async loaders on reload -qs -c caelestia --log-rules "$dbus;$text_input" | grep -vE -e $hypr -e $intercept -e $loop -e $process -e $async_loader +qs -c caelestia --log-rules "$dbus;$text_input;$notifs;$sni" | grep -vE -e $hypr -e $intercept -e $loop -e $process -e $async_loader diff --git a/services/Notifs.qml b/services/Notifs.qml new file mode 100644 index 0000000..965e4f3 --- /dev/null +++ b/services/Notifs.qml @@ -0,0 +1,76 @@ +pragma Singleton + +import "root:/config" +import Quickshell +import Quickshell.Services.Notifications +import QtQuick + +Singleton { + id: root + + readonly property list<Notif> list: [] + + NotificationServer { + id: server + + keepOnReload: false + actionsSupported: true + bodyHyperlinksSupported: true + bodyImagesSupported: true + bodyMarkupSupported: true + imageSupported: true + + onNotification: notif => { + notif.tracked = true; + + root.list.push(notifComp.createObject(root, { + popup: true, + notification: notif + })); + } + } + + component Notif: QtObject { + id: notif + + property bool popup + readonly property date time: new Date() + readonly property string timeStr: { + const diff = Time.date.getTime() - time.getTime(); + const m = Math.floor(diff / 60000); + const h = Math.floor(m / 60); + + if (h < 1 && m < 1) + return "now"; + if (h < 1) + return `${m}m`; + return `${h}h`; + } + + required property Notification notification + readonly property string summary: notification.summary + readonly property string body: notification.body + readonly property string appIcon: notification.appIcon + readonly property string image: notification.image + + readonly property Timer timer: Timer { + running: true + interval: notif.notification.expireTimeout > 0 ? notif.notification.expireTimeout : NotifsConfig.defaultExpireTimeout + onTriggered: notif.popup = false + } + + readonly property Connections conn: Connections { + target: notif.notification + + function onClosed(): void { + notif.destroy(); + } + } + } + + Component { + id: notifComp + + Notif {} + } +} diff --git a/widgets/Colouriser.qml b/widgets/Colouriser.qml new file mode 100644 index 0000000..3c06f5a --- /dev/null +++ b/widgets/Colouriser.qml @@ -0,0 +1,15 @@ +import "root:/config" +import QtQuick +import QtQuick.Effects + +MultiEffect { + colorization: 1 + + Behavior on colorizationColor { + ColorAnimation { + duration: Appearance.anim.durations.normal + easing.type: Easing.BezierSpline + easing.bezierCurve: Appearance.anim.curves.standard + } + } +} |