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

@@ -31,11 +31,12 @@ class OverlayWidget(QWidget):
def __init__(self, parent: QWidget | None = None) -> None: def __init__(self, parent: QWidget | None = None) -> None:
super().__init__(parent) super().__init__(parent)
# Make widget transparent to mouse and visual background # Child widget — NO window flags (FramelessWindowHint would detach it
# from the parent and create an invisible top-level window).
# WA_TranslucentBackground on a child only works when the parent is
# also translucent, so we paint the background ourselves in paintEvent.
self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, True) self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, True)
self.setAttribute(Qt.WidgetAttribute.WA_NoSystemBackground, True) self.setAttribute(Qt.WidgetAttribute.WA_NoSystemBackground, True)
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, True)
self.setWindowFlags(Qt.WindowType.FramelessWindowHint)
self._snapshot: TelemetrySnapshot | None = None self._snapshot: TelemetrySnapshot | None = None
self._visible_overlay: bool = True self._visible_overlay: bool = True

View File

@@ -6,7 +6,7 @@ import logging
from PySide6.QtCore import Qt, QTimer from PySide6.QtCore import Qt, QTimer
from PySide6.QtMultimediaWidgets import QVideoWidget 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_enumerator import CameraEnumerator, CameraInfo
from app.camera.camera_service import CameraService from app.camera.camera_service import CameraService
@@ -41,21 +41,34 @@ class MainWindow(QMainWindow):
self._dispatcher = FrameDispatcher(self) self._dispatcher = FrameDispatcher(self)
self._telemetry = TelemetryCollector(parent=self) self._telemetry = TelemetryCollector(parent=self)
# --- Video widget (central widget) --- # --- Central container ---
self._video_widget = QVideoWidget(self) # 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( self._video_widget.setSizePolicy(
QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding
) )
self._video_widget.setAspectRatioMode(Qt.AspectRatioMode.KeepAspectRatio) 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 # Connect camera session to video widget — this is the zero-copy render path
self._camera_service.capture_session().setVideoOutput(self._video_widget) self._camera_service.capture_session().setVideoOutput(self._video_widget)
# --- Overlay --- # --- 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.raise_()
self._overlay.resize(self._video_widget.size())
# --- Menu bar --- # --- Menu bar ---
self._menu = AppMenuBar(self) self._menu = AppMenuBar(self)
@@ -72,6 +85,8 @@ class MainWindow(QMainWindow):
# --- Enumerate cameras and start --- # --- Enumerate cameras and start ---
QTimer.singleShot(0, self._initialise_cameras) QTimer.singleShot(0, self._initialise_cameras)
# Reposition overlay after the event loop starts (layout is finalised)
QTimer.singleShot(0, self._reposition_overlay)
# ------------------------------------------------------------------ # ------------------------------------------------------------------
# Initialisation # Initialisation
@@ -161,9 +176,16 @@ class MainWindow(QMainWindow):
def resizeEvent(self, event) -> None: # noqa: N802 def resizeEvent(self, event) -> None: # noqa: N802
super().resizeEvent(event) super().resizeEvent(event)
# Keep overlay covering the video widget self._reposition_overlay()
if hasattr(self, "_overlay") and hasattr(self, "_video_widget"):
self._overlay.resize(self._video_widget.size()) 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 def closeEvent(self, event) -> None: # noqa: N802
self._camera_service.stop() self._camera_service.stop()