97 lines
		
	
	
		
			3.1 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			97 lines
		
	
	
		
			3.1 KiB
		
	
	
	
		
			Python
		
	
	
	
| """Theme and window helpers."""
 | |
| 
 | |
| from __future__ import annotations
 | |
| 
 | |
| import platform
 | |
| from tkinter import ttk
 | |
| 
 | |
| try:
 | |
|     import winreg
 | |
| except Exception:  # pragma: no cover - platform-specific
 | |
|     winreg = None  # type: ignore
 | |
| 
 | |
| 
 | |
| class ThemeMixin:
 | |
|     """Provides theme handling utilities for the main application."""
 | |
| 
 | |
|     theme: str
 | |
|     style: ttk.Style
 | |
|     scale_style: str
 | |
| 
 | |
|     def init_theme(self) -> None:
 | |
|         """Initialise ttk style handling and apply the detected theme."""
 | |
|         self.style = ttk.Style()
 | |
|         self.style.theme_use("clam")
 | |
| 
 | |
|         self.theme = "light"
 | |
|         self.apply_theme(self.detect_system_theme())
 | |
| 
 | |
|     def apply_theme(self, mode: str) -> None:
 | |
|         """Apply light/dark theme including widget palette."""
 | |
|         mode = (mode or "light").lower()
 | |
|         self.theme = "dark" if mode == "dark" else "light"
 | |
| 
 | |
|         self.scale_style = "Horizontal.TScale"
 | |
| 
 | |
|         if self.theme == "dark":
 | |
|             bg, fg = "#0f0f10", "#f1f1f1"
 | |
|             status_fg = "#f5f5f5"
 | |
|         else:
 | |
|             bg, fg = "#ededf2", "#202020"
 | |
|             status_fg = "#1c1c1c"
 | |
|         self.root.configure(bg=bg)  # type: ignore[attr-defined]
 | |
| 
 | |
|         s = self.style
 | |
|         s.configure("TFrame", background=bg)
 | |
|         s.configure("TLabel", background=bg, foreground=fg, font=("Segoe UI", 10))
 | |
|         s.configure(
 | |
|             "TButton", padding=8, relief="flat", background="#e0e0e0", foreground=fg, font=("Segoe UI", 10)
 | |
|         )
 | |
|         s.map("TButton", background=[("active", "#d0d0d0")])
 | |
| 
 | |
|         button_refresher = getattr(self, "_refresh_toolbar_buttons_theme", None)
 | |
|         if callable(button_refresher):
 | |
|             button_refresher()
 | |
| 
 | |
|         nav_refresher = getattr(self, "_refresh_navigation_buttons_theme", None)
 | |
|         if callable(nav_refresher):
 | |
|             nav_refresher()
 | |
| 
 | |
|         status_refresher = getattr(self, "_refresh_status_palette", None)
 | |
|         if callable(status_refresher) and hasattr(self, "status"):
 | |
|             status_refresher(status_fg)
 | |
| 
 | |
|     def detect_system_theme(self) -> str:
 | |
|         """Best-effort detection of the OS theme preference."""
 | |
|         try:
 | |
|             if platform.system() == "Windows" and winreg is not None:
 | |
|                 key = winreg.OpenKey(
 | |
|                     winreg.HKEY_CURRENT_USER,
 | |
|                     r"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize",
 | |
|                 )
 | |
|                 value, _ = winreg.QueryValueEx(key, "AppsUseLightTheme")
 | |
|                 return "light" if int(value) == 1 else "dark"
 | |
|         except Exception:
 | |
|             pass
 | |
|         return "light"
 | |
| 
 | |
|     def bring_to_front(self) -> None:
 | |
|         """Try to focus the window and raise it to the foreground."""
 | |
|         try:
 | |
|             self.root.lift()
 | |
|             self.root.focus_force()
 | |
|             self.root.attributes("-topmost", True)
 | |
|             self.root.update()
 | |
|             self.root.attributes("-topmost", False)
 | |
|         except Exception:
 | |
|             pass
 | |
| 
 | |
|     def toggle_theme(self) -> None:
 | |
|         """Toggle between light and dark themes."""
 | |
|         next_mode = "dark" if self.theme == "light" else "light"
 | |
|         self.apply_theme(next_mode)
 | |
|         self.update_preview()  # type: ignore[attr-defined]
 | |
| 
 | |
| 
 | |
| __all__ = ["ThemeMixin"]
 |