diff --git a/customtkinter/__init__.py b/customtkinter/__init__.py index 0b2ec47..bb88c14 100644 --- a/customtkinter/__init__.py +++ b/customtkinter/__init__.py @@ -9,32 +9,30 @@ from .appearance_mode_tracker import AppearanceModeTracker from .theme_manager import ThemeManager from .scaling_tracker import ScalingTracker from .font_manager import FontManager - -Settings.init_font_character_mapping() -Settings.init_drawing_method() +from .draw_engine import DrawEngine AppearanceModeTracker.init_appearance_mode() - ThemeManager.load_theme("blue") - FontManager.init_font_manager() +# determine draw method based on current platform +if sys.platform == "darwin": + DrawEngine.preferred_drawing_method = "polygon_shapes" +else: + DrawEngine.preferred_drawing_method = "font_shapes" + # load Roboto fonts script_directory = os.path.dirname(os.path.abspath(__file__)) FontManager.load_font(os.path.join(script_directory, "assets", "fonts", "Roboto", "Roboto-Regular.ttf")) FontManager.load_font(os.path.join(script_directory, "assets", "fonts", "Roboto", "Roboto-Medium.ttf")) # load font necessary for rendering the widgets on Windows, Linux -if FontManager.load_font(os.path.join(script_directory, "assets", "fonts", "CustomTkinter_shapes_font-fine.otf")) is True: - Settings.circle_font_is_ready = True -else: - Settings.circle_font_is_ready = False - - if Settings.preferred_drawing_method == "font_shapes": +if FontManager.load_font(os.path.join(script_directory, "assets", "fonts", "CustomTkinter_shapes_font-fine.otf")) is False: + if DrawEngine.preferred_drawing_method == "font_shapes": sys.stderr.write("customtkinter.__init__ warning: " + "Preferred drawing method 'font_shapes' can not be used because the font file could not be loaded.\n" + "Using 'circle_shapes' instead. The rendering quality will be bad!") - Settings.preferred_drawing_method = "circle_shapes" + DrawEngine.preferred_drawing_method = "circle_shapes" # import widgets from .widgets.ctk_button import CTkButton @@ -69,10 +67,10 @@ def set_default_color_theme(color_string): ThemeManager.load_theme(color_string) -def deactivate_dpi_awareness(deactivate_awareness: bool): - Settings.deactivate_automatic_dpi_awareness = deactivate_awareness - - def set_user_scaling(scaling_value: float): ScalingTracker.set_spacing_scaling(scaling_value) ScalingTracker.set_widget_scaling(scaling_value) + + +def deactivate_automatic_dpi_awareness(): + ScalingTracker.deactivate_automatic_dpi_awareness = False diff --git a/customtkinter/appearance_mode_tracker.py b/customtkinter/appearance_mode_tracker.py index d1939b7..607ccf4 100644 --- a/customtkinter/appearance_mode_tracker.py +++ b/customtkinter/appearance_mode_tracker.py @@ -98,7 +98,7 @@ class AppearanceModeTracker: # find an existing tkinter.Tk object for the next call of .after() for root_tk in cls.root_tk_list: try: - root_tk.after(200, cls.update) + root_tk.after(500, cls.update) return except Exception: continue diff --git a/customtkinter/assets/themes/blue.json b/customtkinter/assets/themes/blue.json index 24634f9..597adae 100644 --- a/customtkinter/assets/themes/blue.json +++ b/customtkinter/assets/themes/blue.json @@ -51,7 +51,7 @@ "radiobutton_border_width_unchecked": 3, "radiobutton_border_width_checked": 6, "entry_border_width": 2, - "frame_corner_radius": 10, + "frame_corner_radius": 8, "frame_border_width": 0, "label_corner_radius": 8, "progressbar_border_width": 0, @@ -65,4 +65,4 @@ "switch_button_corner_radius": 1000, "switch_button_length": 0 } -} \ No newline at end of file +} diff --git a/customtkinter/draw_engine.py b/customtkinter/draw_engine.py index 9e6caa7..65eefbe 100644 --- a/customtkinter/draw_engine.py +++ b/customtkinter/draw_engine.py @@ -1,9 +1,11 @@ +from __future__ import annotations import sys import math import tkinter -from typing import Union +from typing import Union, TYPE_CHECKING -from .widgets.ctk_canvas import CTkCanvas +if TYPE_CHECKING: + from .widgets.ctk_canvas import CTkCanvas class DrawEngine: @@ -21,25 +23,26 @@ class DrawEngine: """ - def __init__(self, canvas: CTkCanvas, rendering_method: str): + preferred_drawing_method: str = None # 'polygon_shapes', 'font_shapes', 'circle_shapes' + + def __init__(self, canvas: CTkCanvas): self._canvas = canvas - self._rendering_method = rendering_method # "polygon_shapes" (macOS), "font_shapes" (Windows, Linux), "circle_shapes" (backup without fonts) self._existing_tags = set() def _calc_optimal_corner_radius(self, user_corner_radius: Union[float, int]) -> Union[float, int]: # optimize for drawing with polygon shapes - if self._rendering_method == "polygon_shapes": + if self.preferred_drawing_method == "polygon_shapes": if sys.platform == "darwin": return user_corner_radius else: return round(user_corner_radius) # optimize forx drawing with antialiased font shapes - elif self._rendering_method == "font_shapes": + elif self.preferred_drawing_method == "font_shapes": return round(user_corner_radius) # optimize for drawing with circles and rects - elif self._rendering_method == "circle_shapes": + elif self.preferred_drawing_method == "circle_shapes": user_corner_radius = 0.5 * round(user_corner_radius / 0.5) # round to 0.5 steps # make sure the value is always with .5 at the end for smoother corners @@ -71,11 +74,11 @@ class DrawEngine: else: inner_corner_radius = 0 - if self._rendering_method == "polygon_shapes": + if self.preferred_drawing_method == "polygon_shapes": return self._draw_rounded_rect_with_border_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius) - elif self._rendering_method == "font_shapes": + elif self.preferred_drawing_method == "font_shapes": return self._draw_rounded_rect_with_border_font_shapes(width, height, corner_radius, border_width, inner_corner_radius, ()) - elif self._rendering_method == "circle_shapes": + elif self.preferred_drawing_method == "circle_shapes": return self._draw_rounded_rect_with_border_circle_shapes(width, height, corner_radius, border_width, inner_corner_radius) def _draw_rounded_rect_with_border_polygon_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int) -> bool: @@ -365,10 +368,10 @@ class DrawEngine: else: inner_corner_radius = 0 - if self._rendering_method == "polygon_shapes" or self._rendering_method == "circle_shapes": + if self.preferred_drawing_method == "polygon_shapes" or self.preferred_drawing_method == "circle_shapes": return self._draw_rounded_progress_bar_with_border_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius, progress_value, orientation) - elif self._rendering_method == "font_shapes": + elif self.preferred_drawing_method == "font_shapes": return self._draw_rounded_progress_bar_with_border_font_shapes(width, height, corner_radius, border_width, inner_corner_radius, progress_value, orientation) @@ -534,10 +537,10 @@ class DrawEngine: else: inner_corner_radius = 0 - if self._rendering_method == "polygon_shapes" or self._rendering_method == "circle_shapes": + if self.preferred_drawing_method == "polygon_shapes" or self.preferred_drawing_method == "circle_shapes": return self._draw_rounded_slider_with_border_and_button_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius, button_length, button_corner_radius, slider_value, orientation) - elif self._rendering_method == "font_shapes": + elif self.preferred_drawing_method == "font_shapes": return self._draw_rounded_slider_with_border_and_button_font_shapes(width, height, corner_radius, border_width, inner_corner_radius, button_length, button_corner_radius, slider_value, orientation) @@ -659,7 +662,7 @@ class DrawEngine: size = round(size) requires_recoloring = False - if self._rendering_method == "polygon_shapes" or self._rendering_method == "circle_shapes": + if self.preferred_drawing_method == "polygon_shapes" or self.preferred_drawing_method == "circle_shapes": x, y, radius = width / 2, height / 2, size / 2.8 if not self._canvas.find_withtag("checkmark"): self._canvas.create_line(0, 0, 0, 0, tags=("checkmark", "create_line"), width=round(height / 8), joinstyle=tkinter.MITER, capstyle=tkinter.ROUND) @@ -670,7 +673,7 @@ class DrawEngine: x + radius, y - radius, x - radius / 4, y + radius * 0.8, x - radius, y + radius / 6) - elif self._rendering_method == "font_shapes": + elif self.preferred_drawing_method == "font_shapes": if not self._canvas.find_withtag("checkmark"): self._canvas.create_text(0, 0, text="Z", font=("CustomTkinter_shapes_font", -size), tags=("checkmark", "create_text"), anchor=tkinter.CENTER) self._canvas.tag_raise("checkmark") diff --git a/customtkinter/scaling_tracker.py b/customtkinter/scaling_tracker.py index 8007567..2609d2a 100644 --- a/customtkinter/scaling_tracker.py +++ b/customtkinter/scaling_tracker.py @@ -1,11 +1,10 @@ import tkinter import sys -from typing import Callable, Type - -from .settings import Settings +from typing import Callable class ScalingTracker: + deactivate_automatic_dpi_awareness = False 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 scaling factors @@ -34,17 +33,17 @@ class ScalingTracker: @classmethod def set_widget_scaling(cls, widget_scaling_factor: float): cls.widget_scaling = max(widget_scaling_factor, 0.4) - cls.update_scaling_callbacks() + cls.update_scaling_callbacks_all() @classmethod def set_spacing_scaling(cls, spacing_scaling_factor: float): cls.spacing_scaling = max(spacing_scaling_factor, 0.4) - cls.update_scaling_callbacks() + cls.update_scaling_callbacks_all() @classmethod def set_window_scaling(cls, window_scaling_factor: float): cls.window_scaling = max(window_scaling_factor, 0.4) - cls.update_scaling_callbacks() + cls.update_scaling_callbacks_all() @classmethod def get_window_root_of_widget(cls, widget): @@ -57,17 +56,29 @@ class ScalingTracker: return current_widget @classmethod - def update_scaling_callbacks(cls): + def update_scaling_callbacks_all(cls): for window, callback_list in cls.window_widgets_dict.items(): - for callback in callback_list: - if not Settings.deactivate_automatic_dpi_awareness: - callback(cls.window_dpi_scaling_dict[window] * cls.widget_scaling, - cls.window_dpi_scaling_dict[window] * cls.spacing_scaling, - cls.window_dpi_scaling_dict[window] * cls.window_scaling) + for set_scaling_callback in callback_list: + if not cls.deactivate_automatic_dpi_awareness: + set_scaling_callback(cls.window_dpi_scaling_dict[window] * cls.widget_scaling, + cls.window_dpi_scaling_dict[window] * cls.spacing_scaling, + cls.window_dpi_scaling_dict[window] * cls.window_scaling) else: - callback(cls.widget_scaling, - cls.spacing_scaling, - cls.window_scaling) + set_scaling_callback(cls.widget_scaling, + cls.spacing_scaling, + cls.window_scaling) + + @classmethod + def update_scaling_callbacks_for_window(cls, window): + for set_scaling_callback in cls.window_widgets_dict[window]: + if not cls.deactivate_automatic_dpi_awareness: + set_scaling_callback(cls.window_dpi_scaling_dict[window] * cls.widget_scaling, + cls.window_dpi_scaling_dict[window] * cls.spacing_scaling, + cls.window_dpi_scaling_dict[window] * cls.window_scaling) + else: + set_scaling_callback(cls.widget_scaling, + cls.spacing_scaling, + cls.window_scaling) @classmethod def add_widget(cls, widget_callback: Callable, widget): @@ -81,9 +92,9 @@ class ScalingTracker: if window_root not in cls.window_dpi_scaling_dict: cls.window_dpi_scaling_dict[window_root] = cls.get_window_dpi_scaling(window_root) - #if not cls.update_loop_running: - # window_root.after(100, cls.check_dpi_scaling) - # cls.update_loop_running = True + if not cls.update_loop_running: + window_root.after(100, cls.check_dpi_scaling) + cls.update_loop_running = True @classmethod def remove_widget(cls, widget_callback, widget): @@ -115,7 +126,7 @@ class ScalingTracker: """ make process DPI aware, customtkinter elemets will get scaled automatically, only gets activated when CTk object is created """ - if not Settings.deactivate_automatic_dpi_awareness: + if not cls.deactivate_automatic_dpi_awareness: if sys.platform == "darwin": pass # high DPI scaling works automatically on macOS @@ -151,10 +162,16 @@ class ScalingTracker: # check for every window if scaling value changed # (not implemented yet) + for window in cls.window_widgets_dict: + current_dpi_scaling_value = cls.get_window_dpi_scaling(window) + if current_dpi_scaling_value != cls.window_dpi_scaling_dict[window]: + cls.window_dpi_scaling_dict[window] = current_dpi_scaling_value + cls.update_scaling_callbacks_for_window(window) + # 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) + root_tk.after(200, cls.check_dpi_scaling) return except Exception: continue diff --git a/customtkinter/settings.py b/customtkinter/settings.py index cf91d45..cf1c681 100644 --- a/customtkinter/settings.py +++ b/customtkinter/settings.py @@ -1,42 +1,5 @@ -import sys - class Settings: - - circle_font_is_ready = False - preferred_drawing_method: str = None # 'polygon_shapes', 'font_shapes', 'circle_shapes' - radius_to_char_fine: dict = None # set in self.init_font_character_mapping() cursor_manipulation_enabled = True deactivate_macos_window_header_manipulation = False deactivate_windows_window_header_manipulation = False - deactivate_automatic_dpi_awareness = False - - @classmethod - def init_font_character_mapping(cls): - """ optimizations made for Windows 10, 11 only """ - - radius_to_char_warped = {19: 'B', 18: 'B', 17: 'B', 16: 'B', 15: 'B', 14: 'B', 13: 'B', 12: 'B', 11: 'B', 10: 'B', - 9: 'C', 8: 'D', 7: 'C', 6: 'E', 5: 'F', 4: 'G', 3: 'H', 2: 'H', 1: 'H', 0: 'A'} - - radius_to_char_fine_windows_10 = {19: 'A', 18: 'A', 17: 'B', 16: 'B', 15: 'B', 14: 'B', 13: 'C', 12: 'C', 11: 'C', 10: 'C', - 9: 'D', 8: 'D', 7: 'D', 6: 'F', 5: 'D', 4: 'G', 3: 'G', 2: 'H', 1: 'H', 0: 'A'} - - radius_to_char_fine_windows_11 = {19: 'A', 18: 'A', 17: 'B', 16: 'B', 15: 'B', 14: 'B', 13: 'C', 12: 'C', 11: 'D', 10: 'D', - 9: 'E', 8: 'F', 7: 'C', 6: 'I', 5: 'E', 4: 'G', 3: 'P', 2: 'R', 1: 'R', 0: 'A'} - - if sys.platform.startswith("win"): - if sys.getwindowsversion().build > 20000: # Windows 11 - cls.radius_to_char_fine = radius_to_char_fine_windows_11 - else: # < Windows 11 - cls.radius_to_char_fine = radius_to_char_fine_windows_10 - else: # macOS and Linux - cls.radius_to_char_fine = radius_to_char_fine_windows_10 - - @classmethod - def init_drawing_method(cls): - """ possible: 'polygon_shapes', 'font_shapes', 'circle_shapes' """ - - if sys.platform == "darwin": - cls.preferred_drawing_method = "polygon_shapes" - else: - cls.preferred_drawing_method = "font_shapes" diff --git a/customtkinter/widgets/__init__.py b/customtkinter/widgets/__init__.py index e69de29..62acbf5 100644 --- a/customtkinter/widgets/__init__.py +++ b/customtkinter/widgets/__init__.py @@ -0,0 +1,3 @@ +from .ctk_canvas import CTkCanvas + +CTkCanvas.init_font_character_mapping() diff --git a/customtkinter/widgets/ctk_button.py b/customtkinter/widgets/ctk_button.py index 6a0f36f..e7dabff 100644 --- a/customtkinter/widgets/ctk_button.py +++ b/customtkinter/widgets/ctk_button.py @@ -69,7 +69,7 @@ class CTkButton(CTkBaseClass): width=self.apply_widget_scaling(self.desired_width), height=self.apply_widget_scaling(self.desired_height)) self.canvas.grid(row=0, column=0, rowspan=2, columnspan=2, sticky="nsew") - self.draw_engine = DrawEngine(self.canvas, Settings.preferred_drawing_method) + self.draw_engine = DrawEngine(self.canvas) # event bindings self.canvas.bind("", self.on_enter) diff --git a/customtkinter/widgets/ctk_canvas.py b/customtkinter/widgets/ctk_canvas.py index d9a12a0..6502cf0 100644 --- a/customtkinter/widgets/ctk_canvas.py +++ b/customtkinter/widgets/ctk_canvas.py @@ -1,23 +1,50 @@ import tkinter -from ..settings import Settings +import sys +from typing import Union class CTkCanvas(tkinter.Canvas): - - radius_to_char_fine = Settings.radius_to_char_fine # dict to map radius to font circle character + radius_to_char_fine: dict = None # dict to map radius to font circle character def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.aa_circle_canvas_ids = set() - def get_char_from_radius(self, radius): + @classmethod + def init_font_character_mapping(cls): + """ optimizations made for Windows 10, 11 only """ + + radius_to_char_warped = {19: 'B', 18: 'B', 17: 'B', 16: 'B', 15: 'B', 14: 'B', 13: 'B', 12: 'B', 11: 'B', + 10: 'B', + 9: 'C', 8: 'D', 7: 'C', 6: 'E', 5: 'F', 4: 'G', 3: 'H', 2: 'H', 1: 'H', 0: 'A'} + + radius_to_char_fine_windows_10 = {19: 'A', 18: 'A', 17: 'B', 16: 'B', 15: 'B', 14: 'B', 13: 'C', 12: 'C', + 11: 'C', 10: 'C', + 9: 'D', 8: 'D', 7: 'D', 6: 'F', 5: 'D', 4: 'G', 3: 'G', 2: 'H', 1: 'H', + 0: 'A'} + + radius_to_char_fine_windows_11 = {19: 'A', 18: 'A', 17: 'B', 16: 'B', 15: 'B', 14: 'B', 13: 'C', 12: 'C', + 11: 'D', 10: 'D', + 9: 'E', 8: 'F', 7: 'C', 6: 'I', 5: 'E', 4: 'G', 3: 'P', 2: 'R', 1: 'R', + 0: 'A'} + + if sys.platform.startswith("win"): + if sys.getwindowsversion().build > 20000: # Windows 11 + cls.radius_to_char_fine = radius_to_char_fine_windows_11 + else: # < Windows 11 + cls.radius_to_char_fine = radius_to_char_fine_windows_10 + else: # macOS and Linux + cls.radius_to_char_fine = radius_to_char_fine_windows_10 + + def get_char_from_radius(self, radius: int) -> str: if radius >= 20: return "A" else: return self.radius_to_char_fine[radius] - def create_aa_circle(self, x_pos, y_pos, radius, angle=0, fill="white", tags="", anchor=tkinter.CENTER) -> str: + def create_aa_circle(self, x_pos: int, y_pos: int, radius: int, angle: int = 0, fill: str = "white", + tags: Union[str, tuple[str, ...]] = "", anchor: str = tkinter.CENTER) -> int: # create a circle with a font element circle_1 = self.create_text(x_pos, y_pos, text=self.get_char_from_radius(radius), anchor=anchor, fill=fill, font=("CustomTkinter_shapes_font", -radius * 2), tags=tags, angle=angle) @@ -61,5 +88,3 @@ class CTkCanvas(tkinter.Canvas): super().itemconfigure(configure_id, *args, **kwargs_except_outline) else: super().itemconfigure(configure_id, *args, **kwargs) - - diff --git a/customtkinter/widgets/ctk_checkbox.py b/customtkinter/widgets/ctk_checkbox.py index 723aad1..5fd1651 100644 --- a/customtkinter/widgets/ctk_checkbox.py +++ b/customtkinter/widgets/ctk_checkbox.py @@ -83,7 +83,7 @@ class CTkCheckBox(CTkBaseClass): width=self.apply_widget_scaling(self.desired_width), height=self.apply_widget_scaling(self.desired_height)) self.canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=1, rowspan=1) - self.draw_engine = DrawEngine(self.canvas, Settings.preferred_drawing_method) + self.draw_engine = DrawEngine(self.canvas) if self.hover is True: self.canvas.bind("", self.on_enter) diff --git a/customtkinter/widgets/ctk_entry.py b/customtkinter/widgets/ctk_entry.py index b2471b3..6393b8a 100644 --- a/customtkinter/widgets/ctk_entry.py +++ b/customtkinter/widgets/ctk_entry.py @@ -50,7 +50,7 @@ class CTkEntry(CTkBaseClass): width=self.apply_widget_scaling(self.current_width), height=self.apply_widget_scaling(self.current_height)) self.canvas.grid(column=0, row=0, sticky="we") - self.draw_engine = DrawEngine(self.canvas, Settings.preferred_drawing_method) + self.draw_engine = DrawEngine(self.canvas) self.entry = tkinter.Entry(master=self, bd=0, diff --git a/customtkinter/widgets/ctk_frame.py b/customtkinter/widgets/ctk_frame.py index 22b767f..fccbcce 100644 --- a/customtkinter/widgets/ctk_frame.py +++ b/customtkinter/widgets/ctk_frame.py @@ -45,7 +45,7 @@ class CTkFrame(CTkBaseClass): height=self.apply_widget_scaling(self.current_height)) self.canvas.place(x=0, y=0, relwidth=1, relheight=1) self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self.appearance_mode)) - self.draw_engine = DrawEngine(self.canvas, Settings.preferred_drawing_method) + self.draw_engine = DrawEngine(self.canvas) self.bind('', self.update_dimensions_event) diff --git a/customtkinter/widgets/ctk_label.py b/customtkinter/widgets/ctk_label.py index 8a2f28b..546ac82 100644 --- a/customtkinter/widgets/ctk_label.py +++ b/customtkinter/widgets/ctk_label.py @@ -48,7 +48,7 @@ class CTkLabel(CTkBaseClass): width=self.apply_widget_scaling(self.desired_width), height=self.apply_widget_scaling(self.desired_height)) self.canvas.grid(row=0, column=0, sticky="nswe") - self.draw_engine = DrawEngine(self.canvas, Settings.preferred_drawing_method) + self.draw_engine = DrawEngine(self.canvas) self.text_label = tkinter.Label(master=self, highlightthickness=0, diff --git a/customtkinter/widgets/ctk_progressbar.py b/customtkinter/widgets/ctk_progressbar.py index f49d304..c592261 100644 --- a/customtkinter/widgets/ctk_progressbar.py +++ b/customtkinter/widgets/ctk_progressbar.py @@ -62,7 +62,7 @@ class CTkProgressBar(CTkBaseClass): width=self.apply_widget_scaling(self.desired_width), height=self.apply_widget_scaling(self.desired_height)) self.canvas.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="nswe") - self.draw_engine = DrawEngine(self.canvas, Settings.preferred_drawing_method) + self.draw_engine = DrawEngine(self.canvas) # Each time an item is resized due to pack position mode, the binding Configure is called on the widget self.bind('', self.update_dimensions_event) diff --git a/customtkinter/widgets/ctk_radiobutton.py b/customtkinter/widgets/ctk_radiobutton.py index 0617baa..a0af546 100644 --- a/customtkinter/widgets/ctk_radiobutton.py +++ b/customtkinter/widgets/ctk_radiobutton.py @@ -79,7 +79,7 @@ class CTkRadioButton(CTkBaseClass): width=self.apply_widget_scaling(self.current_width), height=self.apply_widget_scaling(self.current_height)) self.canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=1) - self.draw_engine = DrawEngine(self.canvas, Settings.preferred_drawing_method) + self.draw_engine = DrawEngine(self.canvas) self.canvas.bind("", self.on_enter) self.canvas.bind("", self.on_leave) diff --git a/customtkinter/widgets/ctk_slider.py b/customtkinter/widgets/ctk_slider.py index b7abce5..c2c1439 100644 --- a/customtkinter/widgets/ctk_slider.py +++ b/customtkinter/widgets/ctk_slider.py @@ -84,7 +84,7 @@ class CTkSlider(CTkBaseClass): width=self.apply_widget_scaling(self.desired_width), height=self.apply_widget_scaling(self.desired_height)) self.canvas.grid(column=0, row=0, rowspan=1, columnspan=1, sticky="nswe") - self.draw_engine = DrawEngine(self.canvas, Settings.preferred_drawing_method) + self.draw_engine = DrawEngine(self.canvas) self.canvas.bind("", self.on_enter) self.canvas.bind("", self.on_leave) diff --git a/customtkinter/widgets/ctk_switch.py b/customtkinter/widgets/ctk_switch.py index 501f2fc..a6ce372 100644 --- a/customtkinter/widgets/ctk_switch.py +++ b/customtkinter/widgets/ctk_switch.py @@ -84,7 +84,7 @@ class CTkSwitch(CTkBaseClass): width=self.apply_widget_scaling(self.current_width), height=self.apply_widget_scaling(self.current_height)) self.canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=1, sticky="nswe") - self.draw_engine = DrawEngine(self.canvas, Settings.preferred_drawing_method) + self.draw_engine = DrawEngine(self.canvas) self.canvas.bind("", self.on_enter) self.canvas.bind("", self.on_leave) diff --git a/customtkinter/windows/ctk_tk.py b/customtkinter/windows/ctk_tk.py index 42168f4..e7e1ecc 100644 --- a/customtkinter/windows/ctk_tk.py +++ b/customtkinter/windows/ctk_tk.py @@ -69,24 +69,21 @@ class CTk(tkinter.Tk): if self.current_width != round(detected_width / self.window_scaling) or self.current_height != round(detected_height / self.window_scaling): self.current_width = round(detected_width / self.window_scaling) # adjust current size according to new size given by event self.current_height = round(detected_height / self.window_scaling) # current_width and current_height are independent of the scale + print("update_dimensions_event:", self.current_width) def set_scaling(self, new_widget_scaling, new_spacing_scaling, new_window_scaling): self.window_scaling = new_window_scaling - # reset min, max and resizable constraints for applying scaling - if self.last_resizable_args is not None: - super().resizable(True, True) - if self.min_width is not None or self.min_height is not None: - super().minsize(0, 0) - if self.max_width is not None or self.max_height is not None: - super().maxsize(1_000_000, 1_000_000) + # force new dimensions on window by using min, max, and geometry + super().minsize(self.apply_window_scaling(self.current_width), self.apply_window_scaling(self.current_height)) + super().maxsize(self.apply_window_scaling(self.current_width), self.apply_window_scaling(self.current_height)) + super().geometry(f"{self.apply_window_scaling(self.current_width)}x"+f"{self.apply_window_scaling(self.current_height)}") + print("set_scaling:", self.apply_window_scaling(self.current_width), self.max_width, self.min_width) - # set new window size by applying scaling to the current window size - self.geometry(f"{self.current_width}x{self.current_height}") + # set new scaled min and max with 400ms delay (otherwise it won't work for some reason) + self.after(400, self.set_scaled_min_max) - # set scaled min, max sizes and reapply resizable - if self.last_resizable_args is not None: - super().resizable(*self.last_resizable_args[0], **self.last_resizable_args[1]) # args, kwargs + def set_scaled_min_max(self): if self.min_width is not None or self.min_height is not None: super().minsize(self.apply_window_scaling(self.min_width), self.apply_window_scaling(self.min_height)) if self.max_width is not None or self.max_height is not None: @@ -106,6 +103,7 @@ class CTk(tkinter.Tk): def mainloop(self, *args, **kwargs): if not self.window_exists: + print("deiconify") self.deiconify() self.window_exists = True super().mainloop(*args, **kwargs) @@ -135,6 +133,7 @@ class CTk(tkinter.Tk): super().maxsize(self.apply_window_scaling(self.max_width), self.apply_window_scaling(self.max_height)) def geometry(self, geometry_string): + print("geometry:", geometry_string) super().geometry(self.apply_geometry_scaling(geometry_string)) # update width and height attributes diff --git a/test/manual_integration_tests/test_scaling/complex_example.py b/test/manual_integration_tests/test_scaling/complex_example.py deleted file mode 100644 index a68dccc..0000000 --- a/test/manual_integration_tests/test_scaling/complex_example.py +++ /dev/null @@ -1,206 +0,0 @@ -import tkinter -import tkinter.messagebox -import customtkinter - -customtkinter.set_appearance_mode("System") # Modes: "System" (standard), "Dark", "Light" -customtkinter.set_default_color_theme("blue") # Themes: "blue" (standard), "green", "dark-blue" - - -class App(customtkinter.CTk): - - WIDTH = 780 - HEIGHT = 520 - - def __init__(self): - super().__init__() - - self.title("CustomTkinter complex example") - self.geometry(f"{App.WIDTH}x{App.HEIGHT}") - # self.minsize(App.WIDTH, App.HEIGHT) - - self.protocol("WM_DELETE_WINDOW", self.on_closing) # call .on_closing() when app gets closed - - # ============ create two frames ============ - - # configure grid layout (2x1) - self.grid_columnconfigure(1, weight=1) - self.grid_rowconfigure(0, weight=1) - - self.frame_left = customtkinter.CTkFrame(master=self, - width=180, - corner_radius=0) - self.frame_left.grid(row=0, column=0, sticky="nswe") - - self.frame_right = customtkinter.CTkFrame(master=self) - self.frame_right.grid(row=0, column=1, sticky="nswe", padx=20, pady=20) - - # ============ frame_left ============ - - # configure grid layout (1x11) - self.frame_left.grid_rowconfigure(0, minsize=10) # empty row with minsize as spacing - self.frame_left.grid_rowconfigure(5, weight=1) # empty row as spacing - self.frame_left.grid_rowconfigure(8, minsize=20) # empty row with minsize as spacing - self.frame_left.grid_rowconfigure(11, minsize=10) # empty row with minsize as spacing - - self.label_1 = customtkinter.CTkLabel(master=self.frame_left, - text="CustomTkinter", - text_font=("Roboto Medium", -16)) # font name and size in px - self.label_1.grid(row=1, column=0, pady=10, padx=10) - - self.button_1 = customtkinter.CTkButton(master=self.frame_left, - text="CTkButton 1", - fg_color=("gray75", "gray30"), # <- custom tuple-color - command=self.button_event) - self.button_1.grid(row=2, column=0, pady=10, padx=20) - - self.button_2 = customtkinter.CTkButton(master=self.frame_left, - text="CTkButton 2", - fg_color=("gray75", "gray30"), # <- custom tuple-color - command=self.button_event) - self.button_2.grid(row=3, column=0, pady=10, padx=20) - - self.button_3 = customtkinter.CTkButton(master=self.frame_left, - text="CTkButton 3", - fg_color=("gray75", "gray30"), # <- custom tuple-color - command=self.button_event) - self.button_3.grid(row=4, column=0, pady=10, padx=20) - - self.switch_1 = customtkinter.CTkSwitch(master=self.frame_left) - self.switch_1.grid(row=9, column=0, pady=10, padx=20, sticky="w") - - self.switch_2 = customtkinter.CTkSwitch(master=self.frame_left, - text="Dark Mode", - command=self.change_mode) - self.switch_2.grid(row=10, column=0, pady=10, padx=20, sticky="w") - - # ============ frame_right ============ - - # configure grid layout (3x7) - self.frame_right.rowconfigure((0, 1, 2, 3), weight=1) - self.frame_right.rowconfigure(7, weight=10) - self.frame_right.columnconfigure((0, 1), weight=1) - self.frame_right.columnconfigure(2, weight=0) - - self.frame_info = customtkinter.CTkFrame(master=self.frame_right) - self.frame_info.grid(row=0, column=0, columnspan=2, rowspan=4, pady=20, padx=20, sticky="nsew") - - # ============ frame_info ============ - - # configure grid layout (1x1) - self.frame_info.rowconfigure(0, weight=1) - self.frame_info.columnconfigure(0, weight=1) - - self.label_info_1 = customtkinter.CTkLabel(master=self.frame_info, - text="CTkLabel: Lorem ipsum dolor sit,\n" + - "amet consetetur sadipscing elitr,\n" + - "sed diam nonumy eirmod tempor" , - height=100, - fg_color=("white", "gray38"), # <- custom tuple-color - justify=tkinter.LEFT) - self.label_info_1.grid(column=0, row=0, sticky="nwe", padx=15, pady=15) - - self.progressbar = customtkinter.CTkProgressBar(master=self.frame_info) - self.progressbar.grid(row=1, column=0, sticky="ew", padx=15, pady=15) - - # ============ frame_right ============ - - self.radio_var = tkinter.IntVar(value=0) - - self.label_radio_group = customtkinter.CTkLabel(master=self.frame_right, - text="CTkRadioButton Group:") - self.label_radio_group.grid(row=0, column=2, columnspan=1, pady=20, padx=10, sticky="") - - self.radio_button_1 = customtkinter.CTkRadioButton(master=self.frame_right, - variable=self.radio_var, - value=0) - self.radio_button_1.grid(row=1, column=2, pady=10, padx=20, sticky="n") - - self.radio_button_2 = customtkinter.CTkRadioButton(master=self.frame_right, - variable=self.radio_var, - value=1) - self.radio_button_2.grid(row=2, column=2, pady=10, padx=20, sticky="n") - - self.radio_button_3 = customtkinter.CTkRadioButton(master=self.frame_right, - variable=self.radio_var, - value=2) - self.radio_button_3.grid(row=3, column=2, pady=10, padx=20, sticky="n") - - self.slider_1 = customtkinter.CTkSlider(master=self.frame_right, - from_=0, - to=1, - number_of_steps=3, - command=None) - self.slider_1.grid(row=4, column=0, columnspan=2, pady=10, padx=20, sticky="we") - - self.slider_2 = customtkinter.CTkSlider(master=self.frame_right, - command=lambda x: customtkinter.set_user_scaling(x * 2)) - self.slider_2.grid(row=5, column=0, columnspan=2, pady=10, padx=20, sticky="we") - - self.slider_button_1 = customtkinter.CTkButton(master=self.frame_right, - height=25, - text="CTkButton", - command=self.button_event) - self.slider_button_1.grid(row=4, column=2, columnspan=1, pady=10, padx=20, sticky="we") - - self.slider_button_2 = customtkinter.CTkButton(master=self.frame_right, - height=25, - text="CTkButton", - command=self.button_event) - self.slider_button_2.grid(row=5, column=2, columnspan=1, pady=10, padx=20, sticky="we") - - self.checkbox_button_1 = customtkinter.CTkButton(master=self.frame_right, - height=25, - text="CTkButton", - border_width=3, # <- custom border_width - fg_color=None, # <- no fg_color - command=self.button_event) - self.checkbox_button_1.grid(row=6, column=2, columnspan=1, pady=10, padx=20, sticky="we") - - self.check_box_1 = customtkinter.CTkCheckBox(master=self.frame_right, - text="CTkCheckBox") - self.check_box_1.grid(row=6, column=0, pady=10, padx=20, sticky="w") - - self.check_box_2 = customtkinter.CTkCheckBox(master=self.frame_right, - text="CTkCheckBox") - self.check_box_2.grid(row=6, column=1, pady=10, padx=20, sticky="w") - - self.entry = customtkinter.CTkEntry(master=self.frame_right, - width=120, - placeholder_text="CTkEntry") - self.entry.grid(row=8, column=0, columnspan=2, pady=20, padx=20, sticky="we") - - self.button_5 = customtkinter.CTkButton(master=self.frame_right, - text="CTkButton", - command=self.button_event) - self.button_5.grid(row=8, column=2, columnspan=1, pady=20, padx=20, sticky="we") - - # set default values - self.radio_button_1.select() - self.switch_2.select() - self.slider_1.set(0.2) - self.slider_2.set(0.7) - self.progressbar.set(0.5) - self.slider_button_1.configure(state=tkinter.DISABLED, text="Disabled Button") - self.radio_button_3.configure(state=tkinter.DISABLED) - self.check_box_1.configure(state=tkinter.DISABLED, text="CheckBox disabled") - self.check_box_2.select() - - def button_event(self): - print("Button pressed") - - def change_mode(self): - if self.switch_2.get() == 1: - customtkinter.set_appearance_mode("dark") - else: - customtkinter.set_appearance_mode("light") - - def on_closing(self, event=0): - self.destroy() - - def start(self): - self.mainloop() - - -if __name__ == "__main__": - app = App() - app.start() diff --git a/test/manual_integration_tests/test_scaling/example_background_image.py b/test/manual_integration_tests/test_scaling/example_background_image.py deleted file mode 100644 index 3b049b6..0000000 --- a/test/manual_integration_tests/test_scaling/example_background_image.py +++ /dev/null @@ -1,76 +0,0 @@ -import tkinter -import tkinter.messagebox -import customtkinter -from PIL import Image, ImageTk -import os - -customtkinter.ScalingTracker.set_window_scaling(1.5) -customtkinter.ScalingTracker.set_widget_scaling(1.5) -customtkinter.ScalingTracker.set_spacing_scaling(1.5) - - -customtkinter.set_appearance_mode("Dark") # Modes: "System" (standard), "Dark", "Light" -customtkinter.set_default_color_theme("blue") # Themes: "blue" (standard), "green", "dark-blue" - -PATH = os.path.dirname(os.path.realpath(__file__)) - - -class App(customtkinter.CTk): - - APP_NAME = "CustomTkinter background gradient image" - WIDTH = 900 - HEIGHT = 600 - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - self.title(App.APP_NAME) - self.geometry(f"{App.WIDTH}x{App.HEIGHT}") - self.minsize(App.WIDTH, App.HEIGHT) - self.maxsize(App.WIDTH, App.HEIGHT) - - self.protocol("WM_DELETE_WINDOW", self.on_closing) - self.bind("", self.on_closing) - self.bind("", self.on_closing) - self.createcommand('tk::mac::Quit', self.on_closing) - - # load image with PIL and convert to PhotoImage - image = Image.open(PATH + "/../test_images/bg_gradient.jpg").resize((self.WIDTH, self.HEIGHT)) - self.bg_image = ImageTk.PhotoImage(image) - - self.image_label = tkinter.Label(master=self, image=self.bg_image) - self.image_label.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER) - - self.frame = customtkinter.CTkFrame(master=self, - width=300, - height=App.HEIGHT, - corner_radius=0) - self.frame.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER) - - self.label_1 = customtkinter.CTkLabel(master=self.frame, width=200, height=60, - fg_color=("gray70", "gray35"), text="CustomTkinter\ninterface example") - self.label_1.place(relx=0.5, rely=0.3, anchor=tkinter.CENTER) - - self.entry_1 = customtkinter.CTkEntry(master=self.frame, corner_radius=20, width=200, placeholder_text="username") - self.entry_1.place(relx=0.5, rely=0.52, anchor=tkinter.CENTER) - - self.entry_2 = customtkinter.CTkEntry(master=self.frame, corner_radius=20, width=200, show="*", placeholder_text="password") - self.entry_2.place(relx=0.5, rely=0.6, anchor=tkinter.CENTER) - - self.button_2 = customtkinter.CTkButton(master=self.frame, text="Login", - corner_radius=6, command=self.button_event, width=200) - self.button_2.place(relx=0.5, rely=0.7, anchor=tkinter.CENTER) - - def button_event(self): - print("Login pressed - username:", self.entry_1.get(), "password:", self.entry_2.get()) - - def on_closing(self, event=0): - self.destroy() - - def start(self): - self.mainloop() - - -if __name__ == "__main__": - app = App() - app.start() diff --git a/test/manual_integration_tests/test_scaling/simple_example.py b/test/manual_integration_tests/test_scaling/simple_example.py index 296b451..6afb23e 100644 --- a/test/manual_integration_tests/test_scaling/simple_example.py +++ b/test/manual_integration_tests/test_scaling/simple_example.py @@ -1,21 +1,22 @@ import tkinter import customtkinter # <- import the CustomTkinter module -customtkinter.ScalingTracker.set_window_scaling(1.5) +customtkinter.ScalingTracker.set_window_scaling(0.5) customtkinter.set_appearance_mode("dark") # Modes: "System" (standard), "Dark", "Light" customtkinter.set_default_color_theme("blue") # Themes: "blue" (standard), "green", "dark-blue" root_tk = customtkinter.CTk() # create CTk window like you do with the Tk window (you can also use normal tkinter.Tk window) root_tk.geometry("400x480") -root_tk.title("CustomTkinter Test") +root_tk.title("CustomTkinter manual scaling test") -root_tk.minsize(480, 480) -root_tk.maxsize(520, 520) +#root_tk.minsize(200, 200) +#root_tk.maxsize(520, 520) +root_tk.resizable(True, False) -print(customtkinter.ScalingTracker.get_window_scaling(root_tk)) def button_function(): + root_tk.geometry(f"{200}x{200}") print("Button click", label_1.text_label.cget("text")) @@ -31,7 +32,7 @@ def check_box_function(): y_padding = 13 -frame_1 = customtkinter.CTkFrame(master=root_tk, corner_radius=15) +frame_1 = customtkinter.CTkFrame(master=root_tk) frame_1.pack(pady=20, padx=60, fill="both", expand=True) label_1 = customtkinter.CTkLabel(master=frame_1, justify=tkinter.LEFT)