Source code for simvx.core.ui.toolbar
"""Toolbar: horizontal button strip with toggle/radio group support."""
import logging
from ..signals import Signal
from ..math.types import Vec2
from .containers import HBoxContainer
from .core import ThemeColour
from .widgets import Button, Panel
from ..input.enums import MouseButton
log = logging.getLogger(__name__)
__all__ = ["Toolbar", "ToolbarButton"]
# ============================================================================
# ToolbarButton: Button with toggle and radio group support
# ============================================================================
[docs]
class ToolbarButton(Button):
"""Button that can toggle on/off and participate in radio groups.
When toggle_mode is True, the button stays pressed after clicking.
When group is set, clicking deselects other ToolbarButtons in the
same group (radio behaviour).
Example:
btn = ToolbarButton("Move", toggle=True, group="tools")
btn.toggled.connect(lambda active: print(f"active={active}"))
"""
def __init__(self, text: str = "Button", toggle_mode: bool = False, group: str = "", on_press=None, **kwargs):
super().__init__(text=text, on_press=on_press, **kwargs)
self.toggle_mode = toggle_mode
self.group = group
self.active = False
self.toggled = Signal()
def _on_gui_input(self, event):
if event.button == MouseButton.LEFT:
if event.pressed and self.is_point_inside(event.position):
self._is_pressed = True
self.queue_redraw()
self.button_down()
elif not event.pressed and self._is_pressed:
self._is_pressed = False
self.queue_redraw()
self.button_up()
if self.is_point_inside(event.position):
if self.toggle_mode:
# If in a radio group, don't allow deselecting
if self.group and self.active:
return
self.active = not self.active
# Deselect siblings in the same radio group
if self.group and self.active and self.parent:
for child in self.parent.children:
if (
isinstance(child, ToolbarButton)
and child is not self
and child.group == self.group
and child.active
):
child.active = False
child.queue_redraw()
child.toggled(False)
self.toggled(self.active)
self.pressed()
# Colours for active-state tinting (set externally by editor)
active_bg_colour: tuple | None = None
[docs]
def on_draw(self, renderer):
x, y, w, h = self.get_global_rect()
if self.disabled:
box = self.style_disabled
if box is not None:
box.draw(renderer, x, y, w, h)
if self.text:
scale = self.font_size / 14.0
tw = renderer.text_width(self.text, scale)
renderer.draw_text(
self.text,
(x + (w - tw) / 2, y + (h - self.font_size) / 2),
colour=self.text_disabled_colour,
scale=scale,
)
return
# Active state with optional colour override
if self.active and self.active_bg_colour:
renderer.draw_rect((x, y), (w, h), colour=self.active_bg_colour, filled=True)
elif self.active:
box = self.style_pressed
if box is not None:
box.draw(renderer, x, y, w, h)
else:
box = self._get_current_style()
if box is not None:
box.draw(renderer, x, y, w, h)
# Text (centered)
if self.text:
scale = self.font_size / 14.0
text_width = renderer.text_width(self.text, scale)
text_x = x + (w - text_width) / 2
text_y = y + (h - self.font_size) / 2
renderer.draw_text(self.text, (text_x, text_y), colour=self.text_colour, scale=scale)
# ============================================================================
# Toolbar: Horizontal strip of buttons with background bar
# ============================================================================
[docs]
class Toolbar(HBoxContainer):
"""Horizontal toolbar with buttons, separators, and a background bar.
Example:
toolbar = Toolbar()
toolbar.add_button("File")
toolbar.add_separator()
toolbar.add_button("Move", toggle=True, group="tools")
toolbar.add_button("Rotate", toggle=True, group="tools")
"""
toolbar_bg = ThemeColour("toolbar_bg")
toolbar_border = ThemeColour("border")
separator_colour = ThemeColour("border_light")
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.size = Vec2(self.size.x, 36)
self.separation = 2
[docs]
def add_button(self, text: str, icon=None, toggle: bool = False, group: str = "", on_press=None) -> ToolbarButton:
"""Add a toolbar button.
Args:
text: Button label text.
icon: Reserved for future icon support.
toggle: If True, button stays pressed when clicked.
group: Radio group name; clicking deselects other buttons in group.
on_press: Callback invoked when button is clicked.
Returns:
The created ToolbarButton instance.
"""
btn = ToolbarButton(
text=text,
toggle_mode=toggle,
group=group,
on_press=on_press,
)
btn.size = Vec2(max(60, len(text) * 10 + 20), self.size.y - 4)
self.add_child(btn)
return btn
[docs]
def add_separator(self):
"""Add a thin vertical separator line between buttons."""
from .theme import StyleBox
sep = Panel()
sep.size = Vec2(2, self.size.y - 8)
sep.style = StyleBox(bg_colour=self.separator_colour, border_width=0.0)
self.add_child(sep)
[docs]
def on_draw(self, renderer):
# Draw background bar
x, y, w, h = self.get_global_rect()
renderer.draw_rect((x, y), (w, h), colour=self.toolbar_bg, filled=True)
renderer.draw_line((x, y + h), (x + w, y + h), colour=self.toolbar_border)