Checkpoint before more window sizing work
This commit is contained in:
97
ui_theme.py
Normal file
97
ui_theme.py
Normal file
@@ -0,0 +1,97 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user