Source code for simvx.core.ui.button_group

"""ButtonGroup: keeps at most one of a set of toggle buttons checked."""

from __future__ import annotations

from typing import TYPE_CHECKING

from ..signals import Signal

if TYPE_CHECKING:
    from .widgets import Button

__all__ = ["ButtonGroup"]


[docs] class ButtonGroup: """Makes its buttons mutually exclusive: checking one unchecks the rest. The toggle state lives on each button (``Button.checked``); the group only enforces exclusivity, so a button joins with ``Button(group=grp)`` or ``button.group = grp`` and leaves with ``button.group = None``. A button in a group is a toggle button: joining sets its ``toggle_mode``. The group is a plain object, not a node, so its buttons can sit anywhere in the tree, in different containers or different panels. ``allow_unchecked`` decides what a click on the checked member does: by default it holds, so one member stays checked once the player has chosen, the way radio buttons behave; with ``allow_unchecked=True`` the click unchecks it and the group is left with none checked, which suits a tool palette where "no tool" is a valid choice. A ``RadioButton`` holds either way. ``checked_changed(button)`` fires when the player's click changes which member is checked, with the new one or ``None``. Setting ``checked`` from code keeps the group exclusive and fires nothing, as every widget's own signals do. Example: tools = ButtonGroup() for name in ("Brush", "Eraser", "Fill"): bar.add_child(Button(name, group=tools)) tools.checked_changed.connect(lambda b: print("tool:", b.text if b else None)) """ def __init__(self, *, allow_unchecked: bool = False): self.allow_unchecked = allow_unchecked self._buttons: list[Button] = [] self.checked_changed = Signal()
[docs] @property def buttons(self) -> tuple[Button, ...]: """The member buttons, in the order they joined.""" return tuple(self._buttons)
[docs] @property def checked_button(self) -> Button | None: """The checked member, or ``None`` when none is.""" for button in self._buttons: if button.checked: return button return None
def _add(self, button: Button) -> None: if button not in self._buttons: self._buttons.append(button) if button.checked: self._uncheck_others(button) def _remove(self, button: Button) -> None: if button in self._buttons: self._buttons.remove(button) def _uncheck_others(self, keep: Button) -> None: for button in self._buttons: if button is not keep and button.checked: button.checked = False
[docs] def __repr__(self) -> str: return f"ButtonGroup({len(self._buttons)} buttons, checked={self.checked_button!r})"