Dotfiles/desktopenvs/hyprlua/astal-menu/ui/quadcard.py

149 lines
5.8 KiB
Python

"""Generic chrome around any module: a header (icon, title, expand, settings) and
a body that is either the module's compact widget or an 'enable me' placeholder.
A disabled quad never calls the module's build(), so a disabled Bluetooth/Network
quad spawns no backend work at all.
"""
from __future__ import annotations
import gi
gi.require_version("Gtk", "4.0")
from gi.repository import Gtk # noqa: E402
from module_base import ModuleContext, ModuleInstance, ModuleSpec
class QuadCard(Gtk.Box):
def __init__(self, spec: ModuleSpec, settings, services,
request_expand, request_collapse):
super().__init__(orientation=Gtk.Orientation.VERTICAL)
self.add_css_class("quad-card")
self.spec = spec
self.settings = settings
self.services = services
self.ctx = ModuleContext(spec, settings, services, request_expand, request_collapse)
self.instance: ModuleInstance | None = None
self._header = self._build_header()
self.append(self._header)
self._body_holder = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self._body_holder.add_css_class("quad-body")
self._body_holder.set_vexpand(True)
self.append(self._body_holder)
self._rebuild_body()
# -- header ------------------------------------------------------------
def _build_header(self) -> Gtk.Widget:
header = Gtk.CenterBox()
header.add_css_class("quad-header")
title = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
icon = Gtk.Label(label=self.spec.icon)
icon.add_css_class("quad-icon")
name = Gtk.Label(label=self.spec.title, xalign=0.0)
name.add_css_class("quad-title")
title.append(icon)
title.append(name)
header.set_start_widget(title)
actions = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=4)
self._expand_btn = Gtk.Button(label="") # nf-fa-expand
self._expand_btn.add_css_class("quad-action")
self._expand_btn.set_tooltip_text("Expand")
self._expand_btn.connect("clicked", lambda *_: self.ctx.expand())
actions.append(self._build_settings_button())
actions.append(self._expand_btn)
header.set_end_widget(actions)
return header
def _build_settings_button(self) -> Gtk.Widget:
btn = Gtk.MenuButton(label="") # nf-fa-cog
btn.add_css_class("quad-action")
btn.set_tooltip_text("Settings")
pop = Gtk.Popover()
pop.add_css_class("quad-settings")
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
box.append(self._switch_row("Enabled", self.settings.quad_enabled(
self.spec.id, self.spec.default_enabled),
lambda v: self.settings.set_quad_enabled(self.spec.id, v)))
for feat in self.spec.features:
box.append(Gtk.Separator())
box.append(self._switch_row(
feat.label, self.settings.feature(self.spec.id, feat.id, feat.default),
lambda v, fid=feat.id: self.settings.set_feature(self.spec.id, fid, v)))
pop.set_child(box)
btn.set_popover(pop)
return btn
@staticmethod
def _switch_row(label: str, value: bool, on_change) -> Gtk.Widget:
row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=12)
row.add_css_class("switch-row")
lbl = Gtk.Label(label=label, xalign=0.0, hexpand=True)
sw = Gtk.Switch(active=value)
sw.connect("state-set", lambda _sw, v: (on_change(v), False)[1])
row.append(lbl)
row.append(sw)
return row
# -- body --------------------------------------------------------------
def _clear_body(self) -> None:
if self.instance and self.instance.destroy:
self.instance.destroy()
self.instance = None
child = self._body_holder.get_first_child()
while child:
self._body_holder.remove(child)
child = self._body_holder.get_first_child()
def _rebuild_body(self) -> None:
self._clear_body()
enabled = self.settings.quad_enabled(self.spec.id, self.spec.default_enabled)
if enabled:
inst = self.spec.build(self.ctx)
self.instance = inst
self._body_holder.append(inst.compact)
self._expand_btn.set_sensitive(inst.expanded is not None)
else:
self._expand_btn.set_sensitive(False)
self._body_holder.append(self._disabled_placeholder())
def _disabled_placeholder(self) -> Gtk.Widget:
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10,
valign=Gtk.Align.CENTER, halign=Gtk.Align.CENTER)
box.add_css_class("quad-disabled")
box.append(Gtk.Label(label=f"{self.spec.title} is off"))
btn = Gtk.Button(label="Enable")
btn.add_css_class("enable-btn")
btn.connect("clicked",
lambda *_: self.settings.set_quad_enabled(self.spec.id, True))
box.append(btn)
return box
# -- lifecycle ---------------------------------------------------------
def on_settings_changed(self) -> None:
"""Called by the grid when settings.json changes; rebuild if enablement flipped."""
enabled = self.settings.quad_enabled(self.spec.id, self.spec.default_enabled)
has_module = self.instance is not None
if enabled != has_module:
self._rebuild_body()
@property
def expanded_widget(self) -> Gtk.Widget | None:
return self.instance.expanded if self.instance else None
def on_show(self) -> None:
if self.instance and self.instance.on_show:
self.instance.on_show()
def on_hide(self) -> None:
if self.instance and self.instance.on_hide:
self.instance.on_hide()