"""主题管理:注册 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") # 浅色主题:沿用旧的 yeti(首发布兼容) LIGHT_THEME = "yeti" # 深色主题:自定义 Calman 风格 DARK_THEME = "calman_dark" # ---------------------------------------------------------------------- # Calman 风格深色主题色板(参考实测截图取色) # ---------------------------------------------------------------------- _CALMAN_DARK_COLORS = { "primary": "#343A41", # 主色改为炭灰,避免大面积亮蓝 "secondary": "#444A51", # 中性深灰(用于 header / 分组背景) "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 DARK_THEME in style.theme_names(): return theme_def = ThemeDefinition( name=DARK_THEME, themetype="dark", colors=_CALMAN_DARK_COLORS, ) style.register_theme(theme_def) # ---------------------------------------------------------------------- # 偏好持久化 # ---------------------------------------------------------------------- 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 = get_saved_theme() or LIGHT_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() 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