242 lines
11 KiB
Python
242 lines
11 KiB
Python
"""µ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)
|