diff options
Diffstat (limited to 'notifications/NotificationBalloon.qml')
| -rw-r--r-- | notifications/NotificationBalloon.qml | 61 |
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. |
