Files
CCMA/src/ccma/ui/icons.py
T

80 lines
3.0 KiB
Python

from __future__ import annotations
import tkinter as tk
from importlib import import_module, resources
from pathlib import Path
from tkinter import ttk
class IconStore:
"""Load and cache Material Design icons used by ttk widgets."""
def __init__(self, master: tk.Misc):
self.master = master
self.style = ttk.Style(master)
self._cache: dict[tuple[str, int, str], tk.PhotoImage | None] = {}
self._mat_icon = self._load_mat_icon_class()
def get(self, name: str, size: int = 16, color: str | None = None) -> tk.PhotoImage | None:
resolved_color = color or self._theme_color()
key = (name, size, resolved_color)
if key not in self._cache:
self._cache[key] = self._load(name, size, resolved_color)
return self._cache[key]
def clear(self) -> None:
self._cache.clear()
def _load(self, name: str, size: int, color: str) -> tk.PhotoImage | None:
if self._mat_icon:
try:
return self._mat_icon(name, size=size, color=color).image
except Exception:
pass
return self._load_asset_fallback(name, size)
def _load_asset_fallback(self, name: str, size: int) -> tk.PhotoImage | None:
for module_name in ("ttkbootstrap_icons_mat", "ttkbootstrap_icons"):
try:
root = resources.files(import_module(module_name))
except (ImportError, TypeError):
continue
candidates = {name, name.replace("-", "_"), name.replace("_", "-")}
for candidate in candidates:
for extension in (".png", ".gif"):
try:
path = Path(next(root.rglob(f"{candidate}{extension}")))
image = tk.PhotoImage(master=self.master, file=str(path))
if size and image.width() > size:
factor = max(1, round(image.width() / size))
image = image.subsample(factor, factor)
return image
except (StopIteration, tk.TclError, OSError):
continue
return None
@staticmethod
def _load_mat_icon_class():
try:
module = import_module("ttkbootstrap_icons_mat")
except ImportError:
return None
return getattr(module, "MatIcon", None)
def _theme_color(self) -> str:
try:
theme_name = self.style.theme_use().lower()
except tk.TclError:
theme_name = ""
if "dark" in theme_name:
return "#eeeeee"
if "light" in theme_name:
return "#313131"
background = self.style.lookup("TFrame", "background") or "#ffffff"
try:
red, green, blue = self.master.winfo_rgb(background)
luminance = (0.2126 * red + 0.7152 * green + 0.0722 * blue) / 65535
return "#eeeeee" if luminance < 0.5 else "#313131"
except tk.TclError:
return "#313131"