class ResizableRubberBand(QWidget): def __init__(self, parent=None): super(ResizableRubberBand, self).__init__(parent) self.draggable = True self.dragging_threshold = 0 self.mousePressPos = None self.mouseMovePos = None self.borderRadius = 5 self.setWindowFlags(Qt.SubWindow) layout = QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) self.grip2 = QSizeGrip(self) self.grip2.setFixedSize(15, 15) layout.addWidget(self.grip2, 0, Qt.AlignRight | Qt.AlignBottom) self.band = QRubberBand(QRubberBand.Rectangle, self) self.band.show() self.show() def resizeEvent(self, event): # Note: resizing from a top left grip creates loop rect = self.geometry() side = min(rect.width(), rect.height()) self.band.setGeometry(0, 0, side, side) self.setGeometry(self.x(), self.y(), side, side) def paintEvent(self, event): # Get current window size window_size = self.size() qp = QPainter() qp.begin(self) qp.setRenderHint(QPainter.Antialiasing, True) side = min(window_size.width(), window_size.height()) qp.drawRoundedRect(0, 0, window_size.width(), window_size.height(), self.borderRadius, self.borderRadius) qp.end() def mousePressEvent(self, event): if self.draggable and event.button() == Qt.LeftButton: self.mousePressPos = event.globalPos() # global self.mouseMovePos = event.globalPos() - self.pos() # local super(ResizableRubberBand, self).mousePressEvent(event) def mouseMoveEvent(self, event): if self.draggable and event.buttons() & Qt.LeftButton: globalPos = event.globalPos() moved = globalPos - self.mousePressPos if moved.manhattanLength() > self.dragging_threshold: # Move when user drag window more than dragging_threshold diff = globalPos - self.mouseMovePos diffNew = QPoint( max( 0, min( diff.x(), self.parent().geometry().width() - self.size().width())), max( 0, min( diff.y(), self.parent().geometry().height() - self.size().height()))) self.move(diffNew) self.mouseMovePos = globalPos - self.pos() super(ResizableRubberBand, self).mouseMoveEvent(event) def mouseReleaseEvent(self, event): if self.mousePressPos is not None: if event.button() == Qt.LeftButton: moved = event.globalPos() - self.mousePressPos if moved.manhattanLength() > self.dragging_threshold: # Do not call click event or so on event.ignore() self.mousePressPos = None super(ResizableRubberBand, self).mouseReleaseEvent(event)
class ResizableFramelessContainer(QWidget): """A resizable frameless container ResizableFramelessContainer can be moved and resized by mouse. Call `attach_widget` to attach an inner widget and `detach` to detach the inner widget. NOTE: this is mainly designed for picture in picture mode currently. """ def __init__(self, ): super().__init__(parent=None) self._widget = None self._timer = QTimer(self) self._old_pos = None self._widget = None self._size_grip = QSizeGrip(self) self._timer.timeout.connect(self.__on_timeout) # setup window layout self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint) self._size_grip.setFixedSize(20, 20) self._layout = QVBoxLayout(self) self._layout.setContentsMargins(0, 0, 0, 0) self._layout.setSpacing(0) self._layout.addWidget(self._size_grip) self._layout.setAlignment(self._size_grip, Qt.AlignBottom | Qt.AlignRight) self.setMouseTracking(True) def attach_widget(self, widget): """set inner widget""" self._widget = widget self._widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self._layout.insertWidget(0, self._widget) def detach(self): self._layout.removeWidget(self._widget) self._widget = None def paintEvent(self, e): painter = QPainter(self) if self._size_grip.isVisible(): painter.save() painter.setPen(QColor('white')) option = QTextOption() option.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) rect = QRect(self._size_grip.pos(), self._size_grip.size()) painter.drawText(QRectF(rect), '●', option) painter.restore() def mousePressEvent(self, e): self._old_pos = e.globalPos() def mouseMoveEvent(self, e): # NOTE: e.button() == Qt.LeftButton don't work on Windows # on Windows, even I drag with LeftButton, the e.button() return 0, # which means no button if self._old_pos is not None: delta = e.globalPos() - self._old_pos self.move(self.x() + delta.x(), self.y() + delta.y()) self._old_pos = e.globalPos() def mouseReleaseEvent(self, e): self._old_pos = None def enterEvent(self, e): super().enterEvent(e) if not self._size_grip.isVisible(): self.resize(self.width(), self.height() + 20) self._size_grip.show() self._timer.stop() def leaveEvent(self, e): super().leaveEvent(e) self._timer.start(2000) def resizeEvent(self, e): super().resizeEvent(e) def __on_timeout(self): if self._size_grip.isVisible(): self._size_grip.hide() self.resize(self.width(), self.height() - 20)