aboutsummaryrefslogtreecommitdiffstats
path: root/notifications/Balloons.qml
diff options
context:
space:
mode:
Diffstat (limited to 'notifications/Balloons.qml')
-rw-r--r--notifications/Balloons.qml72
1 files changed, 72 insertions, 0 deletions
diff --git a/notifications/Balloons.qml b/notifications/Balloons.qml
new file mode 100644
index 0000000..3676e67
--- /dev/null
+++ b/notifications/Balloons.qml
@@ -0,0 +1,72 @@
+// Copyright (C) 2026 Danilo M. <danix@danix.xyz>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+import Quickshell
+import Quickshell.Wayland
+import QtQuick
+
+// The balloon stack, bottom-right of DP-1, over conky.
+Scope {
+ id: root
+
+ // A tick drives both expiry and the end of a snooze without waiting for a
+ // file change. 250ms keeps ronema's -t 1 near-instant.
+ property double now: Date.now()
+ Timer {
+ interval: 250
+ running: true
+ repeat: true
+ onTriggered: root.now = Date.now()
+ }
+
+ // The Repeater model is Notify.queue itself, so its identity only changes
+ // when the daemon rewrites the file, not on the 250ms tick. Expiry and
+ // snooze are the balloon's own business: the tick reassigns `now`, each
+ // balloon re-evaluates its visible binding and leaves the column at
+ // expiry. Rebuilding the model on the tick would recreate every delegate
+ // (reloading icons, resetting hover) four times a second.
+ readonly property var live: Notify.queue || []
+
+ PanelWindow {
+ id: win
+
+ // From the queue, never from layout: a hidden window stops polishing,
+ // and Column computes implicitHeight during polish, so deriving
+ // visibility from it would never turn true again after the first
+ // hide. Queue membership is a plain property the binding re-reads
+ // whether or not the window is mapped, so a later notification
+ // re-maps it. Expired entries that stay in the queue keep a
+ // zero-height window mapped, which is harmless.
+ visible: root.live.length > 0
+ screen: Quickshell.screens.find(s => s.name === "DP-1") ?? Quickshell.screens[0]
+ anchors { bottom: true; right: true }
+ margins { bottom: 12; right: 12 }
+ implicitWidth: 340
+ implicitHeight: column.implicitHeight
+ color: "transparent"
+ exclusionMode: ExclusionMode.Ignore
+ WlrLayershell.layer: WlrLayer.Overlay
+ WlrLayershell.namespace: "quickshell-notifications"
+ WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
+
+ Column {
+ id: column
+ width: parent.width
+ anchors { bottom: parent.bottom; right: parent.right }
+ spacing: 8
+
+ Repeater {
+ model: root.live
+ NotificationBalloon { notification: modelData; now: root.now }
+ }
+ }
+ }
+}