class Container(QWidget): def __init__(self, parent=None): super().__init__(parent=parent) self._border_radius = 10 self.label = QLabel('...', self) self._size_grip = QSizeGrip(self) self._size_grip.setFixedWidth(self._border_radius * 2) font = self.font() font.setPointSize(24) self.label.setFont(font) self.label.setAlignment(Qt.AlignBaseline | Qt.AlignVCenter | Qt.AlignHCenter) self.label.setWordWrap(False) self._layout = QHBoxLayout(self) self._layout.setContentsMargins(0, 0, 0, 0) self._layout.setSpacing(0) self._layout.addSpacing(self._border_radius * 2) self._layout.addWidget(self.label) self._layout.addWidget(self._size_grip) self._layout.setAlignment(self._size_grip, Qt.AlignBottom) def paintEvent(self, e): painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) painter.setPen(Qt.NoPen) painter.setBrush(self.palette().color(QPalette.Window)) painter.drawRoundedRect(self.rect(), self._border_radius, self._border_radius) painter.save() painter.setPen(QColor('white')) option = QTextOption() option.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) rect = QRect(self.mapToParent(self._size_grip.pos()), self._size_grip.size()) painter.drawText(QRectF(rect), '●', option) painter.restore() def show_color_dialog(self, bg=True): def set_color(color): palette = self.palette() if bg: palette.setColor(QPalette.Active, QPalette.Window, color) palette.setColor(QPalette.Active, QPalette.Base, color) palette.setColor(QPalette.Inactive, QPalette.Window, color) palette.setColor(QPalette.Inactive, QPalette.Base, color) else: palette.setColor(QPalette.Active, QPalette.WindowText, color) palette.setColor(QPalette.Active, QPalette.Text, color) palette.setColor(QPalette.Inactive, QPalette.WindowText, color) palette.setColor(QPalette.Inactive, QPalette.Text, color) self.label.setPalette(palette) self.setPalette(palette) dialog = QColorDialog(self) dialog.currentColorChanged.connect(set_color) dialog.colorSelected.connect(set_color) dialog.setOption(QColorDialog.ShowAlphaChannel, True) dialog.exec() def show_font_dialog(self): dialog = QFontDialog(self.label.font(), self) dialog.currentFontChanged.connect(self.label.setFont) dialog.fontSelected.connect(self.label.setFont) dialog.exec() def contextMenuEvent(self, e): menu = QMenu() bg_color_action = QAction('背景颜色', menu) fg_color_action = QAction('文字颜色', menu) font_action = QAction('字体', menu) menu.addAction(bg_color_action) menu.addAction(fg_color_action) menu.addSeparator() menu.addAction(font_action) bg_color_action.triggered.connect( lambda: self.show_color_dialog(bg=True)) fg_color_action.triggered.connect( lambda: self.show_color_dialog(bg=False)) font_action.triggered.connect(self.show_font_dialog) menu.exec(e.globalPos())
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)