149 lines
5.8 KiB
Python
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()
|