Visual demo for the StyleBox theming system.

Shows all theme presets side by side. Each column applies its own theme to the widget subtree so backgrounds, buttons, inputs, sliders, checkboxes, and tabs all render with the correct colours. Buttons are shown in every state: normal, hover, pressed, disabled, focused.

▶ Run in browser

Tags: ui

Run: uv run python examples/features/ui/theme.py

Source

  1#!/usr/bin/env python3
  2"""Visual demo for the StyleBox theming system.
  3
  4Shows all theme presets side by side. Each column applies its own theme to
  5the widget subtree so backgrounds, buttons, inputs, sliders, checkboxes,
  6and tabs all render with the correct colours.  Buttons are shown in every
  7state: normal, hover, pressed, disabled, focused.
  8
  9Run:
 10    uv run python examples/features/ui/theme.py
 11"""
 12
 13from simvx.core.ui import (
 14    AnchorPreset,
 15    AppTheme,
 16    Button,
 17    CheckBox,
 18    Control,
 19    HBoxContainer,
 20    Label,
 21    Panel,
 22    ProgressBar,
 23    ScrollContainer,
 24    Slider,
 25    TabContainer,
 26    TextEdit,
 27    VBoxContainer,
 28)
 29from simvx.graphics import App
 30
 31# All theme presets to show
 32THEMES: list[tuple[str, AppTheme]] = [
 33    ("Dark", AppTheme.dark()),
 34    ("Abyss", AppTheme.abyss()),
 35    ("Midnight", AppTheme.midnight()),
 36    ("Light", AppTheme.light()),
 37    ("Monokai", AppTheme.monokai()),
 38    ("Solarised", AppTheme.solarised_dark()),
 39    ("Nord", AppTheme.nord()),
 40]
 41
 42COL_W = 210.0
 43COL_H = 780.0
 44PAD = 8.0
 45
 46
 47class ThemeColumn(VBoxContainer):
 48    """A column showing widgets rendered with a specific theme."""
 49
 50    def __init__(self, theme_name: str, theme: AppTheme, **kwargs):
 51        super().__init__(**kwargs)
 52        self.separation = 4.0
 53        self._app_theme = theme
 54        # Assign theme to this subtree so all children inherit it
 55        self.theme = theme
 56
 57        # --- Column background panel (uses theme bg) ---
 58        # (We are the VBox, our draw fills the column bg)
 59
 60        # Title
 61        title = Label(f" {theme_name} ")
 62        title.font_size = 15.0
 63        title.alignment = "center"
 64        title.size_x = COL_W
 65        self.add_child(title)
 66
 67        # --- Buttons ---
 68        self._section("Buttons")
 69        for label, state in [
 70            ("Normal", Button.VisualState.NORMAL),
 71            ("Hover", Button.VisualState.HOVER),
 72            ("Pressed", Button.VisualState.PRESSED),
 73            ("Disabled", Button.VisualState.DISABLED),
 74            ("Focused", Button.VisualState.FOCUSED),
 75        ]:
 76            btn = Button(label)
 77            btn.size_x = COL_W - 2 * PAD
 78            btn.size_y = 26
 79            btn.set_visual_state_override(state)
 80            self.add_child(btn)
 81
 82        # --- Panel ---
 83        self._section("Panel")
 84        panel = Panel()
 85        panel.size_x = COL_W - 2 * PAD
 86        panel.size_y = 36
 87        pl = Label("Panel content")
 88        pl.font_size = 12.0
 89        pl.set_anchor_preset(AnchorPreset.TOP_LEFT)
 90        pl.margin_left = 6
 91        pl.margin_top = 8
 92        panel.add_child(pl)
 93        self.add_child(panel)
 94
 95        # --- TextEdit ---
 96        self._section("TextEdit")
 97        edit = TextEdit("Editable text")
 98        edit.size_x = COL_W - 2 * PAD
 99        edit.size_y = 26
100        self.add_child(edit)
101
102        edit_f = TextEdit("Focused input")
103        edit_f.size_x = COL_W - 2 * PAD
104        edit_f.size_y = 26
105        edit_f.focused = True
106        self.add_child(edit_f)
107
108        # --- Slider ---
109        self._section("Slider")
110        sl = Slider(0, 100, value=65)
111        sl.size_x = COL_W - 2 * PAD
112        sl.size_y = 18
113        self.add_child(sl)
114
115        # --- ProgressBar ---
116        self._section("Progress")
117        pb = ProgressBar(0, 100)
118        pb.value = 72
119        pb.size_x = COL_W - 2 * PAD
120        pb.size_y = 18
121        self.add_child(pb)
122
123        # --- CheckBox ---
124        self._section("CheckBox")
125        cb_on = CheckBox("Enabled", checked=True)
126        self.add_child(cb_on)
127        cb_off = CheckBox("Disabled feature")
128        self.add_child(cb_off)
129
130        # --- Tabs ---
131        self._section("Tabs")
132        tabs = TabContainer()
133        tabs.size_x = COL_W - 2 * PAD
134        tabs.size_y = 60
135        for name in ("Scene", "Debug", "Output"):
136            tabs.add_child(Control(name=name))
137        self.add_child(tabs)
138
139    def _section(self, name: str):
140        lbl = Label(f"  {name}")
141        lbl.font_size = 10.0
142        self.add_child(lbl)
143
144    def on_draw(self, renderer):
145        """Draw themed column background before children."""
146        x, y, w, h = self.get_global_rect()
147        t = self._app_theme
148        renderer.draw_rect((x, y), (w, h), colour=t.bg, filled=True)
149        # Subtle top accent bar
150        renderer.draw_rect((x, y), (w, 2), colour=t.accent, filled=True)
151
152
153class ThemeDemoRoot(Control):
154    """Root: a scrollable horizontal row of themed columns.
155
156    The columns are wrapped in a ScrollContainer so every theme and the full
157    height of each column (down to the Tabs row) stay reachable at any window
158    size: the mouse wheel scrolls vertically and the arrow keys scroll
159    horizontally.
160    """
161
162    def __init__(self):
163        super().__init__()
164        self._scroll = ScrollContainer()
165        self.add_child(self._scroll)
166
167        self._hbox = HBoxContainer()
168        self._hbox.separation = 4.0
169        self._scroll.add_child(self._hbox)
170
171        for name, theme in THEMES:
172            col = ThemeColumn(name, theme)
173            col.size_x = COL_W
174            col.size_y = COL_H
175            self._hbox.add_child(col)
176
177    def on_ready(self):
178        self.set_anchor_preset(AnchorPreset.FULL_RECT)
179        self.margin_left = 6
180        self.margin_top = 6
181        self._scroll.set_anchor_preset(AnchorPreset.FULL_RECT)
182        self._hbox.size_x = len(THEMES) * (COL_W + 4)
183        self._hbox.size_y = COL_H
184
185if __name__ == "__main__":
186    total_w = int(len(THEMES) * (COL_W + 4) + 16)
187    # bg_colour omitted → clear colour auto-syncs from the active theme's bg_black
188    app = App(width=total_w, height=800, title="StyleBox Theme Demo")
189    app.run(ThemeDemoRoot())