67 lines
2.4 KiB
Python
67 lines
2.4 KiB
Python
"""ScrollRow — a horizontally-scrolling row of id-keyed cards, diffed in place
|
|
on every refresh instead of torn down and rebuilt. That matters here for two
|
|
reasons: rebuilding would fight an in-progress VolumeMeter drag (see that
|
|
widget's own drag-guard), and it would tear down/respawn each card's live
|
|
`parec` peak-monitor process on every single `pactl subscribe` event instead
|
|
of only when a device/app actually appears or disappears.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import gi
|
|
|
|
gi.require_version("Gtk", "4.0")
|
|
from gi.repository import Gtk # noqa: E402
|
|
|
|
|
|
class ScrollRow(Gtk.ScrolledWindow):
|
|
def __init__(self, make_card, empty_text: str = "Nothing here") -> None:
|
|
super().__init__()
|
|
self.set_policy(Gtk.PolicyType.EXTERNAL, Gtk.PolicyType.NEVER)
|
|
self.add_css_class("sb-scroll-row")
|
|
self._make_card = make_card
|
|
self._row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=14)
|
|
self._row.add_css_class("sb-row")
|
|
self.set_child(self._row)
|
|
self._cards: dict = {}
|
|
self._empty_label = Gtk.Label(label=empty_text)
|
|
self._empty_label.add_css_class("sb-empty")
|
|
self._active = False
|
|
|
|
def sync(self, items: list[dict], *extra) -> None:
|
|
seen = set()
|
|
for item in items:
|
|
iid = item["id"]
|
|
seen.add(iid)
|
|
card = self._cards.get(iid)
|
|
if card is None:
|
|
card = self._make_card(item, *extra)
|
|
self._cards[iid] = card
|
|
self._row.append(card)
|
|
if self._active:
|
|
card.start_peak()
|
|
else:
|
|
card.update(item, *extra)
|
|
for iid in [i for i in self._cards if i not in seen]:
|
|
card = self._cards.pop(iid)
|
|
card.stop_peak()
|
|
self._row.remove(card)
|
|
self._sync_empty_state()
|
|
|
|
def _sync_empty_state(self) -> None:
|
|
empty = not self._cards
|
|
if empty and self._empty_label.get_parent() is None:
|
|
self._row.append(self._empty_label)
|
|
elif not empty and self._empty_label.get_parent() is not None:
|
|
self._row.remove(self._empty_label)
|
|
|
|
def activate(self) -> None:
|
|
self._active = True
|
|
for card in self._cards.values():
|
|
card.start_peak()
|
|
|
|
def deactivate(self) -> None:
|
|
self._active = False
|
|
for card in self._cards.values():
|
|
card.stop_peak()
|