feat(hyprdrive/beacon): draw the divider as a Cairo magenta sine path

Replaces the ∿-glyph label divider with a Gtk.DrawingArea that strokes a sampled
sine wave (soft-glow + bright-core passes), colour per urgency (magenta / accent),
phase-advanced each tick so it travels like a live signal. Background stays
transparent — a class-specific rule overrides the CyberQueer theme's universal
`background-color`, so no dark pill shows behind it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
main
Amir Alexander Abdelbaki 2026-07-19 17:36:04 +02:00
parent 838f954cad
commit 74fc9aefaf
2 changed files with 50 additions and 10 deletions

View File

@ -12,6 +12,7 @@ from __future__ import annotations
import math
from typing import Callable, Optional
import cairo
import gi
gi.require_version("Gtk", "4.0")
@ -21,9 +22,17 @@ from gi.repository import Gdk, GdkPixbuf, GLib, Gtk # noqa: E402
from lib.hologram import HologramOverlay
_SQUIGGLE = "" * 16 # ∿ sine-wave "tuned signal" divider
_CARD_RADIUS = 16.0
# Divider "radio wave" colour (Cairo, not CSS): a magenta wave line on a
# transparent background. Critical keeps an accent wave to match its frame.
_MAGENTA = (0xEB / 255, 0x00 / 255, 0xA6 / 255) # foreground wave (normal)
_ACCENT = (0xE4 / 255, 0x00 / 255, 0x46 / 255) # foreground wave (critical)
_SQUIGGLE_H = 14 # divider row height
_SQUIGGLE_AMP = 3.0 # wave amplitude (px)
_SQUIGGLE_CYCLES = 0.045 # cycles per px of width
_SQUIGGLE_SPEED = 2.4 # phase advance (rad/s) — a slowly travelling signal
def _rounded_path(cr, w: float, h: float, r: float = _CARD_RADIUS) -> None:
r = min(r, w / 2, h / 2)
@ -66,9 +75,14 @@ class NotificationCard:
lbl.set_max_width_chars(30)
textcol.append(lbl)
squig = Gtk.Label(label=_SQUIGGLE, xalign=0.0)
squig.add_css_class("beacon-squiggle")
textcol.append(squig)
self._squiggle_color = _ACCENT if critical else _MAGENTA
self._squiggle_phase = 0.0
self._squiggle = Gtk.DrawingArea()
self._squiggle.add_css_class("beacon-squiggle")
self._squiggle.set_content_height(_SQUIGGLE_H)
self._squiggle.set_hexpand(True)
self._squiggle.set_draw_func(self._draw_squiggle)
textcol.append(self._squiggle)
if body:
blbl = Gtk.Label(xalign=0.0)
@ -123,6 +137,29 @@ class NotificationCard:
# -- public ---------------------------------------------------------------
def tick(self, dt: float) -> None:
self._holo.tick(dt)
self._squiggle_phase += dt * _SQUIGGLE_SPEED
self._squiggle.queue_draw() # travel the wave along like a live signal
def _draw_squiggle(self, _area, cr, width: int, height: int) -> None:
if width <= 0:
return
# transparent background — just the magenta (accent for critical) radio wave
mid = height / 2.0
amp = min(_SQUIGGLE_AMP, mid - 2.0)
k = _SQUIGGLE_CYCLES * 2.0 * math.pi # angular freq per px
steps = max(2, int(width))
cr.set_line_cap(cairo.LINE_CAP_ROUND)
cr.set_line_join(cairo.LINE_JOIN_ROUND)
# two passes: a soft wide glow, then a bright thin core — reads as emitted
# light over the purple bar, matching the card's holographic frame.
for line_w, alpha in ((3.0, 0.30), (1.4, 1.0)):
cr.set_line_width(line_w)
cr.set_source_rgba(*self._squiggle_color, alpha)
for i in range(steps + 1):
x = width * i / steps
y = mid + amp * math.sin(x * k + self._squiggle_phase)
cr.line_to(x, y) if i else cr.move_to(x, y)
cr.stroke()
def dismiss(self) -> None:
"""Play the dissolve, then hand the id back to the window for removal."""

View File

@ -54,14 +54,17 @@ drawingarea {
}
.beacon-card.critical .beacon-summary { color: @accent; }
/* radio-squiggle divider (the tuned-signal line) */
/* radio-wave divider a Cairo sine path drawn in notification.py (colour handled
* there, per urgency); this only spaces it from the title/body. The explicit
* transparent bg beats the CyberQueer theme's `* { background-color:#1a1a1a }`
* (a class selector out-specifies its universal one), so no dark pill shows. */
.beacon-squiggle {
color: #8A5CFF;
font-size: 9pt;
margin: 1px 0;
opacity: 0.9;
margin: 2px 0;
background: none;
background-color: transparent;
border: none;
box-shadow: none;
}
.beacon-card.critical .beacon-squiggle { color: @accent; }
.beacon-body {
color: @text;