Files
Mainline/engine/render/gradient.py
David Gwilliam ead4cc3d5a 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
2026-03-21 15:31:35 -07:00

137 lines
4.1 KiB
Python

"""Gradient coloring for rendered block characters.
Provides left-to-right and complementary gradient effects for terminal display.
"""
from engine.terminal import RST
# Left → right: white-hot leading edge fades to near-black
GRAD_COLS = [
"\033[1;38;5;231m", # white
"\033[1;38;5;195m", # pale cyan-white
"\033[38;5;123m", # bright cyan
"\033[38;5;118m", # bright lime
"\033[38;5;82m", # lime
"\033[38;5;46m", # bright green
"\033[38;5;40m", # green
"\033[38;5;34m", # medium green
"\033[38;5;28m", # dark green
"\033[38;5;22m", # deep green
"\033[2;38;5;22m", # dim deep green
"\033[2;38;5;235m", # near black
]
# Complementary sweep for queue messages (opposite hue family from ticker greens)
MSG_GRAD_COLS = [
"\033[1;38;5;231m", # white
"\033[1;38;5;225m", # pale pink-white
"\033[38;5;219m", # bright pink
"\033[38;5;213m", # hot pink
"\033[38;5;207m", # magenta
"\033[38;5;201m", # bright magenta
"\033[38;5;165m", # orchid-red
"\033[38;5;161m", # ruby-magenta
"\033[38;5;125m", # dark magenta
"\033[38;5;89m", # deep maroon-magenta
"\033[2;38;5;89m", # dim deep maroon-magenta
"\033[2;38;5;235m", # near black
]
def lr_gradient(rows, offset=0.0, grad_cols=None):
"""Color each non-space block character with a shifting left-to-right gradient.
Args:
rows: List of text lines with block characters
offset: Gradient offset (0.0-1.0) for animation
grad_cols: List of ANSI color codes (default: GRAD_COLS)
Returns:
List of lines with gradient coloring applied
"""
cols = grad_cols or GRAD_COLS
n = len(cols)
max_x = max((len(r.rstrip()) for r in rows if r.strip()), default=1)
out = []
for row in rows:
if not row.strip():
out.append(row)
continue
buf = []
for x, ch in enumerate(row):
if ch == " ":
buf.append(" ")
else:
shifted = (x / max(max_x - 1, 1) + offset) % 1.0
idx = min(round(shifted * (n - 1)), n - 1)
buf.append(f"{cols[idx]}{ch}{RST}")
out.append("".join(buf))
return out
def lr_gradient_opposite(rows, offset=0.0):
"""Complementary (opposite wheel) gradient used for queue message panels.
Args:
rows: List of text lines with block characters
offset: Gradient offset (0.0-1.0) for animation
Returns:
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