Theme Presets¶
every built-in AppTheme side by side.
▶ Run in browserTags: ui theme
An AppTheme is the palette and spacing every widget reads its colours from.
The engine ships presets as class methods: AppTheme.dark(), abyss(),
midnight(), light(), monokai(), solarised_dark() and nord().
A theme applies to a subtree. Each column here assigns one preset with a single
self.theme = theme, and every widget below it (buttons, a panel, text edits,
a slider, a progress bar, checkboxes and tabs) resolves that preset through
get_theme() and draws in its colours. A widget with no theme of its own
anywhere above it uses the application default. Custom drawing can read the
same palette: each column paints its background from theme.bg and a thin
top bar from theme.accent in its on_draw.
The button row pins one button to each Button.VisualState with
set_visual_state_override, so normal, hover, pressed, disabled, focused and
checked are all visible at once instead of only under the pointer.
The columns sit in an HBoxContainer inside a ScrollContainer. The mouse
wheel scrolls it vertically, and because the scroll area takes keyboard focus
(focus_mode=FocusMode.ALL, then grab_focus() once it is in the tree), the
left and right arrow keys scroll it horizontally on a window too narrow for
every column.
Source¶
1# /// simvx
2# tags = ["ui", "theme"]
3# ///
4"""Theme Presets: every built-in AppTheme side by side.
5
6An `AppTheme` is the palette and spacing every widget reads its colours from.
7The engine ships presets as class methods: `AppTheme.dark()`, `abyss()`,
8`midnight()`, `light()`, `monokai()`, `solarised_dark()` and `nord()`.
9
10A theme applies to a subtree. Each column here assigns one preset with a single
11`self.theme = theme`, and every widget below it (buttons, a panel, text edits,
12a slider, a progress bar, checkboxes and tabs) resolves that preset through
13`get_theme()` and draws in its colours. A widget with no theme of its own
14anywhere above it uses the application default. Custom drawing can read the
15same palette: each column paints its background from `theme.bg` and a thin
16top bar from `theme.accent` in its `on_draw`.
17
18The button row pins one button to each `Button.VisualState` with
19`set_visual_state_override`, so normal, hover, pressed, disabled, focused and
20checked are all visible at once instead of only under the pointer.
21
22The columns sit in an `HBoxContainer` inside a `ScrollContainer`. The mouse
23wheel scrolls it vertically, and because the scroll area takes keyboard focus
24(`focus_mode=FocusMode.ALL`, then `grab_focus()` once it is in the tree), the
25left and right arrow keys scroll it horizontally on a window too narrow for
26every column.
27"""
28
29from simvx.core.ui import (
30 Anchor,
31 AppTheme,
32 Button,
33 CheckBox,
34 Control,
35 FocusMode,
36 HBoxContainer,
37 Label,
38 Panel,
39 ProgressBar,
40 ScrollContainer,
41 Slider,
42 TabContainer,
43 TextEdit,
44 VBoxContainer,
45)
46from simvx.graphics import App
47
48THEMES = {
49 "Dark": AppTheme.dark(),
50 "Abyss": AppTheme.abyss(),
51 "Midnight": AppTheme.midnight(),
52 "Light": AppTheme.light(),
53 "Monokai": AppTheme.monokai(),
54 "Solarised": AppTheme.solarised_dark(),
55 "Nord": AppTheme.nord(),
56}
57
58COLUMN_W, COLUMN_H, GAP = 210.0, 780.0, 4.0
59
60
61class ThemeColumn(VBoxContainer):
62 """One preset applied to a column of sample widgets."""
63
64 def __init__(self, name: str, theme: AppTheme):
65 super().__init__(size_x=COLUMN_W, separation=4)
66 self.theme = theme # every widget below inherits it
67
68 self.add_child(Label(name, font_size=15, alignment="centre"))
69
70 self.section("Buttons")
71 for state in Button.VisualState:
72 button = self.add_child(Button(state.value.title()))
73 button.set_visual_state_override(state)
74
75 self.section("Panel")
76 panel = self.add_child(Panel(size_y=36))
77 panel.add_child(Label("Panel content", font_size=12, anchor=Anchor.TOP_LEFT, inset=(6, 8)))
78
79 self.section("TextEdit")
80 self.add_child(TextEdit("Editable text"))
81 self.add_child(TextEdit(placeholder="Placeholder"))
82
83 self.section("Slider")
84 self.add_child(Slider(0, 100, value=65))
85
86 self.section("Progress")
87 self.add_child(ProgressBar(value=72, size_y=18))
88
89 self.section("CheckBox")
90 self.add_child(CheckBox("Enabled", checked=True))
91 self.add_child(CheckBox("Disabled feature"))
92
93 self.section("Tabs")
94 tabs = self.add_child(TabContainer(size_y=60))
95 for tab in ("Scene", "Debug", "Output"):
96 tabs.add_child(Control(name=tab))
97
98 def section(self, title: str):
99 self.add_child(Label(title, font_size=10))
100
101 def on_draw(self, renderer):
102 _, _, w, h = self.get_rect()
103 renderer.rect((0, 0), (w, h), fill=self.theme.bg)
104 renderer.rect((0, 0), (w, 2), fill=self.theme.accent)
105
106
107class ThemeGallery(ScrollContainer):
108 """Every preset in a column of its own, in a strip that scrolls both ways."""
109
110 def __init__(self):
111 super().__init__(anchor=Anchor.FULL_RECT, inset=(6, 6, 0, 0), focus_mode=FocusMode.ALL)
112 columns = [ThemeColumn(name, theme) for name, theme in THEMES.items()]
113 # A container keeps the size it is given rather than growing to its
114 # children, so the strip is sized to hold every column.
115 strip = (len(columns) * (COLUMN_W + GAP), COLUMN_H)
116 self.add_child(HBoxContainer(size=strip, separation=GAP, children=columns))
117
118 def on_ready(self):
119 super().on_ready()
120 self.grab_focus() # arrow keys scroll only the focused control
121
122
123if __name__ == "__main__":
124 App(width=1510, height=800, title="Theme Presets").run(ThemeGallery())