119 lines
4.5 KiB
Python
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)
|
|
]
|