0

I'm creating a video player, below is an MRE implementation of the VideoWidget and a widget to display the video controls, called ControlBar:

import os
from PySide6 import QtMultimedia as qtm
from PySide6 import QtMultimediaWidgets as qtmw
from PySide6 import QtWidgets as qtw
from PySide6 import QtCore as qtc
from PySide6 import QtGui as qtg

class VideoWidget(qtmw.QVideoWidget):
    load_finished_signal = qtc.Signal()

    _playback_state_changed_signal = qtc.Signal()

    def __init__(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        self.setFocusPolicy(qtc.Qt.FocusPolicy.NoFocus)
        self.setMouseTracking(True)
        # In Qt6, a private internal class is used for rendering the video, so
        # add `vw.children()[0].setMouseTracking(True)`
        self.children()[0].setMouseTracking(True)

        self._seeking = False

        self._audio_output = qtm.QAudioOutput(qtm.QMediaDevices.defaultAudioOutput())
        self._media_player = qtm.QMediaPlayer(self)
        self._control_bar = ControlBar(self)
        self._hide_control_bar_timer = qtc.QTimer()

        self._media_player.setVideoOutput(self)
        self._media_player.setAudioOutput(self._audio_output)
        self._media_player.mediaStatusChanged.connect(self._video_loaded)
        self._media_player.durationChanged.connect(
            self._control_bar.update_total_duration
        )
        self._media_player.positionChanged.connect(self._control_bar.update_progress)
        self.videoSink().videoFrameChanged.connect(self._seeked)

        self._control_bar.seek_requested_signal.connect(self._seek)
        self._control_bar.pause_play_requested_signal.connect(self._change_playback_state)
        self._playback_state_changed_signal.connect(self._control_bar.playback_state_changed)
        self._hide_control_bar_timer.setSingleShot(True)
        self._hide_control_bar_timer.setInterval(1000)
        self._hide_control_bar_timer.timeout.connect(self._hide_control_bar)

    def _hide_control_bar(self) -> None:
        if not self._control_bar.underMouse():
            self._control_bar.setVisible(False)
            cursor = self.cursor()
            cursor.setShape(qtc.Qt.CursorShape.BlankCursor)
            self.setCursor(cursor)

    def load(self, location: str) -> None:
        self._media_player.setSource(qtc.QUrl.fromLocalFile(location))

    def _video_loaded(self, location: str) -> None:
        self._control_bar.set_filename(
            os.path.basename(self._media_player.source().toString())
        )
        if self._media_player.source():
            self._media_player.play()
            self.load_finished_signal.emit()

    def close_item(self) -> None:
        self._media_player.stop()
        self._media_player.setSource(qtc.QUrl())

    def _screenshot(self, video_frame: qtm.QVideoFrame):
        image = video_frame.toImage()
        image.save(filename)

    def _update_conrol_bar_position(self) -> None:
        geometry = self.geometry()
        top_left_global = geometry.topLeft()
        self._control_bar.update_geometry_to_width(self.width())
        self._control_bar.move(
            top_left_global.x(),
            top_left_global.y() + geometry.height() - self._control_bar.height(),
        )

    def _change_playback_state(self) -> None:
        if (
            self._media_player.playbackState()
            == qtm.QMediaPlayer.PlaybackState.PlayingState
        ):
            self._media_player.pause()
        elif (
            self._media_player.playbackState()
            == qtm.QMediaPlayer.PlaybackState.PausedState
            or self._media_player.playbackState()
            == qtm.QMediaPlayer.PlaybackState.StoppedState
        ):
            self._media_player.play()

        self._playback_state_changed_signal.emit()

    def mouseMoveEvent(self, event: qtg.QMouseEvent) -> None:
        if not self.cursor().shape() & qtc.Qt.CursorShape.ArrowCursor:
            cursor = self.cursor()
            cursor.setShape(qtc.Qt.CursorShape.ArrowCursor)
            self.setCursor(cursor)
        if (
            not self.width() == self._control_bar.width()
            or not self.geometry().contains(self._control_bar.geometry())
        ):
            self._update_conrol_bar_position()
        if not self._control_bar.isVisible():
            self._control_bar.setVisible(True)
        self._hide_control_bar_timer.start()

    def mousePressEvent(self, event: qtg.QMouseEvent) -> None:
        self._change_playback_state()
        event.accept()
        return

    def mouseDoubleClickEvent(self, event: qtg.QMouseEvent) -> None:
        F_event = qtg.QKeyEvent(
            qtc.QEvent.KeyPress,
            qtc.Qt.Key.Key_F,
            qtc.Qt.KeyboardModifier.NoModifier,
            "F",
        )
        qtc.QCoreApplication.sendEvent(self, F_event)
        super().mouseDoubleClickEvent(event)

    def resizeEvent(self, event: qtg.QResizeEvent) -> None:
        super().resizeEvent(event)
        self._update_conrol_bar_position()

    def contextMenuEvent(self, event: qtg.QContextMenuEvent):
        """"""

    def keyPressEvent(self, event: qtg.QKeyEvent) -> None:
        key = event.key()

        if (
            key == qtc.Qt.Key.Key_Left
            or key == qtc.Qt.Key.Key_Right
            or key == qtc.Qt.Key.Key_Less
            or key == qtc.Qt.Key.Key_Greater
        ):
            factor = (
                1
                if event.modifiers() == qtc.Qt.KeyboardModifier.ShiftModifier
                else 60
                if event.modifiers() == qtc.Qt.KeyboardModifier.ControlModifier
                else 5
            )
            if key == qtc.Qt.Key.Key_Left:
                self._seek(max(self._media_player.position() - (1000 * factor), 0))
            elif key == qtc.Qt.Key.Key_Right:
                self._seek(
                    min(
                        self._media_player.position() + (1000 * factor),
                        self._media_player.duration(),
                    )
                )

        elif key == qtc.Qt.Key.Key_F5:
            self._screenshot(self.videoSink().videoFrame())

        elif key == qtc.Qt.Key.Key_Space:
            self._change_playback_state()

        elif key == qtc.Qt.Key.Key_Down:
            self._media_player.audioOutput().setVolume(
                max(self._media_player.audioOutput().volume() - 0.1, 0)
            )
        elif key == qtc.Qt.Key.Key_Up:
            self._media_player.audioOutput().setVolume(
                min(self._media_player.audioOutput().volume() + 0.1, 1)
            )
        elif key == qtc.Qt.Key.Key_M:
            self._media_player.audioOutput().setMuted(
                not self._media_player.audioOutput().isMuted()
            )

        else:
            super().keyPressEvent(event)
            return None
        event.accept()
        return None
        """
        - next in directory
        - previous in directory
        """

    def _seek(self, position: int) -> None:
        if self._seeking:
            return
        self._seeking = True
        if self._media_player.isSeekable():
            self._media_player.setPosition(position)
        else:
            self._seeked()

    def _seeked(self):
        self._seeking = False

    def wheelEvent(self, event: qtg.QWheelEvent) -> None:
        "volume"

class ControlBar(qtw.QWidget):
    seek_requested_signal = qtc.Signal(int)
    pause_play_requested_signal = qtc.Signal()

    _WRT_HEIGHT = 100
    _MAX_FONT_POINT_F = 20
    _WRT_WIDTH = 1920 / 2
    # Height should be _WRT_HEIGHT if width is _WRT_WIDTH.
    _CONTROL_BAR_WIDTH_TO_HEIGHT_RATIO = _WRT_HEIGHT / _WRT_WIDTH
    _FONT_POINT_F_RATIO = _MAX_FONT_POINT_F / _WRT_HEIGHT

    _WIDGETS_SIZE_RATIOS = {
        "filename": (_WRT_WIDTH / _WRT_WIDTH, 40 / _WRT_HEIGHT),
        "pause_play": (30 / _WRT_WIDTH, 35 / _WRT_HEIGHT),
        "pause": (10 / _WRT_WIDTH, 35 / _WRT_HEIGHT),
        "play": (25 / _WRT_WIDTH, 35 / _WRT_HEIGHT),
        "left_duration": (0 / _WRT_WIDTH, 40 / _WRT_HEIGHT),
        "right_duration": (0 / _WRT_WIDTH, 40 / _WRT_HEIGHT),
        "total_progress_bar": (578 / _WRT_WIDTH, 40 / _WRT_HEIGHT),
        "completed_progress_bar": (0 / _WRT_WIDTH, 40 / _WRT_HEIGHT),
    }
    _PADDINGS_RATIOS = {"v_pad": 5 / _WRT_HEIGHT, "h_pad": 10 / _WRT_WIDTH}

    def __init__(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        self.setWindowFlag(qtc.Qt.WindowType.ToolTip)

        self._total_duration_ms = 0
        self._completed_duration_ms = 0
        self._widget_shapes = {
            "filename_rect": qtc.QRect(),
            "pause_play_button_rect": qtc.QRect(),
            "pause_path": qtg.QPainterPath(),
            "play_triangle": qtg.QPolygon(),
            "left_duration_rect": qtc.QRect(),
            "progress_bar_rect": qtc.QRect(),
            "right_duration_rect": qtc.QRect(),
        }
        self._show_milliseconds = False
        self._filename = ""
        self._paused = False

    def playback_state_changed(self) -> None:
        self._paused = not self._paused

    def paintEvent(self, event: qtg.QPaintEvent) -> None:
        painter = qtg.QPainter(self)
        painter.setRenderHint(qtg.QPainter.RenderHint.Antialiasing)

        painter.fillRect(0, 0, self.width(), self.height(), qtc.Qt.GlobalColor.black)

        pen = qtg.QPen()
        pen.setColor(qtc.Qt.GlobalColor.white)
        painter.setPen(pen)
        painter.setFont(self._get_font())

        painter.drawText(
            self._widget_shapes["filename_rect"],
            qtc.Qt.AlignmentFlag.AlignLeft,
            self._filename,
        )

        if self._paused:
            path = qtg.QPainterPath()
            path.addPolygon(self._widget_shapes["play_triangle"])
            painter.fillPath(path, qtc.Qt.GlobalColor.white)
        else:
            painter.fillPath(self._widget_shapes["pause_path"], qtc.Qt.GlobalColor.white)

        painter.drawText(
            self._widget_shapes["left_duration_rect"],
            qtc.Qt.AlignmentFlag.AlignCenter,
            _format_milliseconds(self._completed_duration_ms, self._show_milliseconds),
        )

        painter.fillRect(
            self._widget_shapes["progress_bar_rect"], qtc.Qt.GlobalColor.gray
        )
        painter.fillRect(
            self._get_completed_progress_bar_rect(), qtc.Qt.GlobalColor.white
        )
        painter.drawText(
            self._widget_shapes["right_duration_rect"],
            qtc.Qt.AlignmentFlag.AlignCenter,
            _format_milliseconds(self._total_duration_ms, self._show_milliseconds),
        )

    def set_filename(self, filename: str) -> None:
        self._filename = filename

    def update_total_duration(self, duration: int) -> None:
        self._total_duration_ms = duration
        self.update()

    def update_progress(self, progress: int) -> None:
        self._completed_duration_ms = progress
        self.update()

    def update_geometry_to_width(self, width: int) -> None:
        new_control_bar_height = int(self._CONTROL_BAR_WIDTH_TO_HEIGHT_RATIO * width)
        self.setFixedSize(width, min(new_control_bar_height, self._WRT_HEIGHT))
        self._update_widgets_geometry()

    def _update_widgets_geometry(self) -> None:
        total_height = 0
        row_1_total_width = 0
        row_2_total_width = 0

        total_height += self.height() * self._PADDINGS_RATIOS["v_pad"]
        row_1_total_width += self.width() * self._PADDINGS_RATIOS["h_pad"]
        row_2_total_width += self.width() * self._PADDINGS_RATIOS["h_pad"]

        font_metric = qtg.QFontMetrics(self._get_font())
        horizontal_advance = font_metric.horizontalAdvance("A")

        self._widget_shapes["filename_rect"].setX(row_1_total_width)
        self._widget_shapes["filename_rect"].setY(total_height)
        self._widget_shapes["filename_rect"].setWidth(
            horizontal_advance * len(self._filename)
        )
        self._widget_shapes["filename_rect"].setHeight(
            self.height() * self._WIDGETS_SIZE_RATIOS["filename"][1]
        )
        total_height += self._widget_shapes["filename_rect"].height()
        row_1_total_width += self._widget_shapes["filename_rect"].width()

        total_height += self.height() * self._PADDINGS_RATIOS["v_pad"]
        row_1_total_width += self.width() * self._PADDINGS_RATIOS["h_pad"]

        self._widget_shapes["pause_play_button_rect"].setX(row_2_total_width)
        self._widget_shapes["pause_play_button_rect"].setY(total_height)
        self._widget_shapes["pause_play_button_rect"].setWidth(
            min(30, self.width() * self._WIDGETS_SIZE_RATIOS["pause_play"][0])
        )
        self._widget_shapes["pause_play_button_rect"].setHeight(
            self.height() * self._WIDGETS_SIZE_RATIOS["pause_play"][1]
        )

        self._widget_shapes["pause_path"] = qtg.QPainterPath()
        pause_1 = qtc.QRect(
            self._widget_shapes["pause_play_button_rect"].x(),
            self._widget_shapes["pause_play_button_rect"].y(),
            min(10, self.width() * self._WIDGETS_SIZE_RATIOS["pause"][0]),
            self.height() * self._WIDGETS_SIZE_RATIOS["pause"][1],
        )
        self._widget_shapes["pause_path"].addRect(pause_1)

        pause_2 = qtc.QRect(
            self._widget_shapes["pause_play_button_rect"].topRight().x(),
            self._widget_shapes["pause_play_button_rect"].y(),
            -min(10, self.width() * self._WIDGETS_SIZE_RATIOS["pause"][0]),
            self.height() * self._WIDGETS_SIZE_RATIOS["pause"][1],
        )
        self._widget_shapes["pause_path"].addRect(pause_2)

        self._widget_shapes["play_triangle"] = qtg.QPolygon()
        right = self._widget_shapes["pause_play_button_rect"].center()
        right.setX(self._widget_shapes["pause_play_button_rect"].right())
        self._widget_shapes["play_triangle"].append(
            [
                self._widget_shapes["pause_play_button_rect"].topLeft(),
                self._widget_shapes["pause_play_button_rect"].bottomLeft(),
                right,
            ]
        )
        row_2_total_width += self._widget_shapes["pause_play_button_rect"].width()

        row_2_total_width += self.width() * self._PADDINGS_RATIOS["h_pad"]

        self._widget_shapes["left_duration_rect"].setX(row_2_total_width)
        self._widget_shapes["left_duration_rect"].setY(total_height)
        self._widget_shapes["left_duration_rect"].setWidth(
            horizontal_advance
            * len(
                _format_milliseconds(
                    self._completed_duration_ms, self._show_milliseconds
                )
            )
        )
        self._widget_shapes["left_duration_rect"].setHeight(
            self.height() * self._WIDGETS_SIZE_RATIOS["left_duration"][1]
        )
        row_2_total_width += self._widget_shapes["left_duration_rect"].width()

        row_2_total_width += self.width() * self._PADDINGS_RATIOS["h_pad"]

        self._widget_shapes["progress_bar_rect"].setX(row_2_total_width)
        self._widget_shapes["progress_bar_rect"].setY(total_height)
        self._widget_shapes["progress_bar_rect"].setWidth(
            self.width() * self._WIDGETS_SIZE_RATIOS["total_progress_bar"][0]
        )
        self._widget_shapes["progress_bar_rect"].setHeight(
            self.height() * self._WIDGETS_SIZE_RATIOS["total_progress_bar"][1]
        )
        row_2_total_width += self._widget_shapes["progress_bar_rect"].width()
        row_2_total_width += self.width() * self._PADDINGS_RATIOS["h_pad"]

        # To adjust the size of the progress bar according to the remaining width
        # left after adding all remaining widget's widths.
        theoritical_width = row_2_total_width
        theoritical_width += horizontal_advance * len(
            _format_milliseconds(self._total_duration_ms, self._show_milliseconds)
        )
        theoritical_width += self.width() * self._PADDINGS_RATIOS["h_pad"]

        remaining_width = self.width() - theoritical_width

        self._widget_shapes["progress_bar_rect"].setWidth(
            self._widget_shapes["progress_bar_rect"].width() + remaining_width
        )
        row_2_total_width += remaining_width

        self._widget_shapes["right_duration_rect"].setX(row_2_total_width)
        self._widget_shapes["right_duration_rect"].setY(total_height)
        self._widget_shapes["right_duration_rect"].setWidth(
            horizontal_advance
            * len(
                _format_milliseconds(self._total_duration_ms, self._show_milliseconds)
            )
        )
        self._widget_shapes["right_duration_rect"].setHeight(
            self.height() * self._WIDGETS_SIZE_RATIOS["right_duration"][1]
        )
        row_2_total_width += self._widget_shapes["right_duration_rect"].width()
        row_2_total_width += self.width() * self._PADDINGS_RATIOS["h_pad"]

    def _get_font(self) -> qtg.QFont:
        font = qtg.QFont()
        font.setPointSizeF(self.height() * self._FONT_POINT_F_RATIO)
        return font

    def _get_completed_progress_bar_rect(self) -> qtc.QRect:
        completed_width = int(
            (self._completed_duration_ms / (self._total_duration_ms or 1))
            * self._widget_shapes["progress_bar_rect"].width()
        )
        completed_rect = qtc.QRect(self._widget_shapes["progress_bar_rect"])
        completed_rect.setWidth(completed_width)
        return completed_rect

    def _get_time_from_mouse_press(self, point: qtc.QPoint) -> None:
        return int(
            (point.x() / self._widget_shapes["progress_bar_rect"].width())
            * self._total_duration_ms
        )

    def mousePressEvent(self, event: qtg.QMouseEvent) -> None:
        point = event.pos()
        if self._widget_shapes["left_duration_rect"].contains(point):
            self._show_milliseconds = not self._show_milliseconds
            self._update_widgets_geometry()

        elif self._widget_shapes["progress_bar_rect"].contains(point):
            self.seek_requested_signal.emit(
                self._get_time_from_mouse_press(
                    point - self._widget_shapes["progress_bar_rect"].topLeft()
                )
            )

        elif self._widget_shapes["pause_play_button_rect"].contains(point):
            self.pause_play_requested_signal.emit()

        self.update()
        event.accept()
        return

    def mouseMoveEvent(self, event: qtg.QMouseEvent) -> None:
        if self._widget_shapes["progress_bar_rect"].contains(event.pos()):
            self.seek_requested_signal.emit(
                self._get_time_from_mouse_press(
                    event.pos() - self._widget_shapes["progress_bar_rect"].topLeft()
                )
            )
        event.accept()
        return


def _format_milliseconds(milliseconds: int, show_milliseconds: bool = False) -> str:
    seconds, milliseconds = divmod(milliseconds, 1000)
    minutes, seconds = divmod(seconds, 60)
    hours, minutes = divmod(minutes, 60)
    return f"{hours:02}:{minutes:02}:{seconds:02}" + (
        f".{milliseconds:03}" if show_milliseconds else ""
    )


app = qtw.QApplication()
vw = VideoWidget()
vw.show()
vw.load("video_file")
app.exec()

While this works as expected for the most part, if I alt-tab into another window (effectively hiding the program) with the ControlBar displayed, the bar is visible on top of the other window as well. I'm guessing such is the nature of the Qt.ToolTip window flag. How do I have it "show" or "hide" with the main program?
Also, the _update_widgets_geometry() function's implementation is rather tedious, is there a better way to do this? "This" being the way I layout (set the QRects for) the shapes in the ControlBar.

EDIT: The code should work "out of the box", just pass a valid video file path to the vw.load() at the end of the code. A screenshot.

Neat
  • 61
  • 1
  • 5
  • 1
    Why are you using the tooltip flag? – ekhumoro Sep 25 '22 at 21:04
  • And why are you using such a function to "layout" controls, instead of using *actual* widgets and layouts (or even a Graphics View)? – musicamante Sep 25 '22 at 22:09
  • @ekhumoro not sure how else I'd create a floating widget, going through the `Qt.WindowFlag` documentation that was the only thing I could find. – Neat Sep 26 '22 at 01:46
  • @musicamante I can't have it be a floating widget if I were to use actual widgets, can I? Because the last time I tried to paint widgets, it was better to just draw from scratch than to mess with paining widgets. Also, this is less memory heavy compared to having five (or more) widgets I believe. And I like the way it looks (probably do-able with normal widgets and CSS). Is there anything wrong with this method? Apart from it being tedious to calculate the `QRect`s. – Neat Sep 26 '22 at 01:53
  • @Neat: 1. widgets can be put anywhere you want, if you *really* need it; that's actually the default behavior: if you don't want to use layouts, just add widgets as children of their parent (`self.myFloatingButton = QPushButton(self)`) and set their geometry according to your needs; 2. I haven't tried your code since it's **really too extensive**, but, unless you have a very peculiar custom layout (for instance, widgets put around a circle), my experience is that *not* using Qt layout managers due to "custom layouts" is just due to of lack of proper knowledge and experience with those layouts; – musicamante Sep 26 '22 at 02:36
  • @Neat 3. "better to just draw from scratch", but, still, you complain about `_update_widgets_geometry`, so what about that? 4. widgets require a handful of RAM (to my *approximate tests*, complex widgets as QTreeWidget require less than 50KB, simpler ones as QPushButton use less than 1KB); I wrote programs with thousands of widgets performing perfectly fine even with stylesheets, animations and custom painting (and I'm on a 2012 CPU), so, with any decent PC, that's not even to consider, especially if talking about **just FIVE widgets**: you're reading a browser tab that takes more than 10MB. – musicamante Sep 26 '22 at 02:37
  • @musicamante the widget without the `ToolTip` (or `Dialog`) flag was not visible, I guess it's geometry was out of the screen. As for using a layout, there is no overlay layout as far as I know. – Neat Sep 26 '22 at 02:52
  • @Neat 1. if the widget geometry was off, then, using the `ToolTip` is *not* a solution, is a bad workaround. Check the geometry against the [`QApplication.screens()`](https://doc.qt.io/qt-5/qguiapplication.html#screens); 2. please clarify what you mean by "overlay layout": as said, I (and probably, nobody) cannot nor will test or debug such a long code; at least add an image of *that* layout so that we don't have to go through almost 500 lines and fix any potential issue in order to see what you want to do; we're here to help, the least you should do is to make our efforts as easy as possible. – musicamante Sep 26 '22 at 02:58
  • @musicamante the code should work just fine, there is no bug in it. You just need to pass a video file to the `vw.load()` function at the end of the code. Here's a [screenshot](https://up.ovpn.to/file/LKDyfD54yxWCHmVifw62beBTg5bFbVf8yYih7vVX/Screenshot_2022-09-26_08-49-43.png) if you'd rather not run the code, I don't know how informative the screenshot will be though. – Neat Sep 26 '22 at 03:06
  • @Neat 1. It's not about bugs, but possible different configurations (Python/Qt versions, alternate bindings, etc): if the code is short, we may consider fixing a few aspects, but not with that amount of lines; 2. the image is fine, it shows that the layouts is quite elementary: it's basically a grid or a nested vbox/hbox layout, so using custom functions for all that (layout, drawing, mouse detection) is a pointless overhead, especially if using dict keys that make the code uglier and extremely difficult to read (and debug) also in your part. Still, the usage of `ToolTip` remains unexplained. – musicamante Sep 26 '22 at 03:18
  • @musicamante the controls are supposed to "overlay" over the video being played, they're not in a vertical layout but one on top of (a small portion of) the other. Though not clear from the image, the controls widget is hiding x% of the video when displayed. If you've ever used mpv, it's the same design concept I'm trying to replicate. As for the `ToolTip`, it was the only widget flag (apart from `Dialog`) that made the control widget visible over the video widget. I will check a normal widget's geometry against the `QApplication.screens()` to see if its visible. – Neat Sep 26 '22 at 03:26
  • 1
    @Neat You don't need any flags (apart from the default `Widget`). If it's not in a layout, then the only other thing it can do is "float". I suppose you might need to `raise()` it to ensure it's on top. I also don't see the point of all the custom drawing. Just use normal widgets. Worrying about memory overhead seems a bit 1990's. – ekhumoro Sep 26 '22 at 03:27
  • @ekhumoro I was not aware of the `raise()` method, perhaps the control widget (without the flags change) was just below the video widget. I agree, it does sound like a dated concept, but doing it this way is just fun for me. Maybe I'll regret doing it like this in the future but that's a problem for future me. – Neat Sep 26 '22 at 03:47
  • 1
    @Neat I'm very familiar with mpv, that's why I recognized it's OSD layout. And the concept remains: while the `raise()` correctly mentioned above is a valid suggestion, you could just make your `ControlBar` have its own layout and make it a child widget of your top level window, then override the `resizeEvent()` of that window in order to properly call `setGeometry` on the control bar based on the window contents. This may be also very important since on some configurations the video overlay could possibly completely hide the drawing done on the `paintEvent()` if the playback is accelerated. – musicamante Sep 26 '22 at 04:06
  • @Neat I'm under the impression that you're misunderstanding how Qt layout management works. Layout managers don't do any "magic" on the object hierarchy, they just set the geometry (and, in some cases, visibility) of the layout items they are managing, but those items are actually children of the widget that the layout belongs to (`addWidget()` takes care of reparenting). In fact, all Qt layout managers just call `setGeometry()` on those items. In reality, a basic layout system could set the geometry of each **child** widget in a `resizeEvent()`; which is exactly what you should probably do. – musicamante Sep 26 '22 at 04:18
  • That is what my code does, in `resizeEvent` all the "widget"s `QRect` coordinates are changed accordingly. – Neat Sep 26 '22 at 04:29
  • @Neat Not exactly: in your `resizeEvent` you start a convoluted process that then requires: 1. explicit positioning of each element; 2. manual drawing of all of them; 3. iteration through them to get the element under mouse; 4. possible failing of complex conditions. If you used an *actual* layout (even with custom widgets), all you'd need to take care of is the container geometry, Qt would took care of all the rest. Interestingly enough, you were (unnecessarily) worried about RAM, while all you should care in media playback is *performance*, something Python is really not that good as C++ is. – musicamante Sep 26 '22 at 05:21
  • @musicamante After some deliberation, I decided to just let a layout do the layoutting for me. Though, I ran into a problem trying to implement the "floating widget" with a `QVideoWidget`, I created a new [question](https://stackoverflow.com/questions/73853232/qt-cant-create-floating-widget-with-qvideowidget-as-parent) on it. – Neat Sep 26 '22 at 11:05
  • @ekhumoro After some deliberation, I decided to just let a layout do the layoutting for me. Though, I ran into a problem trying to implement the "floating widget" with a `QVideoWidget`, I created a new [question](https://stackoverflow.com/questions/73853232/qt-cant-create-floating-widget-with-qvideowidget-as-parent) on it. – Neat Sep 26 '22 at 11:06

0 Answers0