Files
kefu/wechat_rpa/desktop_window.py
T
2026-09-21 10:34:06 +08:00

126 lines
5.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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_()