feat: restructure overlay and video widget integration for improved rendering

This commit is contained in:
2026-05-12 20:06:37 +02:00
parent 03d3332b35
commit ece4e1cd6e
2 changed files with 35 additions and 12 deletions

View File

@@ -6,7 +6,7 @@ import logging
from PySide6.QtCore import Qt, QTimer
from PySide6.QtMultimediaWidgets import QVideoWidget
from PySide6.QtWidgets import QLabel, QMainWindow, QSizePolicy, QStatusBar
from PySide6.QtWidgets import QLabel, QMainWindow, QSizePolicy, QStatusBar, QVBoxLayout, QWidget
from app.camera.camera_enumerator import CameraEnumerator, CameraInfo
from app.camera.camera_service import CameraService
@@ -41,21 +41,34 @@ class MainWindow(QMainWindow):
self._dispatcher = FrameDispatcher(self)
self._telemetry = TelemetryCollector(parent=self)
# --- Video widget (central widget) ---
self._video_widget = QVideoWidget(self)
# --- Central container ---
# We need an extra QWidget layer between QMainWindow and QVideoWidget so
# that OverlayWidget can be a sibling of QVideoWidget (not its child).
# QVideoWidget renders via a native D3D/GL surface that occludes any
# QWidget children painted on top of it.
self._container = QWidget(self)
self._container.setStyleSheet("background: black;")
container_layout = QVBoxLayout(self._container)
container_layout.setContentsMargins(0, 0, 0, 0)
container_layout.setSpacing(0)
# --- Video widget ---
self._video_widget = QVideoWidget(self._container)
self._video_widget.setSizePolicy(
QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding
)
self._video_widget.setAspectRatioMode(Qt.AspectRatioMode.KeepAspectRatio)
self.setCentralWidget(self._video_widget)
container_layout.addWidget(self._video_widget)
self.setCentralWidget(self._container)
# Connect camera session to video widget — this is the zero-copy render path
self._camera_service.capture_session().setVideoOutput(self._video_widget)
# --- Overlay ---
self._overlay = OverlayWidget(parent=self._video_widget)
# Sibling of QVideoWidget inside _container; positioned manually so it
# floats above the video without being occluded by the native GL surface.
self._overlay = OverlayWidget(parent=self._container)
self._overlay.raise_()
self._overlay.resize(self._video_widget.size())
# --- Menu bar ---
self._menu = AppMenuBar(self)
@@ -72,6 +85,8 @@ class MainWindow(QMainWindow):
# --- Enumerate cameras and start ---
QTimer.singleShot(0, self._initialise_cameras)
# Reposition overlay after the event loop starts (layout is finalised)
QTimer.singleShot(0, self._reposition_overlay)
# ------------------------------------------------------------------
# Initialisation
@@ -161,9 +176,16 @@ class MainWindow(QMainWindow):
def resizeEvent(self, event) -> None: # noqa: N802
super().resizeEvent(event)
# Keep overlay covering the video widget
if hasattr(self, "_overlay") and hasattr(self, "_video_widget"):
self._overlay.resize(self._video_widget.size())
self._reposition_overlay()
def _reposition_overlay(self) -> None:
"""Keep the overlay covering the video widget exactly."""
if not (hasattr(self, "_overlay") and hasattr(self, "_video_widget")):
return
# _overlay and _video_widget share the same parent (_container),
# so video_widget.geometry() is already in the right coordinate space.
self._overlay.setGeometry(self._video_widget.geometry())
self._overlay.raise_()
def closeEvent(self, event) -> None: # noqa: N802
self._camera_service.stop()