优化程序

This commit is contained in:
hero920103 committed 2026-10-12 14:57:46 +08:00
1 parent 68e8eb91a7
commit 6df29c9254
27 files changed
+1974 -203

No files matched your search

+139 -9
View File
@@ -432,6 +432,8 @@ class PanelWindow(QWidget):
def showEvent(self, event):
super().showEvent(event)
self._apply_win32_blur(theme.name() == "dark")
# 亚克力方形底靠 DWM 圆角裁剪盖掉;窗口重建后属性丢失,每次 show 重应用
self._apply_win11_round_corners()
if not self._screen_hooked:
wh = self.windowHandle()
if wh is not None:
@@ -571,6 +573,8 @@ class PanelWindow(QWidget):
每项: (tooltip, qtawesome_icon, callback)
排除重启/退出。"""
return [
("新建桌面文件框", "fa5s.window-restore", self._create_file_box),
("新建文件夹框", "fa5s.folder-open", self._create_folder_box),
("微信多开", "fa5b.weixin", self._open_wechat_multi),
("管理员运行 CMD", "fa5s.terminal", self._open_admin_cmd),
("管理员运行 PowerShell", "fa5b.windows", self._open_admin_powershell),
@@ -840,7 +844,7 @@ class PanelWindow(QWidget):
f"""
QWidget#container {{
background: {t['panel_bg']};
border-radius: 12px;
border-radius: 8px;
border: 1px solid {t['panel_border']};
}}
"""
@@ -964,7 +968,7 @@ class PanelWindow(QWidget):
f"""
QWidget#bottom_bar {{
background: {bar_bg2};
border-radius: 0 0 12px 12px;
border-radius: 0 0 8px 8px;
border-top: 1px solid {t['panel_border']};
}}
QPushButton {{ border:none; background:transparent; border-radius:4px; }}
@@ -993,6 +997,14 @@ class PanelWindow(QWidget):
if item and item.widget():
item.widget()._apply_theme()
# ✅ 主题变化时通知所有 FileBox 重绘
try:
from ui.box import FileBox
for b in FileBox._instances:
b._apply_theme() # 重设 QSS(container + title_bar 都会刷新)
except Exception:
pass
def _apply_tooltip_theme(self, t: dict, is_dark: bool):
"""让所有控件 tooltip 跟随主题(含快捷栏按钮)。"""
from PyQt6.QtGui import QPalette, QColor
@@ -1093,6 +1105,54 @@ class PanelWindow(QWidget):
dlg = WechatMultiDialog(self)
dlg.exec()
# ── FileBoxes ─────────────────────────────────────────
_file_boxes: list = []
def _create_file_box(self):
"""新建一个空的拖入卡片文件框。"""
self._create_box_common(title="文件框", folder_path="")
def _create_folder_box(self):
"""新建文件夹框 — 先让用户选文件夹。"""
from PyQt6.QtWidgets import QFileDialog
folder = QFileDialog.getExistingDirectory(self, "选择文件夹", os.path.expanduser("~"))
if not folder: return
title = os.path.basename(folder) or folder
self._create_box_common(title=title, folder_path=folder)
def _create_box_common(self, title: str, folder_path: str):
from ui.box import FileBox
# 注册 panel 引用 —— FileBox 在 mousePress 时会 raise_ 主面板
FileBox._panel_ref = self
screen = QApplication.primaryScreen().availableGeometry()
idx = len(self._file_boxes)
x = screen.center().x() - 120 + idx * 20
y = screen.center().y() - 160 + idx * 20
new_id = database.add_file_box(title, x=x, y=y, w=240, h=320)
database.update_file_box(new_id, folder_path=folder_path) # 文件夹框模式
box = FileBox({
"id": new_id, "title": title, "x": x, "y": y,
"w": 240, "h": 320, "folder_path": folder_path,
})
self._file_boxes.append(box)
box.show(); box.raise_(); box.activateWindow()
# 主面板始终在 FileBox 之上
self.raise_()
def _load_file_boxes(self):
"""启动时从 DB 恢复所有文件框。"""
from ui.box import FileBox
FileBox._panel_ref = self
for d in database.get_file_boxes():
box = FileBox(d)
self._file_boxes.append(box)
box.show()
def _close_all_file_boxes(self):
for b in list(self._file_boxes):
b.close()
self._file_boxes.clear()
def _toggle_theme(self):
theme.set_theme("light" if theme.name() == "dark" else "dark")
self._apply_theme()
@@ -1210,6 +1270,18 @@ class PanelWindow(QWidget):
self._open_url_in_browser("https://www.doubao.com/chat/", "豆包")
def refresh_groups(self):
# 同步刷新所有分组联动的 FileBox(双向联动)
try:
from ui.box import FileBox
for b in FileBox._instances:
if b._is_group_linked:
b.refresh_items()
# 如果分组被删了 → 自动解除关联
if not database.get_items(b._group_id):
pass # 分组可能空了,正常
except Exception:
pass
# 记录当前折叠状态
collapsed_ids = set()
for i in range(self.groups_layout.count() - 1):
@@ -1308,6 +1380,8 @@ class PanelWindow(QWidget):
self.setGeometry(geo)
self.show()
self._apply_win32_blur(theme.name() == "dark")
# setWindowFlags 重建了原生窗口,DWM 圆角属性丢失,重新应用
self._apply_win11_round_corners()
ball = getattr(self, "_ball_ref", None)
if ball is not None:
ball.set_stays_on_top(on_top)
@@ -1653,7 +1727,8 @@ class PanelWindow(QWidget):
if event.buttons() & Qt.MouseButton.LeftButton and self._win_drag_pos:
self._dock_state = "none"
self._is_collapsed = False
self.move(gp - self._win_drag_pos)
raw_pos = gp - self._win_drag_pos
self.move(self._magnetic_snap(raw_pos))
return True
# 悬停时更新光标
self._update_cursor(gp)
@@ -1676,7 +1751,8 @@ class PanelWindow(QWidget):
if event.buttons() & Qt.MouseButton.LeftButton and self._win_drag_pos:
self._dock_state = "none"
self._is_collapsed = False
self.move(event.globalPosition().toPoint() - self._win_drag_pos)
raw_pos = event.globalPosition().toPoint() - self._win_drag_pos
self.move(self._magnetic_snap(raw_pos))
return
self._update_cursor(event.globalPosition().toPoint())
@@ -1764,6 +1840,39 @@ class PanelWindow(QWidget):
self.show_near(self._ball_ref.pos(), self._ball_ref.width())
# ── 边缘吸附与收起 ─────────────────────────────────────
_SNAP_THRESHOLD = 30 # 拖拽中磁吸触发阈值(像素)
def _magnetic_snap(self, pos: QPoint) -> QPoint:
"""拖拽中实时磁吸:窗口接近边缘时自动吸附到边缘,给用户"磁性"手感。"""
screen = QApplication.screenAt(pos)
if not screen:
screen = QApplication.primaryScreen()
geom = screen.availableGeometry()
x, y, w, h = pos.x(), pos.y(), self.width(), self.height()
t = self._SNAP_THRESHOLD
# 优先吸附距离最近的边缘
top_dist = abs(y - geom.top())
bottom_dist = abs((y + h) - geom.bottom())
left_dist = abs(x - geom.left())
right_dist = abs((x + w) - geom.right())
min_dist = min(top_dist, bottom_dist, left_dist, right_dist)
if min_dist <= t:
if min_dist == top_dist:
y = geom.top()
elif min_dist == bottom_dist:
y = geom.bottom() - h
elif min_dist == left_dist:
x = geom.left()
else:
x = geom.right() - w
# 限制在屏幕范围内
x = max(geom.left(), min(x, geom.right() - w))
y = max(geom.top(), min(y, geom.bottom() - h))
return QPoint(x, y)
def _check_dock_state(self):
if self._pinned:
self._dock_state = "none"
@@ -1772,10 +1881,11 @@ class PanelWindow(QWidget):
screen = QApplication.screenAt(self.geometry().center())
if not screen:
screen = QApplication.primaryScreen()
screen_geom = screen.geometry()
# 用 availableGeometry 排除任务栏区域,避免吸附到任务栏上
screen_geom = screen.availableGeometry()
x, y, w, h = self.x(), self.y(), self.width(), self.height()
threshold = 20
threshold = 30 # 吸附阈值(像素)
# 检测顶部吸附
if abs(y - screen_geom.top()) <= threshold:
@@ -1803,7 +1913,7 @@ class PanelWindow(QWidget):
if not is_hovered:
screen = QApplication.screenAt(cursor_pos)
if screen:
screen_geom = screen.geometry()
screen_geom = screen.availableGeometry()
tolerance = 3
if self._dock_state == "top" and cursor_pos.y() <= screen_geom.top() + tolerance:
if geom.left() <= cursor_pos.x() <= geom.right():
@@ -1831,7 +1941,7 @@ class PanelWindow(QWidget):
screen = QApplication.screenAt(self.geometry().center())
if not screen:
screen = QApplication.primaryScreen()
screen_geom = screen.geometry()
screen_geom = screen.availableGeometry()
x, y, w, h = self.x(), self.y(), self.width(), self.height()
target_x, target_y = x, y
@@ -1854,7 +1964,7 @@ class PanelWindow(QWidget):
screen = QApplication.screenAt(self.geometry().center())
if not screen:
screen = QApplication.primaryScreen()
screen_geom = screen.geometry()
screen_geom = screen.availableGeometry()
x, y, w, h = self.x(), self.y(), self.width(), self.height()
target_x, target_y = x, y
@@ -1881,6 +1991,26 @@ class PanelWindow(QWidget):
self._dock_anim.finished.connect(self.raise_)
self._dock_anim.start()
def _apply_win11_round_corners(self):
"""Windows 11: DWMWA_WINDOW_CORNER_PREFERENCE(33) = DWMWCP_ROUND(2)。
必须调用:亚克力合成层(SetWindowCompositionAttribute)作用于**整个方形窗口**,
QSS border-radius 只能裁 Qt 内容层,裁不掉 OS 层的方形亚克力 →
四角圆弧外露出方形底座("圆角下面垫着直角"的根因)。
DWMWCP_ROUND 让 DWM 在 OS 层物理裁剪窗口四角,亚克力方形一起被裁掉。
setWindowFlags 重建窗口后 DWM 属性会丢,需在 showEvent / 置顶切换后重应用。"""
if sys.platform != "win32":
return
try:
import ctypes
hwnd = int(self.winId())
pref = ctypes.c_int(2) # DWMWCP_ROUND
ctypes.windll.dwmapi.DwmSetWindowAttribute(
hwnd, 33, ctypes.byref(pref), ctypes.sizeof(pref)
)
except Exception:
pass
def _apply_win32_blur(self, is_dark: bool):
if sys.platform != "win32":
return