更新0.02版本

This commit is contained in:
2026-04-07 10:48:27 +08:00
parent 77f718cca6
commit 3051f6ca49
19 changed files with 345 additions and 37 deletions
+114 -24
View File
@@ -38,10 +38,11 @@ from db import database
from ui.group import GroupWidget
import ui.theme as theme
import ui.dialog_style as dialog_style
from ui.updater import Updater
PANEL_W = 260
PANEL_H = 40
MIN_W, MIN_H = 180, 40
MIN_W, MIN_H = 400, 5
ANIM_MS = 180
RESIZE_M = 8
@@ -354,6 +355,8 @@ class PanelWindow(QWidget):
self._time_timer.setInterval(1000)
self._time_timer.timeout.connect(self._refresh_time_label)
self._updater = Updater(self)
def showEvent(self, event):
super().showEvent(event)
if not self._screen_hooked:
@@ -499,18 +502,63 @@ class PanelWindow(QWidget):
self._time_timer.start()
# ── UI ──────────────────────────────────────────────
def _get_quick_actions(self):
"""返回快捷动作列表,悬浮球右键菜单和左侧快捷栏共用同一份。
每项: (tooltip, qtawesome_icon, callback)
排除重启/退出。"""
return [
("管理员运行 CMD", "fa5s.terminal", self._open_admin_cmd),
("管理员运行 PowerShell", "fa5b.windows", self._open_admin_powershell),
("打开默认浏览器", "fa5s.globe", self._open_default_browser),
]
def _build_ui(self):
root = QVBoxLayout(self)
root.setContentsMargins(0, 0, 0, 0)
self.container = QWidget()
self.container.setObjectName("container")
# container 也开启鼠标追踪,子 widget 的 MouseMove 会冒泡上来
self.container.setMouseTracking(True)
inner = QVBoxLayout(self.container)
# 横向:左侧快捷栏 + 右侧主内容
h_layout = QHBoxLayout(self.container)
h_layout.setContentsMargins(0, 0, 0, 0)
h_layout.setSpacing(0)
# ── 左侧快捷栏
self._quick_bar = QWidget()
self._quick_bar.setObjectName("quick_bar")
self._quick_bar.setFixedWidth(50)
quick_layout = QVBoxLayout(self._quick_bar)
quick_layout.setContentsMargins(0, 10, 0, 10)
quick_layout.setSpacing(6)
quick_layout.setAlignment(Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignHCenter)
self._quick_btns = []
for tooltip, icon_name, callback in self._get_quick_actions():
btn = QPushButton()
btn.setFixedSize(34, 34)
btn.setToolTip(tooltip)
btn.setStyleSheet("border:none; background:transparent; border-radius:4px;")
try:
btn.setIcon(qta.icon(icon_name, color="#888"))
btn.setIconSize(QSize(16, 16))
except Exception:
btn.setText(tooltip[0])
btn.clicked.connect(callback)
quick_layout.addWidget(btn, alignment=Qt.AlignmentFlag.AlignHCenter)
self._quick_btns.append((btn, icon_name))
quick_layout.addStretch()
h_layout.addWidget(self._quick_bar)
# ── 右侧主内容
right_widget = QWidget()
right_widget.setMouseTracking(True)
inner = QVBoxLayout(right_widget)
inner.setContentsMargins(8, 10, 8, 8)
inner.setSpacing(6)
h_layout.addWidget(right_widget)
# ── 标题栏(仅此处可拖动窗口,避免分组内框选时带动面板)
self._title_bar = QWidget()
@@ -683,6 +731,12 @@ class PanelWindow(QWidget):
bottom_layout.addWidget(self.menu_btn)
bottom_layout.addStretch()
self.update_btn = QPushButton()
self.update_btn.setFixedSize(28, 28)
self.update_btn.setToolTip("检查更新")
self.update_btn.setStyleSheet("border:none; background:transparent; border-radius:4px;")
self.update_btn.clicked.connect(lambda: self._updater.check(silent_if_latest=False))
bottom_layout.addWidget(self.update_btn)
bottom_layout.addWidget(self.theme_btn)
bottom_layout.addWidget(self.settings_btn)
bottom_layout.addWidget(self.quit_btn)
@@ -781,6 +835,26 @@ class PanelWindow(QWidget):
if hasattr(self, "time_title_label"):
self.time_title_label.setStyleSheet("font-size:11px; color: #888;")
# 左侧快捷栏
bar_side_bg = "rgba(0,0,0,20)" if is_dark else "rgba(0,0,0,6)"
self._quick_bar.setStyleSheet(
f"""
QWidget#quick_bar {{
background: {bar_side_bg};
border-right: 1px solid {t['panel_border']};
border-radius: 10px 0 0 10px;
}}
QPushButton {{ border:none; background:transparent; border-radius:4px; }}
QPushButton:hover {{ background:{t['header_hover']}; }}
"""
)
for btn, icon_name in self._quick_btns:
try:
btn.setIcon(qta.icon(icon_name, color=ic))
btn.setIconSize(QSize(14, 14))
except Exception:
pass
# 底部工具栏
bar_bg2 = "rgba(0,0,0,30)" if is_dark else "rgba(0,0,0,8)"
self.bottom_bar.setStyleSheet(
@@ -798,6 +872,8 @@ class PanelWindow(QWidget):
qta.icon("fa5s.sun" if is_dark else "fa5s.moon", color=ic)
)
self.theme_btn.setIconSize(QSize(14, 14))
self.update_btn.setIcon(qta.icon("fa5s.sync-alt", color=ic))
self.update_btn.setIconSize(QSize(14, 14))
self.menu_btn.setIcon(qta.icon("fa5s.bars", color=ic))
self.menu_btn.setIconSize(QSize(14, 14))
self.settings_btn.setIcon(qta.icon("fa5s.cog", color=ic))
@@ -820,6 +896,23 @@ class PanelWindow(QWidget):
if ret == QMessageBox.StandardButton.Yes:
QApplication.quit()
def _restart_application(self):
import subprocess
subprocess.Popen([sys.executable] + sys.argv)
QApplication.quit()
def _open_admin_cmd(self):
import ctypes
ctypes.windll.shell32.ShellExecuteW(None, "runas", "cmd.exe", None, None, 1)
def _open_admin_powershell(self):
import ctypes
ctypes.windll.shell32.ShellExecuteW(None, "runas", "powershell.exe", None, None, 1)
def _open_default_browser(self):
import webbrowser
webbrowser.open("https://www.baidu.com")
def _toggle_theme(self):
theme.set_theme("light" if theme.name() == "dark" else "dark")
self._apply_theme()
@@ -1050,13 +1143,9 @@ class PanelWindow(QWidget):
"""收缩:隐藏内容区,窗口缩到只剩标题栏高度"""
self._body.hide()
self.weather_bar.hide()
# 记录当前高度,展开时恢复
self._quick_bar.hide()
self._expanded_height = self.height()
title_h = (
self.container.layout().contentsMargins().top()
+ self.container.layout().contentsMargins().bottom()
+ self._title_bar.sizeHint().height()
)
title_h = self._title_bar.sizeHint().height() + 20
self.setFixedHeight(title_h)
ic = "#cccccc" if theme.name() == "dark" else "#555555"
self._min_btn.setIcon(qta.icon("fa5s.chevron-down", color=ic))
@@ -1066,6 +1155,7 @@ class PanelWindow(QWidget):
"""展开:恢复内容区和窗口高度"""
self.weather_bar.show()
self._body.show()
self._quick_bar.show()
h = getattr(self, "_expanded_height", 520)
self.setMinimumHeight(MIN_H)
self.setMaximumHeight(16777215)
@@ -1096,7 +1186,11 @@ class PanelWindow(QWidget):
y = int(database.get_setting("panel_y", ""))
w = int(database.get_setting("panel_w", str(PANEL_W)))
h = int(database.get_setting("panel_h", str(PANEL_H)))
screen = QApplication.primaryScreen().availableGeometry()
# 用保存坐标所在屏幕来限制范围,支持多显示器
screen_obj = QApplication.screenAt(QPoint(x, y))
if screen_obj is None:
screen_obj = QApplication.primaryScreen()
screen = screen_obj.availableGeometry()
x = max(screen.left(), min(x, screen.right() - w))
y = max(screen.top(), min(y, screen.bottom() - h))
self._persist_w = w
@@ -1119,7 +1213,11 @@ class PanelWindow(QWidget):
# ── 显示/隐藏 ────────────────────────────────────────
def show_near(self, ball_pos: QPoint, ball_size: int):
screen = QApplication.primaryScreen().availableGeometry()
# 根据球所在屏幕来定位 panel,避免跑到主屏幕
screen_obj = QApplication.screenAt(ball_pos)
if screen_obj is None:
screen_obj = QApplication.primaryScreen()
screen = screen_obj.availableGeometry()
pw, ph = self.width(), self.height()
bx, by = ball_pos.x(), ball_pos.y()
@@ -1165,6 +1263,7 @@ class PanelWindow(QWidget):
def minimize_to_ball(self):
self.hide_panel()
if hasattr(self, "_ball_ref"):
self._ball_ref.place_on_screen_of(self)
self._ball_ref.show()
self._ball_ref.setWindowOpacity(1.0)
@@ -1360,20 +1459,11 @@ class PanelWindow(QWidget):
QMenu::separator { height:1px; background:#444; margin:4px 8px; }
"""
)
menu.addAction("🖥 显示面板").triggered.connect(
lambda: (
self.show_near(self._ball_ref.pos(), self._ball_ref.width())
if hasattr(self, "_ball_ref")
else None
)
)
menu.addAction("🔽 最小化到悬浮球").triggered.connect(self.minimize_to_ball)
for tooltip, _icon, callback in self._get_quick_actions():
menu.addAction(tooltip).triggered.connect(callback)
menu.addSeparator()
self._autostart_act = menu.addAction("🚀 开机自启: 开")
self._autostart_act.triggered.connect(self._toggle_autostart)
self._autostart_enabled = True
menu.addSeparator()
menu.addAction("❌ 退出").triggered.connect(QApplication.quit)
menu.addAction("🔄 重启本程序").triggered.connect(self._restart_application)
menu.addAction("❌ 退出本程序").triggered.connect(QApplication.quit)
self.tray.setContextMenu(menu)
self.tray.activated.connect(self._on_tray_activated)