"""Overlay primitives — 7 типов через pydantic discriminated union. Все координаты **normalized** (0.0–1.0 относительно cell или output frame). Color — hex RGB string + alpha как float. Phase 4a: data models + state. Rendering — Phase 4b (filter-side CUDA kernels). """ from __future__ import annotations import uuid from typing import Annotated, Literal, Union from pydantic import BaseModel, Field # ─── Common ────────────────────────────────────────────────────────────── class OverlayBase(BaseModel): """Общие поля всех overlay'ев.""" id: str = Field(default_factory=lambda: str(uuid.uuid4())[:8]) cell: int | None = Field( default=None, description="Привязка к cell layout (0..N-1). None = относительно " "всего output frame.", ) z_order: int = Field(default=0, description="Higher = поверх. Default 0.") opacity: float = Field(default=1.0, ge=0.0, le=1.0) visible: bool = True # ─── Rect ──────────────────────────────────────────────────────────────── class RectOverlay(OverlayBase): type: Literal["rect"] = "rect" x: float = Field(ge=0.0, le=1.0) y: float = Field(ge=0.0, le=1.0) w: float = Field(gt=0.0, le=1.0) h: float = Field(gt=0.0, le=1.0) color: str = Field(default="#FF0000", description="HEX RGB e.g. #FF0000") border_only: bool = Field(default=False, description="Если true — только рамка") border_width: int = Field(default=2, ge=1, le=64) # ─── Text ──────────────────────────────────────────────────────────────── class TextOverlay(OverlayBase): type: Literal["text"] = "text" x: float = Field(ge=0.0, le=1.0) y: float = Field(ge=0.0, le=1.0) text: str font_size: int = Field(default=24, ge=8, le=256) color: str = "#FFFFFF" bg_color: str | None = Field( default="#000000", description="Background — None = transparent" ) bg_opacity: float = Field(default=0.5, ge=0.0, le=1.0) # ─── Icon ──────────────────────────────────────────────────────────────── class IconOverlay(OverlayBase): type: Literal["icon"] = "icon" name: str = Field(description="Имя из preloaded sprite sheet, e.g. 'warning', 'person'") x: float = Field(ge=0.0, le=1.0) y: float = Field(ge=0.0, le=1.0) size: float = Field(default=0.05, gt=0.0, le=1.0, description="Размер относительно frame") tint: str | None = Field(default=None, description="HEX RGB — None = original color") # ─── Image (любой PNG/JPG как texture) ────────────────────────────────── class ImageOverlay(OverlayBase): type: Literal["image"] = "image" url: str = Field(description="file://path или http://... PNG/JPG") x: float = Field(ge=0.0, le=1.0) y: float = Field(ge=0.0, le=1.0) w: float = Field(gt=0.0, le=1.0) h: float = Field(gt=0.0, le=1.0) # ─── Dim / privacy mask ────────────────────────────────────────────────── class DimOverlay(OverlayBase): """Затемнение области — используется как privacy mask либо out-of-zone dim.""" type: Literal["dim"] = "dim" x: float = Field(ge=0.0, le=1.0) y: float = Field(ge=0.0, le=1.0) w: float = Field(gt=0.0, le=1.0) h: float = Field(gt=0.0, le=1.0) color: str = "#000000" dim_factor: float = Field(default=0.8, ge=0.0, le=1.0, description="0=без затемнения, 1=полное") # ─── Graph / chart ─────────────────────────────────────────────────────── class GraphOverlay(OverlayBase): """Live chart — controller рендерит CPU-side (Cairo) и uploads texture.""" type: Literal["graph"] = "graph" x: float = Field(ge=0.0, le=1.0) y: float = Field(ge=0.0, le=1.0) w: float = Field(gt=0.0, le=1.0) h: float = Field(gt=0.0, le=1.0) data_topic: str = Field(description="MQTT topic с time-series данными") chart_type: Literal["line", "bar", "histogram"] = "line" style: dict = Field(default_factory=dict, description="Цвета, axis, тp.") refresh_hz: float = Field(default=1.0, gt=0.0, le=10.0) # ─── Chat / scrolling text ─────────────────────────────────────────────── class ChatOverlay(OverlayBase): """Scrolling text — notifications, alerts, etc.""" type: Literal["chat"] = "chat" x: float = Field(ge=0.0, le=1.0) y: float = Field(ge=0.0, le=1.0) w: float = Field(gt=0.0, le=1.0) h: float = Field(gt=0.0, le=1.0) source_topic: str = Field(description="MQTT topic для новых сообщений (newline-separated)") font_size: int = Field(default=20, ge=8, le=128) color: str = "#FFFFFF" bg_opacity: float = Field(default=0.6, ge=0.0, le=1.0) max_messages: int = Field(default=10, ge=1, le=100) scroll_speed_px_s: int = Field(default=30, ge=0, le=1000) # ─── Discriminated union ───────────────────────────────────────────────── Overlay = Annotated[ Union[ RectOverlay, TextOverlay, IconOverlay, ImageOverlay, DimOverlay, GraphOverlay, ChatOverlay, ], Field(discriminator="type"), ]