diff --git a/CleanDesktopOrganizer.spec b/CleanDesktopOrganizer.spec index b0f47c5..271343c 100644 --- a/CleanDesktopOrganizer.spec +++ b/CleanDesktopOrganizer.spec @@ -1,7 +1,7 @@ # -*- mode: python ; coding: utf-8 -*- from PyInstaller.utils.hooks import collect_all -datas = [('assets', 'assets'), ('logo.png', '.')] +datas = [('assets', 'assets'), ('logo.png', '.'), ('logo.ico', '.')] binaries = [] hiddenimports = ['qtawesome'] tmp_ret = collect_all('qtawesome') diff --git a/logo.ico b/logo.ico index aa94ed2..c57c87c 100644 Binary files a/logo.ico and b/logo.ico differ diff --git a/logo.png b/logo.png index bfe99ec..2c9758f 100644 Binary files a/logo.png and b/logo.png differ diff --git a/main.py b/main.py index d35cc6a..39381a5 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,5 @@ import sys +import os import time from PyQt6.QtCore import Qt from PyQt6.QtWidgets import QApplication @@ -8,6 +9,17 @@ from ui.ball import FloatBall, BALL_SIZE import ui.theme as theme from db import database +__VERSION__ = "0.0.1" + +# ===================== 打包兼容核心函数 ===================== +def get_resource_path(relative_path): + """ + 打包 EXE 后获取资源路径,开发环境也能用 + """ + if hasattr(sys, '_MEIPASS'): + return os.path.join(sys._MEIPASS, relative_path) + return os.path.join(os.path.abspath("."), relative_path) +# ========================================================== def _wake_existing_or_exit() -> bool: """ @@ -22,7 +34,7 @@ def _wake_existing_or_exit() -> bool: import ctypes.wintypes mutex_name = r"Global\CleanDesktopOrganizerSingleton" - title = "牛马软件柜" + title = "牛马软件柜 v" + __VERSION__ kernel32 = ctypes.windll.kernel32 user32 = ctypes.windll.user32 @@ -34,16 +46,14 @@ def _wake_existing_or_exit() -> bool: last_err = kernel32.GetLastError() if last_err == ERROR_ALREADY_EXISTS: - # 轮询一小段时间,避免首次窗口尚未创建 hwnd = None - for _ in range(8): # ~2.4s + for _ in range(8): hwnd = user32.FindWindowW(None, title) if hwnd: break time.sleep(0.3) if hwnd: - # SW_SHOW = 5 user32.ShowWindow(hwnd, 5) user32.SetForegroundWindow(hwnd) return False @@ -52,42 +62,41 @@ def _wake_existing_or_exit() -> bool: def main(): - # 必须在创建 QApplication 之前:不按各显示器缩放,逻辑像素固定(跨分辨率/跨屏拖动宽高保持一致) if not _wake_existing_or_exit(): return + QApplication.setAttribute(Qt.ApplicationAttribute.AA_Use96Dpi, True) - # 这个策略同样要求在创建 QApplication 前设置 try: QApplication.setHighDpiScaleFactorRoundingPolicy( Qt.HighDpiScaleFactorRoundingPolicy.PassThrough ) except Exception: pass + app = QApplication(sys.argv) app.setQuitOnLastWindowClosed(False) init_db() - theme.load() # 从数据库读取上次主题 + theme.load() _set_autostart(True) panel = PanelWindow() ball = FloatBall() panel._ball_ref = ball + panel._apply_pin_window_layer() ball.clicked.connect(lambda: panel.show_near(ball.pos(), BALL_SIZE)) ball.right_clicked.connect(lambda pos: panel.tray.contextMenu().exec(pos)) - # 判断是否有保存的位置:有则直接在原位显示,没有则用悬浮球旁边 has_saved = bool(database.get_setting("panel_x", "")) if has_saved: - # _restore_geometry 已在 PanelWindow.__init__ 里执行,直接 show panel.setWindowOpacity(0) panel.show() panel.raise_() if hasattr(panel, "_ball_ref"): panel._ball_ref.hide() - # 淡入 + from PyQt6.QtCore import QPropertyAnimation anim = QPropertyAnimation(panel, b"windowOpacity") anim.setDuration(180) @@ -103,4 +112,4 @@ def main(): if __name__ == "__main__": - main() + main() \ No newline at end of file diff --git a/ui/__pycache__/ball.cpython-314.pyc b/ui/__pycache__/ball.cpython-314.pyc index 532c323..52cea95 100644 Binary files a/ui/__pycache__/ball.cpython-314.pyc and b/ui/__pycache__/ball.cpython-314.pyc differ diff --git a/ui/__pycache__/dock.cpython-314.pyc b/ui/__pycache__/dock.cpython-314.pyc index 6e88cb9..1b6abc9 100644 Binary files a/ui/__pycache__/dock.cpython-314.pyc and b/ui/__pycache__/dock.cpython-314.pyc differ diff --git a/ui/__pycache__/settings_window.cpython-314.pyc b/ui/__pycache__/settings_window.cpython-314.pyc index eeea6e5..a8e5032 100644 Binary files a/ui/__pycache__/settings_window.cpython-314.pyc and b/ui/__pycache__/settings_window.cpython-314.pyc differ diff --git a/ui/ball.py b/ui/ball.py index 9fbf2a4..d92a915 100644 --- a/ui/ball.py +++ b/ui/ball.py @@ -1,55 +1,154 @@ +import ctypes import os -from PyQt6.QtWidgets import QWidget, QApplication -from PyQt6.QtCore import Qt, QPoint, QTimer, pyqtSignal, QPropertyAnimation, QEasingCurve, QRectF -from PyQt6.QtGui import (QPainter, QPixmap, QBrush, QColor, QPen, - QPainterPath, QRadialGradient, QIcon) +import sys +from ctypes import wintypes -BALL_SIZE = 60 -LOGO_SIZE = 34 -LOGO_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), "logo.png") +from PyQt6.QtWidgets import QWidget, QApplication +from PyQt6.QtCore import Qt, QPoint, pyqtSignal, QPropertyAnimation, QEasingCurve, QRectF +from PyQt6.QtGui import (QPainter, QPixmap, QBrush, QColor, + QPainterPath, QRadialGradient, QLinearGradient, + QIcon, QRegion) + +# 球体内容直径;外侧留一圈给投影 +BALL_DIAM = 96 +SHADOW_PAD = 5 +# 窗口边长 = 圆直径(含投影环),main 里 show_near 用此尺寸 +BALL_SIZE = BALL_DIAM + 1 * SHADOW_PAD DRAG_THRESHOLD = 6 +def _resource_path(*parts: str) -> str: + """开发与 PyInstaller 打包后均能定位项目根目录资源。""" + base = sys._MEIPASS if hasattr(sys, "_MEIPASS") else os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + return os.path.join(base, *parts) + + +def _resolve_logo_path(): + for name in ("logo.png", "logo.ico"): + p = _resource_path(name) + if os.path.isfile(p): + return p + return None + + +def _load_logo_pixmap(path): + """ICO 多尺寸时用 QIcon 取较大图,避免位图过小、放大后发糊或带边线感。""" + if not path: + return QPixmap() + if os.path.splitext(path)[1].lower() == ".ico": + pm = QIcon(path).pixmap(512, 512) + if not pm.isNull(): + return pm + return QPixmap(path) + + +def _win32_set_ellipse_window_rgn(hwnd, w, h): + """用系统区域裁切 HWND。""" + if not hwnd or w < 2 or h < 2: + return + gdi32 = ctypes.windll.gdi32 + user32 = ctypes.windll.user32 + hrgn = gdi32.CreateEllipticRgn(0, 0, w, h) + if not hrgn: + return + user32.SetWindowRgn(hwnd, hrgn, True) + + +def _win32_disable_dwm_rounded_shell(hwnd): + """减弱 Win11 给无边框窗套的圆角矩形描边/阴影(与自绘圆叠在一起会像方框)。""" + if not hwnd: + return + try: + dwm = ctypes.windll.dwmapi + DWMWA_WINDOW_CORNER_PREFERENCE = 33 + DWMWCP_DONOTROUND = 1 + pref = ctypes.c_uint(DWMWCP_DONOTROUND) + dwm.DwmSetWindowAttribute( + wintypes.HWND(hwnd), + wintypes.DWORD(DWMWA_WINDOW_CORNER_PREFERENCE), + ctypes.byref(pref), + ctypes.sizeof(pref), + ) + except Exception: + pass + + class FloatBall(QWidget): clicked = pyqtSignal() right_clicked = pyqtSignal(QPoint) def __init__(self, parent=None): super().__init__(parent) - self.setWindowFlags( - Qt.WindowType.FramelessWindowHint | - Qt.WindowType.WindowStaysOnTopHint | - Qt.WindowType.Tool - ) - if os.path.exists(LOGO_PATH): - self.setWindowIcon(QIcon(LOGO_PATH)) + self._stays_on_top = False + self._apply_window_flags() + self._logo_path = _resolve_logo_path() + if self._logo_path: + self.setWindowIcon(QIcon(self._logo_path)) self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) self.setAttribute(Qt.WidgetAttribute.WA_NoSystemBackground) - # 关键:让整个窗口区域都透明,不显示系统边框 - self.setFixedSize(BALL_SIZE + 20, BALL_SIZE + 20) # 留出光晕空间 + self.setAutoFillBackground(False) + # 正方形窗口边长 = 圆直径,绘制也用满圆,避免出现「大圆环套小图」 + self.setFixedSize(BALL_SIZE, BALL_SIZE) + self._apply_circular_window_shape() self.setCursor(Qt.CursorShape.PointingHandCursor) - self._logo = QPixmap(LOGO_PATH) if os.path.exists(LOGO_PATH) else QPixmap() + self._logo = _load_logo_pixmap(self._logo_path) if self._logo_path else QPixmap() self._drag_start = QPoint() self._dragging = False self._hovered = False self._glow = 0.0 # 0.0~1.0 光晕强度 - # 光晕动画 self._glow_anim = QPropertyAnimation(self, b"_glow_prop") self._glow_anim.setDuration(300) self._glow_anim.setEasingCurve(QEasingCurve.Type.OutCubic) - # 闲置淡出 - self._idle_timer = QTimer(self) - self._idle_timer.setSingleShot(True) - self._idle_timer.setInterval(3000) - self._idle_timer.timeout.connect(self._fade_out) - self._place_default() - self._idle_timer.start() - # Qt property 用于动画 + def set_stays_on_top(self, on_top): + """与主面板图钉联动:仅图钉开启时置顶,避免挡在其他程序前面。""" + if on_top == self._stays_on_top: + return + self._stays_on_top = bool(on_top) + was_visible = self.isVisible() + geo = self.geometry() + self._apply_window_flags() + self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, True) + self.setAttribute(Qt.WidgetAttribute.WA_NoSystemBackground, True) + if self._logo_path: + self.setWindowIcon(QIcon(self._logo_path)) + self.setCursor(Qt.CursorShape.PointingHandCursor) + self._apply_circular_window_shape() + if was_visible: + self.setGeometry(geo) + self.show() + + def _apply_circular_window_shape(self): + w, h = self.width(), self.height() + self.setMask(QRegion(0, 0, w, h, QRegion.RegionType.Ellipse)) + if sys.platform == "win32": + wid = int(self.winId()) + if wid: + _win32_set_ellipse_window_rgn(wid, w, h) + _win32_disable_dwm_rounded_shell(wid) + + def showEvent(self, event): + super().showEvent(event) + if sys.platform == "win32": + wid = int(self.winId()) + if wid: + _win32_set_ellipse_window_rgn(wid, self.width(), self.height()) + _win32_disable_dwm_rounded_shell(wid) + + def _apply_window_flags(self): + flags = ( + Qt.WindowType.FramelessWindowHint + | Qt.WindowType.Tool + | Qt.WindowType.NoDropShadowWindowHint + ) + if self._stays_on_top: + flags |= Qt.WindowType.WindowStaysOnTopHint + self.setWindowFlags(flags) + def _get_glow(self): return self._glow def _set_glow(self, v): self._glow = v; self.update() from PyQt6.QtCore import pyqtProperty @@ -57,79 +156,118 @@ class FloatBall(QWidget): def _place_default(self): screen = QApplication.primaryScreen().availableGeometry() - pad = (self.width() - BALL_SIZE) // 2 - self.move(screen.right() - self.width() - 20 + pad, - screen.top() + screen.height() // 2 - self.height() // 2) + self.move( + screen.right() - self.width() - 20, + screen.top() + screen.height() // 2 - self.height() // 2, + ) + + def _paint_radius_outer(self): + w, h = self.width(), self.height() + return min(w, h) * 0.5 - 0.5 + + def _paint_radius_ball(self): + return self._paint_radius_outer() - SHADOW_PAD - # ── 绘制 ──────────────────────────────────────────── def paintEvent(self, event): p = QPainter(self) p.setRenderHint(QPainter.RenderHint.Antialiasing) - pad = (self.width() - BALL_SIZE) // 2 # 光晕留白 - cx = self.width() / 2 - cy = self.height() / 2 - r = BALL_SIZE / 2 - 1 + w, h = self.width(), self.height() + cx = w * 0.5 + cy = h * 0.5 + r_out = self._paint_radius_outer() + r = self._paint_radius_ball() + g = self._glow + glow_strength = 0.28 + 0.72 * g - # 1. 外部光晕(hover 时) - if self._glow > 0: - glow_r = r + 10 * self._glow - grad = QRadialGradient(cx, cy, glow_r) - grad.setColorAt(0, QColor(255, 255, 255, int(80 * self._glow))) - grad.setColorAt(0.5, QColor(255, 255, 255, int(30 * self._glow))) - grad.setColorAt(1, QColor(255, 255, 255, 0)) - p.setBrush(QBrush(grad)) - p.setPen(Qt.PenStyle.NoPen) - p.drawEllipse(QRectF(cx - glow_r, cy - glow_r, glow_r * 2, glow_r * 2)) + disc_out = QRectF(cx - r_out, cy - r_out, r_out * 2, r_out * 2) + disc = QRectF(cx - r, cy - r, r * 2, r * 2) + + # 0. 外环投影(内容圆与窗口边之间的环形:下重上轻,模拟落地阴影) + p.setPen(Qt.PenStyle.NoPen) + outer_path = QPainterPath() + outer_path.addEllipse(disc_out) + inner_hole = QPainterPath() + inner_hole.addEllipse(disc) + ring = outer_path.subtracted(inner_hole) + sh = QLinearGradient(0, cy - r_out, 0, cy + r_out) + sh.setColorAt(0.0, QColor(0, 0, 0, 0)) + sh.setColorAt(0.45, QColor(0, 0, 0, 0)) + sh.setColorAt(0.62, QColor(0, 0, 0, int(18 + 22 * glow_strength))) + sh.setColorAt(0.82, QColor(0, 0, 0, int(38 + 35 * g))) + sh.setColorAt(1.0, QColor(0, 0, 0, int(52 + 40 * g))) + p.fillPath(ring, QBrush(sh)) - # 2. 圆形裁剪 clip = QPainterPath() - clip.addEllipse(QRectF(cx - r, cy - r, r * 2, r * 2)) + clip.addEllipse(disc) p.setClipPath(clip) - # 3. 白色半透明毛玻璃底 - p.setBrush(QBrush(QColor(255, 255, 255, 210))) - p.setPen(Qt.PenStyle.NoPen) - p.drawEllipse(QRectF(cx - r, cy - r, r * 2, r * 2)) + # 1. 毛玻璃底:半透明冷白渐变(磨砂基底) + frost_bg = QLinearGradient(0, cy - r, 0, cy + r) + frost_bg.setColorAt(0.0, QColor(255, 255, 255, int(175 + 35 * glow_strength))) + frost_bg.setColorAt(0.42, QColor(244, 249, 255, int(130 + 40 * glow_strength))) + frost_bg.setColorAt(1.0, QColor(228, 236, 248, int(155 + 45 * glow_strength))) + p.setBrush(QBrush(frost_bg)) + p.drawEllipse(disc) - # 4. 顶部高光 - hi = QRadialGradient(cx, cy - r * 0.3, r * 0.9) - hi.setColorAt(0, QColor(255, 255, 255, 140)) - hi.setColorAt(1, QColor(255, 255, 255, 0)) - p.setBrush(QBrush(hi)) - p.drawEllipse(QRectF(cx - r, cy - r, r * 2, r * 2)) + # 2. 斜向磨砂高光(模拟玻璃反光) + sheen = QLinearGradient(cx - r, cy - r, cx + r * 0.55, cy + r * 0.85) + sheen.setColorAt(0.0, QColor(255, 255, 255, 0)) + sheen.setColorAt(0.38, QColor(255, 255, 255, int(38 + 45 * glow_strength))) + sheen.setColorAt(0.52, QColor(255, 255, 255, int(14 + 18 * glow_strength))) + sheen.setColorAt(1.0, QColor(255, 255, 255, 0)) + p.setCompositionMode(QPainter.CompositionMode.CompositionMode_SoftLight) + p.setBrush(QBrush(sheen)) + p.drawEllipse(disc) + p.setCompositionMode(QPainter.CompositionMode.CompositionMode_SourceOver) - # 5. logo 居中 + # 3. Logo(cover 铺满圆) if not self._logo.isNull(): + p.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform, True) + cover = max(1, int(2 * r)) scaled = self._logo.scaled( - LOGO_SIZE, LOGO_SIZE, - Qt.AspectRatioMode.KeepAspectRatio, + cover, cover, + Qt.AspectRatioMode.KeepAspectRatioByExpanding, Qt.TransformationMode.SmoothTransformation ) lx = int(cx - scaled.width() / 2) ly = int(cy - scaled.height() / 2) p.drawPixmap(lx, ly, scaled) - # 6. 边框(取消裁剪后画) + # 4. 覆在图上的雾面(毛玻璃「蒙在内容上」) + veil = QLinearGradient(cx, cy - r, cx, cy + r) + veil.setColorAt(0.0, QColor(255, 255, 255, int(52 + 38 * g))) + veil.setColorAt(0.35, QColor(255, 255, 255, int(28 + 22 * g))) + veil.setColorAt(0.75, QColor(248, 252, 255, int(18 + 15 * g))) + veil.setColorAt(1.0, QColor(240, 245, 252, int(22 + 18 * g))) + p.setBrush(QBrush(veil)) + p.drawEllipse(disc) + + # 5. 内缘光晕(边缘略亮,悬停时更柔) + rim = QRadialGradient(cx, cy, r) + rim.setColorAt(0.0, QColor(255, 255, 255, 0)) + rim.setColorAt(0.62, QColor(255, 255, 255, 0)) + rim.setColorAt(0.88, QColor(255, 255, 255, int(28 + 55 * glow_strength))) + rim.setColorAt(0.97, QColor(255, 255, 255, int(45 + 85 * glow_strength))) + rim.setColorAt(1.0, QColor(255, 255, 255, int(22 + 40 * glow_strength))) + p.setBrush(QBrush(rim)) + p.drawEllipse(disc) + + # 6. 顶区高光(玻璃上沿反光) + hi = QRadialGradient(cx, cy - r * 0.22, r * 1.02) + hi.setColorAt(0.0, QColor(255, 255, 255, int(42 + 55 * glow_strength))) + hi.setColorAt(0.45, QColor(255, 255, 255, int(12 + 18 * glow_strength))) + hi.setColorAt(1.0, QColor(255, 255, 255, 0)) + p.setBrush(QBrush(hi)) + p.drawEllipse(disc) + p.setClipping(False) - p.setBrush(Qt.BrushStyle.NoBrush) - p.setPen(QPen(QColor(200, 200, 200, 120), 1.5)) - p.drawEllipse(QRectF(cx - r, cy - r, r * 2, r * 2)) - - # 7. hover 时内圈白色光晕边 - if self._glow > 0: - p.setPen(QPen(QColor(255, 255, 255, int(160 * self._glow)), 2)) - p.drawEllipse(QRectF(cx - r + 1, cy - r + 1, (r - 1) * 2, (r - 1) * 2)) - p.end() - # ── 鼠标事件 ──────────────────────────────────────── def mousePressEvent(self, event): if event.button() == Qt.MouseButton.LeftButton: self._drag_start = event.globalPosition().toPoint() self._dragging = False - self.setWindowOpacity(1.0) - self._idle_timer.stop() elif event.button() == Qt.MouseButton.RightButton: self.right_clicked.emit(event.globalPosition().toPoint()) @@ -153,12 +291,9 @@ class FloatBall(QWidget): self.clicked.emit() else: self._snap_to_edge() - self._idle_timer.start() def enterEvent(self, event): self._hovered = True - self.setWindowOpacity(1.0) - self._idle_timer.stop() self._glow_anim.stop() self._glow_anim.setStartValue(self._glow) self._glow_anim.setEndValue(1.0) @@ -170,9 +305,7 @@ class FloatBall(QWidget): self._glow_anim.setStartValue(self._glow) self._glow_anim.setEndValue(0.0) self._glow_anim.start() - self._idle_timer.start() - # ── 吸附边缘 ───────────────────────────────────────── def _snap_to_edge(self): screen = QApplication.primaryScreen().availableGeometry() cx = self.x() + self.width() // 2 @@ -197,10 +330,3 @@ class FloatBall(QWidget): anim.setEndValue(ends[side]) self._snap_anim = anim anim.start() - - def _fade_out(self): - self._anim_fade = QPropertyAnimation(self, b"windowOpacity") - self._anim_fade.setDuration(600) - self._anim_fade.setStartValue(1.0) - self._anim_fade.setEndValue(0.3) - self._anim_fade.start() diff --git a/ui/dock.py b/ui/dock.py index 3a147a7..6ed0c0f 100644 --- a/ui/dock.py +++ b/ui/dock.py @@ -314,9 +314,8 @@ class PanelWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle("牛马软件柜") - self.setWindowFlags( - Qt.WindowType.FramelessWindowHint | Qt.WindowType.WindowStaysOnTopHint - ) + # 默认不置顶;仅图钉开启时与悬浮球一并置顶(见 _apply_pin_window_layer) + self.setWindowFlags(Qt.WindowType.FramelessWindowHint) # 任务栏/开始菜单图标:使用项目 logo.png logo_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "logo.png") if os.path.exists(logo_path): @@ -522,7 +521,9 @@ class PanelWindow(QWidget): self.pin_btn = QPushButton() self.pin_btn.setFixedSize(26, 26) - self.pin_btn.setToolTip("固定:开启后最小化只收缩内容,不变悬浮球") + self.pin_btn.setToolTip( + "固定:面板与悬浮球置顶;开启后最小化只收缩内容,不变悬浮球" + ) self.pin_btn.setStyleSheet("border:none; background:transparent;") self.pin_btn.clicked.connect(self._toggle_pin) @@ -995,18 +996,45 @@ class PanelWindow(QWidget): f"已创建分组「{group_name}」,共 {count} 个文件(实时读取)。", ) - def _toggle_pin(self): - self._pinned = not self._pinned + def _sync_pin_dependent_ui(self): + """图钉图标、最小化提示、置顶标志(面板 + 悬浮球)。""" ic = "#cccccc" if theme.name() == "dark" else "#555555" self.pin_btn.setIcon( qta.icon("fa5s.thumbtack", color="#f90" if self._pinned else ic) ) self.pin_btn.setIconSize(QSize(13, 13)) - # 更新最小化按钮 tooltip if self._pinned: self._min_btn.setToolTip("收缩内容区(固定模式)") else: self._min_btn.setToolTip("最小化到悬浮球") + self._apply_pin_window_layer() + + def _apply_pin_window_layer(self): + """图钉开 → 面板与悬浮球 WindowStaysOnTop;关 → 普通叠放,不挡其他程序。""" + on_top = self._pinned + if getattr(self, "_pin_top_applied", None) is not on_top: + self._pin_top_applied = on_top + was_visible = self.isVisible() + geo = self.geometry() + flags = Qt.WindowType.FramelessWindowHint + if on_top: + flags |= Qt.WindowType.WindowStaysOnTopHint + self.setWindowFlags(flags) + self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, True) + self.setAcceptDrops(True) + logo_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "logo.png") + if os.path.exists(logo_path): + self.setWindowIcon(QIcon(logo_path)) + if was_visible: + self.setGeometry(geo) + self.show() + ball = getattr(self, "_ball_ref", None) + if ball is not None: + ball.set_stays_on_top(on_top) + + def _toggle_pin(self): + self._pinned = not self._pinned + self._sync_pin_dependent_ui() def _on_min_click(self): """图钉开启时收缩内容区,否则最小化到悬浮球""" @@ -1139,7 +1167,6 @@ class PanelWindow(QWidget): if hasattr(self, "_ball_ref"): self._ball_ref.show() self._ball_ref.setWindowOpacity(1.0) - self._ball_ref._idle_timer.start() def toggle_near(self, ball_pos: QPoint, ball_size: int): if self.isVisible(): diff --git a/ui/settings_window.py b/ui/settings_window.py index 010d669..f7a92b9 100644 --- a/ui/settings_window.py +++ b/ui/settings_window.py @@ -181,7 +181,7 @@ class SettingsWindow(QDialog): layout.addWidget(self._section_title("窗口行为")) - self._pin_check = QCheckBox("固定面板(不自动隐藏)") + self._pin_check = QCheckBox("固定面板(置顶、不自动隐藏)") self._pin_check.setChecked(self._panel._pinned) self._pin_check.toggled.connect(self._on_pin) layout.addWidget(self._pin_check) @@ -213,11 +213,7 @@ class SettingsWindow(QDialog): def _on_pin(self, checked: bool): self._panel._pinned = checked - ic = "#cccccc" if theme.name() == "dark" else "#555555" - import qtawesome as qta - self._panel.pin_btn.setIcon( - qta.icon("fa5s.thumbtack", color="#f90" if checked else ic) - ) + self._panel._sync_pin_dependent_ui() def _on_icon_size(self): val = self._icon_size_spin.value()