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