diff --git a/customtkinter/__init__.py b/customtkinter/__init__.py index bbc63c9..da54001 100644 --- a/customtkinter/__init__.py +++ b/customtkinter/__init__.py @@ -16,7 +16,7 @@ from .widgets.customtkinter_toplevel import CTkToplevel from .customtkinter_settings import CTkSettings from .appearance_mode_tracker import AppearanceModeTracker -from .customtkinter_theme_manager import CTkThemeManager +from .theme_manager import CTkThemeManager from distutils.version import StrictVersion as Version import tkinter diff --git a/customtkinter/customtkinter_settings.py b/customtkinter/customtkinter_settings.py index ea54f65..442a580 100644 --- a/customtkinter/customtkinter_settings.py +++ b/customtkinter/customtkinter_settings.py @@ -3,11 +3,9 @@ import sys class CTkSettings: - scaling_factor = 1 circle_font_is_ready = False hand_cursor_enabled = True preferred_drawing_method = None - radius_to_char_fine = None @classmethod @@ -42,7 +40,6 @@ class CTkSettings: @classmethod def print_settings(cls): print(f"CTkSettings current values:") - print(f"scaling_factor = {cls.scaling_factor}") print(f"circle_font_is_ready = {cls.circle_font_is_ready}") print(f"hand_cursor_enabled = {cls.hand_cursor_enabled}") print(f"preferred_drawing_method = {cls.preferred_drawing_method}") diff --git a/customtkinter/scaling_tracker.py b/customtkinter/scaling_tracker.py new file mode 100644 index 0000000..851dd7e --- /dev/null +++ b/customtkinter/scaling_tracker.py @@ -0,0 +1,100 @@ +import tkinter +import sys + + +class ScalingTracker: + + window_widgets_dict = {} # contains window objects as keys with list of widget callbacks as elements + + window_dpi_scaling_dict = {} # contains window objects as keys and corresponding DPI values + user_scaling = 1 # scale change of all widgets and windows + + update_loop_running = False + + @classmethod + def get_widget_scaling(cls, widget): + window_root = cls.get_window_root_of_widget(widget) + return cls.window_dpi_scaling_dict[window_root] * cls.user_scaling + + @classmethod + def get_window_scaling(cls, window): + return cls.window_dpi_scaling_dict[window] * cls.user_scaling + + @classmethod + def set_user_scaling(cls, user_scaling_factor): + cls.user_scaling = user_scaling_factor + cls.update_scaling_callbacks() + + @classmethod + def get_window_root_of_widget(cls, widget): + current_widget = widget + + while isinstance(current_widget, tkinter.Tk) is False and\ + isinstance(current_widget, tkinter.Toplevel) is False: + current_widget = current_widget.master + + return current_widget + + @classmethod + def update_scaling_callbacks(cls): + for window, callback_list in cls.window_widgets_dict.items(): + for callback in callback_list: + callback(cls.window_dpi_scaling_dict[window]) + + @classmethod + def add_widget(cls, widget_callback, widget): + window_root = cls.get_window_root_of_widget(widget) + + if window_root not in cls.window_widgets_dict: + cls.window_widgets_dict[window_root] = [widget_callback] + else: + cls.window_widgets_dict[window_root].append(widget_callback) + + if window_root not in cls.window_dpi_scaling_dict: + cls.window_dpi_scaling_dict[window_root] = cls.get_window_dpi_value(window_root) + + if not cls.update_loop_running: + window_root.after(100, cls.check_dpi_scaling) + cls.update_loop_running = True + + @classmethod + def add_window(cls, window_callback, window): + if window not in cls.window_widgets_dict: + cls.window_widgets_dict[window] = [window_callback] + else: + cls.window_widgets_dict[window].append(window_callback) + + if window not in cls.window_dpi_scaling_dict: + cls.window_dpi_scaling_dict[window] = cls.get_window_dpi_value(window) + + @classmethod + def get_window_dpi_value(cls, window): + if sys.platform == "darwin": + return 1 # scaling works automatically on macOS + + elif sys.platform.startswith("win"): + from ctypes import windll, pointer, wintypes + + DPI100pc = 96 # DPI 96 is 100% scaling + DPI_type = 0 # MDT_EFFECTIVE_DPI = 0, MDT_ANGULAR_DPI = 1, MDT_RAW_DPI = 2 + window_hwnd = wintypes.HWND(window) + monitor_handle = windll.user32.MonitorFromWindow(window_hwnd, wintypes.DWORD(2)) # MONITOR_DEFAULTTONEAREST = 2 + x_dpi, y_dpi = wintypes.UINT(), wintypes.UINT() + windll.shcore.GetDpiForMonitor(monitor_handle, DPI_type, pointer(x_dpi), pointer(y_dpi)) + return (x_dpi.value + y_dpi.value) / (2 * DPI100pc) + + else: + return 1 + + @classmethod + def check_dpi_scaling(cls): + # find an existing tkinter object for the next call of .after() + for root_tk in cls.window_widgets_dict.keys(): + try: + root_tk.after(500, cls.check_dpi_scaling) + return + except Exception: + continue + + cls.update_loop_running = False + diff --git a/customtkinter/customtkinter_theme_manager.py b/customtkinter/theme_manager.py similarity index 100% rename from customtkinter/customtkinter_theme_manager.py rename to customtkinter/theme_manager.py diff --git a/customtkinter/widgets/customtkinter_button.py b/customtkinter/widgets/customtkinter_button.py index e8f73c9..9e39ae1 100644 --- a/customtkinter/widgets/customtkinter_button.py +++ b/customtkinter/widgets/customtkinter_button.py @@ -6,9 +6,10 @@ from .customtkinter_tk import CTk from .customtkinter_frame import CTkFrame from .customtkinter_canvas import CTkCanvas from customtkinter.appearance_mode_tracker import AppearanceModeTracker -from ..customtkinter_theme_manager import CTkThemeManager +from ..theme_manager import CTkThemeManager from ..customtkinter_settings import CTkSettings from ..customtkinter_draw_engine import CTkDrawEngine +from ..scaling_tracker import ScalingTracker class CTkButton(tkinter.Frame): @@ -61,6 +62,9 @@ class CTkButton(tkinter.Frame): AppearanceModeTracker.add(self.set_appearance_mode, self) self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark" + ScalingTracker.add_widget(self.set_scaling, self) + self.scaling = ScalingTracker.get_widget_scaling(self) + self.configure_basic_grid() # color variables @@ -72,7 +76,7 @@ class CTkButton(tkinter.Frame): # shape and size self.width = width self.height = height - self.configure(width=self.width, height=self.height) + self.configure(width=self.width * self.scaling, height=self.height * self.scaling) self.corner_radius = CTkThemeManager.theme["shape"]["button_corner_radius"] if corner_radius == "default_theme" else corner_radius self.border_width = CTkThemeManager.theme["shape"]["button_border_width"] if border_width == "default_theme" else border_width @@ -95,8 +99,8 @@ class CTkButton(tkinter.Frame): self.canvas = CTkCanvas(master=self, highlightthickness=0, - width=self.width, - height=self.height) + width=self.width * self.scaling, + height=self.height * self.scaling) self.canvas.grid(row=0, column=0, rowspan=2, columnspan=2, sticky="nsew") self.draw_engine = CTkDrawEngine(self.canvas, CTkSettings.preferred_drawing_method) @@ -414,3 +418,9 @@ class CTkButton(tkinter.Frame): self.bg_color = self.master.cget("bg") self.draw() + + def set_scaling(self, scaling_factor): + self.scaling = scaling_factor + self.configure(width=self.width * self.scaling, height=self.height * self.scaling) + self.canvas.configure(width=self.width * self.scaling, height=self.height * self.scaling) + self.draw(no_color_updates=True) diff --git a/customtkinter/widgets/customtkinter_checkbox.py b/customtkinter/widgets/customtkinter_checkbox.py index b59d593..a6cfabb 100644 --- a/customtkinter/widgets/customtkinter_checkbox.py +++ b/customtkinter/widgets/customtkinter_checkbox.py @@ -6,7 +6,7 @@ from .customtkinter_tk import CTk from .customtkinter_frame import CTkFrame from .customtkinter_canvas import CTkCanvas from ..appearance_mode_tracker import AppearanceModeTracker -from ..customtkinter_theme_manager import CTkThemeManager +from ..theme_manager import CTkThemeManager from ..customtkinter_settings import CTkSettings from ..customtkinter_draw_engine import CTkDrawEngine diff --git a/customtkinter/widgets/customtkinter_entry.py b/customtkinter/widgets/customtkinter_entry.py index c5aee99..ecb7b71 100644 --- a/customtkinter/widgets/customtkinter_entry.py +++ b/customtkinter/widgets/customtkinter_entry.py @@ -5,7 +5,7 @@ from .customtkinter_tk import CTk from .customtkinter_frame import CTkFrame from .customtkinter_canvas import CTkCanvas from ..appearance_mode_tracker import AppearanceModeTracker -from ..customtkinter_theme_manager import CTkThemeManager +from ..theme_manager import CTkThemeManager from ..customtkinter_settings import CTkSettings from ..customtkinter_draw_engine import CTkDrawEngine diff --git a/customtkinter/widgets/customtkinter_frame.py b/customtkinter/widgets/customtkinter_frame.py index 11fda7b..b9a73a5 100644 --- a/customtkinter/widgets/customtkinter_frame.py +++ b/customtkinter/widgets/customtkinter_frame.py @@ -4,7 +4,7 @@ import tkinter.ttk as ttk from .customtkinter_tk import CTk from .customtkinter_canvas import CTkCanvas from ..appearance_mode_tracker import AppearanceModeTracker -from ..customtkinter_theme_manager import CTkThemeManager +from ..theme_manager import CTkThemeManager from ..customtkinter_settings import CTkSettings from ..customtkinter_draw_engine import CTkDrawEngine diff --git a/customtkinter/widgets/customtkinter_input_dialog.py b/customtkinter/widgets/customtkinter_input_dialog.py index 6172f33..93fc63d 100644 --- a/customtkinter/widgets/customtkinter_input_dialog.py +++ b/customtkinter/widgets/customtkinter_input_dialog.py @@ -7,7 +7,7 @@ from .customtkinter_frame import CTkFrame from .customtkinter_toplevel import CTkToplevel from .customtkinter_button import CTkButton from ..appearance_mode_tracker import AppearanceModeTracker -from ..customtkinter_theme_manager import CTkThemeManager +from ..theme_manager import CTkThemeManager class CTkInputDialog: diff --git a/customtkinter/widgets/customtkinter_label.py b/customtkinter/widgets/customtkinter_label.py index 02e3c68..f43ae4d 100644 --- a/customtkinter/widgets/customtkinter_label.py +++ b/customtkinter/widgets/customtkinter_label.py @@ -5,7 +5,7 @@ from .customtkinter_tk import CTk from .customtkinter_frame import CTkFrame from .customtkinter_canvas import CTkCanvas from ..appearance_mode_tracker import AppearanceModeTracker -from ..customtkinter_theme_manager import CTkThemeManager +from ..theme_manager import CTkThemeManager from ..customtkinter_settings import CTkSettings from ..customtkinter_draw_engine import CTkDrawEngine diff --git a/customtkinter/widgets/customtkinter_progressbar.py b/customtkinter/widgets/customtkinter_progressbar.py index 572f6f3..8868450 100644 --- a/customtkinter/widgets/customtkinter_progressbar.py +++ b/customtkinter/widgets/customtkinter_progressbar.py @@ -6,7 +6,7 @@ from .customtkinter_tk import CTk from .customtkinter_frame import CTkFrame from .customtkinter_canvas import CTkCanvas from ..appearance_mode_tracker import AppearanceModeTracker -from ..customtkinter_theme_manager import CTkThemeManager +from ..theme_manager import CTkThemeManager from ..customtkinter_draw_engine import CTkDrawEngine from ..customtkinter_settings import CTkSettings diff --git a/customtkinter/widgets/customtkinter_radiobutton.py b/customtkinter/widgets/customtkinter_radiobutton.py index b3943c9..e5cb1a4 100644 --- a/customtkinter/widgets/customtkinter_radiobutton.py +++ b/customtkinter/widgets/customtkinter_radiobutton.py @@ -6,7 +6,7 @@ from .customtkinter_tk import CTk from .customtkinter_frame import CTkFrame from .customtkinter_canvas import CTkCanvas from ..appearance_mode_tracker import AppearanceModeTracker -from ..customtkinter_theme_manager import CTkThemeManager +from ..theme_manager import CTkThemeManager from ..customtkinter_settings import CTkSettings from ..customtkinter_draw_engine import CTkDrawEngine diff --git a/customtkinter/widgets/customtkinter_slider.py b/customtkinter/widgets/customtkinter_slider.py index 4e57759..27fb88e 100644 --- a/customtkinter/widgets/customtkinter_slider.py +++ b/customtkinter/widgets/customtkinter_slider.py @@ -6,7 +6,7 @@ from .customtkinter_tk import CTk from .customtkinter_frame import CTkFrame from .customtkinter_canvas import CTkCanvas from ..appearance_mode_tracker import AppearanceModeTracker -from ..customtkinter_theme_manager import CTkThemeManager +from ..theme_manager import CTkThemeManager from ..customtkinter_settings import CTkSettings from ..customtkinter_draw_engine import CTkDrawEngine diff --git a/customtkinter/widgets/customtkinter_switch.py b/customtkinter/widgets/customtkinter_switch.py index b6d9c66..96a760d 100644 --- a/customtkinter/widgets/customtkinter_switch.py +++ b/customtkinter/widgets/customtkinter_switch.py @@ -6,7 +6,7 @@ from .customtkinter_tk import CTk from .customtkinter_frame import CTkFrame from .customtkinter_canvas import CTkCanvas from ..appearance_mode_tracker import AppearanceModeTracker -from ..customtkinter_theme_manager import CTkThemeManager +from ..theme_manager import CTkThemeManager from ..customtkinter_settings import CTkSettings from ..customtkinter_draw_engine import CTkDrawEngine diff --git a/customtkinter/widgets/customtkinter_tk.py b/customtkinter/widgets/customtkinter_tk.py index 9a9d1b1..fd1e155 100644 --- a/customtkinter/widgets/customtkinter_tk.py +++ b/customtkinter/widgets/customtkinter_tk.py @@ -6,7 +6,7 @@ import platform import ctypes from ..appearance_mode_tracker import AppearanceModeTracker -from ..customtkinter_theme_manager import CTkThemeManager +from ..theme_manager import CTkThemeManager class CTk(tkinter.Tk): diff --git a/customtkinter/widgets/customtkinter_toplevel.py b/customtkinter/widgets/customtkinter_toplevel.py index 6ff801f..dfea95e 100644 --- a/customtkinter/widgets/customtkinter_toplevel.py +++ b/customtkinter/widgets/customtkinter_toplevel.py @@ -6,7 +6,7 @@ import platform import ctypes from ..appearance_mode_tracker import AppearanceModeTracker -from ..customtkinter_theme_manager import CTkThemeManager +from ..theme_manager import CTkThemeManager class CTkToplevel(tkinter.Toplevel): diff --git a/test/test_ctk_toplevel.py b/test/test_ctk_toplevel.py index e62bc41..e66ba7a 100644 --- a/test/test_ctk_toplevel.py +++ b/test/test_ctk_toplevel.py @@ -15,6 +15,8 @@ class ExampleApp(customtkinter.CTk): window = customtkinter.CTkToplevel(self) window.geometry("400x200") + print(window.master.winfo_class()) + label = customtkinter.CTkLabel(window, text="CTkToplevel window") label.pack(side="top", fill="both", expand=True, padx=40, pady=40)