Theming

What is a theme?

A theme is a small declarative file (.pyhmt) that holds the appearance defaults of an HMI: semantic colors (accent, text, backgrounds), font sizes and corner radii. Instead of hard-coding colors widget by widget, a project references one theme file; switching the file switches the appearance of every new element and of the scene window — without touching the UI description.

Themes are inert data: loading or applying a theme never executes code.

File format

A theme file is YAML with four optional sections. Indentation and keys follow the same convention as PyHML.

window:
  background: [15, 23, 42, 255]
canvas:
  rect:
    fill: [203, 213, 225]
    stroke: [51, 65, 85]
widgets:
  Button:
    background_color: [37, 99, 235]
    text_color: [255, 255, 255]
global:
  accent: [37, 99, 235]
  font_size: 14
  button_radius: 6
SectionKeyApplies to
windowbackgroundthe scene window element (background color)
canvasrect, ellipse, line, text, imagedefaults for NEW canvas elements of that type
widgetsany widget class name (Button, Slider, ...)defaults for NEW widget elements of that class
globalaccent, font_size, button_radius, widget_radius, panel_radius, corner_radiusshared values; radii map onto the border_radius property of elements that expose it

Value forms: colors as [r, g, b(, a)] lists or #rrggbb strings (normalized to lists on load), booleans as true/1/yes (for known boolean properties) and numbers as plain ints (digit strings are coerced for known numeric properties).

Editor ThemeBar

The UI Scene Editor has a Theme bar in the top toolbar: a name field, three icon buttons and a screen-background swatch.

The editor also shows the PyHMI logo in the top-left corner, left of the project name field.

Theme semantics: values apply to new elements created after the theme was loaded — existing scene elements are not mutated. Loading a theme does not create an undo entry (no scene mutation).

When nothing is selected, the properties pane shows a Theme section with the editable global values (accent, font size, radii). Property rows that are driven by the theme carry a (theme) hint.

Python API

from pathlib import Path
from pyhmi.cli.editor.theme import (
    Theme, load_theme_file, save_theme_file,
    resolve_theme_path, default_template_path,
    apply_theme_to_tree,
)

theme = load_theme_file("mytheme.pyhmt")      # Theme with 4 sections
accent = theme.accent                          # [r, g, b(, a)] or None
applied = apply_theme_to_tree(theme, window)   # apply to a live widget tree
save_theme_file(theme, Path("out") / "theme.pyhmt")
FunctionPurpose
Theme.from_dict(raw)build a theme from loaded data (tolerant of unknown sections/junk)
theme.get(section, name, prop, fallback)3-level lookup with fallback
theme.element_defaults(etype, kind)defaults for one element type
theme.element_overrides(etype, kind)defaults + global font/radius, section value wins
theme.merge(other)new theme with other on top
load_theme_file(path)load .pyhmt/YAML (BOM tolerant); non-mapping raises ValueError
save_theme_file(theme, path)write YAML, creating parent directories
resolve_theme_path(name)name or path; appends .pyhmt to missing names
default_template_path()the default.pyhmt shipped with the editor
apply_theme_to_tree(theme, root)set matching properties on a live widget tree; returns applied count

Generated project launchers inject apply_theme_to_tree automatically when the project ships a .pyhmt next to the scene, so the same theme drives the editor preview and the running app.

Bundled templates

default.pyhmtdark.pyhmt
Window background[247, 248, 250, 255] light[15, 23, 42, 255] dark slate
Accent[37, 99, 235][96, 165, 250]
Textslate-900slate-200
Font size1414

See the examples features/09_theme.py (dark theme applied to a scene) and features/10_theme_editor_export.py (save/load round-trip).