7.2 KiB
Themes — Authoring Guide
File Layout
- Global themes:
themes/<name>.toml— one file per theme, in a directory at the project root. - Project-specific overrides:
<project>/project_themes.toml— a single bundled TOML file with one[themes.<name>]table per theme. - Override the global path via the
SLOP_GLOBAL_THEMESenvironment variable (must be a directory).
Both layouts are scanned and merged; project themes with the same name as a global theme override it.
Schema
# human-readable label (optional)
description = "Solarized Dark by Ethan Schoonover"
# one of: dark | light | mariana | retro_blue
# selects which built-in imgui_color_text_edit palette to apply
# to code blocks in markdown viewers
syntax_palette = "dark"
[colors]
# RGB triples, 0-255
window_bg = [ 0, 43, 54]
text = [147, 161, 161]
button_hovered = [ 38, 139, 210]
# ... any imgui.Col_ key is accepted
syntax_paletteis required for TOML-defined themes. Unknown values fall back to"dark".[colors]is required. Missing it is a hard error (logged to stderr, theme skipped).- Color keys are imgui
Col_enum members in snake_case. The loader does best-effort mapping; unknown keys are silently ignored.
Common Color Keys
| Key | ImGui Col_ |
Use |
|---|---|---|
window_bg |
WindowBg |
Panel/window background |
child_bg |
ChildBg |
Nested child regions |
popup_bg |
PopupBg |
Modal/popup backdrop |
border |
Border |
Separator/border |
frame_bg |
FrameBg |
Input field background |
title_bg |
TitleBg |
Window title bar |
menu_bar_bg |
MenuBarBg |
Top menu strip |
scrollbar_bg |
ScrollbarBg |
Scrollbar track |
button |
Button |
Standard button |
header |
Header |
Collapsible section header |
separator |
Separator |
Divider line |
tab |
Tab |
Tab bar item |
text |
Text |
Default text |
text_disabled |
TextDisabled |
Greyed-out text |
check_mark |
CheckMark |
Checkbox/radio check |
slider_grab |
SliderGrab |
Slider thumb |
table_header_bg |
TableHeaderBg |
Table column headers |
status_info |
(semantic) | Informational accent |
status_success |
(semantic) | Success/positive accent |
status_warning |
(semantic) | Warning accent |
status_error |
(semantic) | Error/negative accent |
The status_* keys are semantic — they map to the theme's accent colors and are used by the C_* color helpers in src/gui_2.py:80-92.
The 4-Syntax-Palette Upstream Limit
imgui-bundle ships four built-in imgui_color_text_edit palettes and exposes no API to define new ones:
| Palette | Style |
|---|---|
dark |
Default dark; balanced contrast |
light |
Default light; balanced contrast |
mariana |
VS Code Mariana-inspired; muted blues |
retro_blue |
High-contrast blue-on-black retro CRT |
You select which one your theme uses by setting the syntax_palette field. The system picks the closest match for you when you omit the field (built-in non-TOML themes get dark by default). To get a different palette, set the field explicitly.
This is a hard upstream limit; there is no way to define a 5th palette without forking imgui-bundle. If you find yourself wanting to, the answer is to pick the closest of the four and adjust your UI theme's colors to harmonize.
Public API (src/theme_2.py)
The system exposes three functions for runtime use:
| Function | Purpose |
|---|---|
load_themes_from_disk() -> None |
Re-scan the global themes directory and <project>/project_themes.toml, re-parse, and refresh the palette registry. Call this after dropping a new .toml file into themes/. |
get_syntax_palette_for_theme(theme_name: str) -> str |
Return the syntax palette name (dark/light/mariana/retro_blue) associated with a UI theme. Returns "dark" for unknown themes. |
apply_syntax_palette(palette_name: str) -> None |
Set the active imgui_color_text_edit default palette. No-op for unknown names. |
The MarkdownRenderer.__init__ (src/markdown_helper.py) automatically calls apply_syntax_palette(get_syntax_palette_for_theme(get_current_palette())), so code blocks in markdown viewers track the active theme. When the user switches themes, new TextEditor instances pick up the new palette; cached editors keep their previous palette until the next block renders.
Usage Examples
from src import theme_2 as theme
# Re-scan disk (e.g. after dropping a new theme file)
theme.load_themes_from_disk()
# Look up the syntax palette for a UI theme name
syntax = theme.get_syntax_palette_for_theme("solarized_dark") # "dark"
# Force a specific syntax palette (e.g. for a one-off preview)
theme.apply_syntax_palette("mariana")
The C_* Color-Callable Convention
src/gui_2.py:80-92 defines 13 module-level getter functions for semantic colors used throughout the GUI:
def C_LBL() -> imgui.ImVec4: return theme.get_color("text_disabled")
def C_VAL() -> imgui.ImVec4: return theme.get_color("text")
def C_OUT() -> imgui.ImVec4: return theme.get_color("status_info")
def C_IN() -> imgui.ImVec4: return theme.get_color("status_success")
# ... and 9 more (C_REQ, C_RES, C_TC, C_TR, C_TRS, C_KEY, C_NUM, C_TRM, C_SUB)
These are callables, not ImVec4 values. They resample the current theme's color on each call, so theme switches take effect on the next render frame.
Correct usage — call the function at the use site:
imgui.text_colored(C_LBL(), "Completed:")
imgui.text_colored(C_VAL(), str(value))
Common bug — storing the function in a dict keyed by name, then passing the function (not its result) to imgui:
DIR_COLORS = {"request": C_OUT, "response": C_IN}
d_col_fn = DIR_COLORS.get(direction, C_VAL) # stores the function
imgui.text_colored(d_col_fn(), direction) # CORRECT: call it
The bug shipped in commit 7ea52cbb (multi-themes track) at src/gui_2.py:3705-3707 and was fixed in 1469ecac. When writing tests that assert theme color usage, patch src.theme_2.imgui so theme.get_color() returns the mock's ImVec4, and assert with C_LBL() (called), not C_LBL (the function).
Hot Reload
Theme TOMLs are loaded once at module init and can be reloaded on demand via theme.load_themes_from_disk(). The function is safe to call from the GUI thread; it mutates the global registry atomically.
Typical workflow when authoring a new theme:
- Drop a new file into
themes/. - From the AI Settings panel's theme dropdown, the new theme is not yet visible — the registry is cached.
- To see it without restarting, call
theme.load_themes_from_disk()from a Python console hooked into the running process, OR add a "Refresh Themes" button that calls it, OR restart the app.
project_themes.toml is scanned for every project load, so changes there are picked up automatically when you switch projects.
Cross-References
- guide_gui_2.md — The C_* callables in detail; the DIR_COLORS bug history.
- guide_testing.md — How to test theme color usage without crashing
imgui.color(). - conductor/tracks.md — The
multi_themes_20260604track entry (the 8 shipped themes and the API design).