"""µFrame Theme System — decorative styles for rich terminal UIs. A theme maps abstract UI elements to concrete character sets and color palettes. The same .uf source renders with different visual character when a different theme is applied. """ from __future__ import annotations from dataclasses import dataclass, field @dataclass class BorderChars: tl: str = "┌"; t: str = "─"; tr: str = "┐" l: str = "│"; r: str = "│" bl: str = "└"; b: str = "─"; br: str = "┘" @dataclass class Indicators: online: str = "●" offline: str = "○" degraded: str = "◐" unknown: str = "◌" alert: str = "⚠" @dataclass class GaugeChars: filled: str = "█" empty: str = "░" @dataclass class FormChars: field_l: str = "[ " field_r: str = " ]" radio_on: str = "(•)" radio_off: str = "( )" check_on: str = "[✓]" check_off: str = "[ ]" @dataclass class Ornaments: bullet: str = "•" header: str = "" separator: str = "" footer: str = "" @dataclass class TitleCaps: left: str = "─ " right: str = " ─" @dataclass class Palette: accent: str = "0f0" # headings, primary highlights accent2: str = "0cf" # secondary (H2, links) accent3: str = "88f" # tertiary (H3) muted: str = "555" # dividers, empty gauge border: str = "" # border color (empty = no color) success: str = "0f0" # online, gauge ok warning: str = "ff0" # degraded, gauge warn danger: str = "f00" # offline, gauge crit info: str = "0cf" # links, sparklines form: str = "0cf" # form element accents label: str = "888" # labels, field names button: str = "0f0" # form buttons @dataclass class DividerChars: light: str = "─" heavy: str = "━" double: str = "═" dash: str = "╌" dot: str = "┄" @dataclass class ThemeDef: name: str = "default" description: str = "Clean engineering — standard box-drawing" borders_light: BorderChars = field(default_factory=BorderChars) borders_heavy: BorderChars = field(default_factory=lambda: BorderChars( tl="┏", t="━", tr="┓", l="┃", r="┃", bl="┗", b="━", br="┛")) borders_double: BorderChars = field(default_factory=lambda: BorderChars( tl="╔", t="═", tr="╗", l="║", r="║", bl="╚", b="═", br="╝")) borders_rounded: BorderChars = field(default_factory=lambda: BorderChars( tl="╭", t="─", tr="╮", l="│", r="│", bl="╰", b="─", br="╯")) dividers: DividerChars = field(default_factory=DividerChars) title_caps: TitleCaps = field(default_factory=TitleCaps) indicators: Indicators = field(default_factory=Indicators) gauge: GaugeChars = field(default_factory=GaugeChars) form: FormChars = field(default_factory=FormChars) ornaments: Ornaments = field(default_factory=Ornaments) palette: Palette = field(default_factory=Palette) def border_chars(self, weight_name: str) -> BorderChars: return { "light": self.borders_light, "heavy": self.borders_heavy, "double": self.borders_double, "rounded": self.borders_rounded, }.get(weight_name, self.borders_light) def border_dict(self, weight_name: str) -> dict[str, str]: """Return BOX_CHARS-compatible dict for a border weight.""" bc = self.border_chars(weight_name) return { "tl": bc.tl, "tr": bc.tr, "bl": bc.bl, "br": bc.br, "h": bc.t, "v": bc.l, "t_down": bc.t, "t_up": bc.b, "t_right": bc.l, "t_left": bc.r, "cross": bc.t, } # --------------------------------------------------------------------------- # Built-in themes # --------------------------------------------------------------------------- THEME_DEFAULT = ThemeDef() THEME_NOUVEAU = ThemeDef( name="nouveau", description="Art Nouveau — organic flowing ornament", borders_heavy=BorderChars(tl="☙", t="━", tr="❧", l="┃", r="┃", bl="☙", b="━", br="❧"), borders_light=BorderChars(tl="╭", t="┈", tr="╮", l="┊", r="┊", bl="╰", b="┈", br="╯"), borders_double=BorderChars(tl="☙", t="━", tr="❧", l="┃", r="┃", bl="☙", b="━", br="❧"), borders_rounded=BorderChars(tl="╭", t="┈", tr="╮", l="┊", r="┊", bl="╰", b="┈", br="╯"), dividers=DividerChars(light="┈", heavy="━", double="━", dash="┈", dot="┈"), title_caps=TitleCaps(left="✾─── ", right=" ───✾"), indicators=Indicators(online="❀", offline="✿", degraded="⚘", unknown="✿", alert="❋"), gauge=GaugeChars(filled="▐", empty="░"), form=FormChars(field_l="❴ ", field_r=" ❵", radio_on="❀", radio_off="✿", check_on="❀", check_off="✿"), ornaments=Ornaments(bullet="❀", header="─✾──────✾─", separator="☙━━━━━━━━━━━━━❧"), palette=Palette(accent="da5", accent2="8b5", accent3="886", muted="886", border="a85", success="6b4", warning="da5", danger="a33", info="68a", form="da5", label="886", button="6b4"), ) THEME_GOTHIC = ThemeDef( name="gothic", description="Gothic — heavy blackletter, monumental", borders_heavy=BorderChars(tl="╬", t="═", tr="╬", l="║", r="║", bl="╬", b="═", br="╬"), borders_light=BorderChars(tl="╔", t="═", tr="╗", l="║", r="║", bl="╚", b="═", br="╝"), borders_double=BorderChars(tl="╬", t="═", tr="╬", l="║", r="║", bl="╬", b="═", br="╬"), borders_rounded=BorderChars(tl="╔", t="═", tr="╗", l="║", r="║", bl="╚", b="═", br="╝"), dividers=DividerChars(light="═", heavy="═", double="═", dash="═", dot="═"), title_caps=TitleCaps(left="═══╡ ", right=" ╞═══"), indicators=Indicators(online="⚑", offline="⚐", degraded="⚑", unknown="⚐", alert="⚔"), gauge=GaugeChars(filled="▓", empty="░"), form=FormChars(field_l="║ ", field_r=" ║", radio_on="⚑", radio_off="⚐", check_on="⚑", check_off="⚐"), ornaments=Ornaments(bullet="▪", header="═══╡══════╞═══"), palette=Palette(accent="cc8", accent2="a66", accent3="888", muted="666", border="888", success="8a8", warning="cc8", danger="a44", info="8ac", form="cc8", label="888", button="cc8"), ) THEME_BAMBOO = ThemeDef( name="bamboo", description="Bamboo — East Asian minimalism, light brush strokes", borders_heavy=BorderChars(tl="〔", t=" ", tr="〕", l=" ", r=" ", bl=" ", b=" ", br=" "), borders_light=BorderChars(tl="┌", t="╌", tr="┐", l="╎", r="╎", bl="└", b="╌", br="┘"), borders_double=BorderChars(tl="〔", t=" ", tr="〕", l=" ", r=" ", bl=" ", b=" ", br=" "), borders_rounded=BorderChars(tl="┌", t="╌", tr="┐", l="╎", r="╎", bl="└", b="╌", br="┘"), dividers=DividerChars(light="┄", heavy="┄", double="┄", dash="┄", dot="┄"), title_caps=TitleCaps(left="┄┄┄ ", right=" ┄┄┄"), indicators=Indicators(online="◉", offline="◦", degraded="◎", unknown="◦", alert="◈"), gauge=GaugeChars(filled="▏", empty=" "), form=FormChars(field_l="〈 ", field_r=" 〉", radio_on="◉", radio_off="◦", check_on="◉", check_off="◦"), ornaments=Ornaments(bullet="‣"), palette=Palette(accent="bca", accent2="ab9", accent3="998", muted="998", border="776", success="8b8", warning="cc9", danger="b77", info="9ab", form="bca", label="998", button="8b8"), ) THEME_CIRCUIT = ThemeDef( name="circuit", description="Circuit — digital, technical, neon", borders_heavy=BorderChars(tl="╒", t="═", tr="╕", l="│", r="│", bl="╘", b="═", br="╛"), borders_light=BorderChars(tl="┌", t="─", tr="┐", l="│", r="│", bl="└", b="─", br="┘"), borders_double=BorderChars(tl="╒", t="═", tr="╕", l="│", r="│", bl="╘", b="═", br="╛"), borders_rounded=BorderChars(tl="╒", t="═", tr="╕", l="│", r="│", bl="╘", b="═", br="╛"), dividers=DividerChars(light="─", heavy="═", double="═", dash="╌", dot="┄"), title_caps=TitleCaps(left="══[ ", right=" ]═══"), indicators=Indicators(online="◈", offline="◇", degraded="◈", unknown="◇", alert="⚡"), gauge=GaugeChars(filled="▰", empty="▱"), form=FormChars(field_l=">_ [ ", field_r=" ]", radio_on="[▰]", radio_off="[▱]", check_on="[▰]", check_off="[▱]"), ornaments=Ornaments(bullet="▸"), palette=Palette(accent="0ff", accent2="f0f", accent3="0af", muted="555", border="0aa", success="0f0", warning="ff0", danger="f00", info="0ff", form="0ff", label="0aa", button="0f0"), ) THEME_BRUTALIST = ThemeDef( name="brutalist", description="Brutalist — raw blocks, monochrome, anti-decorative", borders_heavy=BorderChars(tl="█", t="█", tr="█", l="█", r="█", bl="█", b="█", br="█"), borders_light=BorderChars(tl="▛", t="▀", tr="▜", l="▌", r="▐", bl="▙", b="▄", br="▟"), borders_double=BorderChars(tl="█", t="█", tr="█", l="█", r="█", bl="█", b="█", br="█"), borders_rounded=BorderChars(tl="▛", t="▀", tr="▜", l="▌", r="▐", bl="▙", b="▄", br="▟"), dividers=DividerChars(light="▔", heavy="█", double="█", dash="▔", dot="▔"), title_caps=TitleCaps(left="▌ ", right=" ▐"), indicators=Indicators(online="■", offline="□", degraded="■", unknown="□", alert="!"), gauge=GaugeChars(filled="█", empty=" "), form=FormChars(field_l="[", field_r="]", radio_on="■", radio_off="□", check_on="■", check_off="□"), ornaments=Ornaments(bullet="▪"), palette=Palette(accent="fff", accent2="fff", accent3="ccc", muted="888", border="fff", success="fff", warning="fff", danger="fff", info="fff", form="fff", label="aaa", button="fff"), ) # --------------------------------------------------------------------------- # Theme registry # --------------------------------------------------------------------------- BUILTIN_THEMES: dict[str, ThemeDef] = { "default": THEME_DEFAULT, "nouveau": THEME_NOUVEAU, "gothic": THEME_GOTHIC, "bamboo": THEME_BAMBOO, "circuit": THEME_CIRCUIT, "brutalist": THEME_BRUTALIST, } def get_theme(name: str) -> ThemeDef: """Get a built-in theme by name. Returns default if not found.""" return BUILTIN_THEMES.get(name.lower(), THEME_DEFAULT)