import tkinter as tk from tkinter import font, ttk from typing import Dict, Tuple THEME_DARK: str = "black" PADX: Tuple[int, int] = (0, 5) PADY: Tuple[int, int] = (0, 5) FRAME_PAD: int = 5 DIALOG_PAD: int = 5 class Styles: tooltip: str = "Tooltip.TLabel" tooltip_frame: str = "Tooltip.TFrame" service_checkbutton: str = "Service.TCheckbutton" picker_button: str = "Picker.TButton" no_alert: str = "NAlert.TButton" green_alert: str = "GAlert.TButton" red_alert: str = "RAlert.TButton" yellow_alert: str = "YAlert.TButton" class Colors: disabledfg: str = "DarkGrey" frame: str = "#424242" dark: str = "#222222" darker: str = "#121212" darkest: str = "black" lighter: str = "#626262" lightest: str = "#ffffff" selectbg: str = "#4a6984" selectfg: str = "#ffffff" white: str = "white" black: str = "black" listboxbg: str = "#f2f1f0" def load(style: ttk.Style) -> None: style.theme_create( THEME_DARK, "clam", { ".": { "configure": { "background": Colors.frame, "foreground": Colors.white, "bordercolor": Colors.darkest, "darkcolor": Colors.dark, "lightcolor": Colors.lighter, "troughcolor": Colors.darker, "selectbackground": Colors.selectbg, "selectforeground": Colors.selectfg, "selectborderwidth": 0, "font": "TkDefaultFont", }, "map": { "background": [ ("disabled", Colors.frame), ("active", Colors.lighter), ], "foreground": [("disabled", Colors.disabledfg)], "selectbackground": [("!focus", Colors.darkest)], "selectforeground": [("!focus", Colors.white)], }, }, "TButton": { "configure": { "width": 8, "padding": (5, 1), "relief": tk.RAISED, "anchor": tk.CENTER, }, "map": { "relief": [("pressed", tk.SUNKEN)], "shiftrelief": [("pressed", 1)], }, }, "TMenubutton": {"configure": {"padding": (5, 1), "relief": tk.RAISED}}, "TCheckbutton": { "configure": { "indicatorbackground": Colors.white, "indicatormargin": (1, 1, 4, 1), } }, "TRadiobutton": { "configure": { "indicatorbackground": Colors.white, "indicatormargin": (1, 1, 4, 1), } }, "TEntry": { "configure": { "fieldbackground": Colors.white, "foreground": Colors.black, "padding": (2, 0), }, "map": {"fieldbackground": [("disabled", Colors.frame)]}, }, "TSpinbox": { "configure": { "fieldbackground": Colors.white, "foreground": Colors.black, "padding": (2, 0), }, "map": {"fieldbackground": [("disabled", Colors.frame)]}, }, "TCombobox": { "configure": { "fieldbackground": Colors.white, "foreground": Colors.black, "padding": (2, 0), } }, "TLabelframe": {"configure": {"relief": tk.GROOVE}}, "TNotebook.Tab": { "configure": {"padding": (6, 2, 6, 2)}, "map": {"background": [("selected", Colors.lighter)]}, }, "Treeview": { "configure": { "fieldbackground": Colors.white, "background": Colors.white, "foreground": Colors.black, }, "map": { "background": [("selected", Colors.selectbg)], "foreground": [("selected", Colors.selectfg)], }, }, Styles.tooltip: { "configure": {"justify": tk.LEFT, "relief": tk.SOLID, "borderwidth": 0} }, Styles.tooltip_frame: {"configure": {}}, Styles.service_checkbutton: { "configure": { "background": Colors.listboxbg, "foreground": Colors.black, } }, }, ) def theme_change_menu(event: tk.Event) -> None: if not isinstance(event.widget, tk.Menu): return style_menu(event.widget) def style_menu(widget: tk.Widget) -> None: style = ttk.Style() bg = style.lookup(".", "background") fg = style.lookup(".", "foreground") abg = style.lookup(".", "lightcolor") if not abg: abg = bg widget.config( background=bg, foreground=fg, activebackground=abg, activeforeground=fg, bd=0 ) def style_listbox(widget: tk.Widget) -> None: style = ttk.Style() bg = style.lookup(".", "background") fg = style.lookup(".", "foreground") bc = style.lookup(".", "bordercolor") if not bc: bc = "black" widget.config( background=bg, foreground=fg, highlightthickness=1, highlightcolor=bc, highlightbackground=bc, bd=0, ) def _alert_style(style: ttk.Style, name: str, background: str): style.configure( name, background=background, padding=0, relief=tk.RIDGE, borderwidth=1, font="TkDefaultFont", foreground="black", highlightbackground="white", ) style.map(name, background=[("!active", background), ("active", "white")]) def theme_change(event: tk.Event) -> None: style = ttk.Style() style.configure(Styles.picker_button, font="TkSmallCaptionFont") style.configure( Styles.no_alert, padding=0, relief=tk.RIDGE, borderwidth=1, font="TkDefaultFont" ) _alert_style(style, Styles.green_alert, "green") _alert_style(style, Styles.yellow_alert, "yellow") _alert_style(style, Styles.red_alert, "red") def scale_fonts(fonts_size: Dict[str, int], scale: float) -> None: for name in font.names(): f = font.nametofont(name) if name in fonts_size: if name == "TkSmallCaptionFont": f.config(size=int(fonts_size[name] * scale * 8 / 9)) else: f.config(size=int(fonts_size[name] * scale))