Source code for simvx.core.ui.theme

"""Shared application theme -- single source of truth for colours and layout.

``AppTheme`` extends the base ``Theme`` with named attributes covering
backgrounds, text, accents, semantic colours, buttons, editor viewports,
gizmos, IDE minimap, autocomplete, and scrollbar styling.

``SyntaxTheme`` provides syntax highlighting colours for code editors.

Factory classmethods ``dark()``, ``light()``, and ``monokai()`` return
pre-configured instances.  Module-level ``get_theme()`` / ``set_theme()``
manage a runtime-swappable singleton.

Usage::

    from simvx.core.ui.theme import AppTheme, get_theme, set_theme

    theme = get_theme()           # module-level singleton (dark by default)
    bg = theme.bg                 # direct attribute access
    set_theme(AppTheme.monokai()) # runtime theme switch
"""


from __future__ import annotations

from __future__ import annotations

import logging

from .types import Theme

log = logging.getLogger(__name__)

Colour4 = tuple[float, float, float, float]


# ---------------------------------------------------------------------------
# StyleBox — rich background with gradients and per-side borders
# ---------------------------------------------------------------------------


[docs] class StyleBox: """Themed background with optional gradient and per-side embossed borders. Use ``draw()`` to render the background and borders. Use ``inset`` to offset child content past the border + content margin. """ __slots__ = ( "bg_colour", "bg_gradient", "border_colour", "border_top", "border_bottom", "border_left", "border_right", "border_width", "content_margin", ) def __init__( self, bg_colour: Colour4 = (0.2, 0.2, 0.2, 1.0), bg_gradient: tuple[Colour4, Colour4] | None = None, border_colour: Colour4 = (0.3, 0.3, 0.3, 1.0), border_top: Colour4 | None = None, border_bottom: Colour4 | None = None, border_left: Colour4 | None = None, border_right: Colour4 | None = None, border_width: float = 1.0, content_margin: float = 2.0, ): self.bg_colour = bg_colour self.bg_gradient = bg_gradient self.border_colour = border_colour self.border_top = border_top self.border_bottom = border_bottom self.border_left = border_left self.border_right = border_right self.border_width = border_width self.content_margin = content_margin @property def inset(self) -> float: """Total inward offset (border + content margin) for child positioning.""" return self.border_width + self.content_margin
[docs] def draw(self, renderer, x: float, y: float, w: float, h: float): """Render background and borders into *renderer* at the given rect.""" bw = self.border_width # Background (inset by border width) if self.bg_gradient is not None: renderer.fill_rect_gradient(x + bw, y + bw, w - 2 * bw, h - 2 * bw, *self.bg_gradient) else: renderer.draw_filled_rect(x + bw, y + bw, w - 2 * bw, h - 2 * bw, self.bg_colour) # Borders — four thin filled rects with per-side colour overrides if bw > 0: ct = self.border_top or self.border_colour cb = self.border_bottom or self.border_colour cl = self.border_left or self.border_colour cr = self.border_right or self.border_colour renderer.draw_filled_rect(x, y, w, bw, ct) # top renderer.draw_filled_rect(x, y + h - bw, w, bw, cb) # bottom renderer.draw_filled_rect(x, y + bw, bw, h - 2 * bw, cl) # left renderer.draw_filled_rect(x + w - bw, y + bw, bw, h - 2 * bw, cr) # right
# --------------------------------------------------------------------------- # SyntaxTheme -- colours for code/syntax highlighting # ---------------------------------------------------------------------------
[docs] class SyntaxTheme: """Syntax highlighting colour set for code editors. Each attribute is a Colour4 tuple used to colour a token category. """ __slots__ = ("keyword", "string", "comment", "number", "decorator", "builtin", "normal") def __init__( self, keyword: Colour4 = (0.4, 0.6, 1.0, 1.0), string: Colour4 = (0.5, 0.9, 0.5, 1.0), comment: Colour4 = (0.5, 0.5, 0.5, 1.0), number: Colour4 = (1.0, 0.7, 0.3, 1.0), decorator: Colour4 = (1.0, 0.9, 0.4, 1.0), builtin: Colour4 = (0.4, 0.9, 0.9, 1.0), normal: Colour4 = (0.9, 0.9, 0.9, 1.0), ): self.keyword = keyword self.string = string self.comment = comment self.number = number self.decorator = decorator self.builtin = builtin self.normal = normal
# --------------------------------------------------------------------------- # AppTheme # ---------------------------------------------------------------------------
[docs] class AppTheme(Theme): """Full application theme with named colour and layout attributes. Subclasses :class:`Theme` so ``Control.get_theme()`` still works. All attributes are also written into ``self.colours`` / ``self.sizes`` so the dict-based ``get_colour()`` / ``get_size()`` API stays valid. """ def __init__(self) -> None: super().__init__() # -- Background hierarchy (dark defaults) ---------------------------- self.bg_black: Colour4 = (0.0, 0.0, 0.0, 1.0) self.bg_darkest: Colour4 = (0.02, 0.02, 0.025, 1.0) self.bg_darker: Colour4 = (0.03, 0.03, 0.035, 1.0) self.bg_dark: Colour4 = (0.045, 0.045, 0.05, 1.0) self.bg: Colour4 = (0.055, 0.055, 0.06, 1.0) self.bg_light: Colour4 = (0.075, 0.075, 0.085, 1.0) self.bg_lighter: Colour4 = (0.095, 0.095, 0.105, 1.0) self.bg_input: Colour4 = (0.025, 0.025, 0.03, 1.0) # -- Panel roles ----------------------------------------------------- self.panel_bg: Colour4 = self.bg self.header_bg: Colour4 = self.bg_light self.toolbar_bg: Colour4 = (0.04, 0.04, 0.045, 1.0) self.status_bar_bg: Colour4 = (0.03, 0.03, 0.035, 1.0) self.section_bg: Colour4 = (0.07, 0.07, 0.08, 1.0) # -- Text ------------------------------------------------------------ self.text: Colour4 = (0.86, 0.86, 0.88, 1.0) self.text_bright: Colour4 = (0.95, 0.95, 0.97, 1.0) self.text_label: Colour4 = (0.75, 0.75, 0.78, 1.0) self.text_dim: Colour4 = (0.46, 0.46, 0.48, 1.0) self.text_muted: Colour4 = (0.56, 0.56, 0.58, 1.0) self.text_faint: Colour4 = (0.62, 0.62, 0.64, 1.0) # -- Accent / semantic ----------------------------------------------- self.accent: Colour4 = (0.28, 0.58, 0.98, 1.0) self.error: Colour4 = (0.94, 0.33, 0.31, 1.0) self.warning: Colour4 = (0.95, 0.76, 0.19, 1.0) self.success: Colour4 = (0.36, 0.72, 0.36, 1.0) self.info: Colour4 = (0.4, 0.6, 0.9, 1.0) # -- Selection / highlight ------------------------------------------- self.selection: Colour4 = (0.2, 0.4, 0.7, 0.5) self.selection_bg: Colour4 = (0.2, 0.45, 0.8, 1.0) self.hover_bg: Colour4 = (0.08, 0.08, 0.095, 1.0) self.highlight: Colour4 = (0.3, 0.3, 0.0, 0.3) # -- Border / separator ---------------------------------------------- self.border: Colour4 = (0.125, 0.125, 0.14, 1.0) self.border_light: Colour4 = (0.15, 0.15, 0.165, 1.0) # -- Button ---------------------------------------------------------- self.btn_bg: Colour4 = (0.10, 0.10, 0.115, 1.0) self.btn_hover: Colour4 = (0.14, 0.14, 0.16, 1.0) self.btn_pressed: Colour4 = (0.03, 0.03, 0.035, 1.0) self.btn_primary: Colour4 = self.accent self.btn_danger: Colour4 = (0.75, 0.25, 0.25, 1.0) # -- Input fields ---------------------------------------------------- self.input_border: Colour4 = self.border self.input_focus: Colour4 = self.accent self.placeholder: Colour4 = self.text_dim # -- Scrollbar ------------------------------------------------------- self.scrollbar_hover: Colour4 = (0.26, 0.26, 0.29, 0.8) self.scrollbar_track: Colour4 = (0.04, 0.04, 0.045, 0.3) # -- Tabs ------------------------------------------------------------ self.tab_bg: Colour4 = (0.04, 0.04, 0.045, 1.0) self.tab_active: Colour4 = (0.09, 0.09, 0.105, 1.0) self.tab_hover: Colour4 = (0.07, 0.07, 0.085, 1.0) self.tab_text: Colour4 = self.text_label self.tab_active_text: Colour4 = (1.0, 1.0, 1.0, 1.0) # -- Tree view ------------------------------------------------------- self.tree_bg: Colour4 = self.bg_darker self.tree_select: Colour4 = (0.2, 0.4, 0.7, 1.0) self.tree_hover: Colour4 = self.hover_bg self.tree_arrow: Colour4 = self.text_muted # -- Check / spin ---------------------------------------------------- self.check_colour: Colour4 = self.accent self.check_box: Colour4 = self.border # -- Slider ---------------------------------------------------------- self.slider_fill: Colour4 = self.accent self.slider_handle: Colour4 = (0.8, 0.8, 0.8, 1.0) # -- Dock panels ----------------------------------------------------- self.dock_title_bg: Colour4 = self.bg_darker self.dock_title_text: Colour4 = (0.85, 0.85, 0.85, 1.0) # -- Popup menus ----------------------------------------------------- self.popup_bg: Colour4 = self.bg self.popup_hover: Colour4 = self.selection_bg self.popup_separator: Colour4 = self.border_light # -- Split divider --------------------------------------------------- self.divider: Colour4 = self.border_light self.divider_hover: Colour4 = self.accent # -- Code editor ----------------------------------------------------- self.current_line: Colour4 = (0.07, 0.07, 0.085, 1.0) self.bracket_match: Colour4 = (0.4, 0.7, 0.4, 0.5) self.bracket_mismatch: Colour4 = (0.9, 0.2, 0.2, 0.5) self.line_number: Colour4 = self.text_dim self.gutter_bg: Colour4 = self.bg_darker # -- Syntax highlighting --------------------------------------------- self.syntax: SyntaxTheme = SyntaxTheme() # -- Editor-specific (viewport, gizmo, grid) ------------------------ self.viewport_bg: Colour4 = (0.05, 0.05, 0.06, 1.0) self.gizmo_x: Colour4 = (0.96, 0.26, 0.28, 1.0) self.gizmo_y: Colour4 = (0.40, 0.84, 0.36, 1.0) self.gizmo_z: Colour4 = (0.26, 0.52, 0.96, 1.0) self.selection_outline: Colour4 = (1.0, 0.6, 0.0, 1.0) self.grid_major: Colour4 = (0.38, 0.38, 0.40, 1.0) self.grid_minor: Colour4 = (0.26, 0.26, 0.28, 0.6) # -- IDE-specific (minimap, autocomplete, scrollbar) ----------------- self.minimap_text: Colour4 = (0.42, 0.42, 0.47, 1.0) self.minimap_keyword: Colour4 = (0.55, 0.40, 0.70, 1.0) self.minimap_string: Colour4 = (0.55, 0.70, 0.40, 1.0) self.minimap_comment: Colour4 = (0.32, 0.32, 0.36, 1.0) self.autocomplete_bg: Colour4 = self.bg_lighter self.autocomplete_selected: Colour4 = (0.28, 0.58, 0.98, 0.30) self.autocomplete_border: Colour4 = (0.38, 0.38, 0.42, 1.0) self.autocomplete_hover: Colour4 = (0.26, 0.56, 0.96, 0.12) self.autocomplete_dim: Colour4 = (0.46, 0.46, 0.52, 1.0) self.autocomplete_kind: Colour4 = (0.55, 0.75, 0.95, 1.0) self.scrollbar_bg: Colour4 = (0.06, 0.06, 0.07, 1.0) self.scrollbar_fg: Colour4 = (0.175, 0.175, 0.20, 1.0) # -- Layout sizes ---------------------------------------------------- self.header_h: float = 28.0 self.row_h: float = 22.0 self.tab_h: float = 24.0 self.font_size: float = 11.0 self.ui_scale: float = 1.0 self.scrollbar_width: float = 8.0 self.dock_title_h: float = 24.0 # -- StyleBoxes (dark defaults) -------------------------------------- self._init_styleboxes_dark() # Sync dicts so dict-based API works self._sync_dicts() # -- StyleBox helpers ---------------------------------------------------- @staticmethod def _lighten(c: Colour4, amount: float = 0.08) -> Colour4: return (min(c[0] + amount, 1.0), min(c[1] + amount, 1.0), min(c[2] + amount, 1.0), c[3]) @staticmethod def _darken(c: Colour4, amount: float = 0.06) -> Colour4: return (max(c[0] - amount, 0.0), max(c[1] - amount, 0.0), max(c[2] - amount, 0.0), c[3]) def _init_styleboxes_dark(self) -> None: """Initialise StyleBox slots with dark-theme defaults (embossed buttons, flat panels).""" L, D = self._lighten, self._darken # Button states self.btn_style_normal = StyleBox( bg_colour=self.btn_bg, border_width=1.0, content_margin=2.0, border_top=L(self.btn_bg), border_left=L(self.btn_bg), border_bottom=D(self.btn_bg), border_right=D(self.btn_bg), ) self.btn_style_hover = StyleBox( bg_colour=self.btn_hover, border_width=1.0, content_margin=2.0, border_top=L(self.btn_hover), border_left=L(self.btn_hover), border_bottom=D(self.btn_hover), border_right=D(self.btn_hover), ) self.btn_style_pressed = StyleBox( bg_colour=self.btn_pressed, border_width=1.0, content_margin=2.0, border_top=D(self.btn_pressed), border_left=D(self.btn_pressed), border_bottom=L(self.btn_pressed), border_right=L(self.btn_pressed), ) self.btn_style_disabled = StyleBox( bg_colour=D(self.btn_bg, 0.04), border_width=1.0, content_margin=2.0, border_colour=D(self.border, 0.08), ) self.btn_style_focused = StyleBox( bg_colour=self.btn_bg, border_width=1.0, content_margin=2.0, border_colour=self.accent, ) # Panel self.panel_style = StyleBox( bg_colour=self.bg, border_width=1.0, content_margin=0.0, border_colour=self.border, ) # TextEdit self.input_style_normal = StyleBox( bg_colour=self.bg_input, border_width=1.0, content_margin=4.0, border_colour=self.input_border, ) self.input_style_focused = StyleBox( bg_colour=self.bg_input, border_width=1.0, content_margin=4.0, border_colour=self.accent, ) self.input_style_disabled = StyleBox( bg_colour=D(self.bg_input, 0.02), border_width=1.0, content_margin=4.0, border_colour=D(self.border, 0.08), ) # TabContainer self.tab_style_normal = StyleBox( bg_colour=self.tab_bg, border_width=0.0, content_margin=0.0, ) self.tab_style_active = StyleBox( bg_colour=self.tab_active, border_width=0.0, content_margin=0.0, ) self.tab_style_hover = StyleBox( bg_colour=self.tab_hover, border_width=0.0, content_margin=0.0, ) # PopupMenu self.popup_style = StyleBox( bg_colour=self.popup_bg, border_width=1.0, content_margin=0.0, border_colour=self.border_light, ) self.popup_style_hover = StyleBox( bg_colour=self.popup_hover, border_width=0.0, content_margin=0.0, ) # DockPanel title self.dock_title_style = StyleBox( bg_colour=self.dock_title_bg, border_width=0.0, content_margin=0.0, ) # -- Dict synchronisation ------------------------------------------------ def _sync_dicts(self) -> None: """Populate ``self.colours`` and ``self.sizes`` from named attributes.""" self.colours.update( { "bg_black": self.bg_black, "bg_darkest": self.bg_darkest, "bg_darker": self.bg_darker, "bg_dark": self.bg_dark, "bg": self.bg, "bg_light": self.bg_light, "bg_lighter": self.bg_lighter, "bg_input": self.bg_input, "panel_bg": self.panel_bg, "header_bg": self.header_bg, "toolbar_bg": self.toolbar_bg, "status_bar_bg": self.status_bar_bg, "section_bg": self.section_bg, "text": self.text, "text_bright": self.text_bright, "text_label": self.text_label, "text_dim": self.text_dim, "text_muted": self.text_muted, "text_faint": self.text_faint, "text_disabled": self.text_dim, "accent": self.accent, "error": self.error, "warning": self.warning, "success": self.success, "info": self.info, "selection": self.selection, "selection_bg": self.selection_bg, "hover_bg": self.hover_bg, "highlight": self.highlight, "border": self.border, "border_light": self.border_light, "btn_bg": self.btn_bg, "btn_hover": self.btn_hover, "btn_pressed": self.btn_pressed, "btn_primary": self.btn_primary, "btn_danger": self.btn_danger, "viewport_bg": self.viewport_bg, "gizmo_x": self.gizmo_x, "gizmo_y": self.gizmo_y, "gizmo_z": self.gizmo_z, "selection_outline": self.selection_outline, "grid_major": self.grid_major, "grid_minor": self.grid_minor, "minimap_text": self.minimap_text, "minimap_keyword": self.minimap_keyword, "minimap_string": self.minimap_string, "minimap_comment": self.minimap_comment, "autocomplete_bg": self.autocomplete_bg, "autocomplete_selected": self.autocomplete_selected, "autocomplete_border": self.autocomplete_border, "autocomplete_hover": self.autocomplete_hover, "autocomplete_dim": self.autocomplete_dim, "autocomplete_kind": self.autocomplete_kind, "scrollbar_bg": self.scrollbar_bg, "scrollbar_fg": self.scrollbar_fg, # Keep legacy Theme keys alive "accent_hover": (self.accent[0] + 0.1, self.accent[1] + 0.1, min(self.accent[2] + 0.04, 1.0), 1.0), "accent_pressed": (self.accent[0] - 0.06, self.accent[1] - 0.06, self.accent[2] - 0.06, 1.0), "focus": self.accent, } ) self.sizes.update( { "header_h": self.header_h, "row_h": self.row_h, "tab_h": self.tab_h, "font_size": self.font_size, "ui_scale": self.ui_scale, "scrollbar_width": self.scrollbar_width, "dock_title_h": self.dock_title_h, } ) # -- Factory presets -----------------------------------------------------
[docs] @classmethod def dark(cls) -> AppTheme: """Dark theme (default).""" return cls()
[docs] @classmethod def abyss(cls) -> AppTheme: """Abyss — near-black with a subtle cool-blue tint. OLED-friendly.""" t = cls() # Backgrounds — true-dark with faint blue bias t.bg_black = (0.0, 0.0, 0.01, 1.0) t.bg_darkest = (0.02, 0.02, 0.035, 1.0) t.bg_darker = (0.03, 0.03, 0.05, 1.0) t.bg_dark = (0.045, 0.045, 0.065, 1.0) t.bg = (0.055, 0.055, 0.08, 1.0) t.bg_light = (0.08, 0.08, 0.11, 1.0) t.bg_lighter = (0.10, 0.10, 0.14, 1.0) t.bg_input = (0.025, 0.025, 0.04, 1.0) t.panel_bg = t.bg t.header_bg = t.bg_light t.toolbar_bg = t.bg_darker t.status_bar_bg = t.bg_darkest t.section_bg = t.bg_light # Text — cool grey with slight blue t.text = (0.78, 0.80, 0.86, 1.0) t.text_bright = (0.90, 0.92, 0.97, 1.0) t.text_label = (0.58, 0.60, 0.68, 1.0) t.text_dim = (0.36, 0.38, 0.46, 1.0) t.text_muted = (0.44, 0.46, 0.54, 1.0) t.text_faint = (0.50, 0.52, 0.60, 1.0) # Accent — electric blue t.accent = (0.30, 0.55, 1.0, 1.0) t.error = (0.90, 0.28, 0.30, 1.0) t.warning = (0.92, 0.72, 0.16, 1.0) t.success = (0.30, 0.72, 0.40, 1.0) t.info = (0.35, 0.60, 0.95, 1.0) t.selection = (0.20, 0.40, 0.80, 0.35) t.selection_bg = (0.20, 0.40, 0.80, 1.0) t.hover_bg = t.bg_light t.highlight = (0.30, 0.45, 0.80, 0.2) t.border = (0.12, 0.12, 0.18, 1.0) t.border_light = (0.16, 0.16, 0.24, 1.0) t.btn_bg = (0.08, 0.08, 0.12, 1.0) t.btn_hover = (0.12, 0.12, 0.18, 1.0) t.btn_pressed = (0.03, 0.03, 0.05, 1.0) t.btn_primary = t.accent t.btn_danger = (0.75, 0.22, 0.25, 1.0) t.input_border = t.border t.input_focus = t.accent t.placeholder = t.text_dim t.scrollbar_hover = (0.35, 0.38, 0.50, 0.7) t.scrollbar_track = (0.03, 0.03, 0.05, 0.3) t.tab_bg = t.bg_darker t.tab_active = t.bg t.tab_hover = t.bg_light t.tab_text = t.text_label t.tab_active_text = t.text_bright t.tree_bg = t.bg_darkest t.tree_select = t.selection_bg t.tree_hover = t.hover_bg t.tree_arrow = t.text_dim t.check_colour = t.accent t.check_box = t.border t.slider_fill = t.accent t.slider_handle = t.text_label t.dock_title_bg = t.bg_darker t.dock_title_text = t.text t.popup_bg = t.bg_dark t.popup_hover = t.selection_bg t.popup_separator = t.border_light t.divider = t.border t.divider_hover = t.accent t.current_line = (0.06, 0.06, 0.09, 1.0) t.bracket_match = (0.25, 0.55, 0.35, 0.4) t.bracket_mismatch = (0.80, 0.20, 0.25, 0.4) t.line_number = t.text_dim t.gutter_bg = t.bg_darkest t.syntax = SyntaxTheme( keyword=(0.45, 0.62, 1.0, 1.0), string=(0.40, 0.82, 0.55, 1.0), comment=t.text_dim, number=(0.82, 0.58, 1.0, 1.0), decorator=(0.90, 0.70, 0.30, 1.0), builtin=(0.40, 0.80, 0.95, 1.0), normal=t.text, ) t.viewport_bg = t.bg_darkest t.selection_outline = (0.90, 0.55, 0.0, 1.0) t.grid_major = t.border_light t.grid_minor = (0.08, 0.08, 0.12, 0.5) t.minimap_text = t.text_dim t.minimap_keyword = (0.45, 0.35, 0.65, 1.0) t.minimap_string = (0.40, 0.60, 0.45, 1.0) t.minimap_comment = t.text_dim t.autocomplete_bg = t.bg_lighter t.autocomplete_selected = (0.30, 0.55, 1.0, 0.25) t.autocomplete_border = t.border_light t.autocomplete_hover = (0.30, 0.55, 1.0, 0.10) t.autocomplete_dim = t.text_muted t.autocomplete_kind = (0.55, 0.75, 0.95, 1.0) t.scrollbar_bg = t.bg_darkest t.scrollbar_fg = t.text_dim # StyleBoxes — subtle emboss in the dark L, D = cls._lighten, cls._darken t.btn_style_normal = StyleBox( bg_colour=t.btn_bg, border_width=1.0, content_margin=2.0, border_top=L(t.btn_bg, 0.04), border_left=L(t.btn_bg, 0.04), border_bottom=D(t.btn_bg, 0.02), border_right=D(t.btn_bg, 0.02), ) t.btn_style_hover = StyleBox( bg_colour=t.btn_hover, border_width=1.0, content_margin=2.0, border_colour=t.accent, ) t.btn_style_pressed = StyleBox(bg_colour=t.btn_pressed, border_width=1.0, content_margin=2.0, border_colour=t.accent) t.btn_style_disabled = StyleBox(bg_colour=D(t.btn_bg, 0.02), border_width=1.0, content_margin=2.0, border_colour=D(t.border, 0.04)) t.btn_style_focused = StyleBox(bg_colour=t.btn_bg, border_width=1.0, content_margin=2.0, border_colour=t.accent) t.panel_style = StyleBox(bg_colour=t.panel_bg, border_width=1.0, border_colour=t.border) t.input_style_normal = StyleBox(bg_colour=t.bg_input, border_width=1.0, content_margin=4.0, border_colour=t.border) t.input_style_focused = StyleBox(bg_colour=t.bg_input, border_width=1.0, content_margin=4.0, border_colour=t.accent) t.input_style_disabled = StyleBox(bg_colour=D(t.bg_input, 0.01), border_width=1.0, content_margin=4.0, border_colour=D(t.border, 0.04)) t.tab_style_normal = StyleBox(bg_colour=t.tab_bg, border_width=0.0) t.tab_style_active = StyleBox(bg_colour=t.tab_active, border_width=0.0) t.tab_style_hover = StyleBox(bg_colour=t.tab_hover, border_width=0.0) t.popup_style = StyleBox(bg_colour=t.popup_bg, border_width=1.0, border_colour=t.border) t.popup_style_hover = StyleBox(bg_colour=t.popup_hover, border_width=0.0) t.dock_title_style = StyleBox(bg_colour=t.dock_title_bg, border_width=0.0) t._sync_dicts() return t
[docs] @classmethod def midnight(cls) -> AppTheme: """Midnight — near-black with a subtle warm-green tint. OLED-friendly.""" t = cls() # Backgrounds — true-dark with faint green bias t.bg_black = (0.0, 0.01, 0.0, 1.0) t.bg_darkest = (0.02, 0.03, 0.02, 1.0) t.bg_darker = (0.03, 0.04, 0.03, 1.0) t.bg_dark = (0.045, 0.06, 0.045, 1.0) t.bg = (0.06, 0.07, 0.055, 1.0) t.bg_light = (0.08, 0.10, 0.08, 1.0) t.bg_lighter = (0.11, 0.13, 0.10, 1.0) t.bg_input = (0.025, 0.035, 0.025, 1.0) t.panel_bg = t.bg t.header_bg = t.bg_light t.toolbar_bg = t.bg_darker t.status_bar_bg = t.bg_darkest t.section_bg = t.bg_light # Text — warm grey with green tint t.text = (0.78, 0.84, 0.76, 1.0) t.text_bright = (0.90, 0.95, 0.88, 1.0) t.text_label = (0.58, 0.65, 0.55, 1.0) t.text_dim = (0.36, 0.42, 0.34, 1.0) t.text_muted = (0.44, 0.50, 0.42, 1.0) t.text_faint = (0.50, 0.56, 0.48, 1.0) # Accent — muted teal-green t.accent = (0.28, 0.72, 0.56, 1.0) t.error = (0.88, 0.30, 0.28, 1.0) t.warning = (0.90, 0.75, 0.20, 1.0) t.success = (0.35, 0.78, 0.35, 1.0) t.info = (0.35, 0.68, 0.65, 1.0) t.selection = (0.18, 0.45, 0.35, 0.35) t.selection_bg = (0.18, 0.45, 0.35, 1.0) t.hover_bg = t.bg_light t.highlight = (0.35, 0.50, 0.20, 0.2) t.border = (0.10, 0.14, 0.10, 1.0) t.border_light = (0.14, 0.19, 0.14, 1.0) t.btn_bg = (0.07, 0.09, 0.065, 1.0) t.btn_hover = (0.10, 0.14, 0.10, 1.0) t.btn_pressed = (0.03, 0.04, 0.03, 1.0) t.btn_primary = t.accent t.btn_danger = (0.75, 0.24, 0.22, 1.0) t.input_border = t.border t.input_focus = t.accent t.placeholder = t.text_dim t.scrollbar_hover = (0.32, 0.42, 0.32, 0.7) t.scrollbar_track = (0.03, 0.04, 0.03, 0.3) t.tab_bg = t.bg_darker t.tab_active = t.bg t.tab_hover = t.bg_light t.tab_text = t.text_label t.tab_active_text = t.text_bright t.tree_bg = t.bg_darkest t.tree_select = t.selection_bg t.tree_hover = t.hover_bg t.tree_arrow = t.text_dim t.check_colour = t.accent t.check_box = t.border t.slider_fill = t.accent t.slider_handle = t.text_label t.dock_title_bg = t.bg_darker t.dock_title_text = t.text t.popup_bg = t.bg_dark t.popup_hover = t.selection_bg t.popup_separator = t.border_light t.divider = t.border t.divider_hover = t.accent t.current_line = (0.05, 0.07, 0.05, 1.0) t.bracket_match = (0.25, 0.55, 0.30, 0.4) t.bracket_mismatch = (0.80, 0.22, 0.20, 0.4) t.line_number = t.text_dim t.gutter_bg = t.bg_darkest t.syntax = SyntaxTheme( keyword=(0.55, 0.85, 0.65, 1.0), string=(0.85, 0.82, 0.45, 1.0), comment=t.text_dim, number=(0.75, 0.60, 0.90, 1.0), decorator=(0.90, 0.65, 0.30, 1.0), builtin=(0.45, 0.82, 0.80, 1.0), normal=t.text, ) t.viewport_bg = t.bg_darkest t.selection_outline = (0.85, 0.60, 0.0, 1.0) t.grid_major = t.border_light t.grid_minor = (0.07, 0.09, 0.07, 0.5) t.minimap_text = t.text_dim t.minimap_keyword = (0.45, 0.65, 0.50, 1.0) t.minimap_string = (0.65, 0.60, 0.35, 1.0) t.minimap_comment = t.text_dim t.autocomplete_bg = t.bg_lighter t.autocomplete_selected = (0.28, 0.72, 0.56, 0.25) t.autocomplete_border = t.border_light t.autocomplete_hover = (0.28, 0.72, 0.56, 0.10) t.autocomplete_dim = t.text_muted t.autocomplete_kind = (0.45, 0.82, 0.70, 1.0) t.scrollbar_bg = t.bg_darkest t.scrollbar_fg = t.text_dim # StyleBoxes — subtle emboss L, D = cls._lighten, cls._darken t.btn_style_normal = StyleBox( bg_colour=t.btn_bg, border_width=1.0, content_margin=2.0, border_top=L(t.btn_bg, 0.03), border_left=L(t.btn_bg, 0.03), border_bottom=D(t.btn_bg, 0.02), border_right=D(t.btn_bg, 0.02), ) t.btn_style_hover = StyleBox( bg_colour=t.btn_hover, border_width=1.0, content_margin=2.0, border_colour=t.accent, ) t.btn_style_pressed = StyleBox(bg_colour=t.btn_pressed, border_width=1.0, content_margin=2.0, border_colour=t.accent) t.btn_style_disabled = StyleBox(bg_colour=D(t.btn_bg, 0.02), border_width=1.0, content_margin=2.0, border_colour=D(t.border, 0.03)) t.btn_style_focused = StyleBox(bg_colour=t.btn_bg, border_width=1.0, content_margin=2.0, border_colour=t.accent) t.panel_style = StyleBox(bg_colour=t.panel_bg, border_width=1.0, border_colour=t.border) t.input_style_normal = StyleBox(bg_colour=t.bg_input, border_width=1.0, content_margin=4.0, border_colour=t.border) t.input_style_focused = StyleBox(bg_colour=t.bg_input, border_width=1.0, content_margin=4.0, border_colour=t.accent) t.input_style_disabled = StyleBox(bg_colour=D(t.bg_input, 0.01), border_width=1.0, content_margin=4.0, border_colour=D(t.border, 0.03)) t.tab_style_normal = StyleBox(bg_colour=t.tab_bg, border_width=0.0) t.tab_style_active = StyleBox(bg_colour=t.tab_active, border_width=0.0) t.tab_style_hover = StyleBox(bg_colour=t.tab_hover, border_width=0.0) t.popup_style = StyleBox(bg_colour=t.popup_bg, border_width=1.0, border_colour=t.border) t.popup_style_hover = StyleBox(bg_colour=t.popup_hover, border_width=0.0) t.dock_title_style = StyleBox(bg_colour=t.dock_title_bg, border_width=0.0) t._sync_dicts() return t
[docs] @classmethod def light(cls) -> AppTheme: """Light theme with bright backgrounds.""" t = cls() # Backgrounds t.bg_black = (0.80, 0.80, 0.80, 1.0) t.bg_darkest = (0.82, 0.82, 0.82, 1.0) t.bg_darker = (0.85, 0.85, 0.85, 1.0) t.bg_dark = (0.88, 0.88, 0.88, 1.0) t.bg = (0.92, 0.92, 0.92, 1.0) t.bg_light = (0.96, 0.96, 0.96, 1.0) t.bg_lighter = (0.98, 0.98, 0.98, 1.0) t.bg_input = (1.0, 1.0, 1.0, 1.0) # Panel roles t.panel_bg = (0.92, 0.92, 0.93, 1.0) t.header_bg = (0.88, 0.88, 0.88, 1.0) t.toolbar_bg = (0.82, 0.82, 0.82, 1.0) t.status_bar_bg = (0.78, 0.78, 0.8, 1.0) t.section_bg = (0.90, 0.90, 0.90, 1.0) # Text t.text = (0.15, 0.15, 0.15, 1.0) t.text_bright = (0.1, 0.1, 0.1, 1.0) t.text_label = (0.35, 0.35, 0.35, 1.0) t.text_dim = (0.45, 0.45, 0.45, 1.0) t.text_muted = (0.50, 0.50, 0.50, 1.0) t.text_faint = (0.55, 0.55, 0.55, 1.0) # Accent / semantic t.accent = (0.0, 0.45, 0.85, 1.0) t.error = (0.85, 0.18, 0.15, 1.0) t.warning = (0.8, 0.6, 0.0, 1.0) t.success = (0.2, 0.6, 0.2, 1.0) t.info = (0.2, 0.45, 0.8, 1.0) # Selection / highlight t.selection = (0.2, 0.5, 0.9, 0.3) t.selection_bg = (0.3, 0.55, 0.9, 1.0) t.hover_bg = (0.85, 0.85, 0.88, 1.0) t.highlight = (1.0, 1.0, 0.0, 0.2) # Border t.border = (0.72, 0.72, 0.72, 1.0) t.border_light = (0.80, 0.80, 0.80, 1.0) # Button t.btn_bg = (0.85, 0.85, 0.87, 1.0) t.btn_hover = (0.78, 0.78, 0.82, 1.0) t.btn_pressed = (0.70, 0.70, 0.74, 1.0) t.btn_primary = t.accent t.btn_danger = (0.80, 0.20, 0.20, 1.0) # Input t.input_border = t.border t.input_focus = t.accent t.placeholder = t.text_dim # Scrollbar t.scrollbar_hover = (0.55, 0.55, 0.58, 0.8) t.scrollbar_track = (0.80, 0.80, 0.82, 0.3) # Tabs t.tab_bg = (0.85, 0.85, 0.85, 1.0) t.tab_active = (0.92, 0.92, 0.93, 1.0) t.tab_hover = (0.88, 0.88, 0.90, 1.0) t.tab_text = t.text_label t.tab_active_text = t.text_bright # Tree t.tree_bg = t.bg_lighter t.tree_select = (0.3, 0.55, 0.9, 1.0) t.tree_hover = t.hover_bg t.tree_arrow = t.text_muted # Check / spin t.check_colour = t.accent t.check_box = t.border # Slider t.slider_fill = t.accent t.slider_handle = (0.4, 0.4, 0.42, 1.0) # Dock t.dock_title_bg = t.header_bg t.dock_title_text = t.text # Popup t.popup_bg = t.bg_lighter t.popup_hover = t.selection_bg t.popup_separator = t.border # Divider t.divider = t.border t.divider_hover = t.accent # Code editor t.current_line = (0.88, 0.90, 0.95, 1.0) t.bracket_match = (0.2, 0.6, 0.2, 0.4) t.bracket_mismatch = (0.85, 0.2, 0.2, 0.4) t.line_number = t.text_dim t.gutter_bg = t.bg_darker # Syntax t.syntax = SyntaxTheme( keyword=(0.0, 0.0, 0.8, 1.0), string=(0.0, 0.5, 0.0, 1.0), comment=(0.5, 0.5, 0.5, 1.0), number=(0.8, 0.4, 0.0, 1.0), decorator=(0.6, 0.5, 0.0, 1.0), builtin=(0.0, 0.5, 0.5, 1.0), normal=(0.1, 0.1, 0.1, 1.0), ) # Editor t.viewport_bg = (0.75, 0.75, 0.78, 1.0) t.selection_outline = (0.0, 0.45, 1.0, 1.0) t.grid_major = (0.6, 0.6, 0.6, 1.0) t.grid_minor = (0.7, 0.7, 0.7, 0.5) # IDE t.minimap_text = (0.55, 0.55, 0.58, 1.0) t.minimap_keyword = (0.0, 0.0, 0.6, 0.7) t.minimap_string = (0.0, 0.4, 0.0, 0.7) t.minimap_comment = (0.55, 0.55, 0.58, 1.0) t.autocomplete_bg = t.bg_lighter t.autocomplete_selected = (0.0, 0.45, 0.85, 0.25) t.autocomplete_border = t.border t.autocomplete_hover = (0.0, 0.45, 0.85, 0.10) t.autocomplete_dim = t.text_muted t.autocomplete_kind = (0.2, 0.45, 0.7, 1.0) t.scrollbar_bg = (0.82, 0.82, 0.84, 1.0) t.scrollbar_fg = (0.60, 0.60, 0.62, 1.0) # StyleBoxes — light: gradient buttons, clean borders L, D = cls._lighten, cls._darken t.btn_style_normal = StyleBox( bg_colour=t.btn_bg, border_width=1.0, content_margin=2.0, bg_gradient=(L(t.btn_bg, 0.04), D(t.btn_bg, 0.04)), border_colour=t.border, ) t.btn_style_hover = StyleBox( bg_colour=t.btn_hover, border_width=1.0, content_margin=2.0, bg_gradient=(L(t.btn_hover, 0.04), D(t.btn_hover, 0.04)), border_colour=t.border, ) t.btn_style_pressed = StyleBox( bg_colour=t.btn_pressed, border_width=1.0, content_margin=2.0, border_colour=t.border, ) t.btn_style_disabled = StyleBox( bg_colour=D(t.btn_bg, 0.04), border_width=1.0, content_margin=2.0, border_colour=D(t.border, 0.08), ) t.btn_style_focused = StyleBox( bg_colour=t.btn_bg, border_width=1.0, content_margin=2.0, border_colour=t.accent, ) t.panel_style = StyleBox(bg_colour=t.panel_bg, border_width=1.0, border_colour=t.border) t.input_style_normal = StyleBox(bg_colour=t.bg_input, border_width=1.0, content_margin=4.0, border_colour=t.input_border) t.input_style_focused = StyleBox(bg_colour=t.bg_input, border_width=1.0, content_margin=4.0, border_colour=t.accent) t.input_style_disabled = StyleBox(bg_colour=D(t.bg_input, 0.02), border_width=1.0, content_margin=4.0, border_colour=D(t.border, 0.08)) t.tab_style_normal = StyleBox(bg_colour=t.tab_bg, border_width=0.0) t.tab_style_active = StyleBox(bg_colour=t.tab_active, border_width=0.0) t.tab_style_hover = StyleBox(bg_colour=t.tab_hover, border_width=0.0) t.popup_style = StyleBox(bg_colour=t.popup_bg, border_width=1.0, border_colour=t.border) t.popup_style_hover = StyleBox(bg_colour=t.popup_hover, border_width=0.0) t.dock_title_style = StyleBox(bg_colour=t.dock_title_bg, border_width=0.0) t._sync_dicts() return t
[docs] @classmethod def monokai(cls) -> AppTheme: """Monokai-inspired theme.""" t = cls() # Backgrounds t.bg_black = (0.06, 0.07, 0.04, 1.0) t.bg_darkest = (0.09, 0.10, 0.07, 1.0) t.bg_darker = (0.11, 0.12, 0.09, 1.0) t.bg_dark = (0.13, 0.14, 0.11, 1.0) t.bg = (0.15, 0.16, 0.13, 1.0) t.bg_light = (0.22, 0.23, 0.19, 1.0) t.bg_lighter = (0.25, 0.26, 0.22, 1.0) t.bg_input = (0.12, 0.13, 0.10, 1.0) # Panel roles t.panel_bg = t.bg t.header_bg = t.bg_light t.toolbar_bg = (0.14, 0.15, 0.12, 1.0) t.status_bar_bg = (0.12, 0.13, 0.1, 1.0) t.section_bg = (0.20, 0.21, 0.17, 1.0) # Text t.text = (0.97, 0.97, 0.95, 1.0) t.text_bright = (1.0, 1.0, 0.98, 1.0) t.text_label = (0.75, 0.73, 0.66, 1.0) t.text_dim = (0.46, 0.44, 0.37, 1.0) t.text_muted = (0.55, 0.53, 0.46, 1.0) t.text_faint = (0.60, 0.58, 0.50, 1.0) # Accent / semantic t.accent = (0.4, 0.85, 0.94, 1.0) t.error = (0.98, 0.15, 0.45, 1.0) t.warning = (0.9, 0.86, 0.45, 1.0) t.success = (0.65, 0.89, 0.18, 1.0) t.info = (0.4, 0.85, 0.94, 1.0) # Selection / highlight t.selection = (0.3, 0.45, 0.2, 0.5) t.selection_bg = (0.4, 0.6, 0.2, 1.0) t.hover_bg = (0.20, 0.21, 0.17, 1.0) t.highlight = (0.4, 0.4, 0.0, 0.3) # Border t.border = (0.3, 0.31, 0.27, 1.0) t.border_light = (0.38, 0.39, 0.34, 1.0) # Button t.btn_bg = (0.22, 0.23, 0.19, 1.0) t.btn_hover = (0.30, 0.31, 0.26, 1.0) t.btn_pressed = (0.12, 0.13, 0.10, 1.0) t.btn_primary = t.accent t.btn_danger = (0.80, 0.15, 0.35, 1.0) # Input t.input_border = t.border t.input_focus = t.accent t.placeholder = t.text_dim # Scrollbar t.scrollbar_hover = (0.50, 0.48, 0.42, 0.8) t.scrollbar_track = (0.11, 0.12, 0.09, 0.3) # Tabs t.tab_bg = t.bg_dark t.tab_active = t.btn_bg t.tab_hover = t.hover_bg t.tab_text = t.text_label t.tab_active_text = t.text_bright # Tree t.tree_bg = t.bg_darker t.tree_select = t.selection_bg t.tree_hover = t.hover_bg t.tree_arrow = t.text_muted # Check / spin t.check_colour = t.accent t.check_box = t.border # Slider t.slider_fill = t.accent t.slider_handle = (0.75, 0.73, 0.66, 1.0) # Dock t.dock_title_bg = t.bg_darker t.dock_title_text = t.text # Popup t.popup_bg = t.bg t.popup_hover = t.selection_bg t.popup_separator = t.border_light # Divider t.divider = t.border_light t.divider_hover = t.accent # Code editor t.current_line = (0.18, 0.19, 0.15, 1.0) t.bracket_match = (0.4, 0.6, 0.2, 0.5) t.bracket_mismatch = (0.9, 0.15, 0.35, 0.5) t.line_number = t.text_dim t.gutter_bg = t.bg_darker # Syntax (monokai palette) t.syntax = SyntaxTheme( keyword=(0.98, 0.15, 0.45, 1.0), string=(0.9, 0.86, 0.45, 1.0), comment=(0.46, 0.44, 0.37, 1.0), number=(0.68, 0.51, 1.0, 1.0), decorator=(0.65, 0.89, 0.18, 1.0), builtin=(0.4, 0.85, 0.94, 1.0), normal=(0.97, 0.97, 0.95, 1.0), ) # Editor t.viewport_bg = (0.16, 0.17, 0.14, 1.0) t.selection_outline = (0.65, 0.89, 0.18, 1.0) t.grid_major = (0.3, 0.31, 0.27, 1.0) t.grid_minor = (0.22, 0.23, 0.19, 0.5) # IDE t.minimap_text = (0.42, 0.40, 0.35, 1.0) t.minimap_keyword = (0.75, 0.12, 0.35, 0.8) t.minimap_string = (0.70, 0.66, 0.35, 0.8) t.minimap_comment = (0.42, 0.40, 0.35, 1.0) t.autocomplete_bg = t.bg_lighter t.autocomplete_selected = (0.4, 0.85, 0.94, 0.25) t.autocomplete_border = t.border_light t.autocomplete_hover = (0.4, 0.85, 0.94, 0.10) t.autocomplete_dim = t.text_muted t.autocomplete_kind = (0.4, 0.85, 0.94, 0.8) t.scrollbar_bg = (0.20, 0.21, 0.17, 1.0) t.scrollbar_fg = (0.40, 0.38, 0.32, 1.0) # StyleBoxes — monokai: flat buttons with accent highlights D = cls._darken t.btn_style_normal = StyleBox( bg_colour=t.btn_bg, border_width=1.0, content_margin=2.0, border_colour=t.border, ) t.btn_style_hover = StyleBox( bg_colour=t.btn_hover, border_width=1.0, content_margin=2.0, border_colour=t.accent, ) t.btn_style_pressed = StyleBox( bg_colour=t.btn_pressed, border_width=1.0, content_margin=2.0, border_colour=t.accent, ) t.btn_style_disabled = StyleBox( bg_colour=D(t.btn_bg, 0.04), border_width=1.0, content_margin=2.0, border_colour=D(t.border, 0.08), ) t.btn_style_focused = StyleBox( bg_colour=t.btn_bg, border_width=1.0, content_margin=2.0, border_colour=t.accent, ) t.panel_style = StyleBox(bg_colour=t.panel_bg, border_width=1.0, border_colour=t.border) t.input_style_normal = StyleBox(bg_colour=t.bg_input, border_width=1.0, content_margin=4.0, border_colour=t.input_border) t.input_style_focused = StyleBox(bg_colour=t.bg_input, border_width=1.0, content_margin=4.0, border_colour=t.accent) t.input_style_disabled = StyleBox(bg_colour=D(t.bg_input, 0.02), border_width=1.0, content_margin=4.0, border_colour=D(t.border, 0.08)) t.tab_style_normal = StyleBox(bg_colour=t.tab_bg, border_width=0.0) t.tab_style_active = StyleBox(bg_colour=t.tab_active, border_width=0.0) t.tab_style_hover = StyleBox(bg_colour=t.tab_hover, border_width=0.0) t.popup_style = StyleBox(bg_colour=t.popup_bg, border_width=1.0, border_colour=t.border_light) t.popup_style_hover = StyleBox(bg_colour=t.popup_hover, border_width=0.0) t.dock_title_style = StyleBox(bg_colour=t.dock_title_bg, border_width=0.0) t._sync_dicts() return t
[docs] @classmethod def solarised_dark(cls) -> AppTheme: """Solarised Dark theme — Ethan Schoonover's warm-tinted dark palette.""" t = cls() base03 = (0.0, 0.17, 0.21, 1.0) base02 = (0.03, 0.21, 0.26, 1.0) base01 = (0.35, 0.43, 0.46, 1.0) base00 = (0.40, 0.48, 0.51, 1.0) base0 = (0.51, 0.58, 0.59, 1.0) base1 = (0.58, 0.63, 0.63, 1.0) yellow = (0.71, 0.54, 0.0, 1.0) orange = (0.80, 0.29, 0.09, 1.0) red = (0.86, 0.20, 0.18, 1.0) magenta = (0.83, 0.21, 0.51, 1.0) blue = (0.15, 0.55, 0.82, 1.0) cyan = (0.16, 0.63, 0.60, 1.0) green = (0.52, 0.60, 0.0, 1.0) t.bg_black = base03 t.bg_darkest = base03 t.bg_darker = base03 t.bg_dark = base02 t.bg = base02 t.bg_light = (0.06, 0.25, 0.30, 1.0) t.bg_lighter = (0.09, 0.29, 0.34, 1.0) t.bg_input = base03 t.panel_bg = base02 t.header_bg = t.bg_light t.toolbar_bg = base03 t.status_bar_bg = base03 t.section_bg = t.bg_light t.text = base0 t.text_bright = base1 t.text_label = base00 t.text_dim = base01 t.text_muted = base01 t.text_faint = base01 t.accent = blue t.error = red t.warning = yellow t.success = green t.info = cyan t.selection = (0.15, 0.55, 0.82, 0.3) t.selection_bg = blue t.hover_bg = t.bg_light t.highlight = (0.71, 0.54, 0.0, 0.2) t.border = (0.10, 0.30, 0.36, 1.0) t.border_light = (0.14, 0.34, 0.40, 1.0) t.btn_bg = t.bg_light t.btn_hover = t.bg_lighter t.btn_pressed = base03 t.btn_primary = blue t.btn_danger = red t.input_border = t.border t.input_focus = blue t.placeholder = base01 t.scrollbar_hover = (0.40, 0.48, 0.51, 0.7) t.scrollbar_track = (0.0, 0.17, 0.21, 0.3) t.tab_bg = base03 t.tab_active = base02 t.tab_hover = t.bg_light t.tab_text = base00 t.tab_active_text = base1 t.tree_bg = base03 t.tree_select = blue t.tree_hover = t.hover_bg t.tree_arrow = base01 t.check_colour = blue t.check_box = t.border t.slider_fill = blue t.slider_handle = base1 t.dock_title_bg = base03 t.dock_title_text = base0 t.popup_bg = base02 t.popup_hover = blue t.popup_separator = t.border t.divider = t.border t.divider_hover = blue t.current_line = (0.04, 0.22, 0.27, 1.0) t.bracket_match = (0.16, 0.63, 0.60, 0.4) t.bracket_mismatch = (0.86, 0.20, 0.18, 0.4) t.line_number = base01 t.gutter_bg = base03 t.syntax = SyntaxTheme( keyword=green, string=cyan, comment=base01, number=magenta, decorator=orange, builtin=yellow, normal=base0, ) t.viewport_bg = base03 t.selection_outline = orange t.grid_major = (0.10, 0.30, 0.36, 1.0) t.grid_minor = (0.06, 0.25, 0.30, 0.5) t.minimap_text = base01 t.minimap_keyword = green t.minimap_string = cyan t.minimap_comment = base01 t.autocomplete_bg = t.bg_lighter t.autocomplete_selected = (0.15, 0.55, 0.82, 0.25) t.autocomplete_border = t.border_light t.autocomplete_hover = (0.15, 0.55, 0.82, 0.10) t.autocomplete_dim = base00 t.autocomplete_kind = blue t.scrollbar_bg = base03 t.scrollbar_fg = base01 # StyleBoxes D = cls._darken t.btn_style_normal = StyleBox(bg_colour=t.btn_bg, border_width=1.0, content_margin=2.0, border_colour=t.border) t.btn_style_hover = StyleBox(bg_colour=t.btn_hover, border_width=1.0, content_margin=2.0, border_colour=blue) t.btn_style_pressed = StyleBox(bg_colour=t.btn_pressed, border_width=1.0, content_margin=2.0, border_colour=blue) t.btn_style_disabled = StyleBox(bg_colour=D(t.btn_bg, 0.04), border_width=1.0, content_margin=2.0, border_colour=D(t.border, 0.04)) t.btn_style_focused = StyleBox(bg_colour=t.btn_bg, border_width=1.0, content_margin=2.0, border_colour=blue) t.panel_style = StyleBox(bg_colour=t.panel_bg, border_width=1.0, border_colour=t.border) t.input_style_normal = StyleBox(bg_colour=t.bg_input, border_width=1.0, content_margin=4.0, border_colour=t.border) t.input_style_focused = StyleBox(bg_colour=t.bg_input, border_width=1.0, content_margin=4.0, border_colour=blue) t.input_style_disabled = StyleBox(bg_colour=D(t.bg_input, 0.02), border_width=1.0, content_margin=4.0, border_colour=D(t.border, 0.04)) t.tab_style_normal = StyleBox(bg_colour=t.tab_bg, border_width=0.0) t.tab_style_active = StyleBox(bg_colour=t.tab_active, border_width=0.0) t.tab_style_hover = StyleBox(bg_colour=t.tab_hover, border_width=0.0) t.popup_style = StyleBox(bg_colour=t.popup_bg, border_width=1.0, border_colour=t.border) t.popup_style_hover = StyleBox(bg_colour=t.popup_hover, border_width=0.0) t.dock_title_style = StyleBox(bg_colour=t.dock_title_bg, border_width=0.0) t._sync_dicts() return t
[docs] @classmethod def nord(cls) -> AppTheme: """Nord theme — Arctic, north-bluish palette by Arctic Ice Studio.""" t = cls() # Polar Night n0 = (0.18, 0.20, 0.25, 1.0) n1 = (0.23, 0.26, 0.32, 1.0) n2 = (0.26, 0.30, 0.37, 1.0) n3 = (0.30, 0.34, 0.42, 1.0) # Snow Storm n4 = (0.85, 0.87, 0.91, 1.0) n6 = (0.93, 0.94, 0.96, 1.0) # Frost frost0 = (0.56, 0.74, 0.73, 1.0) # teal frost1 = (0.53, 0.75, 0.82, 1.0) # light blue frost2 = (0.51, 0.63, 0.76, 1.0) # blue frost3 = (0.37, 0.51, 0.67, 1.0) # dark blue # Aurora a_red = (0.75, 0.38, 0.42, 1.0) a_orange = (0.82, 0.53, 0.44, 1.0) a_yellow = (0.92, 0.80, 0.55, 1.0) a_green = (0.64, 0.75, 0.55, 1.0) a_purple = (0.71, 0.56, 0.68, 1.0) t.bg_black = n0 t.bg_darkest = n0 t.bg_darker = n0 t.bg_dark = n1 t.bg = n1 t.bg_light = n2 t.bg_lighter = n3 t.bg_input = n0 t.panel_bg = n1 t.header_bg = n2 t.toolbar_bg = n0 t.status_bar_bg = n0 t.section_bg = n2 t.text = n4 t.text_bright = n6 t.text_label = (0.72, 0.75, 0.80, 1.0) t.text_dim = n3 t.text_muted = (0.42, 0.46, 0.54, 1.0) t.text_faint = (0.50, 0.54, 0.62, 1.0) t.accent = frost1 t.error = a_red t.warning = a_yellow t.success = a_green t.info = frost1 t.selection = (0.53, 0.75, 0.82, 0.25) t.selection_bg = frost3 t.hover_bg = n2 t.highlight = (0.92, 0.80, 0.55, 0.2) t.border = n3 t.border_light = (0.36, 0.40, 0.48, 1.0) t.btn_bg = n2 t.btn_hover = n3 t.btn_pressed = n0 t.btn_primary = frost1 t.btn_danger = a_red t.input_border = n3 t.input_focus = frost1 t.placeholder = t.text_dim t.scrollbar_hover = (0.56, 0.74, 0.73, 0.7) t.scrollbar_track = (0.18, 0.20, 0.25, 0.3) t.tab_bg = n0 t.tab_active = n1 t.tab_hover = n2 t.tab_text = t.text_label t.tab_active_text = n6 t.tree_bg = n0 t.tree_select = frost3 t.tree_hover = n2 t.tree_arrow = t.text_muted t.check_colour = frost1 t.check_box = n3 t.slider_fill = frost1 t.slider_handle = n4 t.dock_title_bg = n0 t.dock_title_text = n4 t.popup_bg = n1 t.popup_hover = frost3 t.popup_separator = n3 t.divider = n3 t.divider_hover = frost1 t.current_line = (0.24, 0.27, 0.33, 1.0) t.bracket_match = (0.64, 0.75, 0.55, 0.4) t.bracket_mismatch = (0.75, 0.38, 0.42, 0.4) t.line_number = t.text_muted t.gutter_bg = n0 t.syntax = SyntaxTheme( keyword=frost2, string=a_green, comment=n3, number=a_purple, decorator=a_orange, builtin=frost0, normal=n4, ) t.viewport_bg = n0 t.selection_outline = a_orange t.grid_major = n3 t.grid_minor = (0.26, 0.30, 0.37, 0.5) t.minimap_text = t.text_muted t.minimap_keyword = frost2 t.minimap_string = a_green t.minimap_comment = n3 t.autocomplete_bg = n3 t.autocomplete_selected = (0.53, 0.75, 0.82, 0.25) t.autocomplete_border = (0.36, 0.40, 0.48, 1.0) t.autocomplete_hover = (0.53, 0.75, 0.82, 0.10) t.autocomplete_dim = t.text_muted t.autocomplete_kind = frost1 t.scrollbar_bg = n0 t.scrollbar_fg = t.text_muted # StyleBoxes — nord: subtle gradient buttons L, D = cls._lighten, cls._darken t.btn_style_normal = StyleBox( bg_colour=t.btn_bg, border_width=1.0, content_margin=2.0, bg_gradient=(L(t.btn_bg, 0.02), D(t.btn_bg, 0.02)), border_colour=t.border, ) t.btn_style_hover = StyleBox( bg_colour=t.btn_hover, border_width=1.0, content_margin=2.0, bg_gradient=(L(t.btn_hover, 0.02), D(t.btn_hover, 0.02)), border_colour=frost1, ) t.btn_style_pressed = StyleBox(bg_colour=t.btn_pressed, border_width=1.0, content_margin=2.0, border_colour=frost1) t.btn_style_disabled = StyleBox(bg_colour=D(t.btn_bg, 0.04), border_width=1.0, content_margin=2.0, border_colour=D(n3, 0.04)) t.btn_style_focused = StyleBox(bg_colour=t.btn_bg, border_width=1.0, content_margin=2.0, border_colour=frost1) t.panel_style = StyleBox(bg_colour=t.panel_bg, border_width=1.0, border_colour=n3) t.input_style_normal = StyleBox(bg_colour=t.bg_input, border_width=1.0, content_margin=4.0, border_colour=n3) t.input_style_focused = StyleBox(bg_colour=t.bg_input, border_width=1.0, content_margin=4.0, border_colour=frost1) t.input_style_disabled = StyleBox(bg_colour=D(t.bg_input, 0.02), border_width=1.0, content_margin=4.0, border_colour=D(n3, 0.04)) t.tab_style_normal = StyleBox(bg_colour=t.tab_bg, border_width=0.0) t.tab_style_active = StyleBox(bg_colour=t.tab_active, border_width=0.0) t.tab_style_hover = StyleBox(bg_colour=t.tab_hover, border_width=0.0) t.popup_style = StyleBox(bg_colour=t.popup_bg, border_width=1.0, border_colour=n3) t.popup_style_hover = StyleBox(bg_colour=t.popup_hover, border_width=0.0) t.dock_title_style = StyleBox(bg_colour=t.dock_title_bg, border_width=0.0) t._sync_dicts() return t
# --------------------------------------------------------------------------- # Module-level singleton # --------------------------------------------------------------------------- _current_theme: AppTheme = AppTheme() _theme_generation: int = 0
[docs] def get_theme() -> AppTheme: """Return the current application theme singleton.""" return _current_theme
[docs] def theme_generation() -> int: """Return the current theme generation counter. Incremented by ``set_theme()``. Draw caches use this to detect global theme changes without walking the widget tree. """ return _theme_generation
[docs] def set_theme(theme: AppTheme) -> None: """Swap the module-level theme singleton and bump the generation counter. All ``ThemeColour`` / ``ThemeStyleBox`` descriptors resolve on access, so widgets pick up the new theme on their next draw. The generation counter invalidates draw caches so that next draw actually happens. """ global _current_theme, _theme_generation _current_theme = theme _theme_generation += 1
[docs] def em(multiple: float) -> float: """Return *multiple* of the theme font size in logical pixels. Use for layout dimensions that should scale with the user's font-size preference. Does NOT multiply by ``ui_scale`` — UI layout works in logical (window) coordinates; the GPU rendering pipeline handles the logical-to-physical conversion separately via Draw2DPass:: row_height = em(2.18) # ~24 px at default 11pt padding = em(0.55) # ~6 px label_w = em(7.27) # ~80 px """ return multiple * _current_theme.font_size