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"