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
parent
838f954cad
commit
74fc9aefaf
|
|
@ -12,6 +12,7 @@ from __future__ import annotations
|
||||||
import math
|
import math
|
||||||
from typing import Callable, Optional
|
from typing import Callable, Optional
|
||||||
|
|
||||||
|
import cairo
|
||||||
import gi
|
import gi
|
||||||
|
|
||||||
gi.require_version("Gtk", "4.0")
|
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
|
from lib.hologram import HologramOverlay
|
||||||
|
|
||||||
_SQUIGGLE = "∿" * 16 # ∿ sine-wave "tuned signal" divider
|
|
||||||
_CARD_RADIUS = 16.0
|
_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:
|
def _rounded_path(cr, w: float, h: float, r: float = _CARD_RADIUS) -> None:
|
||||||
r = min(r, w / 2, h / 2)
|
r = min(r, w / 2, h / 2)
|
||||||
|
|
@ -66,9 +75,14 @@ class NotificationCard:
|
||||||
lbl.set_max_width_chars(30)
|
lbl.set_max_width_chars(30)
|
||||||
textcol.append(lbl)
|
textcol.append(lbl)
|
||||||
|
|
||||||
squig = Gtk.Label(label=_SQUIGGLE, xalign=0.0)
|
self._squiggle_color = _ACCENT if critical else _MAGENTA
|
||||||
squig.add_css_class("beacon-squiggle")
|
self._squiggle_phase = 0.0
|
||||||
textcol.append(squig)
|
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:
|
if body:
|
||||||
blbl = Gtk.Label(xalign=0.0)
|
blbl = Gtk.Label(xalign=0.0)
|
||||||
|
|
@ -123,6 +137,29 @@ class NotificationCard:
|
||||||
# -- public ---------------------------------------------------------------
|
# -- public ---------------------------------------------------------------
|
||||||
def tick(self, dt: float) -> None:
|
def tick(self, dt: float) -> None:
|
||||||
self._holo.tick(dt)
|
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:
|
def dismiss(self) -> None:
|
||||||
"""Play the dissolve, then hand the id back to the window for removal."""
|
"""Play the dissolve, then hand the id back to the window for removal."""
|
||||||
|
|
|
||||||
|
|
@ -54,14 +54,17 @@ drawingarea {
|
||||||
}
|
}
|
||||||
.beacon-card.critical .beacon-summary { color: @accent; }
|
.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 {
|
.beacon-squiggle {
|
||||||
color: #8A5CFF;
|
margin: 2px 0;
|
||||||
font-size: 9pt;
|
background: none;
|
||||||
margin: 1px 0;
|
background-color: transparent;
|
||||||
opacity: 0.9;
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
.beacon-card.critical .beacon-squiggle { color: @accent; }
|
|
||||||
|
|
||||||
.beacon-body {
|
.beacon-body {
|
||||||
color: @text;
|
color: @text;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue