Files
RFP_Infinity-Vis/app/ui/preview_widget.py

119 lines
4.5 KiB
Python

from __future__ import annotations
from app.qt_compat import QPainter, QPointF, QRectF, Qt, Signal, QWidget, event_posf
from app.config.models import SegmentConfig, TileConfig
from app.core.geometry import segment_led_positions, segment_side
from app.core.types import PreviewFrame
from .preview_layout import compute_preview_layout, segment_display_rect, tile_aspect_ratio
from .preview_modes import (
PREVIEW_MODE_LEDS,
PREVIEW_MODE_TECHNICAL,
PREVIEW_MODE_TILE,
normalize_preview_mode,
preview_mode_flags,
)
from .preview_painter import paint_empty_preview, paint_preview_scene
class PreviewWidget(QWidget):
tileClicked = Signal(str)
def __init__(
self,
controller,
preview_mode: str = PREVIEW_MODE_TILE,
scene_role: str = "live",
technical_preview: bool | None = None,
parent: QWidget | None = None,
) -> None:
super().__init__(parent)
self.controller = controller
self.scene_role = "next" if str(scene_role).strip().lower() == "next" else "live"
if technical_preview is not None:
preview_mode = PREVIEW_MODE_TECHNICAL if technical_preview else PREVIEW_MODE_TILE
self.preview_mode = normalize_preview_mode(preview_mode)
self.technical_preview = self.preview_mode == PREVIEW_MODE_TECHNICAL
self.current_frame: PreviewFrame | None = self.controller.preview_frame_for(self.scene_role)
self._tile_rects: dict[str, QRectF] = {}
self.setMinimumSize(640, 360)
self.setMouseTracking(True)
if self.scene_role == "next":
self.controller.next_frame_ready.connect(self._on_frame_ready)
else:
self.controller.frame_ready.connect(self._on_frame_ready)
self.controller.config_changed.connect(self.update)
self.controller.state_changed.connect(self.update)
def set_preview_mode(self, mode: str) -> None:
self.preview_mode = normalize_preview_mode(mode)
self.technical_preview = self.preview_mode == PREVIEW_MODE_TECHNICAL
self.update()
def set_technical_preview(self, enabled: bool) -> None:
self.set_preview_mode(PREVIEW_MODE_TECHNICAL if enabled else PREVIEW_MODE_TILE)
def _mode_flags(self) -> dict[str, bool]:
return preview_mode_flags(self.preview_mode)
def _on_frame_ready(self, frame: PreviewFrame) -> None:
self.current_frame = frame
self.update()
def mousePressEvent(self, event) -> None:
point = event_posf(event)
for tile_id, rect in self._tile_rects.items():
if rect.contains(point):
self.tileClicked.emit(tile_id)
break
super().mousePressEvent(event)
def paintEvent(self, event) -> None:
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing, True)
painter.setRenderHint(QPainter.TextAntialiasing, True)
frame = self.current_frame
if frame is None or not self.controller.config.tiles:
paint_empty_preview(painter, QRectF(self.rect()))
return
layout = compute_preview_layout(QRectF(self.rect()), self.controller.config)
self._tile_rects = layout.tile_rects
paint_preview_scene(
painter,
config=self.controller.config,
frame=frame,
preview_mode=self.preview_mode,
selected_tile_id=self.controller.selected_tile_id,
target_rect=QRectF(self.rect()),
layout=layout,
)
def _compute_layout(self) -> tuple[QRectF, dict[str, QRectF]]:
layout = compute_preview_layout(QRectF(self.rect()), self.controller.config)
return layout.canvas_rect, layout.tile_rects
def _tile_aspect_ratio(self) -> float:
return tile_aspect_ratio(self.controller.config)
def _segment_display_rect(self, tile: TileConfig, rect: QRectF) -> QRectF:
return segment_display_rect(tile, rect)
def _segment_side(self, tile: TileConfig, segment: SegmentConfig) -> str | None:
return segment_side(tile, segment)
def _segment_points_for_side(self, tile: TileConfig, segment: SegmentConfig, rect: QRectF) -> list[QPointF]:
inset = max(2.0, min(rect.width(), rect.height()) * 0.02)
insets = (
inset / max(1.0, rect.width()),
inset / max(1.0, rect.height()),
)
return [
QPointF(rect.left() + x_pos * rect.width(), rect.top() + y_pos * rect.height())
for x_pos, y_pos in segment_led_positions(tile, segment, insets=insets)
]