feat: themes
This commit is contained in:
241
backend/uframe/themes.py
Normal file
241
backend/uframe/themes.py
Normal file
@@ -0,0 +1,241 @@
|
||||
"""µ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)
|
||||
Reference in New Issue
Block a user