Add color_cycle as standalone source type; UI polish

- color_cycle is now a top-level source type (alongside picture/static/gradient)
  with a configurable color list and cycle_speed; defaults to full rainbow spectrum
- ColorCycleColorStripSource + ColorCycleColorStripStream: smooth 30 fps interpolation
  between user-defined colors, one full cycle every 20s at speed=1.0
- Removed color_cycle animation sub-type from StaticColorStripStream
- Color cycle editor: compact horizontal swatch layout, proper module-scope fix
  (colorCycleAdd/Remove now exposed on window, DOM-synced before mutations)
- Animation enabled + Frame interpolation checkboxes use toggle-switch style
- Removed Potential FPS metric from targets and KC targets metric grids

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-20 22:14:42 +03:00
parent 872949a7e1
commit c31818a20d
14 changed files with 674 additions and 40 deletions

View File

@@ -8,6 +8,14 @@ from pydantic import BaseModel, Field
from wled_controller.api.schemas.devices import Calibration
class AnimationConfig(BaseModel):
"""Procedural animation configuration for static/gradient color strip sources."""
enabled: bool = True
type: str = "breathing" # breathing | color_cycle | gradient_shift | wave
speed: float = Field(1.0, ge=0.1, le=10.0, description="Speed multiplier (0.110.0)")
class ColorStop(BaseModel):
"""A single color stop in a gradient."""
@@ -23,7 +31,7 @@ class ColorStripSourceCreate(BaseModel):
"""Request to create a color strip source."""
name: str = Field(description="Source name", min_length=1, max_length=100)
source_type: Literal["picture", "static", "gradient"] = Field(default="picture", description="Source type")
source_type: Literal["picture", "static", "gradient", "color_cycle"] = Field(default="picture", description="Source type")
# picture-type fields
picture_source_id: str = Field(default="", description="Picture source ID (for picture type)")
fps: int = Field(default=30, description="Target frames per second", ge=10, le=90)
@@ -37,10 +45,14 @@ class ColorStripSourceCreate(BaseModel):
color: Optional[List[int]] = Field(None, description="Static RGB color [R, G, B] (0-255 each, for static type)")
# gradient-type fields
stops: Optional[List[ColorStop]] = Field(None, description="Color stops for gradient type")
# color_cycle-type fields
colors: Optional[List[List[int]]] = Field(None, description="List of [R,G,B] colors to cycle (color_cycle type)")
cycle_speed: Optional[float] = Field(None, description="Cycle speed multiplier 0.110.0 (color_cycle type)", ge=0.1, le=10.0)
# shared
led_count: int = Field(default=0, description="Total LED count (0 = auto from calibration / device)", ge=0)
description: Optional[str] = Field(None, description="Optional description", max_length=500)
frame_interpolation: bool = Field(default=False, description="Blend between consecutive captured frames for smoother output")
animation: Optional[AnimationConfig] = Field(None, description="Procedural animation config (static/gradient only)")
class ColorStripSourceUpdate(BaseModel):
@@ -60,10 +72,14 @@ class ColorStripSourceUpdate(BaseModel):
color: Optional[List[int]] = Field(None, description="Static RGB color [R, G, B] (0-255 each, for static type)")
# gradient-type fields
stops: Optional[List[ColorStop]] = Field(None, description="Color stops for gradient type")
# color_cycle-type fields
colors: Optional[List[List[int]]] = Field(None, description="List of [R,G,B] colors to cycle (color_cycle type)")
cycle_speed: Optional[float] = Field(None, description="Cycle speed multiplier 0.110.0 (color_cycle type)", ge=0.1, le=10.0)
# shared
led_count: Optional[int] = Field(None, description="Total LED count (0 = auto from calibration / device)", ge=0)
description: Optional[str] = Field(None, description="Optional description", max_length=500)
frame_interpolation: Optional[bool] = Field(None, description="Blend between consecutive captured frames")
animation: Optional[AnimationConfig] = Field(None, description="Procedural animation config (static/gradient only)")
class ColorStripSourceResponse(BaseModel):
@@ -85,10 +101,14 @@ class ColorStripSourceResponse(BaseModel):
color: Optional[List[int]] = Field(None, description="Static RGB color [R, G, B]")
# gradient-type fields
stops: Optional[List[ColorStop]] = Field(None, description="Color stops for gradient type")
# color_cycle-type fields
colors: Optional[List[List[int]]] = Field(None, description="List of [R,G,B] colors to cycle (color_cycle type)")
cycle_speed: Optional[float] = Field(None, description="Cycle speed multiplier (color_cycle type)")
# shared
led_count: int = Field(0, description="Total LED count (0 = auto from calibration / device)")
description: Optional[str] = Field(None, description="Description")
frame_interpolation: Optional[bool] = Field(None, description="Blend between consecutive captured frames")
animation: Optional[AnimationConfig] = Field(None, description="Procedural animation config (static/gradient only)")
overlay_active: bool = Field(False, description="Whether the screen overlay is currently active")
created_at: datetime = Field(description="Creation timestamp")
updated_at: datetime = Field(description="Last update timestamp")