Checkpoint before more window sizing work

This commit is contained in:
2026-05-10 16:29:49 +02:00
parent 6ab42a2303
commit 8489cd7459
15 changed files with 2071 additions and 156 deletions

97
ui_theme.py Normal file
View 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)