Source code for GUIBRUSHR.GUI.LAYOUT.ScaleManager

"""
ScaleManager - Singleton that manages named fonts and handles responsive scaling.

When the main window is resized, all named Font objects are updated automatically,
causing every widget that references them to redraw at the new size.

All configurable values (reference height, scale limits, font families, base
sizes) are read from graphics.yaml via GraphicsConfig.
"""

import tkinter.font as tkfont
from tkinter import ttk

from GUIBRUSHR.GUI.LAYOUT.GraphicsConfig import GraphicsConfig as GC


[docs] class ScaleManager: _instance = None
[docs] @classmethod def init(cls, root): """ Initialize the singleton instance. Must be called once after tk.Tk() is created. Args: root: The root tkinter Tk widget. Returns: ScaleManager: The singleton instance. """ cls._instance = cls(root) return cls._instance
[docs] @classmethod def get(cls): """ Get the singleton instance. Returns: ScaleManager or None: The singleton instance, or None if init() has not been called. """ return cls._instance
[docs] def __init__(self, root): """ Initialize the ScaleManager with the root tkinter widget. Args: root: The root tkinter Tk widget used for resize event binding. """ self._root = root self._scale = 1.0 self._resize_timer = None ff = GC.FONT_FAMILY # general font family fa = GC.FONT_FAMILY_ACCENT # accent font family # Named fonts — base sizes come from graphics.yaml self.font_label = tkfont.Font(family=ff, size=GC.FONT_SIZE_LABEL, weight="bold") self.font_label_large = tkfont.Font(family=ff, size=GC.FONT_SIZE_LABEL_LARGE, weight="bold") self.font_label_italic = tkfont.Font(family=ff, size=GC.FONT_SIZE_LABEL_ITALIC, slant="italic") self.font_label_normal = tkfont.Font(family=ff, size=GC.FONT_SIZE_LABEL_NORMAL) self.font_button = tkfont.Font(family=fa, size=GC.FONT_SIZE_BUTTON, weight="bold") self.font_entry = tkfont.Font(family=ff, size=GC.FONT_SIZE_ENTRY) self.font_link = tkfont.Font(family=fa, size=GC.FONT_SIZE_LINK, underline=True) self.font_help_icon = tkfont.Font(family=fa, size=GC.FONT_SIZE_HELP_ICON, weight="bold") self.font_help_title = tkfont.Font(family=fa, size=GC.FONT_SIZE_HELP_TITLE, weight="bold") self.font_help_param = tkfont.Font(family=fa, size=GC.FONT_SIZE_HELP_PARAM, weight="bold") self.font_help_text = tkfont.Font(family=fa, size=GC.FONT_SIZE_HELP_TEXT) # Registry: (font_object, base_size) for scaling self._fonts = [ (self.font_label, GC.FONT_SIZE_LABEL), (self.font_label_large, GC.FONT_SIZE_LABEL_LARGE), (self.font_label_italic, GC.FONT_SIZE_LABEL_ITALIC), (self.font_label_normal, GC.FONT_SIZE_LABEL_NORMAL), (self.font_button, GC.FONT_SIZE_BUTTON), (self.font_entry, GC.FONT_SIZE_ENTRY), (self.font_link, GC.FONT_SIZE_LINK), (self.font_help_icon, GC.FONT_SIZE_HELP_ICON), (self.font_help_title, GC.FONT_SIZE_HELP_TITLE), (self.font_help_param, GC.FONT_SIZE_HELP_PARAM), (self.font_help_text, GC.FONT_SIZE_HELP_TEXT), ] # ttk styles for Notebook tabs and Treeview self._style = ttk.Style() self._update_ttk_styles() # Bind resize root.bind('<Configure>', self._on_configure)
@property def scale(self): """The current scale factor (float).""" return self._scale def _on_configure(self, event): if event.widget != self._root: return if self._resize_timer is not None: self._root.after_cancel(self._resize_timer) self._resize_timer = self._root.after(150, self._apply_scale, event.height) def _apply_scale(self, new_height): self._resize_timer = None new_scale = max(GC.MIN_SCALE, min(GC.MAX_SCALE, new_height / GC.REFERENCE_HEIGHT)) if abs(new_scale - self._scale) < 0.03: return self._scale = new_scale for font_obj, base_size in self._fonts: font_obj.configure(size=max(7, int(base_size * self._scale))) self._update_ttk_styles() def _update_ttk_styles(self): ff = GC.FONT_FAMILY tab_size = max(7, int(GC.FONT_SIZE_TAB * self._scale)) tree_size = max(7, int(GC.FONT_SIZE_TREE * self._scale)) row_height = max(20, int(GC.TREE_ROW_HEIGHT * self._scale)) self._style.configure("TNotebook.Tab", font=(ff, tab_size, "bold")) self._style.configure("Treeview", font=(ff, tree_size), rowheight=row_height) self._style.configure("Treeview.Heading", font=(ff, tab_size, "bold"))
[docs] def scaled(self, base_size): """ Get a scaled integer size for one-off uses. Args: base_size: The base font size to scale. Returns: int: The scaled size, with a minimum of 7. """ return max(7, int(base_size * self._scale))