"""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()