aboutsummaryrefslogtreecommitdiffstats
path: root/notifications/NotificationBalloon.qml
diff options
context:
space:
mode:
Diffstat (limited to 'notifications/NotificationBalloon.qml')
-rw-r--r--notifications/NotificationBalloon.qml61
1 files changed, 57 insertions, 4 deletions
diff --git a/notifications/NotificationBalloon.qml b/notifications/NotificationBalloon.qml
index b01a848..a9f9459 100644
--- a/notifications/NotificationBalloon.qml
+++ b/notifications/NotificationBalloon.qml
@@ -10,6 +10,7 @@
// GNU General Public License for more details.
import QtQuick
+import QtQuick.Shapes
// One balloon: icon at the left, app, summary and body, an X, and the click
// targets. The body is markup, which is why the daemon advertises body-markup.
@@ -22,6 +23,8 @@ Rectangle {
// silently arrives undefined and every binding on it fails.
property var notification
property double now: 0
+ property var borderColors: []
+ property real borderAngle: 0
// Driven by the shell's tick. The balloon drops itself once its expiry
// passes, or while a suppression says so: the drawer and snooze withhold
@@ -38,10 +41,60 @@ Rectangle {
width: parent ? parent.width : 340
implicitHeight: texts.implicitHeight + 20 + (preview.visible ? preview.height + 8 : 0)
- radius: 10
- color: Qt.alpha(Theme.base, 0.82)
- border.width: 1
- border.color: Qt.alpha(Theme.text, 0.12)
+ // The body is drawn by the Shape below, not by the Rectangle, so its
+ // corners can be Hyprland's: rounding 15 at rounding_power 4, a
+ // superellipse a Rectangle radius cannot draw.
+ color: "transparent"
+
+ // A rounded rectangle as an SVG path, each corner the cubic that best
+ // fits |x|^4 + |y|^4 = 1. A circle's control points sit 0.552 of the
+ // radius from each end; matching the superellipse at 45 degrees,
+ // (4 + 3k) / 8 = 2^-1/4, puts them at k = 0.909.
+ function squircle(x, y, w, h, r) {
+ const c = r * (1 - 0.909);
+ const R = x + w, B = y + h;
+ return `M ${x + r} ${y} L ${R - r} ${y} C ${R - c} ${y} ${R} ${y + c} ${R} ${y + r}`
+ + ` L ${R} ${B - r} C ${R} ${B - c} ${R - c} ${B} ${R - r} ${B}`
+ + ` L ${x + r} ${B} C ${x + c} ${B} ${x} ${B - c} ${x} ${B - r}`
+ + ` L ${x} ${y + r} C ${x} ${y + c} ${x + c} ${y} ${x + r} ${y} Z`;
+ }
+
+ // Hyprland's window border: 2px, the udt gradient, rotating. A Rectangle
+ // border takes one colour, so this is a ring, two squircles
+ // filled odd-even, which keeps the translucent body from showing the
+ // gradient through. The gradient line runs through the centre at the
+ // current angle and is just long enough to span the box.
+ // ponytail: three stops, which is what udt-accent writes; GradientStops
+ // cannot come from a Repeater, so a longer list would need more stops.
+ Shape {
+ id: ring
+ anchors.fill: parent
+ preferredRendererType: Shape.CurveRenderer
+ readonly property real dx: Math.cos(b.borderAngle)
+ readonly property real dy: Math.sin(b.borderAngle)
+ readonly property real reach: Math.abs(width / 2 * dx) + Math.abs(height / 2 * dy)
+
+ ShapePath {
+ strokeWidth: -1
+ fillColor: Qt.alpha(Theme.base, 0.82)
+ PathSvg { path: b.squircle(0, 0, ring.width, ring.height, 15) }
+ }
+
+ ShapePath {
+ strokeWidth: -1
+ fillRule: ShapePath.OddEvenFill
+ fillGradient: LinearGradient {
+ x1: ring.width / 2 - ring.dx * ring.reach
+ y1: ring.height / 2 - ring.dy * ring.reach
+ x2: ring.width / 2 + ring.dx * ring.reach
+ y2: ring.height / 2 + ring.dy * ring.reach
+ GradientStop { position: 0.0; color: b.borderColors[0] ?? Theme.accent }
+ GradientStop { position: 0.5; color: b.borderColors[1] ?? Theme.accent }
+ GradientStop { position: 1.0; color: b.borderColors[2] ?? Theme.accent }
+ }
+ PathSvg { path: b.squircle(0, 0, ring.width, ring.height, 15) + " " + b.squircle(2, 2, ring.width - 4, ring.height - 4, 13) }
+ }
+ }
// The background click target is declared first so the X, declared later,
// sits above it and wins its corner.