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

194 lines
7.1 KiB
Python

from __future__ import annotations
from app.qt_compat import (
QColor,
QFont,
QLinearGradient,
QPainter,
QPainterPath,
QPen,
QRectF,
Qt,
Signal,
QWidget,
event_posf,
)
from app.config.device_assignment import tile_matches_device
from app.config.models import InfinityMirrorConfig
from app.network.wled import DiscoveredWledDevice, normalize_mac_address
from app.ui.preview_layout import compute_preview_layout
class MappingAssignmentPreview(QWidget):
tileClicked = Signal(str)
def __init__(self, parent: QWidget | None = None) -> None:
super().__init__(parent)
self._config = InfinityMirrorConfig()
self._discovered_devices: list[DiscoveredWledDevice] = []
self._active_device: DiscoveredWledDevice | None = None
self._selected_tile_id: str | None = None
self._tile_rects: dict[str, QRectF] = {}
self.setMinimumSize(520, 360)
self.setMouseTracking(True)
def set_assignment_state(
self,
config: InfinityMirrorConfig,
discovered_devices: list[DiscoveredWledDevice],
*,
active_device: DiscoveredWledDevice | None = None,
selected_tile_id: str | None = None,
) -> None:
self._config = config
self._discovered_devices = list(discovered_devices)
self._active_device = active_device
self._selected_tile_id = selected_tile_id
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: # type: ignore[override]
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing, True)
painter.setRenderHint(QPainter.TextAntialiasing, True)
rect = QRectF(self.rect())
background = QLinearGradient(rect.topLeft(), rect.bottomRight())
background.setColorAt(0.0, QColor("#12161D"))
background.setColorAt(1.0, QColor("#1B2230"))
painter.fillRect(rect, background)
if not self._config.tiles:
painter.setPen(QColor("#A8B3C7"))
painter.drawText(rect, Qt.AlignCenter, "Open a mapping to assign WLED devices.")
return
layout = compute_preview_layout(rect, self._config)
self._tile_rects = layout.tile_rects
self._draw_canvas_shell(painter, layout.canvas_rect)
discovered_ips = {device.ip_address for device in self._discovered_devices}
discovered_macs = {normalize_mac_address(device.mac_address) for device in self._discovered_devices if device.mac_address}
active_tile_id = None
if self._active_device is not None:
for tile in self._config.sorted_tiles():
if tile_matches_device(tile, self._active_device):
active_tile_id = tile.tile_id
break
for tile in self._config.sorted_tiles():
tile_rect = layout.tile_rects[tile.tile_id]
tile_assigned = bool(tile.controller_ip.strip() or tile.controller_mac.strip())
tile_is_active = active_tile_id == tile.tile_id
tile_is_selected = self._selected_tile_id == tile.tile_id
if tile_is_active:
fill_color = QColor("#1D4E89")
outline_color = QColor("#90CAF9")
subtitle = self._active_device.instance_name or self._active_device.ip_address
status = "Active Device"
elif not tile_assigned:
fill_color = QColor("#2A2F3A")
outline_color = QColor("#4A5568")
subtitle = "Unmapped"
status = "Click to assign"
elif (tile.controller_mac and normalize_mac_address(tile.controller_mac) in discovered_macs) or tile.controller_ip.strip() in discovered_ips:
fill_color = QColor("#1D5A45")
outline_color = QColor("#81E6D9")
subtitle = tile.controller_name or tile.controller_host or tile.controller_ip
status = "Mapped"
else:
fill_color = QColor("#6B4F1D")
outline_color = QColor("#F6AD55")
subtitle = tile.controller_name or tile.controller_host or tile.controller_ip
status = "Assigned, not seen"
self._draw_tile(
painter,
rect=tile_rect,
tile_id=tile.tile_id,
row=tile.row,
col=tile.col,
subtitle=subtitle,
status=status,
fill_color=fill_color,
outline_color=outline_color,
selected=tile_is_selected,
)
def _draw_canvas_shell(self, painter: QPainter, rect: QRectF) -> None:
path = QPainterPath()
path.addRoundedRect(rect, 10.0, 10.0)
painter.fillPath(path, QColor("#202632"))
painter.setPen(QPen(QColor("#334155"), 1.0))
painter.drawPath(path)
def _draw_tile(
self,
painter: QPainter,
*,
rect: QRectF,
tile_id: str,
row: int,
col: int,
subtitle: str,
status: str,
fill_color: QColor,
outline_color: QColor,
selected: bool,
) -> None:
base = min(rect.width(), rect.height())
rounding = max(4.0, base * 0.045)
tile_path = QPainterPath()
tile_path.addRoundedRect(rect, rounding, rounding)
painter.fillPath(tile_path, fill_color)
highlight = QLinearGradient(rect.topLeft(), rect.bottomLeft())
highlight.setColorAt(0.0, QColor(255, 255, 255, 24))
highlight.setColorAt(0.18, QColor(255, 255, 255, 8))
highlight.setColorAt(1.0, QColor(0, 0, 0, 0))
painter.fillPath(tile_path, highlight)
painter.setPen(QPen(outline_color, 1.3))
painter.drawPath(tile_path)
if selected:
painter.setPen(QPen(QColor("#E2E8F0"), 2.1))
painter.drawRoundedRect(rect.adjusted(-3, -3, 3, 3), rounding + 2.0, rounding + 2.0)
padding_x = max(12.0, rect.width() * 0.08)
padding_top = max(10.0, rect.height() * 0.08)
padding_bottom = max(12.0, rect.height() * 0.08)
title_font = QFont()
title_font.setPointSizeF(max(13.0, base * 0.1))
title_font.setWeight(QFont.DemiBold)
painter.setFont(title_font)
painter.setPen(QColor("#F8FAFC"))
painter.drawText(
rect.adjusted(padding_x, padding_top, -padding_x, -rect.height() * 0.56),
Qt.AlignLeft | Qt.AlignTop | Qt.TextWordWrap,
tile_id,
)
meta_font = QFont(title_font)
meta_font.setPointSizeF(max(9.2, base * 0.06))
meta_font.setWeight(QFont.Normal)
painter.setFont(meta_font)
painter.setPen(QColor(235, 244, 249, 175))
painter.drawText(
rect.adjusted(padding_x, rect.height() * 0.48, -padding_x, -padding_bottom),
Qt.AlignLeft | Qt.AlignBottom | Qt.TextWordWrap,
f"{subtitle}\n{status} | R{row} C{col}",
)