Source code for simvx.core.ui.toolbar

"""Toolbar — horizontal button strip with toggle/radio group support."""


from __future__ import annotations

import logging

from ..descriptors import Signal
from ..math.types import Vec2
from .containers import HBoxContainer
from .core import ThemeColour
from .widgets import Button, Panel

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 == 1: 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 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_coloured( self.text, x + (w - tw) / 2, y + (h - self.font_size) / 2, scale, self.text_disabled_colour ) return # Active state with optional colour override if self.active and self.active_bg_colour: renderer.draw_filled_rect(x, y, w, h, self.active_bg_colour) 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_coloured(self.text, text_x, text_y, scale, self.text_colour)
# ============================================================================ # 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 draw(self, renderer): # Draw background bar x, y, w, h = self.get_global_rect() renderer.draw_filled_rect(x, y, w, h, self.toolbar_bg) renderer.draw_line_coloured(x, y + h, x + w, y + h, self.toolbar_border)