4 Commits

Author SHA1 Message Date
edd1416407 refactor: Make EffectPlugin an abstract base class, update effects to inherit from it, and improve plugin discovery. 2026-03-18 23:06:04 -07:00
ac9b47f668 Merge pull request 'fix: use theme-aware msg_gradient for ntfy messages' (#31) from feat/color-pick into main
Reviewed-on: #31
2026-03-16 10:29:46 +00:00
b149825bcb Merge branch 'main' into feat/color-pick 2026-03-16 10:29:28 +00:00
1b29e91f9d fix: use theme-aware msg_gradient for ntfy messages
Replace lr_gradient_opposite() with msg_gradient() in render_message_overlay().
Messages now render in complementary colors matching the active theme:
- Green theme → magenta messages
- Orange theme → blue messages
- Purple theme → yellow messages
2026-03-16 03:25:52 -07:00
7 changed files with 47 additions and 17 deletions

View File

@@ -5,6 +5,7 @@ PLUGIN_DIR = Path(__file__).parent
def discover_plugins():
from engine.effects.registry import get_registry
from engine.effects.types import EffectPlugin
registry = get_registry()
imported = {}
@@ -22,8 +23,8 @@ def discover_plugins():
attr = getattr(module, attr_name)
if (
isinstance(attr, type)
and hasattr(attr, "name")
and hasattr(attr, "process")
and issubclass(attr, EffectPlugin)
and attr is not EffectPlugin
and attr_name.endswith("Effect")
):
plugin = attr()

View File

@@ -3,7 +3,7 @@ import random
from engine.effects.types import EffectConfig, EffectContext, EffectPlugin
class FadeEffect:
class FadeEffect(EffectPlugin):
name = "fade"
config = EffectConfig(enabled=True, intensity=1.0)

View File

@@ -7,7 +7,7 @@ from engine.sources import FEEDS, POETRY_SOURCES
from engine.terminal import C_DIM, G_DIM, G_LO, RST, W_GHOST
class FirehoseEffect:
class FirehoseEffect(EffectPlugin):
name = "firehose"
config = EffectConfig(enabled=True, intensity=1.0)

View File

@@ -5,7 +5,7 @@ from engine.effects.types import EffectConfig, EffectContext, EffectPlugin
from engine.terminal import C_DIM, DIM, G_DIM, G_LO, RST
class GlitchEffect:
class GlitchEffect(EffectPlugin):
name = "glitch"
config = EffectConfig(enabled=True, intensity=1.0)

View File

@@ -5,7 +5,7 @@ from engine.effects.types import EffectConfig, EffectContext, EffectPlugin
from engine.terminal import C_DIM, G_DIM, G_LO, RST, W_GHOST
class NoiseEffect:
class NoiseEffect(EffectPlugin):
name = "noise"
config = EffectConfig(enabled=True, intensity=0.15)

View File

@@ -1,3 +1,4 @@
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import Any
@@ -8,10 +9,11 @@ class EffectContext:
terminal_height: int
scroll_cam: int
ticker_height: int
mic_excess: float
grad_offset: float
frame_number: int
has_message: bool
camera_x: int = 0
mic_excess: float = 0.0
grad_offset: float = 0.0
frame_number: int = 0
has_message: bool = False
items: list = field(default_factory=list)
@@ -22,15 +24,42 @@ class EffectConfig:
params: dict[str, Any] = field(default_factory=dict)
class EffectPlugin:
class EffectPlugin(ABC):
name: str
config: EffectConfig
def process(self, buf: list[str], ctx: EffectContext) -> list[str]:
raise NotImplementedError
@abstractmethod
def process(self, buf: list[str], ctx: EffectContext) -> list[str]: ...
def configure(self, config: EffectConfig) -> None:
raise NotImplementedError
@abstractmethod
def configure(self, config: EffectConfig) -> None: ...
def create_effect_context(
terminal_width: int = 80,
terminal_height: int = 24,
scroll_cam: int = 0,
ticker_height: int = 0,
camera_x: int = 0,
mic_excess: float = 0.0,
grad_offset: float = 0.0,
frame_number: int = 0,
has_message: bool = False,
items: list | None = None,
) -> EffectContext:
"""Factory function to create EffectContext with sensible defaults."""
return EffectContext(
terminal_width=terminal_width,
terminal_height=terminal_height,
scroll_cam=scroll_cam,
ticker_height=ticker_height,
camera_x=camera_x,
mic_excess=mic_excess,
grad_offset=grad_offset,
frame_number=frame_number,
has_message=has_message,
items=items or [],
)
@dataclass

View File

@@ -18,7 +18,7 @@ from engine.effects import (
noise,
vis_trunc,
)
from engine.render import big_wrap, lr_gradient, lr_gradient_opposite
from engine.render import big_wrap, lr_gradient, msg_gradient
from engine.terminal import RST, W_COOL
MSG_META = "\033[38;5;245m"
@@ -57,7 +57,7 @@ def render_message_overlay(
else:
msg_rows = msg_cache[1]
msg_rows = lr_gradient_opposite(
msg_rows = msg_gradient(
msg_rows, (time.monotonic() * config.GRAD_SPEED) % 1.0
)