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"
|
display: str = "pygame"
|
||||||
websocket: bool = False
|
websocket: bool = False
|
||||||
websocket_port: int = 8765
|
websocket_port: int = 8765
|
||||||
|
theme: str = "green"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_args(cls, argv: list[str] | None = None) -> "Config":
|
def from_args(cls, argv: list[str] | None = None) -> "Config":
|
||||||
@@ -175,6 +176,7 @@ class Config:
|
|||||||
display=_arg_value("--display", argv) or "terminal",
|
display=_arg_value("--display", argv) or "terminal",
|
||||||
websocket="--websocket" in argv,
|
websocket="--websocket" in argv,
|
||||||
websocket_port=_arg_int("--websocket-port", 8765, 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
|
DEMO_EFFECT_DURATION = 5.0 # seconds per effect
|
||||||
PIPELINE_DEMO = "--pipeline-demo" in sys.argv
|
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 (new unified architecture) ─────────────
|
||||||
PIPELINE_MODE = "--pipeline" in sys.argv
|
PIPELINE_MODE = "--pipeline" in sys.argv
|
||||||
PIPELINE_PRESET = _arg_value("--pipeline-preset", sys.argv) or "demo"
|
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 = "--pipeline-diagram" in sys.argv
|
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):
|
def set_font_selection(font_path=None, font_index=None):
|
||||||
"""Set runtime primary font selection."""
|
"""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
|
List of lines with complementary gradient coloring applied
|
||||||
"""
|
"""
|
||||||
return lr_gradient(rows, offset, MSG_GRAD_COLS)
|
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