diff --git a/engine/render.py b/engine/render.py index 8773c90..ccf1e12 100644 --- a/engine/render.py +++ b/engine/render.py @@ -254,6 +254,29 @@ def lr_gradient_opposite(rows, offset=0.0): return lr_gradient(rows, offset, _default_magenta_gradient()) +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 + + cols = ( + _color_codes_to_ansi(config.ACTIVE_THEME.message_gradient) + if config.ACTIVE_THEME + else _default_magenta_gradient() + ) + return lr_gradient(rows, offset, cols) + + # ─── HEADLINE BLOCK ASSEMBLY ───────────────────────────── def make_block(title, src, ts, w): """Render a headline into a content block with color.""" diff --git a/engine/scroll.py b/engine/scroll.py index 810fe9f..b50b793 100644 --- a/engine/scroll.py +++ b/engine/scroll.py @@ -18,7 +18,7 @@ from engine.effects import ( noise, vis_trunc, ) -from engine.render import big_wrap, lr_gradient, lr_gradient_opposite, make_block +from engine.render import big_wrap, lr_gradient, msg_gradient, make_block from engine.terminal import CLR, RST, W_COOL, th, tw @@ -86,7 +86,7 @@ def stream(items, ntfy_poller, mic_monitor): _msg_cache = (cache_key, msg_rows) else: msg_rows = _msg_cache[1] - msg_rows = lr_gradient_opposite( + msg_rows = msg_gradient( msg_rows, (time.monotonic() * config.GRAD_SPEED) % 1.0 ) # Layout: rendered text + meta + border diff --git a/tests/test_render.py b/tests/test_render.py index 5482853..20eb63e 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -172,3 +172,130 @@ class TestLrGradientBasicFunctionality: assert len(result) == 3 finally: config.ACTIVE_THEME = original_theme + + +class TestMsgGradient: + """Tests for msg_gradient function (message/ntfy overlay coloring).""" + + def test_msg_gradient_uses_active_theme(self): + """msg_gradient uses ACTIVE_THEME.message_gradient when theme is set.""" + # Save original state + original_theme = config.ACTIVE_THEME + + try: + # Set a theme + config.set_active_theme("green") + + # Create simple test data + rows = ["MESSAGE"] + + # Call msg_gradient + result = render.msg_gradient(rows, offset=0.0) + + # Should return colored output using theme's message_gradient + assert isinstance(result, list) + assert len(result) == 1 + # Should have ANSI codes from the message gradient + assert result[0] != "MESSAGE" + assert "\033[" in result[0] + finally: + # Restore original state + config.ACTIVE_THEME = original_theme + + def test_msg_gradient_fallback_when_no_theme(self): + """msg_gradient uses fallback magenta when ACTIVE_THEME is None.""" + # Save original state + original_theme = config.ACTIVE_THEME + + try: + # Clear the theme + config.ACTIVE_THEME = None + + # Create simple test data + rows = ["MESSAGE"] + + # Call msg_gradient + result = render.msg_gradient(rows, offset=0.0) + + # Should return colored output using default magenta + assert isinstance(result, list) + assert len(result) == 1 + # Should have ANSI codes + assert result[0] != "MESSAGE" + assert "\033[" in result[0] + finally: + # Restore original state + config.ACTIVE_THEME = original_theme + + def test_msg_gradient_returns_colored_rows(self): + """msg_gradient returns properly colored rows with animation offset.""" + # Save original state + original_theme = config.ACTIVE_THEME + + try: + # Set a theme + config.set_active_theme("orange") + + rows = ["NTFY", "ALERT"] + + # Call with offset + result = render.msg_gradient(rows, offset=0.5) + + # Should return same number of rows + assert len(result) == 2 + # Both should be colored + assert all("\033[" in r for r in result) + # Should not be the original text + assert result != rows + finally: + config.ACTIVE_THEME = original_theme + + def test_msg_gradient_different_themes_produce_different_results(self): + """msg_gradient produces different colors for different themes.""" + original_theme = config.ACTIVE_THEME + + try: + rows = ["TEST"] + + # Get result with green theme + config.set_active_theme("green") + result_green = render.msg_gradient(rows, offset=0.0) + + # Get result with orange theme + config.set_active_theme("orange") + result_orange = render.msg_gradient(rows, offset=0.0) + + # Results should be different (different message gradients) + assert result_green != result_orange + finally: + config.ACTIVE_THEME = original_theme + + def test_msg_gradient_preserves_spacing(self): + """msg_gradient preserves spaces in rows.""" + original_theme = config.ACTIVE_THEME + + try: + config.set_active_theme("purple") + rows = ["M E S S A G E"] + + result = render.msg_gradient(rows, offset=0.0) + + # Spaces should be preserved + assert " " in result[0] + finally: + config.ACTIVE_THEME = original_theme + + def test_msg_gradient_empty_rows(self): + """msg_gradient handles empty rows correctly.""" + original_theme = config.ACTIVE_THEME + + try: + config.set_active_theme("green") + rows = [""] + + result = render.msg_gradient(rows, offset=0.0) + + # Empty row should stay empty + assert result == [""] + finally: + config.ACTIVE_THEME = original_theme