Search code examples
pythonpyqt5qwidget

Overlay widget partially outside the bounds of it's parent


I'm trying to create a compound widget similiar to the following:

My compound widget

A Rectangle overlayed with a button that is partially outside the rectangle's bounds.

Here is the code corresponding to that image:

import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QPoint
from PyQt5.QtGui import QResizeEvent


class MyWidget(QWidget):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.layout = QHBoxLayout()
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(self.layout)

        self.lbl = QLabel()
        self.lbl.setStyleSheet('background: #EE6622')
        self.lbl.setFixedSize(125, 150)
        self.layout.addWidget(self.lbl)

        self.btn = QPushButton(parent=self)
        self.btn.setStyleSheet('background: #ABCDEF')
        self.btn.setFixedSize(25, 25)

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

    def update_btn_pos(self):
        pos = (
            self.lbl.pos() +
            QPoint(
                self.lbl.rect().width() - int(self.btn.width() / 2),
                -int(self.btn.height() / 2))
        )
        self.btn.move(pos)


if __name__ == "__main__":
    a = QApplication(sys.argv)
    window = MyWidget()
    window.show()
    a.exec()

My problem is that the widget's behaviour when resizing suggests that the button is not really "part of" that widget - it is cut-off as if it weren't there:

Widget when resized

I tried to overwrite the sizeHint()-method to include the button, but that only solves the problem on startup, I can still resize the window manually to cut the button off again.

What must be changed in order to make this work?


Solution

  • I think I might have found a solution myself by adding the following to the __init__ - method:

    self.layout.setContentsMargins(
        0, 
        int(self.btn.height() / 2),
        int(self.btn.width() / 2), 
        0
    )
    

    By setting the contentsMargin, the size of the big rectangle doesn't change because it is fixed and the parent widget still covers the space under the button:

    Widget respecting the button's space

    I'm not sure if this is the *right way* to do it though ...


    Alright, thanks to @musicamante this is the final code:

    import sys
    from PyQt5.QtWidgets import *
    from PyQt5.QtGui import QResizeEvent
    
    
    class MyWidget(QWidget):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
    
            self.layout = QHBoxLayout()
            self.setLayout(self.layout)
    
            self.lbl = QLabel()
            self.lbl.setStyleSheet('background: #EE6622')
            self.lbl.setFixedSize(125, 150)
            self.layout.addWidget(self.lbl)
    
            self.btn = QPushButton(parent=self)
            self.btn.setStyleSheet('background: #ABCDEF')
            self.btn.setFixedSize(25, 25)
    
            # set contents margin of layout to half the button's size
            self.layout.setContentsMargins(
                *([int(self.btn.height() / 2), int(self.btn.width() / 2)]*2)
            )
    
        def resizeEvent(self, event: QResizeEvent) -> None:
            super().resizeEvent(event)
            self.update_btn_pos()
    
        def update_btn_pos(self):
            rect = self.btn.rect()
            rect.moveCenter(self.lbl.geometry().topRight())
            self.btn.move(rect.topLeft())
    
    
    if __name__ == "__main__":
        a = QApplication(sys.argv)
        window = MyWidget()
        window.show()
        a.exec()
    

    Result:
    Widget with padding at all sides