feat(theme): Add theme system with ACTIVE_THEME support
- Add Theme class and THEME_REGISTRY to engine/themes.py - Add set_active_theme() function to config.py - Add msg_gradient() function to use theme-based message gradients - Support --theme CLI flag to select theme (green, orange, purple) - Initialize theme on module load with fallback to default
This commit is contained in:
@@ -132,6 +132,7 @@ class Config:
|
||||
display: str = "pygame"
|
||||
websocket: bool = False
|
||||
websocket_port: int = 8765
|
||||
theme: str = "green"
|
||||
|
||||
@classmethod
|
||||
def from_args(cls, argv: list[str] | None = None) -> "Config":
|
||||
@@ -175,6 +176,7 @@ class Config:
|
||||
display=_arg_value("--display", argv) or "terminal",
|
||||
websocket="--websocket" in argv,
|
||||
websocket_port=_arg_int("--websocket-port", 8765, argv),
|
||||
theme=_arg_value("--theme", argv) or "green",
|
||||
)
|
||||
|
||||
|
||||
@@ -246,6 +248,40 @@ DEMO = "--demo" in sys.argv
|
||||
DEMO_EFFECT_DURATION = 5.0 # seconds per effect
|
||||
PIPELINE_DEMO = "--pipeline-demo" in sys.argv
|
||||
|
||||
# ─── THEME MANAGEMENT ─────────────────────────────────────────
|
||||
ACTIVE_THEME = None
|
||||
|
||||
|
||||
def set_active_theme(theme_id: str = "green"):
|
||||
"""Set the active theme by ID.
|
||||
|
||||
Args:
|
||||
theme_id: Theme identifier from theme registry (e.g., "green", "orange", "purple")
|
||||
|
||||
Raises:
|
||||
KeyError: If theme_id is not in the theme registry
|
||||
|
||||
Side Effects:
|
||||
Sets the ACTIVE_THEME global variable
|
||||
"""
|
||||
global ACTIVE_THEME
|
||||
from engine import themes
|
||||
|
||||
ACTIVE_THEME = themes.get_theme(theme_id)
|
||||
|
||||
|
||||
# Initialize theme on module load (lazy to avoid circular dependency)
|
||||
def _init_theme():
|
||||
theme_id = _arg_value("--theme", sys.argv) or "green"
|
||||
try:
|
||||
set_active_theme(theme_id)
|
||||
except KeyError:
|
||||
pass # Theme not found, keep None
|
||||
|
||||
|
||||
_init_theme()
|
||||
|
||||
|
||||
# ─── PIPELINE MODE (new unified architecture) ─────────────
|
||||
PIPELINE_MODE = "--pipeline" in sys.argv
|
||||
PIPELINE_PRESET = _arg_value("--pipeline-preset", sys.argv) or "demo"
|
||||
@@ -256,6 +292,9 @@ PRESET = _arg_value("--preset", sys.argv)
|
||||
# ─── PIPELINE DIAGRAM ────────────────────────────────────
|
||||
PIPELINE_DIAGRAM = "--pipeline-diagram" in sys.argv
|
||||
|
||||
# ─── THEME ──────────────────────────────────────────────────
|
||||
THEME = _arg_value("--theme", sys.argv) or "green"
|
||||
|
||||
|
||||
def set_font_selection(font_path=None, font_index=None):
|
||||
"""Set runtime primary font selection."""
|
||||
|
||||
@@ -80,3 +80,57 @@ def lr_gradient_opposite(rows, offset=0.0):
|
||||
List of lines with complementary gradient coloring applied
|
||||
"""
|
||||
return lr_gradient(rows, offset, MSG_GRAD_COLS)
|
||||
|
||||
|
||||
def msg_gradient(rows, offset):
|
||||
"""Apply message (ntfy) gradient using theme complementary colors.
|
||||
|
||||
Returns colored rows using ACTIVE_THEME.message_gradient if available,
|
||||
falling back to default magenta if no theme is set.
|
||||
|
||||
Args:
|
||||
rows: List of text strings to colorize
|
||||
offset: Gradient offset (0.0-1.0) for animation
|
||||
|
||||
Returns:
|
||||
List of rows with ANSI color codes applied
|
||||
"""
|
||||
from engine import config
|
||||
|
||||
# Check if theme is set and use it
|
||||
if config.ACTIVE_THEME:
|
||||
cols = _color_codes_to_ansi(config.ACTIVE_THEME.message_gradient)
|
||||
else:
|
||||
# Fallback to default magenta gradient
|
||||
cols = MSG_GRAD_COLS
|
||||
|
||||
return lr_gradient(rows, offset, cols)
|
||||
|
||||
|
||||
def _color_codes_to_ansi(color_codes):
|
||||
"""Convert a list of 256-color codes to ANSI escape code strings.
|
||||
|
||||
Pattern: first 2 are bold, middle 8 are normal, last 2 are dim.
|
||||
|
||||
Args:
|
||||
color_codes: List of 12 integers (256-color palette codes)
|
||||
|
||||
Returns:
|
||||
List of ANSI escape code strings
|
||||
"""
|
||||
if not color_codes or len(color_codes) != 12:
|
||||
# Fallback to default green if invalid
|
||||
return GRAD_COLS
|
||||
|
||||
result = []
|
||||
for i, code in enumerate(color_codes):
|
||||
if i < 2:
|
||||
# Bold for first 2 (bright leading edge)
|
||||
result.append(f"\033[1;38;5;{code}m")
|
||||
elif i < 10:
|
||||
# Normal for middle 8
|
||||
result.append(f"\033[38;5;{code}m")
|
||||
else:
|
||||
# Dim for last 2 (dark trailing edge)
|
||||
result.append(f"\033[2;38;5;{code}m")
|
||||
return result
|
||||
|
||||
Reference in New Issue
Block a user