"""主题管理:注册 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