1
1
mirror of https://github.com/KenanZhu/AutoLibrary.git synced 2026-06-18 07:23:03 +08:00

refactor(theme): 将重复的主题逻辑下沉至 ThemeManager

ThemeManager 新增:
- clearTheme(theme) — 清除 QSS 并应用指定色调
- applyThemeOrClear(name, fallback) — 应用或回退的封装
- _applyColorScheme(theme) — Qt ColorScheme 设置的统一入口
- themeToReadable(need_theme) — 静态工具方法

ALSettingsWidget 移除:
- _clearCustomTheme → 改用 themeInstance().clearTheme()
- _applyCustomTheme → 改用 themeInstance().applyThemeOrClear()
- _themeToReadable → 改用 ThemeManager.themeToReadable()

ALSettingsWidget 仅保留 _applyTheme(含 setStyle 逻辑,供 AppInitializer 使用)
This commit is contained in:
2026-05-30 22:51:05 +08:00
parent 2d77cbec79
commit 62f8ec3d91
4 changed files with 123 additions and 68 deletions
+3 -2
View File
@@ -20,6 +20,7 @@ from interfaces.ConfigProvider import CfgKey
from managers.config.ConfigManager import instance as configInstance from managers.config.ConfigManager import instance as configInstance
from managers.driver.WebDriverManager import instance as webdriverInstance from managers.driver.WebDriverManager import instance as webdriverInstance
from managers.log.LogManager import instance as logInstance from managers.log.LogManager import instance as logInstance
from managers.theme.ThemeManager import instance as themeInstance
def _initializeLogManager( def _initializeLogManager(
@@ -82,12 +83,12 @@ def _initializeAppearance(
saved_custom_theme = cfg.get(CfgKey.GLOBAL.APPEARANCE.CUSTOM_THEME, "") saved_custom_theme = cfg.get(CfgKey.GLOBAL.APPEARANCE.CUSTOM_THEME, "")
app.setStyle(saved_style) app.setStyle(saved_style)
_setActiveStyleName(saved_style) _setActiveStyleName(saved_style)
logger = logInstance().getLogger("AppInitializer")
if saved_custom_theme: if saved_custom_theme:
try: try:
from managers.theme.ThemeManager import instance as themeInstance
themeInstance().applyTheme(saved_custom_theme) themeInstance().applyTheme(saved_custom_theme)
except Exception: except Exception:
pass logger.warning("无法应用自定义主题 '%s',回退到默认外观", saved_custom_theme)
_applyTheme(saved_theme) _applyTheme(saved_theme)
def initializeApp( def initializeApp(
+9 -40
View File
@@ -31,7 +31,10 @@ from PySide6.QtWidgets import (
) )
import managers.config.ConfigManager as ConfigManager import managers.config.ConfigManager as ConfigManager
from managers.theme.ThemeManager import instance as themeInstance from managers.theme.ThemeManager import (
ThemeManager,
instance as themeInstance
)
from gui.resources.ui.Ui_ALSettingsWidget import Ui_ALSettingsWidget from gui.resources.ui.Ui_ALSettingsWidget import Ui_ALSettingsWidget
from interfaces.ConfigProvider import ( from interfaces.ConfigProvider import (
@@ -50,28 +53,6 @@ def _setActiveStyleName(
global _active_style_name global _active_style_name
_active_style_name = name _active_style_name = name
def _clearCustomTheme(
theme: str
):
app : QApplication | None = QApplication.instance()
if app:
app.setStyleSheet("")
_applyTheme(theme)
def _applyCustomTheme(
name: str,
fallback_theme: str = "system"
):
if not name:
_clearCustomTheme(fallback_theme)
return
try:
themeInstance().applyTheme(name)
except Exception:
_clearCustomTheme(fallback_theme)
def _applyTheme( def _applyTheme(
theme: str theme: str
): ):
@@ -94,19 +75,6 @@ def _restartApp(
QApplication.instance().quit() QApplication.instance().quit()
QProcess.startDetached(sys.executable, sys.argv) QProcess.startDetached(sys.executable, sys.argv)
def _themeToReadable(
theme: str
) -> str:
if theme == "dark":
return "深色"
elif theme == "light":
return "浅色"
elif theme == "both":
return "所有"
else:
return "未知"
class ALSettingsWidget(QWidget, Ui_ALSettingsWidget): class ALSettingsWidget(QWidget, Ui_ALSettingsWidget):
settingsWidgetIsClosed = Signal() settingsWidgetIsClosed = Signal()
@@ -136,8 +104,8 @@ class ALSettingsWidget(QWidget, Ui_ALSettingsWidget):
self.setNavigationIcons() self.setNavigationIcons()
self.ThemeInfoLabel.setTextFormat(Qt.TextFormat.RichText) self.ThemeInfoLabel.setTextFormat(Qt.TextFormat.RichText)
self.ThemeInfoLabel.setStyleSheet( self.ThemeInfoLabel.setStyleSheet(
"border: 1px solid #ccc; " \ "border: 1px solid palette(mid);"\
"border-radius: 2px;" \ "border-radius: 2px;"\
"padding: 5px;" "padding: 5px;"
) )
@@ -256,7 +224,7 @@ class ALSettingsWidget(QWidget, Ui_ALSettingsWidget):
need_theme = t.get("need_theme", "both") need_theme = t.get("need_theme", "both")
brief = t.get("brief", "没有相关简介") brief = t.get("brief", "没有相关简介")
self.ThemeInfoLabel.setText( self.ThemeInfoLabel.setText(
f"<b>{name}</b> - 适用于 <i>{_themeToReadable(need_theme)}</i> 主题<br>" f"<b>{name}</b> - 适用于 <i>{ThemeManager.themeToReadable(need_theme)}</i> 主题<br>"
f"作者:{author}<br><br>" f"作者:{author}<br><br>"
f"{brief}" f"{brief}"
) )
@@ -299,7 +267,7 @@ class ALSettingsWidget(QWidget, Ui_ALSettingsWidget):
theme, style, custom_theme = self.collectSettings() theme, style, custom_theme = self.collectSettings()
self.__cfg_mgr.set(CfgKey.GLOBAL.APPEARANCE.STYLE, style) self.__cfg_mgr.set(CfgKey.GLOBAL.APPEARANCE.STYLE, style)
self.__cfg_mgr.set(CfgKey.GLOBAL.APPEARANCE.CUSTOM_THEME, custom_theme) self.__cfg_mgr.set(CfgKey.GLOBAL.APPEARANCE.CUSTOM_THEME, custom_theme)
_applyCustomTheme(custom_theme, theme) themeInstance().applyThemeOrClear(custom_theme, theme)
self.syncRadioFromNeedTheme(custom_theme) self.syncRadioFromNeedTheme(custom_theme)
theme, _, _ = self.collectSettings() theme, _, _ = self.collectSettings()
self.__cfg_mgr.set(CfgKey.GLOBAL.APPEARANCE.THEME, theme) self.__cfg_mgr.set(CfgKey.GLOBAL.APPEARANCE.THEME, theme)
@@ -391,6 +359,7 @@ class ALSettingsWidget(QWidget, Ui_ALSettingsWidget):
self.DarkThemeRadio.setChecked(True) self.DarkThemeRadio.setChecked(True)
else: else:
self.SystemThemeRadio.setChecked(True) self.SystemThemeRadio.setChecked(True)
themeInstance().clearTheme(self.__original_theme)
self.updateThemeInfo() self.updateThemeInfo()
@Slot() @Slot()
+107 -25
View File
@@ -20,7 +20,8 @@ from managers.config.ConfigManager import instance as configInstance
from utils.ThemeUtils import ( from utils.ThemeUtils import (
packTheme, packTheme,
readThemeInfo, readThemeInfo,
unpackTheme unpackTheme,
wrapQssToAtheme
) )
@@ -85,14 +86,10 @@ class ThemeManager:
ext = os.path.splitext(source_path)[1].lower() ext = os.path.splitext(source_path)[1].lower()
if ext == ".qss": if ext == ".qss":
name = os.path.splitext(os.path.basename(source_path))[0] name = os.path.splitext(os.path.basename(source_path))[0]
info = {
"name": name,
"author": "未知",
"need_theme": "both",
"brief": "没有相关简介"
}
dest_path = os.path.join(self.__themes_dir, name + ".altheme") dest_path = os.path.join(self.__themes_dir, name + ".altheme")
packTheme(source_path, info, dest_path) if os.path.exists(dest_path):
raise ValueError(f"主题 '{name}' 已存在")
wrapQssToAtheme(source_path, dest_path, "both")
return name return name
elif ext == ".altheme": elif ext == ".altheme":
with zipfile.ZipFile(source_path, "r") as zf: with zipfile.ZipFile(source_path, "r") as zf:
@@ -102,6 +99,8 @@ class ThemeManager:
name = info.get("name", os.path.splitext(os.path.basename(source_path))[0]) name = info.get("name", os.path.splitext(os.path.basename(source_path))[0])
safe_name = os.path.basename(name) safe_name = os.path.basename(name)
dest_path = os.path.join(self.__themes_dir, safe_name + ".altheme") dest_path = os.path.join(self.__themes_dir, safe_name + ".altheme")
if os.path.exists(dest_path):
raise ValueError(f"主题 '{safe_name}' 已存在")
shutil.copy2(source_path, dest_path) shutil.copy2(source_path, dest_path)
return safe_name return safe_name
else: else:
@@ -176,25 +175,108 @@ class ThemeManager:
filepath = os.path.join(self.__themes_dir, name + ".altheme") filepath = os.path.join(self.__themes_dir, name + ".altheme")
if not os.path.isfile(filepath): if not os.path.isfile(filepath):
raise FileNotFoundError(filepath) raise FileNotFoundError(filepath)
info = readThemeInfo(filepath) with self.__lock:
with tempfile.TemporaryDirectory() as tmpdir: info = readThemeInfo(filepath)
unpackTheme(filepath, tmpdir) with tempfile.TemporaryDirectory() as tmpdir:
qss_path = os.path.join(tmpdir, "theme.qss") unpackTheme(filepath, tmpdir)
if os.path.isfile(qss_path): qss_path = os.path.join(tmpdir, "theme.qss")
with open(qss_path, "r", encoding="utf-8") as fh: if os.path.isfile(qss_path):
qss = fh.read() with open(qss_path, "r", encoding="utf-8") as fh:
app = QApplication.instance() qss = fh.read()
if app: app = QApplication.instance()
app.setStyleSheet(qss) if app:
app.setStyleSheet(qss)
app = QApplication.instance()
if app:
need_theme = info.get("need_theme", "both")
if need_theme == "dark":
app.styleHints().setColorScheme(Qt.ColorScheme.Dark)
elif need_theme == "light":
app.styleHints().setColorScheme(Qt.ColorScheme.Light)
self.__current_theme_name = name
def clearTheme(
self,
theme: str
):
"""
Clear the current QSS stylesheet and apply the given color scheme.
Args:
theme (str): The color scheme to apply after clearing
("light", "dark", or "system").
"""
app = QApplication.instance() app = QApplication.instance()
if app: if app:
need_theme = info.get("need_theme", "both") app.setStyleSheet("")
if need_theme == "dark": self._applyColorScheme(theme)
app.styleHints().setColorScheme(Qt.ColorScheme.Dark)
elif need_theme == "light": def applyThemeOrClear(
app.styleHints().setColorScheme(Qt.ColorScheme.Light) self,
with self.__lock: name: str,
self.__current_theme_name = name fallback_theme: str = "system"
):
"""
Apply a custom theme by name, or clear to fallback if empty.
Args:
name (str): The theme name to apply, or empty to clear.
fallback_theme (str): Color scheme to use if name is empty
or if the theme fails to apply.
"""
if not name:
self.clearTheme(fallback_theme)
return
try:
self.applyTheme(name)
except Exception:
self.clearTheme(fallback_theme)
def _applyColorScheme(
self,
theme: str
):
"""
Set the Qt application color scheme.
Args:
theme (str): "dark", "light", or any other value for system default.
"""
app = QApplication.instance()
if not app:
return
if theme == "dark":
app.styleHints().setColorScheme(Qt.ColorScheme.Dark)
elif theme == "light":
app.styleHints().setColorScheme(Qt.ColorScheme.Light)
else:
app.styleHints().setColorScheme(Qt.ColorScheme.Unknown)
@staticmethod
def themeToReadable(
need_theme: str
) -> str:
"""
Convert a need_theme code to human-readable Chinese text.
Args:
need_theme (str): "dark", "light", "both", or other.
Returns:
str: Readable Chinese label.
"""
if need_theme == "dark":
return "深色"
elif need_theme == "light":
return "浅色"
elif need_theme == "both":
return "所有"
else:
return "未知"
def currentThemeName( def currentThemeName(
self self
+4 -1
View File
@@ -89,7 +89,10 @@ def readThemeInfo(
if "info.json" not in zf.namelist(): if "info.json" not in zf.namelist():
raise ValueError("无效的 .altheme: 缺少 info.json") raise ValueError("无效的 .altheme: 缺少 info.json")
with zf.open("info.json") as fh: with zf.open("info.json") as fh:
return json.loads(fh.read().decode("utf-8")) info = json.loads(fh.read().decode("utf-8"))
if "name" not in info:
raise ValueError("无效的 .altheme: info.json 缺少 'name' 字段")
return info
def wrapQssToAtheme( def wrapQssToAtheme(