98 lines
2.8 KiB
Python
98 lines
2.8 KiB
Python
"""External UI theme loader for the warehouse desktop application.
|
|
|
|
The module reads ``ui_theme.json`` from the workspace root and exposes a few
|
|
helpers to resolve fonts, colors, paddings and section-specific configuration
|
|
without hardcoding presentation details inside each window module.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from functools import lru_cache
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
THEME_PATH = Path(__file__).with_name("ui_theme.json")
|
|
|
|
|
|
@lru_cache(maxsize=1)
|
|
def load_theme() -> dict[str, Any]:
|
|
"""Load the external JSON theme, returning an empty dict on failure."""
|
|
|
|
try:
|
|
return json.loads(THEME_PATH.read_text(encoding="utf-8"))
|
|
except Exception:
|
|
return {}
|
|
|
|
|
|
def reload_theme() -> dict[str, Any]:
|
|
"""Clear cache and reload the theme from disk."""
|
|
|
|
load_theme.cache_clear()
|
|
return load_theme()
|
|
|
|
|
|
def theme_section(name: str, default: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
"""Return one top-level theme section as a dictionary."""
|
|
|
|
theme = load_theme()
|
|
value = theme.get(name)
|
|
if isinstance(value, dict):
|
|
return value
|
|
return dict(default or {})
|
|
|
|
|
|
def theme_value(section: dict[str, Any], key: str, default: Any = None) -> Any:
|
|
"""Return a scalar theme value from a section."""
|
|
|
|
return section.get(key, default)
|
|
|
|
|
|
def theme_color(section: dict[str, Any], key: str, default: Any = None) -> Any:
|
|
"""Return a CTk-compatible color value (string or light/dark tuple)."""
|
|
|
|
value = section.get(key, default)
|
|
if isinstance(value, list) and len(value) == 2:
|
|
return tuple(value)
|
|
return value
|
|
|
|
|
|
def theme_padding(section: dict[str, Any], key: str, default: tuple[int, ...]) -> tuple[int, ...]:
|
|
"""Return tuple-like padding values from a JSON list."""
|
|
|
|
value = section.get(key)
|
|
if isinstance(value, list):
|
|
try:
|
|
return tuple(int(v) for v in value)
|
|
except Exception:
|
|
return default
|
|
return default
|
|
|
|
|
|
def theme_font(section: dict[str, Any], key: str, default: tuple[Any, ...]) -> tuple[Any, ...]:
|
|
"""Resolve a Tk font tuple from JSON."""
|
|
|
|
spec = section.get(key)
|
|
if not isinstance(spec, dict):
|
|
return default
|
|
|
|
family = spec.get("family", default[0] if default else "Segoe UI")
|
|
size = int(spec.get("size", default[1] if len(default) > 1 else 10))
|
|
|
|
parts: list[Any] = [family, size]
|
|
|
|
weight = str(spec.get("weight", "")).strip().lower()
|
|
if weight in {"bold", "normal"} and weight != "normal":
|
|
parts.append(weight)
|
|
|
|
slant = str(spec.get("slant", "")).strip().lower()
|
|
if slant == "italic":
|
|
parts.append(slant)
|
|
|
|
if bool(spec.get("underline", False)):
|
|
parts.append("underline")
|
|
if bool(spec.get("overstrike", False)):
|
|
parts.append("overstrike")
|
|
|
|
return tuple(parts)
|