feat: implement overlay architecture with IOverlayLayer interface and telemetry overlay
This commit is contained in:
@@ -10,10 +10,11 @@ from PySide6.QtWidgets import QLabel, QMainWindow, QSizePolicy, QStatusBar
|
||||
from app.camera.camera_enumerator import CameraEnumerator, CameraInfo
|
||||
from app.camera.camera_service import CameraService
|
||||
from app.config import APP_NAME, APP_VERSION
|
||||
from app.overlay.telemetry_overlay import TelemetryOverlay
|
||||
from app.pipeline.frame_dispatcher import FrameDispatcher
|
||||
from app.telemetry.telemetry_collector import TelemetryCollector
|
||||
from app.ui.camera_view import CameraView
|
||||
from app.ui.menu_bar import AppMenuBar
|
||||
from app.ui.video_widget import VideoWidget
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -23,20 +24,18 @@ class MainWindow(QMainWindow):
|
||||
Top-level application window.
|
||||
|
||||
Rendering architecture:
|
||||
QVideoWidget is intentionally NOT used. On Windows its native HWND
|
||||
surface occludes all sibling/child QWidgets regardless of z-order,
|
||||
making overlay rendering impossible.
|
||||
|
||||
Instead, frames are received as QVideoFrame via CameraService.frame_ready,
|
||||
converted to QImage inside VideoWidget.on_frame(), and drawn together with
|
||||
the metrics overlay in a single paintEvent pass.
|
||||
QVideoWidget is intentionally NOT used — on Windows its native HWND
|
||||
surface occludes all sibling/child QWidgets regardless of z-order.
|
||||
CameraView is a plain QWidget that renders frames and overlay layers
|
||||
in a single paintEvent pass.
|
||||
|
||||
Signal flow:
|
||||
CameraService.frame_ready
|
||||
→ FrameDispatcher.dispatch
|
||||
→ VideoWidget.on_frame (render)
|
||||
→ TelemetryCollector.on_frame (metrics)
|
||||
→ VideoWidget.update_metrics (overlay data)
|
||||
→ CameraView.on_frame (render frame)
|
||||
→ TelemetryCollector.on_frame (measure metrics)
|
||||
→ TelemetryOverlay.on_metrics_updated (feed overlay data)
|
||||
(CameraView repaints and calls TelemetryOverlay.paint())
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
@@ -46,18 +45,21 @@ class MainWindow(QMainWindow):
|
||||
self.setMinimumSize(640, 480)
|
||||
self.resize(1280, 720)
|
||||
|
||||
# --- Core components ---
|
||||
# --- Core pipeline components ---
|
||||
self._camera_service = CameraService(self)
|
||||
self._dispatcher = FrameDispatcher(self)
|
||||
self._telemetry = TelemetryCollector(parent=self)
|
||||
|
||||
# --- Video widget (central widget) ---
|
||||
# Plain QWidget — no native surface, overlay rendered in same paintEvent.
|
||||
self._video_widget = VideoWidget(self)
|
||||
self._video_widget.setSizePolicy(
|
||||
# --- Camera view (central widget) ---
|
||||
self._camera_view = CameraView(self)
|
||||
self._camera_view.setSizePolicy(
|
||||
QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding
|
||||
)
|
||||
self.setCentralWidget(self._video_widget)
|
||||
self.setCentralWidget(self._camera_view)
|
||||
|
||||
# --- Overlay layers ---
|
||||
self._telemetry_overlay = TelemetryOverlay()
|
||||
self._camera_view.add_overlay_layer(self._telemetry_overlay)
|
||||
|
||||
# --- Menu bar ---
|
||||
self._menu = AppMenuBar(self)
|
||||
@@ -109,14 +111,14 @@ class MainWindow(QMainWindow):
|
||||
# CameraService → FrameDispatcher
|
||||
self._camera_service.frame_ready.connect(self._dispatcher.dispatch)
|
||||
|
||||
# FrameDispatcher → VideoWidget (render) — drop if busy: skip frame, keep UI fluid
|
||||
self._dispatcher.subscribe(self._video_widget.on_frame, drop_if_busy=True)
|
||||
# FrameDispatcher → CameraView (render) — drop if busy: stay fluid
|
||||
self._dispatcher.subscribe(self._camera_view.on_frame, drop_if_busy=True)
|
||||
|
||||
# FrameDispatcher → TelemetryCollector — never drop, count every frame
|
||||
self._dispatcher.subscribe(self._telemetry.on_frame, drop_if_busy=False)
|
||||
|
||||
# TelemetryCollector → VideoWidget overlay
|
||||
self._telemetry.metrics_updated.connect(self._video_widget.update_metrics)
|
||||
# TelemetryCollector → TelemetryOverlay (data only, no repaint trigger here)
|
||||
self._telemetry.metrics_updated.connect(self._telemetry_overlay.on_metrics_updated)
|
||||
|
||||
# CameraService status
|
||||
self._camera_service.camera_started.connect(self._on_camera_started)
|
||||
@@ -128,7 +130,7 @@ class MainWindow(QMainWindow):
|
||||
self._menu.resolution_selected.connect(self._on_resolution_selected)
|
||||
self._menu.fps_selected.connect(self._on_fps_selected)
|
||||
self._menu.reconnect_requested.connect(self._camera_service.reconnect)
|
||||
self._menu.overlay_toggled.connect(self._video_widget.set_overlay_visible)
|
||||
self._menu.overlay_toggled.connect(self._camera_view.set_all_overlays_visible)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Camera status slots
|
||||
|
||||
Reference in New Issue
Block a user