Files
pqAutomationApp/app/views/theme_manager.py

173 lines
4.8 KiB
Python
Raw Permalink 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.
"""主题管理:注册 Calman 风格深色主题 + 提供运行时切换。
主题在启动时通过 ``apply_initial_theme(root_style)`` 注入到 ttkbootstrap
当前选择持久化到 ``settings/ui_preferences.json``。运行时调用
``toggle_theme(root_style)`` / ``set_theme(root_style, name)`` 可即时切换,
并自动重新调用 ``apply_modern_styles()`` 让自定义样式跟上新色板。
"""
from __future__ import annotations
import json
from pathlib import Path
from typing import Optional
from ttkbootstrap.style import Style, ThemeDefinition
from app.views.modern_styles import apply_modern_styles
_PREFS_PATH = Path("settings/ui_preferences.json")
# 浅色主题:自定义轻量蓝灰色板,恢复旧版浅色观感
LIGHT_THEME = "calman_light"
# 深色主题:自定义 Calman 风格
DARK_THEME = "calman_dark"
_LEGACY_LIGHT_THEMES = {"yeti"}
_CALMAN_LIGHT_COLORS = {
"primary": "#1755a6",
"secondary": "#2B6CB0",
"success": "#2F9E44",
"info": "#247BA0",
"warning": "#C98700",
"danger": "#CC3300",
"light": "#F7FAFC",
"dark": "#1F2A36",
"bg": "#F5F8FB",
"fg": "#1F2933",
"selectbg": "#2B6CB0",
"selectfg": "#FFFFFF",
"border": "#C8D4E3",
"inputfg": "#243240",
"inputbg": "#FFFFFF",
"active": "#D9E6F2",
}
# ----------------------------------------------------------------------
# Calman 风格深色主题色板
# ----------------------------------------------------------------------
_CALMAN_DARK_COLORS = {
# "primary": "#2A2F36",
# "secondary": "#444A51",
"primary": "#6FAFCC",
"secondary": "#AEAEAE",
"success": "#4FB960",
"info": "#6FAFCC",
"warning": "#F2A93B",
"danger": "#E0524A",
"light": "#BFC6CE", # 高亮文本
"dark": "#0D1014", # 最深背景(侧栏底色)
"bg": "#1B1F24", # 主窗口背景
"fg": "#E4E8EE", # 主文本颜色
"selectbg": "#5A6169",
"selectfg": "#E4E8EE",
"border": "#2A2F36",
"inputfg": "#E4E8EE",
"inputbg": "#24292F",
"active": "#2A2F36",
}
def register_themes() -> None:
"""把自定义深色主题注册到 ttkbootstrap可重复调用幂等"""
style = Style()
if LIGHT_THEME not in style.theme_names():
light_def = ThemeDefinition(
name=LIGHT_THEME,
themetype="light",
colors=_CALMAN_LIGHT_COLORS,
)
style.register_theme(light_def)
if DARK_THEME in style.theme_names():
return
dark_def = ThemeDefinition(
name=DARK_THEME,
themetype="dark",
colors=_CALMAN_DARK_COLORS,
)
style.register_theme(dark_def)
def _normalize_theme_name(name: Optional[str]) -> str:
if not name or name in _LEGACY_LIGHT_THEMES:
return LIGHT_THEME
return name
# ----------------------------------------------------------------------
# 偏好持久化
# ----------------------------------------------------------------------
def _read_prefs() -> dict:
try:
return json.loads(_PREFS_PATH.read_text(encoding="utf-8"))
except (FileNotFoundError, json.JSONDecodeError):
return {}
def _write_prefs(data: dict) -> None:
try:
_PREFS_PATH.parent.mkdir(parents=True, exist_ok=True)
_PREFS_PATH.write_text(
json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8"
)
except OSError:
# 写入失败不应影响 UI
pass
def get_saved_theme() -> Optional[str]:
return _read_prefs().get("theme")
def save_theme(name: str) -> None:
prefs = _read_prefs()
prefs["theme"] = name
_write_prefs(prefs)
# ----------------------------------------------------------------------
# 主题应用 / 切换
# ----------------------------------------------------------------------
def apply_initial_theme() -> str:
"""启动时调用:注册主题 + 加载偏好 + 切到对应主题。
返回最终生效的主题名。
"""
register_themes()
name = _normalize_theme_name(get_saved_theme())
style = Style()
if name not in style.theme_names():
name = LIGHT_THEME
style.theme_use(name)
apply_modern_styles()
return name
def set_theme(name: str) -> str:
"""切换到指定主题,持久化偏好,并刷新自定义样式。"""
register_themes()
name = _normalize_theme_name(name)
style = Style()
if name not in style.theme_names():
name = LIGHT_THEME
style.theme_use(name)
apply_modern_styles()
save_theme(name)
return name
def toggle_theme() -> str:
"""在浅 / 深之间切换。返回新主题名。"""
style = Style()
current = style.theme.name
target = DARK_THEME if current != DARK_THEME else LIGHT_THEME
return set_theme(target)
def is_dark() -> bool:
return Style().theme.name == DARK_THEME