From 9d7eca7bb11066c7ae0e4e2a9b9126520fae2b62 Mon Sep 17 00:00:00 2001 From: Tom Schimansky Date: Sat, 22 Oct 2022 01:20:08 +0200 Subject: [PATCH] fixed focus bug of CTk and CTkToplevel on macOS --- customtkinter/widgets/ctk_combobox.py | 12 ++-- customtkinter/widgets/ctk_entry.py | 68 +++++++++++++------- customtkinter/widgets/widget_base_class.py | 4 +- customtkinter/windows/ctk_tk.py | 4 +- customtkinter/windows/ctk_toplevel.py | 4 +- test/manual_integration_tests/test_font.py | 6 +- test/manual_integration_tests/test_states.py | 4 +- 7 files changed, 64 insertions(+), 38 deletions(-) diff --git a/customtkinter/widgets/ctk_combobox.py b/customtkinter/widgets/ctk_combobox.py index f838690..7226515 100644 --- a/customtkinter/widgets/ctk_combobox.py +++ b/customtkinter/widgets/ctk_combobox.py @@ -102,7 +102,7 @@ class CTkComboBox(CTkBaseClass): highlightthickness=0, font=self._apply_font_scaling(self._font)) - self._configure_grid_system() + self._create_grid() # insert default value if len(self._values) > 0: @@ -123,21 +123,21 @@ class CTkComboBox(CTkBaseClass): if self._variable is not None: self._entry.configure(textvariable=self._variable) - def _configure_grid_system(self): + def _create_grid(self): self._canvas.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="nsew") left_section_width = self._current_width - self._current_height self._entry.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="ew", padx=(max(self._apply_widget_scaling(self._corner_radius), self._apply_widget_scaling(3)), max(self._apply_widget_scaling(self._current_width - left_section_width + 3), self._apply_widget_scaling(3))), - pady=self._border_width) + pady=self._apply_widget_scaling(self._border_width)) def _set_scaling(self, *args, **kwargs): super()._set_scaling(*args, **kwargs) # change entry font size and grid padding self._entry.configure(font=self._apply_font_scaling(self._font)) - self._configure_grid_system() + self._create_grid() self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), height=self._apply_widget_scaling(self._desired_height)) @@ -157,7 +157,7 @@ class CTkComboBox(CTkBaseClass): # Workaround to force grid to be resized when text changes size. # Otherwise grid will lag and only resizes if other mouse action occurs. self._canvas.grid_forget() - self._canvas.grid(row=0, column=0, columnspan=3, sticky="nswe") + self._canvas.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="nsew") def destroy(self): if isinstance(self._font, CTkFont): @@ -219,7 +219,7 @@ class CTkComboBox(CTkBaseClass): if "border_width" in kwargs: self._border_width = kwargs.pop("border_width") - self._configure_grid_system() + self._create_grid() require_redraw = True if "state" in kwargs: diff --git a/customtkinter/widgets/ctk_entry.py b/customtkinter/widgets/ctk_entry.py index b056b88..cb6cc17 100644 --- a/customtkinter/widgets/ctk_entry.py +++ b/customtkinter/widgets/ctk_entry.py @@ -5,6 +5,7 @@ from .ctk_canvas import CTkCanvas from ..theme_manager import ThemeManager from ..draw_engine import DrawEngine from .widget_base_class import CTkBaseClass +from ..utility.ctk_font import CTkFont from customtkinter.utility.utility_functions import pop_from_dict_by_set, check_kwargs_empty @@ -37,7 +38,7 @@ class CTkEntry(CTkBaseClass): textvariable: tkinter.Variable = None, placeholder_text: str = None, - font: Union[str, Tuple] = "default_theme", + font: Union[tuple, CTkFont] = "default_theme", state: str = tkinter.NORMAL, **kwargs): @@ -59,7 +60,6 @@ class CTkEntry(CTkBaseClass): self._border_width = ThemeManager.theme["shape"]["entry_border_width"] if border_width == "default_theme" else border_width # text and state - self._font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if font == "default_theme" else font self._is_focused: bool = True self._placeholder_text = placeholder_text self._placeholder_text_active = False @@ -68,6 +68,11 @@ class CTkEntry(CTkBaseClass): self._state = state self._textvariable_callback_name: str = "" + # font + self._font = CTkFont() if font == "default_theme" else self._check_font_type(font) + if isinstance(self._font, CTkFont): + self._font.add_size_configure_callback(self._update_font) + if not (self._textvariable is None or self._textvariable == ""): self._textvariable_callback_name = self._textvariable.trace_add("write", self._textvariable_callback) @@ -75,7 +80,6 @@ class CTkEntry(CTkBaseClass): highlightthickness=0, width=self._apply_widget_scaling(self._current_width), height=self._apply_widget_scaling(self._current_height)) - self._canvas.grid(column=0, row=0, sticky="nswe") self._draw_engine = DrawEngine(self._canvas) self._entry = tkinter.Entry(master=self, @@ -86,14 +90,8 @@ class CTkEntry(CTkBaseClass): state=self._state, textvariable=self._textvariable, **pop_from_dict_by_set(kwargs, self._valid_tk_entry_attributes)) - if self._corner_radius >= self._minimum_x_padding: - self._entry.grid(column=0, row=0, sticky="nswe", - padx=min(self._apply_widget_scaling(self._corner_radius), round(self._apply_widget_scaling(self._current_height/2))), - pady=(self._apply_widget_scaling(self._border_width), self._apply_widget_scaling(self._border_width + 1))) - else: - self._entry.grid(column=0, row=0, sticky="nswe", - padx=self._apply_widget_scaling(self._minimum_x_padding), - pady=(self._apply_widget_scaling(self._border_width), self._apply_widget_scaling(self._border_width + 1))) + + self._create_grid() check_kwargs_empty(kwargs, raise_error=True) @@ -103,6 +101,18 @@ class CTkEntry(CTkBaseClass): self._activate_placeholder() self._draw() + def _create_grid(self): + self._canvas.grid(column=0, row=0, sticky="nswe") + + if self._corner_radius >= self._minimum_x_padding: + self._entry.grid(column=0, row=0, sticky="nswe", + padx=min(self._apply_widget_scaling(self._corner_radius), round(self._apply_widget_scaling(self._current_height/2))), + pady=(self._apply_widget_scaling(self._border_width), self._apply_widget_scaling(self._border_width + 1))) + else: + self._entry.grid(column=0, row=0, sticky="nswe", + padx=self._apply_widget_scaling(self._minimum_x_padding), + pady=(self._apply_widget_scaling(self._border_width), self._apply_widget_scaling(self._border_width + 1))) + def _textvariable_callback(self, var_name, index, mode): if self._textvariable.get() == "": self._activate_placeholder() @@ -111,10 +121,8 @@ class CTkEntry(CTkBaseClass): super()._set_scaling(*args, **kwargs) self._entry.configure(font=self._apply_font_scaling(self._font)) - self._entry.grid(column=0, row=0, sticky="we", - padx=self._apply_widget_scaling(self._corner_radius) if self._corner_radius >= 6 else self._apply_widget_scaling(6)) - self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), height=self._apply_widget_scaling(self._desired_height)) + self._create_grid() self._draw(no_color_updates=True) def _set_dimensions(self, width=None, height=None): @@ -124,6 +132,21 @@ class CTkEntry(CTkBaseClass): height=self._apply_widget_scaling(self._desired_height)) self._draw() + def _update_font(self): + """ pass font to tkinter widgets with applied font scaling and update grid with workaround """ + self._entry.configure(font=self._apply_font_scaling(self._font)) + + # Workaround to force grid to be resized when text changes size. + # Otherwise grid will lag and only resizes if other mouse action occurs. + self._canvas.grid_forget() + self._canvas.grid(column=0, row=0, sticky="nswe") + + def destroy(self): + if isinstance(self._font, CTkFont): + self._font.remove_size_configure_callback(self._update_font) + + super().destroy() + def _draw(self, no_color_updates=False): self._canvas.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode)) @@ -180,16 +203,12 @@ class CTkEntry(CTkBaseClass): if "border_width" in kwargs: self._border_width = kwargs.pop("border_width") + self._create_grid() require_redraw = True if "corner_radius" in kwargs: self._corner_radius = kwargs.pop("corner_radius") - if self._corner_radius >= self._minimum_x_padding: - self._entry.grid(column=0, row=0, sticky="we", - padx=min(self._apply_widget_scaling(self._corner_radius), round(self._apply_widget_scaling(self._current_height/2)))) - else: - self._entry.grid(column=0, row=0, sticky="we", - padx=self._apply_widget_scaling(self._minimum_x_padding)) + self._create_grid() require_redraw = True if "placeholder_text" in kwargs: @@ -209,8 +228,13 @@ class CTkEntry(CTkBaseClass): self._entry.configure(textvariable=self._textvariable) if "font" in kwargs: - self._font = kwargs.pop("font") - self._entry.configure(font=self._apply_font_scaling(self._font)) + if isinstance(self._font, CTkFont): + self._font.remove_size_configure_callback(self._update_font) + self._font = self._check_font_type(kwargs.pop("font")) + if isinstance(self._font, CTkFont): + self._font.add_size_configure_callback(self._update_font) + + self._update_font() if "show" in kwargs: if self._placeholder_text_active: diff --git a/customtkinter/widgets/widget_base_class.py b/customtkinter/widgets/widget_base_class.py index 848fd35..d6a74c9 100644 --- a/customtkinter/widgets/widget_base_class.py +++ b/customtkinter/widgets/widget_base_class.py @@ -239,9 +239,9 @@ class CTkBaseClass(tkinter.Frame): if len(font) == 1: return font elif len(font) == 2: - return font[0], round(font[1] * self._widget_scaling) + return font[0], -abs(round(font[1] * self._widget_scaling)) elif len(font) == 3: - return font[0], round(font[1] * self._widget_scaling), font[2] + return font[0], -abs(round(font[1] * self._widget_scaling)), font[2] else: raise ValueError(f"Can not scale font {font}. font needs to be tuple of len 1, 2 or 3") diff --git a/customtkinter/windows/ctk_tk.py b/customtkinter/windows/ctk_tk.py index 5a5c7f2..7e8791c 100644 --- a/customtkinter/windows/ctk_tk.py +++ b/customtkinter/windows/ctk_tk.py @@ -76,9 +76,9 @@ class CTk(tkinter.Tk): self._block_update_dimensions_event = False def _focus_in_event(self, event): - # sometimes window looses focus on macOS if window is selected from Mission Control, so focus has to be forced again + # sometimes window looses jumps back on macOS if window is selected from Mission Control, so has to be lifted again if sys.platform == "darwin": - self.focus_force() + self.lift() def _update_dimensions_event(self, event=None): if not self._block_update_dimensions_event: diff --git a/customtkinter/windows/ctk_toplevel.py b/customtkinter/windows/ctk_toplevel.py index a00a10e..e18270f 100644 --- a/customtkinter/windows/ctk_toplevel.py +++ b/customtkinter/windows/ctk_toplevel.py @@ -70,9 +70,9 @@ class CTkToplevel(tkinter.Toplevel): self.bind('', self._focus_in_event) def _focus_in_event(self, event): - # sometimes window looses focus on macOS if window is selected from Mission Control, so focus has to be forced again + # sometimes window looses jumps back on macOS if window is selected from Mission Control, so has to be lifted again if sys.platform == "darwin": - self.focus_force() + self.lift() def _update_dimensions_event(self, event=None): detected_width = self.winfo_width() # detect current window size diff --git a/test/manual_integration_tests/test_font.py b/test/manual_integration_tests/test_font.py index d7a2f23..21f00c7 100644 --- a/test/manual_integration_tests/test_font.py +++ b/test/manual_integration_tests/test_font.py @@ -42,7 +42,7 @@ b.pack(pady=2) b1 = customtkinter.CTkButton(frame_1, text="object default modified") b1.pack(pady=(10, 2)) b1.cget("font").configure(size=9) -print(b1.cget("font").cget("size"), b1.cget("font").cget("family")) +print("test_font.py:", b1.cget("font").cget("size"), b1.cget("font").cget("family")) b2 = customtkinter.CTkButton(frame_1, text="object default overridden") b2.pack(pady=10) @@ -58,7 +58,9 @@ for i in range(30): c.grid(row=i, column=2, pady=1) c = customtkinter.CTkComboBox(frame_2, font=label_font, height=15) c.grid(row=i, column=3, pady=1) -frame_2.grid_columnconfigure((0, 1, 2, 3), weight=1) + e = customtkinter.CTkEntry(frame_2, font=label_font, height=15, placeholder_text="testtest") + e.grid(row=i, column=4, pady=1) +frame_2.grid_columnconfigure((0, 1, 2, 3, 4), weight=1) app.after(1500, lambda: label_font.configure(size=10)) # app.after(1500, lambda: l.configure(text="dshgfldjskhfjdslafhdjsgkkjdaslö")) diff --git a/test/manual_integration_tests/test_states.py b/test/manual_integration_tests/test_states.py index 188aba8..e7c5daf 100644 --- a/test/manual_integration_tests/test_states.py +++ b/test/manual_integration_tests/test_states.py @@ -8,9 +8,9 @@ app.title("CustomTkinter Test") def change_state(widget): - if widget.state == tkinter.NORMAL: + if widget.cget("state") == tkinter.NORMAL: widget.configure(state=tkinter.DISABLED) - elif widget.state == tkinter.DISABLED: + elif widget.cget("state") == tkinter.DISABLED: widget.configure(state=tkinter.NORMAL)