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)