"""Borderless desktop window controls, with native move and resize gestures.""" from PySide6.QtCore import QEvent, Qt from PySide6.QtWidgets import QHBoxLayout, QMenuBar, QPushButton, QWidget class _ResizeHandle(QWidget): def __init__(self, window, edges, cursor): super().__init__(window) self.edges = edges self.setCursor(cursor) self.setStyleSheet("background: transparent; border: none;") def mousePressEvent(self, event): if event.button() == Qt.LeftButton: handle = self.window().windowHandle() if handle and handle.startSystemResize(self.edges): event.accept() return super().mousePressEvent(event) class DesktopWindowBar(QMenuBar): """Keep account menus and window actions without the Windows frame.""" RESIZE_MARGIN = 5 def __init__(self, window): super().__init__(window) self.host = window window.setWindowFlag(Qt.FramelessWindowHint, True) window.setContentsMargins(0, 0, 0, 0) self.setNativeMenuBar(False) self.setFixedHeight(32) self.setStyleSheet(""" QMenuBar { background: #F4F8FF; border: none; padding: 0 8px; color: #52658E; font-size: 12px; } QMenuBar::item { background: transparent; padding: 6px 10px; } QMenuBar::item:selected { background: #E8EFFC; border-radius: 6px; } QPushButton { background: transparent; border: none; border-radius: 0; padding: 0; margin: 0; color: #52658E; } QPushButton:hover { background: #E3EBFA; } QPushButton#WindowClose:hover { background: #E95060; color: white; } """) actions = QWidget(self) layout = QHBoxLayout(actions) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) self.minimize_button = self._button("−", "最小化", window.showMinimized, actions) self.maximize_button = self._button("□", "最大化", self.toggle_maximized, actions) self.close_button = self._button("×", "关闭", window.close, actions) self.close_button.setObjectName("WindowClose") for button in (self.minimize_button, self.maximize_button, self.close_button): layout.addWidget(button) self.setCornerWidget(actions, Qt.TopRightCorner) left, right, top, bottom = Qt.LeftEdge, Qt.RightEdge, Qt.TopEdge, Qt.BottomEdge self._resize_handles = { edges: _ResizeHandle(window, edges, cursor) for edges, cursor in ( (left, Qt.SizeHorCursor), (right, Qt.SizeHorCursor), (top, Qt.SizeVerCursor), (bottom, Qt.SizeVerCursor), (top | left, Qt.SizeFDiagCursor), (bottom | right, Qt.SizeFDiagCursor), (top | right, Qt.SizeBDiagCursor), (bottom | left, Qt.SizeBDiagCursor), ) } window.installEventFilter(self) @staticmethod def _button(text, label, callback, parent): button = QPushButton(text, parent) button.setFixedSize(42, 32) button.setToolTip(label) button.setAccessibleName(label) button.clicked.connect(callback) return button def toggle_maximized(self): if self.host.isMaximized(): self.host.showNormal() else: self.host.showMaximized() def mousePressEvent(self, event): # Menu items keep their usual click behavior; empty space moves the window. if event.button() == Qt.LeftButton and self.actionAt(event.position().toPoint()) is None: handle = self.host.windowHandle() if handle and handle.startSystemMove(): event.accept() return super().mousePressEvent(event) def mouseDoubleClickEvent(self, event): if event.button() == Qt.LeftButton and self.actionAt(event.position().toPoint()) is None: self.toggle_maximized() event.accept() return super().mouseDoubleClickEvent(event) def eventFilter(self, watched, event): if watched is self.host and event.type() in ( QEvent.Resize, QEvent.Show, QEvent.WindowStateChange, ): self._update_handles() label = "还原" if self.host.isMaximized() else "最大化" self.maximize_button.setText("❐" if self.host.isMaximized() else "□") self.maximize_button.setToolTip(label) self.maximize_button.setAccessibleName(label) return super().eventFilter(watched, event) def _update_handles(self): margin = self.RESIZE_MARGIN width, height = self.host.width(), self.host.height() for edges, handle in self._resize_handles.items(): horizontal = bool(edges & (Qt.LeftEdge | Qt.RightEdge)) vertical = bool(edges & (Qt.TopEdge | Qt.BottomEdge)) x = width - margin if edges & Qt.RightEdge else (0 if horizontal else margin) y = height - margin if edges & Qt.BottomEdge else (0 if vertical else margin) handle.setGeometry( x, y, margin if horizontal else max(0, width - 2 * margin), margin if vertical else max(0, height - 2 * margin), ) handle.setVisible(not (self.host.isMaximized() or self.host.isFullScreen())) handle.raise_()