From 91e7e3077cf83d9cce282c86744d578e7dc6b62b Mon Sep 17 00:00:00 2001 From: Tom Schimansky Date: Mon, 13 Jun 2022 15:08:13 +0200 Subject: [PATCH 01/33] enhanced dropdown_menu_fallback.py --- customtkinter/settings.py | 1 + customtkinter/widgets/ctk_entry.py | 7 ++- customtkinter/widgets/ctk_optionmenu.py | 26 ++++++--- customtkinter/widgets/dropdown_menu.py | 27 ++++++--- .../widgets/dropdown_menu_fallback.py | 55 +++++++++++++++++++ setup.cfg | 2 +- .../test_optionmenu_combobox.py | 9 +-- 7 files changed, 101 insertions(+), 26 deletions(-) create mode 100644 customtkinter/widgets/dropdown_menu_fallback.py diff --git a/customtkinter/settings.py b/customtkinter/settings.py index cf1c681..a93f800 100644 --- a/customtkinter/settings.py +++ b/customtkinter/settings.py @@ -3,3 +3,4 @@ class Settings: cursor_manipulation_enabled = True deactivate_macos_window_header_manipulation = False deactivate_windows_window_header_manipulation = False + use_dropdown_fallback = True diff --git a/customtkinter/widgets/ctk_entry.py b/customtkinter/widgets/ctk_entry.py index 3c6c9a6..00ed619 100644 --- a/customtkinter/widgets/ctk_entry.py +++ b/customtkinter/widgets/ctk_entry.py @@ -51,7 +51,7 @@ 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="we") + self.canvas.grid(column=0, row=0, sticky="nswe") self.draw_engine = DrawEngine(self.canvas) self.entry = tkinter.Entry(master=self, @@ -61,8 +61,9 @@ class CTkEntry(CTkBaseClass): font=self.apply_font_scaling(self.text_font), state=self.state, **kwargs) - 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.entry.grid(column=0, row=0, sticky="nswe", + padx=self.apply_widget_scaling(self.corner_radius) if self.corner_radius >= 6 else self.apply_widget_scaling(6), + pady=(self.apply_widget_scaling(self.border_width), self.apply_widget_scaling(self.border_width + 1))) super().bind('', self.update_dimensions_event) self.entry.bind('', self.set_placeholder) diff --git a/customtkinter/widgets/ctk_optionmenu.py b/customtkinter/widgets/ctk_optionmenu.py index 6b0253f..3532f29 100644 --- a/customtkinter/widgets/ctk_optionmenu.py +++ b/customtkinter/widgets/ctk_optionmenu.py @@ -3,6 +3,7 @@ import sys from typing import Union from .dropdown_menu import DropdownMenu +from .dropdown_menu_fallback import DropdownMenuFallback from .ctk_canvas import CTkCanvas from ..theme_manager import ThemeManager @@ -168,15 +169,22 @@ class CTkOptionMenu(CTkBaseClass): self.text_label.configure(bg=ThemeManager.single_color(self.fg_color, self._appearance_mode)) def open_dropdown_menu(self): - self.dropdown_menu = DropdownMenu(x_position=self.winfo_rootx(), - y_position=self.winfo_rooty() + self.apply_widget_scaling(self._current_height + 4), - width=self._current_width, - values=self.values, - command=self.set, - fg_color=self.dropdown_color, - button_hover_color=self.dropdown_hover_color, - button_color=self.dropdown_color, - text_color=self.dropdown_text_color) + if not Settings.use_dropdown_fallback: + self.dropdown_menu = DropdownMenu(x_position=self.winfo_rootx(), + y_position=self.winfo_rooty() + self.apply_widget_scaling(self._current_height + 4), + width=self._current_width, + values=self.values, + command=self.set, + fg_color=self.dropdown_color, + button_hover_color=self.dropdown_hover_color, + button_color=self.dropdown_color, + text_color=self.dropdown_text_color) + else: + self.dropdown_menu = DropdownMenuFallback(master=self, + values=self.values, + command=self.set) + self.dropdown_menu.open(x=self.winfo_rootx(), + y=self.winfo_rooty() + self.apply_widget_scaling(self._current_height + 0)) def configure(self, *args, **kwargs): require_redraw = False # some attribute changes require a call of self.draw() at the end diff --git a/customtkinter/widgets/dropdown_menu.py b/customtkinter/widgets/dropdown_menu.py index 9a1dd40..d7057de 100644 --- a/customtkinter/widgets/dropdown_menu.py +++ b/customtkinter/widgets/dropdown_menu.py @@ -1,6 +1,8 @@ import customtkinter import tkinter import sys +from distutils.version import StrictVersion as Version +import platform from typing import Union from ..theme_manager import ThemeManager @@ -55,8 +57,15 @@ class DropdownMenu(tkinter.Toplevel): self.grid_rowconfigure(0, weight=1) if sys.platform.startswith("darwin"): - self.overrideredirect(True) # remove title-bar - self.overrideredirect(False) + if Version(platform.python_version()) < Version("3.10"): + self.focus() + self.overrideredirect(True) # remove title-bar + self.overrideredirect(False) + else: + self.overrideredirect(True) + self.geometry(f"+{round(x_position)}+{round(y_position)}") + self.focus_set() + self.wm_attributes("-transparent", True) # turn off window shadow self.config(bg='systemTransparent') # transparent bg self.frame = customtkinter.CTkFrame(self, @@ -74,16 +83,17 @@ class DropdownMenu(tkinter.Toplevel): border_width=0, width=self.width, corner_radius=self.corner_radius, - fg_color=self.fg_color, overwrite_preferred_drawing_method="circle_shapes") - else: + fg_color=self.fg_color, + overwrite_preferred_drawing_method="circle_shapes") + else: # Linux self.overrideredirect(True) # remove title-bar - self.configure(bg="#010302") - self.wm_attributes("-transparentcolor", "#010302") + # self.configure(bg="#010302") + # self.wm_attributes("-transparentcolor", "#010302") self.frame = customtkinter.CTkFrame(self, border_width=0, width=self.width, - corner_radius=self.corner_radius, - fg_color=self.fg_color, overwrite_preferred_drawing_method="circle_shapes") + corner_radius=0, + fg_color=self.fg_color) self.frame.grid(row=0, column=0, sticky="nsew", rowspan=1) self.frame.grid_rowconfigure(len(self.values) + 1, minsize=self.apply_spacing_scaling(y_spacing)) # add spacing at the bottom @@ -108,7 +118,6 @@ class DropdownMenu(tkinter.Toplevel): self.button_list.append(button) self.bind("", self.focus_loss_event) - self.frame.canvas.bind("", self.focus_loss_event) def apply_widget_scaling(self, value: Union[int, float, str]) -> Union[float, str]: if isinstance(value, (int, float)): diff --git a/customtkinter/widgets/dropdown_menu_fallback.py b/customtkinter/widgets/dropdown_menu_fallback.py new file mode 100644 index 0000000..476f634 --- /dev/null +++ b/customtkinter/widgets/dropdown_menu_fallback.py @@ -0,0 +1,55 @@ +import tkinter +import sys +from distutils.version import StrictVersion as Version +import platform +from typing import Union + +from ..theme_manager import ThemeManager +from ..appearance_mode_tracker import AppearanceModeTracker +from ..scaling_tracker import ScalingTracker + + +class DropdownMenuFallback(tkinter.Menu): + def __init__(self, *args, + fg_color="#555555", + button_hover_color="gray35", + text_color="default_theme", + text_font="default_theme", + command=None, + values=None, + **kwargs): + super().__init__(*args, **kwargs) + + ScalingTracker.add_widget(self.set_scaling, self) + self._widget_scaling = ScalingTracker.get_widget_scaling(self) + self._spacing_scaling = ScalingTracker.get_spacing_scaling(self) + + self.fg_color = fg_color + self.button_hover_color = button_hover_color + self.text_color = ThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color + self.text_font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font + + self.menu = tkinter.Menu(master=self) + + if sys.platform.startswith("win"): + self.menu.configure() + + self.values = values + self.command = command + + for value in self.values: + self.menu.add_command(label=value.ljust(16), command=lambda v=value: self.button_callback(v)) + + def open(self, x, y): + if sys.platform == "darwin": + y = y + 8 + + self.menu.post(x, y) + + def button_callback(self, value): + if self.command is not None: + self.command(value) + + def set_scaling(self, new_widget_scaling, new_spacing_scaling, new_window_scaling): + self._widget_scaling = new_widget_scaling + self._spacing_scaling = new_spacing_scaling diff --git a/setup.cfg b/setup.cfg index 3fa8688..55ddd3e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,7 +2,7 @@ name = customtkinter version = 4.3.0 description = Create modern looking GUIs with Python -long_description = '# CustomTkinter UI-Library\nhttps://github.com/TomSchimansky/CustomTkinter/blob/master/documentation_images/Windows_dark.png\n\nMore Information: https://github.com/TomSchimansky/CustomTkinter' +long_description = CustomTkinter UI-Library\n\n[](https://github.com/TomSchimansky/CustomTkinter/blob/master/documentation_images/Windows_dark.png)\n\nMore Information: https://github.com/TomSchimansky/CustomTkinter long_description_content_type = text/markdown url = https://github.com/TomSchimansky/CustomTkinter author = Tom Schimansky diff --git a/test/manual_integration_tests/test_optionmenu_combobox.py b/test/manual_integration_tests/test_optionmenu_combobox.py index 7543105..32b29e8 100644 --- a/test/manual_integration_tests/test_optionmenu_combobox.py +++ b/test/manual_integration_tests/test_optionmenu_combobox.py @@ -1,4 +1,5 @@ import tkinter +import tkinter.ttk as ttk import customtkinter app = customtkinter.CTk() @@ -16,14 +17,14 @@ countries = ['Bahamas', 'Canada', 'Cuba', 'United States'] variable = tkinter.StringVar() variable.set("test") -# optionmenu_tk = tkinter.OptionMenu(app, variable, *countries, command=select_callback) -# optionmenu_tk.pack(pady=10, padx=10) +optionmenu_tk = tkinter.OptionMenu(app, variable, *countries, command=select_callback) +optionmenu_tk.pack(pady=10, padx=10) optionmenu_1 = customtkinter.CTkOptionMenu(app, variable=variable, values=countries, command=select_callback) optionmenu_1.pack(pady=20, padx=10) -# combobox_tk = ttk.Combobox(app, values=countries) -# combobox_tk.pack(pady=10, padx=10) +combobox_tk = ttk.Combobox(app, values=countries) +combobox_tk.pack(pady=10, padx=10) combobox_1 = customtkinter.CTkComboBox(app, variable=variable, values=countries, command=select_callback) combobox_1.pack(pady=20, padx=10) From 413cedd0935d5fde684468d179fab6a1057762f8 Mon Sep 17 00:00:00 2001 From: TomSchimansky Date: Tue, 14 Jun 2022 23:58:21 +0200 Subject: [PATCH 02/33] refined dropdown_menu_fallback.py --- customtkinter/assets/themes/blue.json | 4 +- customtkinter/widgets/ctk_optionmenu.py | 57 ++++-------- .../widgets/dropdown_menu_fallback.py | 86 ++++++++++++++++--- 3 files changed, 92 insertions(+), 55 deletions(-) diff --git a/customtkinter/assets/themes/blue.json b/customtkinter/assets/themes/blue.json index 41ce683..7c17eb5 100644 --- a/customtkinter/assets/themes/blue.json +++ b/customtkinter/assets/themes/blue.json @@ -31,8 +31,8 @@ "optionmenu_button_hover": ["#27577D", "#203A4F"], "combobox_border": ["#979DA2", "#565B5E"], "combobox_button_hover": ["#6E7174", "#7A848D"], - "dropdown_color": ["#A8ACB1", "#535353"], - "dropdown_hover": ["#D6DCE2", "#393D40"], + "dropdown_color": ["gray90", "gray20"], + "dropdown_hover": ["gray75", "gray28"], "dropdown_text": ["gray10", "#DCE4EE"] }, "text": { diff --git a/customtkinter/widgets/ctk_optionmenu.py b/customtkinter/widgets/ctk_optionmenu.py index 3532f29..49a9445 100644 --- a/customtkinter/widgets/ctk_optionmenu.py +++ b/customtkinter/widgets/ctk_optionmenu.py @@ -13,7 +13,6 @@ from .widget_base_class import CTkBaseClass class CTkOptionMenu(CTkBaseClass): - def __init__(self, *args, bg_color=None, fg_color="default_theme", @@ -74,7 +73,9 @@ class CTkOptionMenu(CTkBaseClass): else: self.current_value = "CTkOptionMenu" - self.dropdown_menu: Union[DropdownMenu, None] = None + self.dropdown_menu: Union[DropdownMenu, DropdownMenuFallback, None] = DropdownMenuFallback(master=self, + values=self.values, + command=self.set) # configure grid system (1x1) self.grid_rowconfigure(0, weight=1) @@ -87,6 +88,12 @@ class CTkOptionMenu(CTkBaseClass): self.canvas.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="nsew") self.draw_engine = DrawEngine(self.canvas) + if Settings.cursor_manipulation_enabled: + if sys.platform == "darwin": + self.configure(cursor="pointinghand") + elif sys.platform.startswith("win"): + self.configure(cursor="hand2") + # event bindings self.canvas.bind("", self.on_enter) self.canvas.bind("", self.on_leave) @@ -132,9 +139,9 @@ class CTkOptionMenu(CTkBaseClass): if self.text_label is None: self.text_label = tkinter.Label(master=self, font=self.apply_font_scaling(self.text_font)) - self.text_label.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="w", - padx=(max(self.apply_widget_scaling(self.corner_radius), 3), - max(self._current_width - left_section_width + 3, 3))) + self.text_label.grid(row=0, column=0, sticky="w", + 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)))) self.text_label.bind("", self.on_enter) self.text_label.bind("", self.on_leave) @@ -169,22 +176,8 @@ class CTkOptionMenu(CTkBaseClass): self.text_label.configure(bg=ThemeManager.single_color(self.fg_color, self._appearance_mode)) def open_dropdown_menu(self): - if not Settings.use_dropdown_fallback: - self.dropdown_menu = DropdownMenu(x_position=self.winfo_rootx(), - y_position=self.winfo_rooty() + self.apply_widget_scaling(self._current_height + 4), - width=self._current_width, - values=self.values, - command=self.set, - fg_color=self.dropdown_color, - button_hover_color=self.dropdown_hover_color, - button_color=self.dropdown_color, - text_color=self.dropdown_text_color) - else: - self.dropdown_menu = DropdownMenuFallback(master=self, - values=self.values, - command=self.set) - self.dropdown_menu.open(x=self.winfo_rootx(), - y=self.winfo_rooty() + self.apply_widget_scaling(self._current_height + 0)) + self.dropdown_menu.open(self.winfo_rootx(), + self.winfo_rooty() + self.apply_widget_scaling(self._current_height + 0)) def configure(self, *args, **kwargs): require_redraw = False # some attribute changes require a call of self.draw() at the end @@ -251,6 +244,7 @@ class CTkOptionMenu(CTkBaseClass): if "values" in kwargs: self.values = kwargs["values"] del kwargs["values"] + self.dropdown_menu.configure(values=self.values) super().configure(*args, **kwargs) @@ -259,34 +253,18 @@ class CTkOptionMenu(CTkBaseClass): def on_enter(self, event=0): if self.hover is True and self.state == tkinter.NORMAL and len(self.values) > 0: - if sys.platform == "darwin" and len(self.values) > 0 and Settings.cursor_manipulation_enabled: - self.configure(cursor="pointinghand") - elif sys.platform.startswith("win") and len(self.values) > 0 and Settings.cursor_manipulation_enabled: - self.configure(cursor="hand2") - # set color of inner button parts to hover color self.canvas.itemconfig("inner_parts_right", outline=ThemeManager.single_color(self.button_hover_color, self._appearance_mode), fill=ThemeManager.single_color(self.button_hover_color, self._appearance_mode)) def on_leave(self, event=0): - self.click_animation_running = False - if self.hover is True: - if sys.platform == "darwin" and len(self.values) > 0 and Settings.cursor_manipulation_enabled: - self.configure(cursor="arrow") - elif sys.platform.startswith("win") and len(self.values) > 0 and Settings.cursor_manipulation_enabled: - self.configure(cursor="arrow") - # set color of inner button parts self.canvas.itemconfig("inner_parts_right", outline=ThemeManager.single_color(self.button_color, self._appearance_mode), fill=ThemeManager.single_color(self.button_color, self._appearance_mode)) - def click_animation(self): - if self.click_animation_running: - self.on_enter() - def variable_callback(self, var_name, index, mode): if not self.variable_callback_blocked: self.set(self.variable.get(), from_variable_callback=True) @@ -314,8 +292,3 @@ class CTkOptionMenu(CTkBaseClass): def clicked(self, event=0): if self.state is not tkinter.DISABLED and len(self.values) > 0: self.open_dropdown_menu() - - # click animation: change color with .on_leave() and back to normal after 100ms with click_animation() - self.on_leave() - self.click_animation_running = True - self.after(100, self.click_animation) diff --git a/customtkinter/widgets/dropdown_menu_fallback.py b/customtkinter/widgets/dropdown_menu_fallback.py index 476f634..da01572 100644 --- a/customtkinter/widgets/dropdown_menu_fallback.py +++ b/customtkinter/widgets/dropdown_menu_fallback.py @@ -1,5 +1,6 @@ import tkinter import sys +import copy from distutils.version import StrictVersion as Version import platform from typing import Union @@ -11,8 +12,9 @@ from ..scaling_tracker import ScalingTracker class DropdownMenuFallback(tkinter.Menu): def __init__(self, *args, - fg_color="#555555", - button_hover_color="gray35", + min_character_width=18, + fg_color="default_theme", + button_hover_color="default_theme", text_color="default_theme", text_font="default_theme", command=None, @@ -24,32 +26,94 @@ class DropdownMenuFallback(tkinter.Menu): self._widget_scaling = ScalingTracker.get_widget_scaling(self) self._spacing_scaling = ScalingTracker.get_spacing_scaling(self) - self.fg_color = fg_color - self.button_hover_color = button_hover_color + AppearanceModeTracker.add(self.set_appearance_mode, self) + self._appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark" + + self.min_character_width = min_character_width + self.fg_color = ThemeManager.theme["color"]["dropdown_color"] if fg_color == "default_theme" else fg_color + self.button_hover_color = ThemeManager.theme["color"]["dropdown_hover"] if button_hover_color == "default_theme" else button_hover_color self.text_color = ThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color self.text_font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font - self.menu = tkinter.Menu(master=self) - if sys.platform.startswith("win"): - self.menu.configure() + self.configure(tearoff=False, + relief="flat", + activebackground=ThemeManager.single_color(self.button_hover_color, self._appearance_mode), + borderwidth=0, + activeborderwidth=self.apply_widget_scaling(4), + bg=ThemeManager.single_color(self.fg_color, self._appearance_mode), + fg=ThemeManager.single_color(self.text_color, self._appearance_mode), + activeforeground=ThemeManager.single_color(self.text_color, self._appearance_mode), + font=self.apply_font_scaling(self.text_font), + cursor="hand2") self.values = values self.command = command + self.add_menu_commands() + + def add_menu_commands(self): for value in self.values: - self.menu.add_command(label=value.ljust(16), command=lambda v=value: self.button_callback(v)) + self.add_command(label=value.ljust(self.min_character_width), command=lambda v=value: self.button_callback(v), compound="left") - def open(self, x, y): + def open(self, x: Union[int, float], y: Union[int, float]): if sys.platform == "darwin": - y = y + 8 + y += self.apply_widget_scaling(8) + elif sys.platform.startswith("win"): + y += self.apply_widget_scaling(3) - self.menu.post(x, y) + self.post(int(x), int(y)) def button_callback(self, value): if self.command is not None: self.command(value) + def configure(self, **kwargs): + if "values" in kwargs: + self.values = kwargs["values"] + del kwargs["values"] + self.delete(0, "end") + self.add_menu_commands() + + super().configure(**kwargs) + + def apply_widget_scaling(self, value: Union[int, float, str]) -> Union[float, str]: + if isinstance(value, (int, float)): + return value * self._widget_scaling + else: + return value + + def apply_font_scaling(self, font): + if type(font) == tuple or type(font) == list: + font_list = list(font) + for i in range(len(font_list)): + if (type(font_list[i]) == int or type(font_list[i]) == float) and font_list[i] < 0: + font_list[i] = int(font_list[i] * self._widget_scaling) + return tuple(font_list) + + elif type(font) == str: + for negative_number in re.findall(r" -\d* ", font): + font = font.replace(negative_number, f" {int(int(negative_number) * self._widget_scaling)} ") + return font + + elif isinstance(font, tkinter.font.Font): + new_font_object = copy.copy(font) + if font.cget("size") < 0: + new_font_object.config(size=int(font.cget("size") * self._widget_scaling)) + return new_font_object + + else: + return font + def set_scaling(self, new_widget_scaling, new_spacing_scaling, new_window_scaling): self._widget_scaling = new_widget_scaling self._spacing_scaling = new_spacing_scaling + + self.configure(font=self.apply_font_scaling(self.text_font), + activeborderwidth=self.apply_widget_scaling(4),) + + def set_appearance_mode(self, mode_string): + if mode_string.lower() == "dark": + self._appearance_mode = 1 + elif mode_string.lower() == "light": + self._appearance_mode = 0 From a86dbd4d07692a29d9d89fdf69a91d04d840affc Mon Sep 17 00:00:00 2001 From: Tom Schimansky Date: Wed, 15 Jun 2022 00:14:35 +0200 Subject: [PATCH 03/33] fixed dropdown_menu_fallback.py for macOS --- customtkinter/widgets/dropdown_menu_fallback.py | 6 +++++- examples/simple_example.py | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/customtkinter/widgets/dropdown_menu_fallback.py b/customtkinter/widgets/dropdown_menu_fallback.py index da01572..cf5317d 100644 --- a/customtkinter/widgets/dropdown_menu_fallback.py +++ b/customtkinter/widgets/dropdown_menu_fallback.py @@ -35,7 +35,11 @@ class DropdownMenuFallback(tkinter.Menu): self.text_color = ThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color self.text_font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font - if sys.platform.startswith("win"): + if sys.platform == "darwin": + self.configure(tearoff=False, + font=self.apply_font_scaling(self.text_font)) + + elif sys.platform.startswith("win"): self.configure(tearoff=False, relief="flat", activebackground=ThemeManager.single_color(self.button_hover_color, self._appearance_mode), diff --git a/examples/simple_example.py b/examples/simple_example.py index e0bdf80..57b13d8 100644 --- a/examples/simple_example.py +++ b/examples/simple_example.py @@ -36,7 +36,7 @@ slider_1.set(0.5) entry_1 = customtkinter.CTkEntry(master=frame_1, placeholder_text="CTkEntry") entry_1.pack(pady=12, padx=10) -optionmenu_1 = customtkinter.CTkOptionMenu(frame_1, values=["Option 1", "Option 2", "Option 42"]) +optionmenu_1 = customtkinter.CTkOptionMenu(frame_1, values=["Option 1", "Option 2", "Option 42 long long long..."]) optionmenu_1.pack(pady=12, padx=10) optionmenu_1.set("CTkOptionMenu") From 20e16969f228903dd9e9d4b006c4d78eeda0e95d Mon Sep 17 00:00:00 2001 From: TomSchimansky Date: Tue, 14 Jun 2022 18:31:10 -0400 Subject: [PATCH 04/33] fixed dropdown_menu_fallback.py for Linux --- customtkinter/widgets/dropdown_menu.py | 7 +++--- .../widgets/dropdown_menu_fallback.py | 25 ++++++++++++++----- examples/simple_example.py | 2 ++ 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/customtkinter/widgets/dropdown_menu.py b/customtkinter/widgets/dropdown_menu.py index d7057de..85c9490 100644 --- a/customtkinter/widgets/dropdown_menu.py +++ b/customtkinter/widgets/dropdown_menu.py @@ -76,13 +76,14 @@ class DropdownMenu(tkinter.Toplevel): elif sys.platform.startswith("win"): self.overrideredirect(True) # remove title-bar - self.configure(bg="#010302") - self.wm_attributes("-transparentcolor", "#010302") + #self.configure(bg="#010302") + #self.wm_attributes("-transparent", "#010302") + self.focus() self.focus() self.frame = customtkinter.CTkFrame(self, border_width=0, width=self.width, - corner_radius=self.corner_radius, + corner_radius=0, fg_color=self.fg_color, overwrite_preferred_drawing_method="circle_shapes") else: # Linux diff --git a/customtkinter/widgets/dropdown_menu_fallback.py b/customtkinter/widgets/dropdown_menu_fallback.py index cf5317d..59f67ff 100644 --- a/customtkinter/widgets/dropdown_menu_fallback.py +++ b/customtkinter/widgets/dropdown_menu_fallback.py @@ -1,8 +1,6 @@ import tkinter import sys import copy -from distutils.version import StrictVersion as Version -import platform from typing import Union from ..theme_manager import ThemeManager @@ -51,6 +49,17 @@ class DropdownMenuFallback(tkinter.Menu): font=self.apply_font_scaling(self.text_font), cursor="hand2") + else: + self.configure(tearoff=False, + relief="flat", + activebackground=ThemeManager.single_color(self.button_hover_color, self._appearance_mode), + borderwidth=0, + activeborderwidth=0, + bg=ThemeManager.single_color(self.fg_color, self._appearance_mode), + fg=ThemeManager.single_color(self.text_color, self._appearance_mode), + activeforeground=ThemeManager.single_color(self.text_color, self._appearance_mode), + font=self.apply_font_scaling(self.text_font)) + self.values = values self.command = command @@ -58,12 +67,14 @@ class DropdownMenuFallback(tkinter.Menu): def add_menu_commands(self): for value in self.values: - self.add_command(label=value.ljust(self.min_character_width), command=lambda v=value: self.button_callback(v), compound="left") + self.add_command(label=value.ljust(self.min_character_width), + command=lambda v=value: self.button_callback(v), + compound="left") def open(self, x: Union[int, float], y: Union[int, float]): if sys.platform == "darwin": y += self.apply_widget_scaling(8) - elif sys.platform.startswith("win"): + else: y += self.apply_widget_scaling(3) self.post(int(x), int(y)) @@ -113,8 +124,10 @@ class DropdownMenuFallback(tkinter.Menu): self._widget_scaling = new_widget_scaling self._spacing_scaling = new_spacing_scaling - self.configure(font=self.apply_font_scaling(self.text_font), - activeborderwidth=self.apply_widget_scaling(4),) + self.configure(font=self.apply_font_scaling(self.text_font)) + + if sys.platform.startswith("win"): + self.configure(activeborderwidth=self.apply_widget_scaling(4)) def set_appearance_mode(self, mode_string): if mode_string.lower() == "dark": diff --git a/examples/simple_example.py b/examples/simple_example.py index 57b13d8..fbe1cdf 100644 --- a/examples/simple_example.py +++ b/examples/simple_example.py @@ -1,6 +1,8 @@ import tkinter import customtkinter +customtkinter.set_widget_scaling(2) + customtkinter.set_appearance_mode("dark") # Modes: "System" (standard), "Dark", "Light" customtkinter.set_default_color_theme("blue") # Themes: "blue" (standard), "green", "dark-blue" From d8b51040283af73297ed25e50928efb93c65d111 Mon Sep 17 00:00:00 2001 From: Tom Schimansky Date: Wed, 15 Jun 2022 01:24:02 +0200 Subject: [PATCH 05/33] adopted new dropdown menu for combobox --- customtkinter/assets/themes/dark-blue.json | 6 +- customtkinter/assets/themes/green.json | 6 +- customtkinter/assets/themes/sweetkind.json | 10 +- customtkinter/widgets/ctk_combobox.py | 59 ++--- customtkinter/widgets/ctk_optionmenu.py | 62 +++-- customtkinter/widgets/dropdown_menu.py | 222 +++++++++--------- .../widgets/dropdown_menu_fallback.py | 136 ----------- customtkinter/widgets/dropdown_menu_old.py | 149 ++++++++++++ examples/simple_example.py | 4 +- ...test_tk_variables.py => test_variables.py} | 0 10 files changed, 350 insertions(+), 304 deletions(-) delete mode 100644 customtkinter/widgets/dropdown_menu_fallback.py create mode 100644 customtkinter/widgets/dropdown_menu_old.py rename test/manual_integration_tests/{test_tk_variables.py => test_variables.py} (100%) diff --git a/customtkinter/assets/themes/dark-blue.json b/customtkinter/assets/themes/dark-blue.json index 544c99a..60abe7f 100644 --- a/customtkinter/assets/themes/dark-blue.json +++ b/customtkinter/assets/themes/dark-blue.json @@ -31,9 +31,9 @@ "optionmenu_button_hover": ["#27577D", "#203A4F"], "combobox_border": ["gray70", "gray32"], "combobox_button_hover": ["#6E7174", "#7A848D"], - "dropdown_color": ["#A8ACB1", "#535353"], - "dropdown_hover": ["#D6DCE2", "#393D40"], - "dropdown_text": ["gray12", "gray90"] + "dropdown_color": ["gray90", "gray20"], + "dropdown_hover": ["gray75", "gray28"], + "dropdown_text": ["gray10", "#DCE4EE"] }, "text": { "macOS": { diff --git a/customtkinter/assets/themes/green.json b/customtkinter/assets/themes/green.json index bc209c2..6032062 100644 --- a/customtkinter/assets/themes/green.json +++ b/customtkinter/assets/themes/green.json @@ -31,9 +31,9 @@ "optionmenu_button_hover":["gray40", "gray70"], "combobox_border": ["gray70", "gray32"], "combobox_button_hover": ["#6E7174", "#7A848D"], - "dropdown_color": ["#A8ACB1", "#535353"], - "dropdown_hover": ["#D6DCE2", "#393D40"], - "dropdown_text": ["gray12", "gray90"] + "dropdown_color": ["gray90", "gray20"], + "dropdown_hover": ["gray75", "gray28"], + "dropdown_text": ["gray10", "#DCE4EE"] }, "text": { "macOS": { diff --git a/customtkinter/assets/themes/sweetkind.json b/customtkinter/assets/themes/sweetkind.json index e962b24..3f3b1a2 100644 --- a/customtkinter/assets/themes/sweetkind.json +++ b/customtkinter/assets/themes/sweetkind.json @@ -27,7 +27,13 @@ "switch_progress": ["#00e6c3", "#00e6c3"], "switch_button": ["#2e324a", "#2e324a"], "switch_button_hover": ["#2e324a", "#2e324a"], - "darken_factor": 0.1 + "optionmenu_button": ["#36719F", "#144870"], + "optionmenu_button_hover": ["#27577D", "#203A4F"], + "combobox_border": ["#979DA2", "#565B5E"], + "combobox_button_hover": ["#6E7174", "#7A848D"], + "dropdown_color": ["gray90", "gray20"], + "dropdown_hover": ["gray75", "gray28"], + "dropdown_text": ["gray10", "#DCE4EE"] }, "text": { "macOS": { @@ -66,4 +72,4 @@ "switch_button_corner_radius": 1000, "switch_button_length": 2 } -} \ No newline at end of file +} diff --git a/customtkinter/widgets/ctk_combobox.py b/customtkinter/widgets/ctk_combobox.py index 68e09a4..174e8bc 100644 --- a/customtkinter/widgets/ctk_combobox.py +++ b/customtkinter/widgets/ctk_combobox.py @@ -2,6 +2,7 @@ import tkinter import sys from typing import Union +from .dropdown_menu_old import DropdownMenu from .dropdown_menu import DropdownMenu from .ctk_canvas import CTkCanvas @@ -12,7 +13,6 @@ from .widget_base_class import CTkBaseClass class CTkComboBox(CTkBaseClass): - def __init__(self, *args, bg_color=None, fg_color="default_theme", @@ -30,6 +30,7 @@ class CTkComboBox(CTkBaseClass): corner_radius="default_theme", border_width="default_theme", text_font="default_theme", + dropdown_text_font="default_theme", text_color="default_theme", text_color_disabled="default_theme", hover=True, @@ -44,16 +45,12 @@ class CTkComboBox(CTkBaseClass): self.border_color = ThemeManager.theme["color"]["combobox_border"] if border_color == "default_theme" else border_color self.button_color = ThemeManager.theme["color"]["combobox_border"] if button_color == "default_theme" else button_color self.button_hover_color = ThemeManager.theme["color"]["combobox_button_hover"] if button_hover_color == "default_theme" else button_hover_color - self.dropdown_color = ThemeManager.theme["color"]["dropdown_color"] if dropdown_color == "default_theme" else dropdown_color - self.dropdown_hover_color = ThemeManager.theme["color"]["dropdown_hover"] if dropdown_hover_color == "default_theme" else dropdown_hover_color - self.dropdown_text_color = ThemeManager.theme["color"]["dropdown_text"] if dropdown_text_color == "default_theme" else dropdown_text_color # shape self.corner_radius = ThemeManager.theme["shape"]["button_corner_radius"] if corner_radius == "default_theme" else corner_radius self.border_width = ThemeManager.theme["shape"]["entry_border_width"] if border_width == "default_theme" else border_width # text and font - self.text_label = None self.text_color = ThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color self.text_color_disabled = ThemeManager.theme["color"]["text_button_disabled"] if text_color_disabled == "default_theme" else text_color_disabled self.text_font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font @@ -63,7 +60,6 @@ class CTkComboBox(CTkBaseClass): self.variable = variable self.state = state self.hover = hover - self.click_animation_running = False if values is None: self.values = ["CTkComboBox"] @@ -75,7 +71,13 @@ class CTkComboBox(CTkBaseClass): else: self.current_value = "CTkComboBox" - self.dropdown_menu: Union[DropdownMenu, None] = None + self.dropdown_menu = DropdownMenu(master=self, + values=self.values, + command=self.set, + fg_color=dropdown_color, + hover_color=dropdown_hover_color, + text_color=dropdown_text_color, + text_font=dropdown_text_font) # configure grid system (1x1) self.grid_rowconfigure(0, weight=1) @@ -96,8 +98,8 @@ class CTkComboBox(CTkBaseClass): font=self.apply_font_scaling(self.text_font)) left_section_width = self._current_width - self._current_height self.entry.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="ew", - padx=(self.apply_widget_scaling(max(self.corner_radius, 3)), - self.apply_widget_scaling(max(self._current_width - left_section_width + 3, 3)))) + 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)))) self.draw() # initial draw @@ -173,15 +175,8 @@ class CTkComboBox(CTkBaseClass): fill=ThemeManager.single_color(self.text_color, self._appearance_mode)) def open_dropdown_menu(self): - self.dropdown_menu = DropdownMenu(x_position=self.winfo_rootx(), - y_position=self.winfo_rooty() + self.apply_widget_scaling(self._current_height + 4), - width=self._current_width, - values=self.values, - command=self.set, - fg_color=self.dropdown_color, - button_hover_color=self.dropdown_hover_color, - button_color=self.dropdown_color, - text_color=self.dropdown_text_color) + self.dropdown_menu.open(self.winfo_rootx(), + self.winfo_rooty() + self.apply_widget_scaling(self._current_height + 0)) def configure(self, *args, **kwargs): require_redraw = False # some attribute changes require a call of self.draw() at the end @@ -240,6 +235,23 @@ class CTkComboBox(CTkBaseClass): if "values" in kwargs: self.values = kwargs["values"] del kwargs["values"] + self.dropdown_menu.configure(values=self.values) + + if "dropdown_color" in kwargs: + self.dropdown_menu.configure(fg_color=kwargs["dropdown_color"]) + del kwargs["dropdown_color"] + + if "dropdown_hover_color" in kwargs: + self.dropdown_menu.configure(hover_color=kwargs["dropdown_hover_color"]) + del kwargs["dropdown_hover_color"] + + if "dropdown_text_color" in kwargs: + self.dropdown_menu.configure(text_color=kwargs["dropdown_text_color"]) + del kwargs["dropdown_text_color"] + + if "dropdown_text_font" in kwargs: + self.dropdown_menu.configure(text_font=kwargs["dropdown_text_font"]) + del kwargs["dropdown_text_font"] super().configure(*args, **kwargs) @@ -262,8 +274,6 @@ class CTkComboBox(CTkBaseClass): fill=ThemeManager.single_color(self.button_hover_color, self._appearance_mode)) def on_leave(self, event=0): - self.click_animation_running = False - if self.hover is True: if sys.platform == "darwin" and len(self.values) > 0 and Settings.cursor_manipulation_enabled: self.canvas.configure(cursor="arrow") @@ -278,10 +288,6 @@ class CTkComboBox(CTkBaseClass): outline=ThemeManager.single_color(self.button_color, self._appearance_mode), fill=ThemeManager.single_color(self.button_color, self._appearance_mode)) - def click_animation(self): - if self.click_animation_running: - self.on_enter() - def set(self, value: str, from_variable_callback: bool = False): self.current_value = value @@ -298,8 +304,3 @@ class CTkComboBox(CTkBaseClass): def clicked(self, event=0): if self.state is not tkinter.DISABLED and len(self.values) > 0: self.open_dropdown_menu() - - # click animation: change color with .on_leave() and back to normal after 100ms with click_animation() - self.on_leave() - self.click_animation_running = True - self.after(100, self.click_animation) diff --git a/customtkinter/widgets/ctk_optionmenu.py b/customtkinter/widgets/ctk_optionmenu.py index 49a9445..4430274 100644 --- a/customtkinter/widgets/ctk_optionmenu.py +++ b/customtkinter/widgets/ctk_optionmenu.py @@ -2,8 +2,8 @@ import tkinter import sys from typing import Union +from .dropdown_menu_old import DropdownMenu from .dropdown_menu import DropdownMenu -from .dropdown_menu_fallback import DropdownMenuFallback from .ctk_canvas import CTkCanvas from ..theme_manager import ThemeManager @@ -18,6 +18,8 @@ class CTkOptionMenu(CTkBaseClass): fg_color="default_theme", button_color="default_theme", button_hover_color="default_theme", + text_color="default_theme", + text_color_disabled="default_theme", dropdown_color="default_theme", dropdown_hover_color="default_theme", dropdown_text_color="default_theme", @@ -28,8 +30,7 @@ class CTkOptionMenu(CTkBaseClass): height=28, corner_radius="default_theme", text_font="default_theme", - text_color="default_theme", - text_color_disabled="default_theme", + dropdown_text_font="default_theme", hover=True, state=tkinter.NORMAL, **kwargs): @@ -41,18 +42,15 @@ class CTkOptionMenu(CTkBaseClass): self.fg_color = ThemeManager.theme["color"]["button"] if fg_color == "default_theme" else fg_color self.button_color = ThemeManager.theme["color"]["optionmenu_button"] if button_color == "default_theme" else button_color self.button_hover_color = ThemeManager.theme["color"]["optionmenu_button_hover"] if button_hover_color == "default_theme" else button_hover_color - self.dropdown_color = ThemeManager.theme["color"]["dropdown_color"] if dropdown_color == "default_theme" else dropdown_color - self.dropdown_hover_color = ThemeManager.theme["color"]["dropdown_hover"] if dropdown_hover_color == "default_theme" else dropdown_hover_color - self.dropdown_text_color = ThemeManager.theme["color"]["dropdown_text"] if dropdown_text_color == "default_theme" else dropdown_text_color # shape self.corner_radius = ThemeManager.theme["shape"]["button_corner_radius"] if corner_radius == "default_theme" else corner_radius # text and font - self.text_label = None self.text_color = ThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color self.text_color_disabled = ThemeManager.theme["color"]["text_button_disabled"] if text_color_disabled == "default_theme" else text_color_disabled self.text_font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font + self.dropdown_text_font = dropdown_text_font # callback and hover functionality self.function = command @@ -61,7 +59,6 @@ class CTkOptionMenu(CTkBaseClass): self.variable_callback_name = None self.state = state self.hover = hover - self.click_animation_running = False if values is None: self.values = ["CTkOptionMenu"] @@ -73,9 +70,13 @@ class CTkOptionMenu(CTkBaseClass): else: self.current_value = "CTkOptionMenu" - self.dropdown_menu: Union[DropdownMenu, DropdownMenuFallback, None] = DropdownMenuFallback(master=self, - values=self.values, - command=self.set) + self.dropdown_menu = DropdownMenu(master=self, + values=self.values, + command=self.set, + fg_color=dropdown_color, + hover_color=dropdown_hover_color, + text_color=dropdown_text_color, + text_font=dropdown_text_font) # configure grid system (1x1) self.grid_rowconfigure(0, weight=1) @@ -88,6 +89,12 @@ class CTkOptionMenu(CTkBaseClass): self.canvas.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="nsew") self.draw_engine = DrawEngine(self.canvas) + left_section_width = self._current_width - self._current_height + self.text_label = tkinter.Label(master=self, font=self.apply_font_scaling(self.text_font)) + self.text_label.grid(row=0, column=0, sticky="w", + 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)))) + if Settings.cursor_manipulation_enabled: if sys.platform == "darwin": self.configure(cursor="pointinghand") @@ -99,6 +106,12 @@ class CTkOptionMenu(CTkBaseClass): self.canvas.bind("", self.on_leave) self.canvas.bind("", self.clicked) self.canvas.bind("", self.clicked) + + self.text_label.bind("", self.on_enter) + self.text_label.bind("", self.on_leave) + self.text_label.bind("", self.clicked) + self.text_label.bind("", self.clicked) + self.bind('', self.update_dimensions_event) self.draw() # initial draw @@ -136,17 +149,6 @@ class CTkOptionMenu(CTkBaseClass): requires_recoloring_2 = self.draw_engine.draw_dropdown_arrow(self.apply_widget_scaling(self._current_width - (self._current_height / 2)), self.apply_widget_scaling(self._current_height / 2), self.apply_widget_scaling(self._current_height / 3)) - if self.text_label is None: - self.text_label = tkinter.Label(master=self, - font=self.apply_font_scaling(self.text_font)) - self.text_label.grid(row=0, column=0, sticky="w", - 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)))) - - self.text_label.bind("", self.on_enter) - self.text_label.bind("", self.on_leave) - self.text_label.bind("", self.clicked) - self.text_label.bind("", self.clicked) if self.current_value is not None: self.text_label.configure(text=self.current_value) @@ -246,6 +248,22 @@ class CTkOptionMenu(CTkBaseClass): del kwargs["values"] self.dropdown_menu.configure(values=self.values) + if "dropdown_color" in kwargs: + self.dropdown_menu.configure(fg_color=kwargs["dropdown_color"]) + del kwargs["dropdown_color"] + + if "dropdown_hover_color" in kwargs: + self.dropdown_menu.configure(hover_color=kwargs["dropdown_hover_color"]) + del kwargs["dropdown_hover_color"] + + if "dropdown_text_color" in kwargs: + self.dropdown_menu.configure(text_color=kwargs["dropdown_text_color"]) + del kwargs["dropdown_text_color"] + + if "dropdown_text_font" in kwargs: + self.dropdown_menu.configure(text_font=kwargs["dropdown_text_font"]) + del kwargs["dropdown_text_font"] + super().configure(*args, **kwargs) if require_redraw: diff --git a/customtkinter/widgets/dropdown_menu.py b/customtkinter/widgets/dropdown_menu.py index 85c9490..a746915 100644 --- a/customtkinter/widgets/dropdown_menu.py +++ b/customtkinter/widgets/dropdown_menu.py @@ -1,8 +1,7 @@ -import customtkinter import tkinter import sys -from distutils.version import StrictVersion as Version -import platform +import copy +import re from typing import Union from ..theme_manager import ThemeManager @@ -10,20 +9,13 @@ from ..appearance_mode_tracker import AppearanceModeTracker from ..scaling_tracker import ScalingTracker -class DropdownMenu(tkinter.Toplevel): +class DropdownMenu(tkinter.Menu): def __init__(self, *args, - fg_color="#555555", - button_color="gray50", - button_hover_color="gray35", - text_color="black", - corner_radius=6, - button_corner_radius=3, - width=120, - button_height=24, - x_position=0, - y_position=0, - x_spacing=3, - y_spacing=3, + min_character_width=18, + fg_color="default_theme", + hover_color="default_theme", + text_color="default_theme", + text_font="default_theme", command=None, values=None, **kwargs): @@ -33,92 +25,93 @@ class DropdownMenu(tkinter.Toplevel): self._widget_scaling = ScalingTracker.get_widget_scaling(self) self._spacing_scaling = ScalingTracker.get_spacing_scaling(self) + AppearanceModeTracker.add(self.set_appearance_mode, self) + self._appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark" + + self.min_character_width = min_character_width + self.fg_color = ThemeManager.theme["color"]["dropdown_color"] if fg_color == "default_theme" else fg_color + self.hover_color = ThemeManager.theme["color"]["dropdown_hover"] if hover_color == "default_theme" else hover_color + self.text_color = ThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color + self.text_font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font + + if sys.platform == "darwin": + self.configure(tearoff=False, + font=self.apply_font_scaling(self.text_font)) + + elif sys.platform.startswith("win"): + self.configure(tearoff=False, + relief="flat", + activebackground=ThemeManager.single_color(self.hover_color, self._appearance_mode), + borderwidth=0, + activeborderwidth=self.apply_widget_scaling(4), + bg=ThemeManager.single_color(self.fg_color, self._appearance_mode), + fg=ThemeManager.single_color(self.text_color, self._appearance_mode), + activeforeground=ThemeManager.single_color(self.text_color, self._appearance_mode), + font=self.apply_font_scaling(self.text_font), + cursor="hand2") + + else: + self.configure(tearoff=False, + relief="flat", + activebackground=ThemeManager.single_color(self.hover_color, self._appearance_mode), + borderwidth=0, + activeborderwidth=0, + bg=ThemeManager.single_color(self.fg_color, self._appearance_mode), + fg=ThemeManager.single_color(self.text_color, self._appearance_mode), + activeforeground=ThemeManager.single_color(self.text_color, self._appearance_mode), + font=self.apply_font_scaling(self.text_font)) + self.values = values self.command = command - # color - self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark" - self.fg_color = fg_color - self.button_color = button_color - self.button_hover_color = button_hover_color - self.text_color = text_color + self.add_menu_commands() - # shape - self.corner_radius = corner_radius - self.button_corner_radius = button_corner_radius - self.button_height = button_height - self.width = width - self.height = max(len(self.values), 1) * (self.button_height + self.apply_spacing_scaling(y_spacing)) + self.apply_spacing_scaling(y_spacing) + def add_menu_commands(self): + for value in self.values: + self.add_command(label=value.ljust(self.min_character_width), + command=lambda v=value: self.button_callback(v), + compound="left") - self.geometry(f"{round(self.apply_widget_scaling(self.width))}x" + - f"{round(self.apply_widget_scaling(self.height))}+" + - f"{round(x_position)}+{round(y_position)}") - self.grid_columnconfigure(0, weight=1) - self.grid_rowconfigure(0, weight=1) + def open(self, x: Union[int, float], y: Union[int, float]): + if sys.platform == "darwin": + y += self.apply_widget_scaling(8) + else: + y += self.apply_widget_scaling(3) - if sys.platform.startswith("darwin"): - if Version(platform.python_version()) < Version("3.10"): - self.focus() - self.overrideredirect(True) # remove title-bar - self.overrideredirect(False) - else: - self.overrideredirect(True) - self.geometry(f"+{round(x_position)}+{round(y_position)}") - self.focus_set() + self.post(int(x), int(y)) - self.wm_attributes("-transparent", True) # turn off window shadow - self.config(bg='systemTransparent') # transparent bg - self.frame = customtkinter.CTkFrame(self, - border_width=0, - width=self.width, - corner_radius=self.corner_radius, - fg_color=ThemeManager.single_color(self.fg_color, self.appearance_mode)) + def button_callback(self, value): + if self.command is not None: + self.command(value) - elif sys.platform.startswith("win"): - self.overrideredirect(True) # remove title-bar - #self.configure(bg="#010302") - #self.wm_attributes("-transparent", "#010302") - self.focus() - self.focus() - self.frame = customtkinter.CTkFrame(self, - border_width=0, - width=self.width, - corner_radius=0, - fg_color=self.fg_color, - overwrite_preferred_drawing_method="circle_shapes") - else: # Linux - self.overrideredirect(True) # remove title-bar - # self.configure(bg="#010302") - # self.wm_attributes("-transparentcolor", "#010302") - self.frame = customtkinter.CTkFrame(self, - border_width=0, - width=self.width, - corner_radius=0, - fg_color=self.fg_color) + def configure(self, **kwargs): + if "values" in kwargs: + self.values = kwargs["values"] + del kwargs["values"] + self.delete(0, "end") # delete all old commands + self.add_menu_commands() - self.frame.grid(row=0, column=0, sticky="nsew", rowspan=1) - self.frame.grid_rowconfigure(len(self.values) + 1, minsize=self.apply_spacing_scaling(y_spacing)) # add spacing at the bottom - self.frame.grid_columnconfigure(0, weight=1) + if "fg_color" in kwargs: + self.fg_color = kwargs["fg_color"] + del kwargs["fg_color"] + self.configure(bg=ThemeManager.single_color(self.fg_color, self._appearance_mode)) - self.button_list = [] - for index, option in enumerate(self.values): - button = customtkinter.CTkButton(self.frame, - text=option, - height=self.button_height, - width=self.width - 2 * self.apply_widget_scaling(x_spacing), - fg_color=self.button_color, - text_color=self.text_color, - hover_color=self.button_hover_color, - corner_radius=self.button_corner_radius, - command=lambda i=index: self.button_callback(i)) - button.text_label.configure(anchor="w") - button.text_label.grid(row=0, column=0, rowspan=2, columnspan=2, sticky="w") - button.grid(row=index, column=0, - padx=self.apply_widget_scaling(x_spacing), - pady=(self.apply_widget_scaling(y_spacing), 0), sticky="ew") - self.button_list.append(button) + if "hover_color" in kwargs: + self.hover_color = kwargs["hover_color"] + del kwargs["hover_color"] + self.configure(activebackground=ThemeManager.single_color(self.hover_color, self._appearance_mode)) - self.bind("", self.focus_loss_event) + if "text_color" in kwargs: + self.text_color = kwargs["text_color"] + del kwargs["text_color"] + self.configure(fg=ThemeManager.single_color(self.text_color, self._appearance_mode)) + + if "text_font" in kwargs: + self.text_font = kwargs["text_font"] + del kwargs["text_font"] + self.configure(font=self.apply_font_scaling(self.text_font)) + + super().configure(**kwargs) def apply_widget_scaling(self, value: Union[int, float, str]) -> Union[float, str]: if isinstance(value, (int, float)): @@ -126,24 +119,41 @@ class DropdownMenu(tkinter.Toplevel): else: return value - def apply_spacing_scaling(self, value: Union[int, float, str]) -> Union[float, str]: - if isinstance(value, (int, float)): - return value * self._spacing_scaling + def apply_font_scaling(self, font): + if type(font) == tuple or type(font) == list: + font_list = list(font) + for i in range(len(font_list)): + if (type(font_list[i]) == int or type(font_list[i]) == float) and font_list[i] < 0: + font_list[i] = int(font_list[i] * self._widget_scaling) + return tuple(font_list) + + elif type(font) == str: + for negative_number in re.findall(r" -\d* ", font): + font = font.replace(negative_number, f" {int(int(negative_number) * self._widget_scaling)} ") + return font + + elif isinstance(font, tkinter.font.Font): + new_font_object = copy.copy(font) + if font.cget("size") < 0: + new_font_object.config(size=int(font.cget("size") * self._widget_scaling)) + return new_font_object + else: - return value + return font def set_scaling(self, new_widget_scaling, new_spacing_scaling, new_window_scaling): - return + self._widget_scaling = new_widget_scaling + self._spacing_scaling = new_spacing_scaling - def focus_loss_event(self, event): - self.destroy() - if sys.platform.startswith("darwin"): - self.update() + self.configure(font=self.apply_font_scaling(self.text_font)) - def button_callback(self, index): - self.destroy() - if sys.platform.startswith("darwin"): - self.update() + if sys.platform.startswith("win"): + self.configure(activeborderwidth=self.apply_widget_scaling(4)) - if self.command is not None: - self.command(self.values[index]) + def set_appearance_mode(self, mode_string): + """ colors won't update on appearance mode change when dropdown is open, because it's not necessary """ + + if mode_string.lower() == "dark": + self._appearance_mode = 1 + elif mode_string.lower() == "light": + self._appearance_mode = 0 diff --git a/customtkinter/widgets/dropdown_menu_fallback.py b/customtkinter/widgets/dropdown_menu_fallback.py deleted file mode 100644 index 59f67ff..0000000 --- a/customtkinter/widgets/dropdown_menu_fallback.py +++ /dev/null @@ -1,136 +0,0 @@ -import tkinter -import sys -import copy -from typing import Union - -from ..theme_manager import ThemeManager -from ..appearance_mode_tracker import AppearanceModeTracker -from ..scaling_tracker import ScalingTracker - - -class DropdownMenuFallback(tkinter.Menu): - def __init__(self, *args, - min_character_width=18, - fg_color="default_theme", - button_hover_color="default_theme", - text_color="default_theme", - text_font="default_theme", - command=None, - values=None, - **kwargs): - super().__init__(*args, **kwargs) - - ScalingTracker.add_widget(self.set_scaling, self) - self._widget_scaling = ScalingTracker.get_widget_scaling(self) - self._spacing_scaling = ScalingTracker.get_spacing_scaling(self) - - AppearanceModeTracker.add(self.set_appearance_mode, self) - self._appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark" - - self.min_character_width = min_character_width - self.fg_color = ThemeManager.theme["color"]["dropdown_color"] if fg_color == "default_theme" else fg_color - self.button_hover_color = ThemeManager.theme["color"]["dropdown_hover"] if button_hover_color == "default_theme" else button_hover_color - self.text_color = ThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color - self.text_font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font - - if sys.platform == "darwin": - self.configure(tearoff=False, - font=self.apply_font_scaling(self.text_font)) - - elif sys.platform.startswith("win"): - self.configure(tearoff=False, - relief="flat", - activebackground=ThemeManager.single_color(self.button_hover_color, self._appearance_mode), - borderwidth=0, - activeborderwidth=self.apply_widget_scaling(4), - bg=ThemeManager.single_color(self.fg_color, self._appearance_mode), - fg=ThemeManager.single_color(self.text_color, self._appearance_mode), - activeforeground=ThemeManager.single_color(self.text_color, self._appearance_mode), - font=self.apply_font_scaling(self.text_font), - cursor="hand2") - - else: - self.configure(tearoff=False, - relief="flat", - activebackground=ThemeManager.single_color(self.button_hover_color, self._appearance_mode), - borderwidth=0, - activeborderwidth=0, - bg=ThemeManager.single_color(self.fg_color, self._appearance_mode), - fg=ThemeManager.single_color(self.text_color, self._appearance_mode), - activeforeground=ThemeManager.single_color(self.text_color, self._appearance_mode), - font=self.apply_font_scaling(self.text_font)) - - self.values = values - self.command = command - - self.add_menu_commands() - - def add_menu_commands(self): - for value in self.values: - self.add_command(label=value.ljust(self.min_character_width), - command=lambda v=value: self.button_callback(v), - compound="left") - - def open(self, x: Union[int, float], y: Union[int, float]): - if sys.platform == "darwin": - y += self.apply_widget_scaling(8) - else: - y += self.apply_widget_scaling(3) - - self.post(int(x), int(y)) - - def button_callback(self, value): - if self.command is not None: - self.command(value) - - def configure(self, **kwargs): - if "values" in kwargs: - self.values = kwargs["values"] - del kwargs["values"] - self.delete(0, "end") - self.add_menu_commands() - - super().configure(**kwargs) - - def apply_widget_scaling(self, value: Union[int, float, str]) -> Union[float, str]: - if isinstance(value, (int, float)): - return value * self._widget_scaling - else: - return value - - def apply_font_scaling(self, font): - if type(font) == tuple or type(font) == list: - font_list = list(font) - for i in range(len(font_list)): - if (type(font_list[i]) == int or type(font_list[i]) == float) and font_list[i] < 0: - font_list[i] = int(font_list[i] * self._widget_scaling) - return tuple(font_list) - - elif type(font) == str: - for negative_number in re.findall(r" -\d* ", font): - font = font.replace(negative_number, f" {int(int(negative_number) * self._widget_scaling)} ") - return font - - elif isinstance(font, tkinter.font.Font): - new_font_object = copy.copy(font) - if font.cget("size") < 0: - new_font_object.config(size=int(font.cget("size") * self._widget_scaling)) - return new_font_object - - else: - return font - - def set_scaling(self, new_widget_scaling, new_spacing_scaling, new_window_scaling): - self._widget_scaling = new_widget_scaling - self._spacing_scaling = new_spacing_scaling - - self.configure(font=self.apply_font_scaling(self.text_font)) - - if sys.platform.startswith("win"): - self.configure(activeborderwidth=self.apply_widget_scaling(4)) - - def set_appearance_mode(self, mode_string): - if mode_string.lower() == "dark": - self._appearance_mode = 1 - elif mode_string.lower() == "light": - self._appearance_mode = 0 diff --git a/customtkinter/widgets/dropdown_menu_old.py b/customtkinter/widgets/dropdown_menu_old.py new file mode 100644 index 0000000..85c9490 --- /dev/null +++ b/customtkinter/widgets/dropdown_menu_old.py @@ -0,0 +1,149 @@ +import customtkinter +import tkinter +import sys +from distutils.version import StrictVersion as Version +import platform +from typing import Union + +from ..theme_manager import ThemeManager +from ..appearance_mode_tracker import AppearanceModeTracker +from ..scaling_tracker import ScalingTracker + + +class DropdownMenu(tkinter.Toplevel): + def __init__(self, *args, + fg_color="#555555", + button_color="gray50", + button_hover_color="gray35", + text_color="black", + corner_radius=6, + button_corner_radius=3, + width=120, + button_height=24, + x_position=0, + y_position=0, + x_spacing=3, + y_spacing=3, + command=None, + values=None, + **kwargs): + super().__init__(*args, **kwargs) + + ScalingTracker.add_widget(self.set_scaling, self) + self._widget_scaling = ScalingTracker.get_widget_scaling(self) + self._spacing_scaling = ScalingTracker.get_spacing_scaling(self) + + self.values = values + self.command = command + + # color + self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark" + self.fg_color = fg_color + self.button_color = button_color + self.button_hover_color = button_hover_color + self.text_color = text_color + + # shape + self.corner_radius = corner_radius + self.button_corner_radius = button_corner_radius + self.button_height = button_height + self.width = width + self.height = max(len(self.values), 1) * (self.button_height + self.apply_spacing_scaling(y_spacing)) + self.apply_spacing_scaling(y_spacing) + + self.geometry(f"{round(self.apply_widget_scaling(self.width))}x" + + f"{round(self.apply_widget_scaling(self.height))}+" + + f"{round(x_position)}+{round(y_position)}") + self.grid_columnconfigure(0, weight=1) + self.grid_rowconfigure(0, weight=1) + + if sys.platform.startswith("darwin"): + if Version(platform.python_version()) < Version("3.10"): + self.focus() + self.overrideredirect(True) # remove title-bar + self.overrideredirect(False) + else: + self.overrideredirect(True) + self.geometry(f"+{round(x_position)}+{round(y_position)}") + self.focus_set() + + self.wm_attributes("-transparent", True) # turn off window shadow + self.config(bg='systemTransparent') # transparent bg + self.frame = customtkinter.CTkFrame(self, + border_width=0, + width=self.width, + corner_radius=self.corner_radius, + fg_color=ThemeManager.single_color(self.fg_color, self.appearance_mode)) + + elif sys.platform.startswith("win"): + self.overrideredirect(True) # remove title-bar + #self.configure(bg="#010302") + #self.wm_attributes("-transparent", "#010302") + self.focus() + self.focus() + self.frame = customtkinter.CTkFrame(self, + border_width=0, + width=self.width, + corner_radius=0, + fg_color=self.fg_color, + overwrite_preferred_drawing_method="circle_shapes") + else: # Linux + self.overrideredirect(True) # remove title-bar + # self.configure(bg="#010302") + # self.wm_attributes("-transparentcolor", "#010302") + self.frame = customtkinter.CTkFrame(self, + border_width=0, + width=self.width, + corner_radius=0, + fg_color=self.fg_color) + + self.frame.grid(row=0, column=0, sticky="nsew", rowspan=1) + self.frame.grid_rowconfigure(len(self.values) + 1, minsize=self.apply_spacing_scaling(y_spacing)) # add spacing at the bottom + self.frame.grid_columnconfigure(0, weight=1) + + self.button_list = [] + for index, option in enumerate(self.values): + button = customtkinter.CTkButton(self.frame, + text=option, + height=self.button_height, + width=self.width - 2 * self.apply_widget_scaling(x_spacing), + fg_color=self.button_color, + text_color=self.text_color, + hover_color=self.button_hover_color, + corner_radius=self.button_corner_radius, + command=lambda i=index: self.button_callback(i)) + button.text_label.configure(anchor="w") + button.text_label.grid(row=0, column=0, rowspan=2, columnspan=2, sticky="w") + button.grid(row=index, column=0, + padx=self.apply_widget_scaling(x_spacing), + pady=(self.apply_widget_scaling(y_spacing), 0), sticky="ew") + self.button_list.append(button) + + self.bind("", self.focus_loss_event) + + def apply_widget_scaling(self, value: Union[int, float, str]) -> Union[float, str]: + if isinstance(value, (int, float)): + return value * self._widget_scaling + else: + return value + + def apply_spacing_scaling(self, value: Union[int, float, str]) -> Union[float, str]: + if isinstance(value, (int, float)): + return value * self._spacing_scaling + else: + return value + + def set_scaling(self, new_widget_scaling, new_spacing_scaling, new_window_scaling): + return + + def focus_loss_event(self, event): + self.destroy() + if sys.platform.startswith("darwin"): + self.update() + + def button_callback(self, index): + self.destroy() + if sys.platform.startswith("darwin"): + self.update() + + if self.command is not None: + self.command(self.values[index]) diff --git a/examples/simple_example.py b/examples/simple_example.py index fbe1cdf..370b4f4 100644 --- a/examples/simple_example.py +++ b/examples/simple_example.py @@ -1,8 +1,6 @@ import tkinter import customtkinter -customtkinter.set_widget_scaling(2) - customtkinter.set_appearance_mode("dark") # Modes: "System" (standard), "Dark", "Light" customtkinter.set_default_color_theme("blue") # Themes: "blue" (standard), "green", "dark-blue" @@ -42,7 +40,7 @@ optionmenu_1 = customtkinter.CTkOptionMenu(frame_1, values=["Option 1", "Option optionmenu_1.pack(pady=12, padx=10) optionmenu_1.set("CTkOptionMenu") -combobox_1 = customtkinter.CTkComboBox(frame_1, values=["Option 1", "Option 2", "Option 42"]) +combobox_1 = customtkinter.CTkComboBox(frame_1, values=["Option 1", "Option 2", "Option 42 long long long..."]) combobox_1.pack(pady=12, padx=10) optionmenu_1.set("CTkComboBox") diff --git a/test/manual_integration_tests/test_tk_variables.py b/test/manual_integration_tests/test_variables.py similarity index 100% rename from test/manual_integration_tests/test_tk_variables.py rename to test/manual_integration_tests/test_variables.py From fc952294f069ffc58243c2dd56c039206c927c84 Mon Sep 17 00:00:00 2001 From: Tom Schimansky Date: Wed, 15 Jun 2022 02:07:51 +0200 Subject: [PATCH 06/33] chnaged complex_example.py and fixed command-variable execution order in CTkSlider --- customtkinter/widgets/ctk_slider.py | 76 ++++++++++++------- examples/complex_example.py | 62 +++++++-------- .../{test_widget_states.py => test_states.py} | 7 +- 3 files changed, 82 insertions(+), 63 deletions(-) rename test/manual_integration_tests/{test_widget_states.py => test_states.py} (90%) diff --git a/customtkinter/widgets/ctk_slider.py b/customtkinter/widgets/ctk_slider.py index 6fc6a5c..4712885 100644 --- a/customtkinter/widgets/ctk_slider.py +++ b/customtkinter/widgets/ctk_slider.py @@ -30,6 +30,7 @@ class CTkSlider(CTkBaseClass): command=None, variable=None, orient="horizontal", + state="normal", **kwargs): # set default dimensions according to orientation @@ -75,6 +76,7 @@ class CTkSlider(CTkBaseClass): self.variable: tkinter.Variable = variable self.variable_callback_blocked = False self.variable_callback_name = None + self.state = state self.grid_rowconfigure(0, weight=1) self.grid_columnconfigure(0, weight=1) @@ -124,12 +126,18 @@ class CTkSlider(CTkBaseClass): super().destroy() def set_cursor(self): - if Settings.cursor_manipulation_enabled: + if self.state == "normal" and Settings.cursor_manipulation_enabled: if sys.platform == "darwin": self.configure(cursor="pointinghand") elif sys.platform.startswith("win"): self.configure(cursor="hand2") + elif self.state == "disabled" and Settings.cursor_manipulation_enabled: + if sys.platform == "darwin": + self.configure(cursor="arrow") + elif sys.platform.startswith("win"): + self.configure(cursor="arrow") + def draw(self, no_color_updates=False): if self.orient.lower() == "horizontal": orientation = "w" @@ -166,41 +174,51 @@ class CTkSlider(CTkBaseClass): self.canvas.itemconfig("progress_parts", fill=ThemeManager.single_color(self.progress_color, self._appearance_mode), outline=ThemeManager.single_color(self.progress_color, self._appearance_mode)) - self.canvas.itemconfig("slider_parts", fill=ThemeManager.single_color(self.button_color, self._appearance_mode), - outline=ThemeManager.single_color(self.button_color, self._appearance_mode)) + if self.hover_state is True: + self.canvas.itemconfig("slider_parts", + fill=ThemeManager.single_color(self.button_hover_color, self._appearance_mode), + outline=ThemeManager.single_color(self.button_hover_color, self._appearance_mode)) + else: + self.canvas.itemconfig("slider_parts", + fill=ThemeManager.single_color(self.button_color, self._appearance_mode), + outline=ThemeManager.single_color(self.button_color, self._appearance_mode)) def clicked(self, event=None): - if self.orient.lower() == "horizontal": - self.value = (event.x / self._current_width) / self._widget_scaling - else: - self.value = 1 - (event.y / self._current_height) / self._widget_scaling + if self.state == "normal": + if self.orient.lower() == "horizontal": + self.value = (event.x / self._current_width) / self._widget_scaling + else: + self.value = 1 - (event.y / self._current_height) / self._widget_scaling - if self.value > 1: - self.value = 1 - if self.value < 0: - self.value = 0 + if self.value > 1: + self.value = 1 + if self.value < 0: + self.value = 0 - self.output_value = self.round_to_step_size(self.from_ + (self.value * (self.to - self.from_))) - self.value = (self.output_value - self.from_) / (self.to - self.from_) + self.output_value = self.round_to_step_size(self.from_ + (self.value * (self.to - self.from_))) + self.value = (self.output_value - self.from_) / (self.to - self.from_) - self.draw(no_color_updates=False) + self.draw(no_color_updates=False) - if self.callback_function is not None: - self.callback_function(self.output_value) + if self.variable is not None: + self.variable_callback_blocked = True + self.variable.set(round(self.output_value) if isinstance(self.variable, tkinter.IntVar) else self.output_value) + self.variable_callback_blocked = False - if self.variable is not None: - self.variable_callback_blocked = True - self.variable.set(round(self.output_value) if isinstance(self.variable, tkinter.IntVar) else self.output_value) - self.variable_callback_blocked = False + if self.callback_function is not None: + self.callback_function(self.output_value) def on_enter(self, event=0): - self.hover_state = True - self.canvas.itemconfig("slider_parts", fill=ThemeManager.single_color(self.button_hover_color, self._appearance_mode), - outline=ThemeManager.single_color(self.button_hover_color, self._appearance_mode)) + if self.state == "normal": + self.hover_state = True + self.canvas.itemconfig("slider_parts", + fill=ThemeManager.single_color(self.button_hover_color, self._appearance_mode), + outline=ThemeManager.single_color(self.button_hover_color, self._appearance_mode)) def on_leave(self, event=0): self.hover_state = False - self.canvas.itemconfig("slider_parts", fill=ThemeManager.single_color(self.button_color, self._appearance_mode), + self.canvas.itemconfig("slider_parts", + fill=ThemeManager.single_color(self.button_color, self._appearance_mode), outline=ThemeManager.single_color(self.button_color, self._appearance_mode)) def round_to_step_size(self, value): @@ -231,8 +249,8 @@ class CTkSlider(CTkBaseClass): self.draw(no_color_updates=False) - if self.callback_function is not None: - self.callback_function(self.output_value) + # if self.callback_function is not None and not from_variable_callback: + # self.callback_function(self.output_value) if self.variable is not None and not from_variable_callback: self.variable_callback_blocked = True @@ -246,6 +264,12 @@ class CTkSlider(CTkBaseClass): def configure(self, *args, **kwargs): require_redraw = False # some attribute changes require a call of self.draw() at the end + if "state" in kwargs: + self.state = kwargs["state"] + self.set_cursor() + require_redraw = True + del kwargs["state"] + if "fg_color" in kwargs: self.fg_color = kwargs["fg_color"] require_redraw = True diff --git a/examples/complex_example.py b/examples/complex_example.py index c2ef545..f2487b2 100644 --- a/examples/complex_example.py +++ b/examples/complex_example.py @@ -46,30 +46,27 @@ class App(customtkinter.CTk): 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 + text="CTkButton", 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 + text="CTkButton", 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 + text="CTkButton", 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.label_mode = customtkinter.CTkLabel(master=self.frame_left, text="Appearance Mode:") + self.label_mode.grid(row=9, column=0, pady=0, 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") + self.optionmenu_1 = customtkinter.CTkOptionMenu(master=self.frame_left, + values=["Light", "Dark", "System"], + command=self.change_appearance_mode) + self.optionmenu_1.grid(row=10, column=0, pady=10, padx=20, sticky="w") # ============ frame_right ============ @@ -134,25 +131,17 @@ class App(customtkinter.CTk): command=self.progressbar.set) 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.switch_1 = customtkinter.CTkSwitch(master=self.frame_right, + text="CTkSwitch") + self.switch_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.switch_2 = customtkinter.CTkSwitch(master=self.frame_right, + text="CTkSwitch") + self.switch_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.combobox_1 = customtkinter.CTkComboBox(master=self.frame_right, + values=["Value 1", "Value 2"]) + self.combobox_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") @@ -169,16 +158,20 @@ class App(customtkinter.CTk): self.button_5 = customtkinter.CTkButton(master=self.frame_right, text="CTkButton", + border_width=3, # <- custom border_width + fg_color=None, # <- no fg_color command=self.button_event) self.button_5.grid(row=8, column=2, columnspan=1, pady=20, padx=20, sticky="we") # set default values + self.optionmenu_1.set("Dark") + self.button_3.configure(state="disabled", text="Disabled CTkButton") + self.combobox_1.set("CTkCombobox") 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.switch_2.select() self.radio_button_3.configure(state=tkinter.DISABLED) self.check_box_1.configure(state=tkinter.DISABLED, text="CheckBox disabled") self.check_box_2.select() @@ -186,11 +179,8 @@ class App(customtkinter.CTk): 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 change_appearance_mode(self, new_appearance_mode): + customtkinter.set_appearance_mode(new_appearance_mode) def on_closing(self, event=0): self.destroy() diff --git a/test/manual_integration_tests/test_widget_states.py b/test/manual_integration_tests/test_states.py similarity index 90% rename from test/manual_integration_tests/test_widget_states.py rename to test/manual_integration_tests/test_states.py index 89c36bb..188aba8 100644 --- a/test/manual_integration_tests/test_widget_states.py +++ b/test/manual_integration_tests/test_states.py @@ -3,7 +3,7 @@ import customtkinter app = customtkinter.CTk() -app.geometry("400x800") +app.geometry("400x900") app.title("CustomTkinter Test") @@ -53,5 +53,10 @@ combobox_1.pack(pady=10, padx=10) button_7 = customtkinter.CTkButton(master=app, text="Disable/Enable combobox_1", command=lambda: change_state(combobox_1)) button_7.pack(padx=20, pady=(10, 20)) +slider_1 = customtkinter.CTkSlider(app) +slider_1.pack(pady=10, padx=10) +button_8 = customtkinter.CTkButton(master=app, text="Disable/Enable slider_1", command=lambda: change_state(slider_1)) +button_8.pack(padx=20, pady=(10, 20)) + app.mainloop() From 0e510dec53a0378530b3a9bc4a947c2c501f36a0 Mon Sep 17 00:00:00 2001 From: Tom Schimansky Date: Wed, 15 Jun 2022 02:08:28 +0200 Subject: [PATCH 07/33] Bump to 4.4.0 --- customtkinter/__init__.py | 2 +- pyproject.toml | 2 +- setup.cfg | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/customtkinter/__init__.py b/customtkinter/__init__.py index 9440bfa..de749d3 100644 --- a/customtkinter/__init__.py +++ b/customtkinter/__init__.py @@ -1,4 +1,4 @@ -__version__ = "4.3.0" +__version__ = "4.4.0" import os import sys diff --git a/pyproject.toml b/pyproject.toml index 52cd27a..d8becea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta" github_url = "https://github.com/TomSchimansky/CustomTkinter" [tool.tbump.version] -current = "4.3.0" +current = "4.4.0" # Example of a semver regexp. # Make sure this matches current_version before diff --git a/setup.cfg b/setup.cfg index 55ddd3e..ed583cd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = customtkinter -version = 4.3.0 +version = 4.4.0 description = Create modern looking GUIs with Python long_description = CustomTkinter UI-Library\n\n[](https://github.com/TomSchimansky/CustomTkinter/blob/master/documentation_images/Windows_dark.png)\n\nMore Information: https://github.com/TomSchimansky/CustomTkinter long_description_content_type = text/markdown From 45e47f597067f40cb29c9904d9f60273ec379517 Mon Sep 17 00:00:00 2001 From: tschiman Date: Wed, 15 Jun 2022 14:51:24 +0200 Subject: [PATCH 08/33] fixed dropdown menu bug on Linux --- customtkinter/widgets/dropdown_menu.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/customtkinter/widgets/dropdown_menu.py b/customtkinter/widgets/dropdown_menu.py index a746915..fad3193 100644 --- a/customtkinter/widgets/dropdown_menu.py +++ b/customtkinter/widgets/dropdown_menu.py @@ -34,6 +34,14 @@ class DropdownMenu(tkinter.Menu): self.text_color = ThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color self.text_font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font + self.configure_menu_for_platforms() + + self.values = values + self.command = command + + self.add_menu_commands() + + def configure_menu_for_platforms(self): if sys.platform == "darwin": self.configure(tearoff=False, font=self.apply_font_scaling(self.text_font)) @@ -61,11 +69,6 @@ class DropdownMenu(tkinter.Menu): activeforeground=ThemeManager.single_color(self.text_color, self._appearance_mode), font=self.apply_font_scaling(self.text_font)) - self.values = values - self.command = command - - self.add_menu_commands() - def add_menu_commands(self): for value in self.values: self.add_command(label=value.ljust(self.min_character_width), @@ -78,7 +81,10 @@ class DropdownMenu(tkinter.Menu): else: y += self.apply_widget_scaling(3) - self.post(int(x), int(y)) + if sys.platform == "darwin" or sys.platform.startswith("win"): + self.post(int(x), int(y)) + else: # Linux + self.tk_popup(int(x), int(y)) def button_callback(self, value): if self.command is not None: @@ -157,3 +163,5 @@ class DropdownMenu(tkinter.Menu): self._appearance_mode = 1 elif mode_string.lower() == "light": self._appearance_mode = 0 + + self.configure_menu_for_platforms() From 3a1d12f8eac89f0d7a8e3a2d6b2e6fa85ae01410 Mon Sep 17 00:00:00 2001 From: Tom Schimansky Date: Wed, 15 Jun 2022 23:26:21 +0200 Subject: [PATCH 09/33] removed old dropdown menu --- customtkinter/widgets/ctk_combobox.py | 1 - customtkinter/widgets/ctk_optionmenu.py | 1 - customtkinter/widgets/dropdown_menu_old.py | 149 --------------------- examples/complex_example.py | 2 +- 4 files changed, 1 insertion(+), 152 deletions(-) delete mode 100644 customtkinter/widgets/dropdown_menu_old.py diff --git a/customtkinter/widgets/ctk_combobox.py b/customtkinter/widgets/ctk_combobox.py index 174e8bc..7117420 100644 --- a/customtkinter/widgets/ctk_combobox.py +++ b/customtkinter/widgets/ctk_combobox.py @@ -2,7 +2,6 @@ import tkinter import sys from typing import Union -from .dropdown_menu_old import DropdownMenu from .dropdown_menu import DropdownMenu from .ctk_canvas import CTkCanvas diff --git a/customtkinter/widgets/ctk_optionmenu.py b/customtkinter/widgets/ctk_optionmenu.py index 4430274..458ecc1 100644 --- a/customtkinter/widgets/ctk_optionmenu.py +++ b/customtkinter/widgets/ctk_optionmenu.py @@ -2,7 +2,6 @@ import tkinter import sys from typing import Union -from .dropdown_menu_old import DropdownMenu from .dropdown_menu import DropdownMenu from .ctk_canvas import CTkCanvas diff --git a/customtkinter/widgets/dropdown_menu_old.py b/customtkinter/widgets/dropdown_menu_old.py deleted file mode 100644 index 85c9490..0000000 --- a/customtkinter/widgets/dropdown_menu_old.py +++ /dev/null @@ -1,149 +0,0 @@ -import customtkinter -import tkinter -import sys -from distutils.version import StrictVersion as Version -import platform -from typing import Union - -from ..theme_manager import ThemeManager -from ..appearance_mode_tracker import AppearanceModeTracker -from ..scaling_tracker import ScalingTracker - - -class DropdownMenu(tkinter.Toplevel): - def __init__(self, *args, - fg_color="#555555", - button_color="gray50", - button_hover_color="gray35", - text_color="black", - corner_radius=6, - button_corner_radius=3, - width=120, - button_height=24, - x_position=0, - y_position=0, - x_spacing=3, - y_spacing=3, - command=None, - values=None, - **kwargs): - super().__init__(*args, **kwargs) - - ScalingTracker.add_widget(self.set_scaling, self) - self._widget_scaling = ScalingTracker.get_widget_scaling(self) - self._spacing_scaling = ScalingTracker.get_spacing_scaling(self) - - self.values = values - self.command = command - - # color - self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark" - self.fg_color = fg_color - self.button_color = button_color - self.button_hover_color = button_hover_color - self.text_color = text_color - - # shape - self.corner_radius = corner_radius - self.button_corner_radius = button_corner_radius - self.button_height = button_height - self.width = width - self.height = max(len(self.values), 1) * (self.button_height + self.apply_spacing_scaling(y_spacing)) + self.apply_spacing_scaling(y_spacing) - - self.geometry(f"{round(self.apply_widget_scaling(self.width))}x" + - f"{round(self.apply_widget_scaling(self.height))}+" + - f"{round(x_position)}+{round(y_position)}") - self.grid_columnconfigure(0, weight=1) - self.grid_rowconfigure(0, weight=1) - - if sys.platform.startswith("darwin"): - if Version(platform.python_version()) < Version("3.10"): - self.focus() - self.overrideredirect(True) # remove title-bar - self.overrideredirect(False) - else: - self.overrideredirect(True) - self.geometry(f"+{round(x_position)}+{round(y_position)}") - self.focus_set() - - self.wm_attributes("-transparent", True) # turn off window shadow - self.config(bg='systemTransparent') # transparent bg - self.frame = customtkinter.CTkFrame(self, - border_width=0, - width=self.width, - corner_radius=self.corner_radius, - fg_color=ThemeManager.single_color(self.fg_color, self.appearance_mode)) - - elif sys.platform.startswith("win"): - self.overrideredirect(True) # remove title-bar - #self.configure(bg="#010302") - #self.wm_attributes("-transparent", "#010302") - self.focus() - self.focus() - self.frame = customtkinter.CTkFrame(self, - border_width=0, - width=self.width, - corner_radius=0, - fg_color=self.fg_color, - overwrite_preferred_drawing_method="circle_shapes") - else: # Linux - self.overrideredirect(True) # remove title-bar - # self.configure(bg="#010302") - # self.wm_attributes("-transparentcolor", "#010302") - self.frame = customtkinter.CTkFrame(self, - border_width=0, - width=self.width, - corner_radius=0, - fg_color=self.fg_color) - - self.frame.grid(row=0, column=0, sticky="nsew", rowspan=1) - self.frame.grid_rowconfigure(len(self.values) + 1, minsize=self.apply_spacing_scaling(y_spacing)) # add spacing at the bottom - self.frame.grid_columnconfigure(0, weight=1) - - self.button_list = [] - for index, option in enumerate(self.values): - button = customtkinter.CTkButton(self.frame, - text=option, - height=self.button_height, - width=self.width - 2 * self.apply_widget_scaling(x_spacing), - fg_color=self.button_color, - text_color=self.text_color, - hover_color=self.button_hover_color, - corner_radius=self.button_corner_radius, - command=lambda i=index: self.button_callback(i)) - button.text_label.configure(anchor="w") - button.text_label.grid(row=0, column=0, rowspan=2, columnspan=2, sticky="w") - button.grid(row=index, column=0, - padx=self.apply_widget_scaling(x_spacing), - pady=(self.apply_widget_scaling(y_spacing), 0), sticky="ew") - self.button_list.append(button) - - self.bind("", self.focus_loss_event) - - def apply_widget_scaling(self, value: Union[int, float, str]) -> Union[float, str]: - if isinstance(value, (int, float)): - return value * self._widget_scaling - else: - return value - - def apply_spacing_scaling(self, value: Union[int, float, str]) -> Union[float, str]: - if isinstance(value, (int, float)): - return value * self._spacing_scaling - else: - return value - - def set_scaling(self, new_widget_scaling, new_spacing_scaling, new_window_scaling): - return - - def focus_loss_event(self, event): - self.destroy() - if sys.platform.startswith("darwin"): - self.update() - - def button_callback(self, index): - self.destroy() - if sys.platform.startswith("darwin"): - self.update() - - if self.command is not None: - self.command(self.values[index]) diff --git a/examples/complex_example.py b/examples/complex_example.py index f2487b2..2a5c088 100644 --- a/examples/complex_example.py +++ b/examples/complex_example.py @@ -158,7 +158,7 @@ class App(customtkinter.CTk): self.button_5 = customtkinter.CTkButton(master=self.frame_right, text="CTkButton", - border_width=3, # <- custom border_width + border_width=2, # <- custom border_width fg_color=None, # <- no fg_color command=self.button_event) self.button_5.grid(row=8, column=2, columnspan=1, pady=20, padx=20, sticky="we") From 9ff6cc826832c0253098dcd33c463c618d513911 Mon Sep 17 00:00:00 2001 From: TomSchimansky Date: Wed, 15 Jun 2022 18:15:24 -0400 Subject: [PATCH 10/33] optimized char mapping on canvas for Linux --- customtkinter/widgets/ctk_canvas.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/customtkinter/widgets/ctk_canvas.py b/customtkinter/widgets/ctk_canvas.py index c3f9cd6..760a247 100644 --- a/customtkinter/widgets/ctk_canvas.py +++ b/customtkinter/widgets/ctk_canvas.py @@ -29,12 +29,19 @@ class CTkCanvas(tkinter.Canvas): 9: 'E', 8: 'F', 7: 'C', 6: 'I', 5: 'E', 4: 'G', 3: 'P', 2: 'R', 1: 'R', 0: 'A'} + radius_to_char_fine_linux = {19: 'A', 18: 'A', 17: 'B', 16: 'B', 15: 'B', 14: 'B', 13: 'F', 12: 'C', + 11: 'F', 10: 'C', + 9: 'D', 8: 'G', 7: 'D', 6: 'F', 5: 'D', 4: 'G', 3: 'M', 2: 'H', 1: 'H', + 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 + elif sys.platform.startswith("linux"): # Optimized on Kali Linux + cls.radius_to_char_fine = radius_to_char_fine_linux + else: cls.radius_to_char_fine = radius_to_char_fine_windows_10 def get_char_from_radius(self, radius: int) -> str: From 7a99aa318c355057c36b657060d69a2bcc918d8f Mon Sep 17 00:00:00 2001 From: TomSchimansky Date: Wed, 15 Jun 2022 18:31:20 -0400 Subject: [PATCH 11/33] fixed disabled color for combobox --- customtkinter/widgets/ctk_combobox.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/customtkinter/widgets/ctk_combobox.py b/customtkinter/widgets/ctk_combobox.py index 7117420..688bb13 100644 --- a/customtkinter/widgets/ctk_combobox.py +++ b/customtkinter/widgets/ctk_combobox.py @@ -161,15 +161,15 @@ class CTkComboBox(CTkBaseClass): outline=ThemeManager.single_color(self.border_color, self._appearance_mode), fill=ThemeManager.single_color(self.border_color, self._appearance_mode)) - self.entry.configure(fg=ThemeManager.single_color(self.text_color, self._appearance_mode)) - self.entry.configure(bg=ThemeManager.single_color(self.fg_color, self._appearance_mode)) + self.entry.configure(bg=ThemeManager.single_color(self.fg_color, self._appearance_mode), + fg=ThemeManager.single_color(self.text_color, self._appearance_mode), + disabledforeground=ThemeManager.single_color(self.text_color_disabled, self._appearance_mode), + disabledbackground=ThemeManager.single_color(self.fg_color, self._appearance_mode)) if self.state == tkinter.DISABLED: - self.entry.configure(fg=(ThemeManager.single_color(self.text_color_disabled, self._appearance_mode))) self.canvas.itemconfig("dropdown_arrow", fill=ThemeManager.single_color(self.text_color_disabled, self._appearance_mode)) else: - self.entry.configure(fg=ThemeManager.single_color(self.text_color, self._appearance_mode)) self.canvas.itemconfig("dropdown_arrow", fill=ThemeManager.single_color(self.text_color, self._appearance_mode)) From 3b259e4d01fb10e75256173ffe58d80ce40b91f3 Mon Sep 17 00:00:00 2001 From: tschiman Date: Thu, 16 Jun 2022 16:55:17 +0200 Subject: [PATCH 12/33] refined dropdown word spacing for linux --- customtkinter/widgets/dropdown_menu.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/customtkinter/widgets/dropdown_menu.py b/customtkinter/widgets/dropdown_menu.py index fad3193..4c0fde6 100644 --- a/customtkinter/widgets/dropdown_menu.py +++ b/customtkinter/widgets/dropdown_menu.py @@ -70,10 +70,16 @@ class DropdownMenu(tkinter.Menu): font=self.apply_font_scaling(self.text_font)) def add_menu_commands(self): - for value in self.values: - self.add_command(label=value.ljust(self.min_character_width), - command=lambda v=value: self.button_callback(v), - compound="left") + if sys.platform.startswith("linux"): + for value in self.values: + self.add_command(label=" " + value.ljust(self.min_character_width) + " ", + command=lambda v=value: self.button_callback(v), + compound="left") + else: + for value in self.values: + self.add_command(label=value.ljust(self.min_character_width), + command=lambda v=value: self.button_callback(v), + compound="left") def open(self, x: Union[int, float], y: Union[int, float]): if sys.platform == "darwin": From 9146e02718e4403838011f6fdc82ea23b5147f19 Mon Sep 17 00:00:00 2001 From: Tom Schimansky Date: Mon, 23 May 2022 11:01:38 +0200 Subject: [PATCH 13/33] updated CHANGELOG.md --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6af824f..592816d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,20 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [4.4.0] - 2022-06-14 +### Changed + - Changed custom dropdown menu to normal tkinter.Menu because of multiple platform specific bugs + +## [4.3.0] - 2022-06-1 +### Added + - Added CTkComboBox + - Small fixes for new dropdown menu + +## [4.2.0] - 2022-05-30 +### Added + - CTkOptionMenu with custom dropdown menu + - Support for clicking on labels of CTkCheckBox, CTkRadioButton, CTkSwitch + ## [4.1.0] - 2022-05-24 ### Added - Configure width and height for frame, button, label, progressbar, slider, entry From 22b4dfb2d33bf877c96c316afa2456f28bcd560a Mon Sep 17 00:00:00 2001 From: Tom Schimansky Date: Fri, 17 Jun 2022 21:13:30 +0200 Subject: [PATCH 14/33] Bump to 4.4.1 --- customtkinter/__init__.py | 2 +- pyproject.toml | 2 +- setup.cfg | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/customtkinter/__init__.py b/customtkinter/__init__.py index de749d3..7d49569 100644 --- a/customtkinter/__init__.py +++ b/customtkinter/__init__.py @@ -1,4 +1,4 @@ -__version__ = "4.4.0" +__version__ = "4.4.1" import os import sys diff --git a/pyproject.toml b/pyproject.toml index d8becea..1f23371 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta" github_url = "https://github.com/TomSchimansky/CustomTkinter" [tool.tbump.version] -current = "4.4.0" +current = "4.4.1" # Example of a semver regexp. # Make sure this matches current_version before diff --git a/setup.cfg b/setup.cfg index ed583cd..6bd5876 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = customtkinter -version = 4.4.0 +version = 4.4.1 description = Create modern looking GUIs with Python long_description = CustomTkinter UI-Library\n\n[](https://github.com/TomSchimansky/CustomTkinter/blob/master/documentation_images/Windows_dark.png)\n\nMore Information: https://github.com/TomSchimansky/CustomTkinter long_description_content_type = text/markdown From 79ecd2e94611f6173353649551cacd62bd3acb66 Mon Sep 17 00:00:00 2001 From: Tom Schimansky Date: Sun, 19 Jun 2022 21:16:19 +0200 Subject: [PATCH 15/33] added CTkScrollbar --- customtkinter/__init__.py | 1 + customtkinter/assets/themes/blue.json | 8 +- customtkinter/draw_engine.py | 167 +++++++++++++-- customtkinter/widgets/ctk_scrollbar.py | 200 ++++++++++++++++++ customtkinter/widgets/ctk_slider.py | 8 +- .../test_scrollbar.py | 37 ++++ 6 files changed, 403 insertions(+), 18 deletions(-) create mode 100644 customtkinter/widgets/ctk_scrollbar.py create mode 100644 test/manual_integration_tests/test_scrollbar.py diff --git a/customtkinter/__init__.py b/customtkinter/__init__.py index de749d3..bb59d8d 100644 --- a/customtkinter/__init__.py +++ b/customtkinter/__init__.py @@ -53,6 +53,7 @@ from .widgets.ctk_canvas import CTkCanvas from .widgets.ctk_switch import CTkSwitch from .widgets.ctk_optionmenu import CTkOptionMenu from .widgets.ctk_combobox import CTkComboBox +from .widgets.ctk_scrollbar import CTkScrollbar # import windows from .windows.ctk_tk import CTk diff --git a/customtkinter/assets/themes/blue.json b/customtkinter/assets/themes/blue.json index 7c17eb5..24b2aab 100644 --- a/customtkinter/assets/themes/blue.json +++ b/customtkinter/assets/themes/blue.json @@ -33,7 +33,9 @@ "combobox_button_hover": ["#6E7174", "#7A848D"], "dropdown_color": ["gray90", "gray20"], "dropdown_hover": ["gray75", "gray28"], - "dropdown_text": ["gray10", "#DCE4EE"] + "dropdown_text": ["gray10", "#DCE4EE"], + "scrollbar": ["gray75", "gray25"], + "scrollbar_hover": ["gray50", "gray40"] }, "text": { "macOS": { @@ -70,6 +72,8 @@ "switch_border_width": 3, "switch_corner_radius": 1000, "switch_button_corner_radius": 1000, - "switch_button_length": 0 + "switch_button_length": 0, + "scrollbar_corner_radius": 6, + "scrollbar_border_spacing": 4 } } diff --git a/customtkinter/draw_engine.py b/customtkinter/draw_engine.py index 8f2a2f5..9af3347 100644 --- a/customtkinter/draw_engine.py +++ b/customtkinter/draw_engine.py @@ -20,6 +20,7 @@ class DrawEngine: - draw_rounded_rect_with_border_vertical_split() - draw_rounded_progress_bar_with_border() - draw_rounded_slider_with_border_and_button() + - draw_rounded_scrollbar() - draw_checkmark() - draw_dropdown_arrow() @@ -38,7 +39,7 @@ class DrawEngine: else: return round(user_corner_radius) - # optimize forx drawing with antialiased font shapes + # optimize for drawing with antialiased font shapes elif self.preferred_drawing_method == "font_shapes": return round(user_corner_radius) @@ -490,22 +491,27 @@ class DrawEngine: # create canvas border corner parts if not already created, but only if needed, and delete if not needed if not self._canvas.find_withtag("border_oval_1_a") and "border_oval_1" not in exclude_parts: self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_1_a", "border_corner_part", "border_parts_left", "border_parts", "left_parts"), anchor=tkinter.CENTER) - self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_1_b", "border_corner_part", "border_parts_left", "border_parts", "left_parts"), anchor=tkinter.CENTER, angle=180) + self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_1_b", "border_corner_part", "border_parts_left", "border_parts", "left_parts"), anchor=tkinter.CENTER, + angle=180) requires_recoloring = True elif self._canvas.find_withtag("border_oval_1_a") and "border_oval_1" in exclude_parts: self._canvas.delete("border_oval_1_a", "border_oval_1_b") if not self._canvas.find_withtag("border_oval_2_a") and width > 2 * corner_radius and "border_oval_2" not in exclude_parts: - self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_2_a", "border_corner_part", "border_parts_right", "border_parts", "right_parts"), anchor=tkinter.CENTER) - self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_2_b", "border_corner_part", "border_parts_right", "border_parts", "right_parts"), anchor=tkinter.CENTER, angle=180) + self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_2_a", "border_corner_part", "border_parts_right", "border_parts", "right_parts"), + anchor=tkinter.CENTER) + self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_2_b", "border_corner_part", "border_parts_right", "border_parts", "right_parts"), + anchor=tkinter.CENTER, angle=180) requires_recoloring = True elif self._canvas.find_withtag("border_oval_2_a") and (not width > 2 * corner_radius or "border_oval_2" in exclude_parts): self._canvas.delete("border_oval_2_a", "border_oval_2_b") if not self._canvas.find_withtag("border_oval_3_a") and height > 2 * corner_radius \ and width > 2 * corner_radius and "border_oval_3" not in exclude_parts: - self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_3_a", "border_corner_part", "border_parts_right", "border_parts", "right_parts"), anchor=tkinter.CENTER) - self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_3_b", "border_corner_part", "border_parts_right", "border_parts", "right_parts"), anchor=tkinter.CENTER, angle=180) + self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_3_a", "border_corner_part", "border_parts_right", "border_parts", "right_parts"), + anchor=tkinter.CENTER) + self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_3_b", "border_corner_part", "border_parts_right", "border_parts", "right_parts"), + anchor=tkinter.CENTER, angle=180) requires_recoloring = True elif self._canvas.find_withtag("border_oval_3_a") and (not (height > 2 * corner_radius and width > 2 * corner_radius) or "border_oval_3" in exclude_parts): @@ -513,7 +519,8 @@ class DrawEngine: if not self._canvas.find_withtag("border_oval_4_a") and height > 2 * corner_radius and "border_oval_4" not in exclude_parts: self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_4_a", "border_corner_part", "border_parts_left", "border_parts", "left_parts"), anchor=tkinter.CENTER) - self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_4_b", "border_corner_part", "border_parts_left", "border_parts", "left_parts"), anchor=tkinter.CENTER, angle=180) + self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_4_b", "border_corner_part", "border_parts_left", "border_parts", "left_parts"), anchor=tkinter.CENTER, + angle=180) requires_recoloring = True elif self._canvas.find_withtag("border_oval_4_a") and (not height > 2 * corner_radius or "border_oval_4" in exclude_parts): self._canvas.delete("border_oval_4_a", "border_oval_4_b") @@ -554,14 +561,16 @@ class DrawEngine: # create canvas border corner parts if not already created, but only if they're needed and delete if not needed if not self._canvas.find_withtag("inner_oval_1_a") and "inner_oval_1" not in exclude_parts: self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_1_a", "inner_corner_part", "inner_parts_left", "inner_parts", "left_parts"), anchor=tkinter.CENTER) - self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_1_b", "inner_corner_part", "inner_parts_left", "inner_parts", "left_parts"), anchor=tkinter.CENTER, angle=180) + self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_1_b", "inner_corner_part", "inner_parts_left", "inner_parts", "left_parts"), anchor=tkinter.CENTER, + angle=180) requires_recoloring = True elif self._canvas.find_withtag("inner_oval_1_a") and "inner_oval_1" in exclude_parts: self._canvas.delete("inner_oval_1_a", "inner_oval_1_b") if not self._canvas.find_withtag("inner_oval_2_a") and width - (2 * border_width) > 2 * inner_corner_radius and "inner_oval_2" not in exclude_parts: - self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_2_a", "inner_corner_part","inner_parts_right", "inner_parts", "right_parts"), anchor=tkinter.CENTER) - self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_2_b", "inner_corner_part", "inner_parts_right", "inner_parts", "right_parts"), anchor=tkinter.CENTER, angle=180) + self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_2_a", "inner_corner_part", "inner_parts_right", "inner_parts", "right_parts"), anchor=tkinter.CENTER) + self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_2_b", "inner_corner_part", "inner_parts_right", "inner_parts", "right_parts"), anchor=tkinter.CENTER, + angle=180) requires_recoloring = True elif self._canvas.find_withtag("inner_oval_2_a") and (not width - (2 * border_width) > 2 * inner_corner_radius or "inner_oval_2" in exclude_parts): self._canvas.delete("inner_oval_2_a", "inner_oval_2_b") @@ -569,7 +578,8 @@ class DrawEngine: if not self._canvas.find_withtag("inner_oval_3_a") and height - (2 * border_width) > 2 * inner_corner_radius \ and width - (2 * border_width) > 2 * inner_corner_radius and "inner_oval_3" not in exclude_parts: self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_3_a", "inner_corner_part", "inner_parts_right", "inner_parts", "right_parts"), anchor=tkinter.CENTER) - self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_3_b", "inner_corner_part", "inner_parts_right", "inner_parts", "right_parts"), anchor=tkinter.CENTER, angle=180) + self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_3_b", "inner_corner_part", "inner_parts_right", "inner_parts", "right_parts"), anchor=tkinter.CENTER, + angle=180) requires_recoloring = True elif self._canvas.find_withtag("inner_oval_3_a") and (not (height - (2 * border_width) > 2 * inner_corner_radius and width - (2 * border_width) > 2 * inner_corner_radius) or "inner_oval_3" in exclude_parts): @@ -577,7 +587,8 @@ class DrawEngine: if not self._canvas.find_withtag("inner_oval_4_a") and height - (2 * border_width) > 2 * inner_corner_radius and "inner_oval_4" not in exclude_parts: self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_4_a", "inner_corner_part", "inner_parts_left", "inner_parts", "left_parts"), anchor=tkinter.CENTER) - self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_4_b", "inner_corner_part", "inner_parts_left", "inner_parts", "left_parts"), anchor=tkinter.CENTER, angle=180) + self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_4_b", "inner_corner_part", "inner_parts_left", "inner_parts", "left_parts"), anchor=tkinter.CENTER, + angle=180) requires_recoloring = True elif self._canvas.find_withtag("inner_oval_4_a") and (not height - (2 * border_width) > 2 * inner_corner_radius or "inner_oval_4" in exclude_parts): self._canvas.delete("inner_oval_4_a", "inner_oval_4_b") @@ -959,6 +970,138 @@ class DrawEngine: return requires_recoloring + def draw_rounded_scrollbar(self, width: Union[float, int], height: Union[float, int], corner_radius: Union[float, int], + border_spacing: Union[float, int], start_value: float, end_value: float, orientation: str) -> bool: + width = math.floor(width / 2) * 2 # round _current_width and _current_height and restrict them to even values only + height = math.floor(height / 2) * 2 + + if corner_radius > width / 2 or corner_radius > height / 2: # restrict corner_radius if it's too larger + corner_radius = min(width / 2, height / 2) + + border_spacing = round(border_spacing) + corner_radius = self.__calc_optimal_corner_radius(corner_radius) # optimize corner_radius for different drawing methods (different rounding) + + if corner_radius >= border_spacing: + inner_corner_radius = corner_radius - border_spacing + else: + inner_corner_radius = 0 + + if self.preferred_drawing_method == "polygon_shapes" or self.preferred_drawing_method == "circle_shapes": + return self.__draw_rounded_scrollbar_polygon_shapes(width, height, corner_radius, inner_corner_radius, + start_value, end_value, orientation) + elif self.preferred_drawing_method == "font_shapes": + return self.__draw_rounded_scrollbar_font_shapes(width, height, corner_radius, inner_corner_radius, + start_value, end_value, orientation) + + def __draw_rounded_scrollbar_polygon_shapes(self, width: int, height: int, corner_radius: int, inner_corner_radius: int, + start_value: float, end_value: float, orientation: str) -> bool: + requires_recoloring = False + + if not self._canvas.find_withtag("border_parts"): + self._canvas.create_rectangle(0, 0, 0, 0, tags=("border_rectangle_1", "border_parts"), width=0) + requires_recoloring = True + self._canvas.coords("border_rectangle_1", 0, 0, width, height) + + if not self._canvas.find_withtag("scrollbar_parts"): + self._canvas.create_polygon((0, 0, 0, 0), tags=("scrollbar_polygon_1", "scrollbar_parts"), joinstyle=tkinter.ROUND) + self._canvas.tag_raise("scrollbar_parts", "border_parts") + requires_recoloring = True + + if orientation == "vertical": + self._canvas.coords("scrollbar_polygon_1", + corner_radius, corner_radius + (height - 2 * corner_radius) * start_value, + width - corner_radius, corner_radius + (height - 2 * corner_radius) * start_value, + width - corner_radius, corner_radius + (height - 2 * corner_radius) * end_value, + corner_radius, corner_radius + (height - 2 * corner_radius) * end_value) + elif orientation == "horizontal": + self._canvas.coords("scrollbar_polygon_1", + corner_radius + (width - 2 * corner_radius) * start_value, corner_radius, + corner_radius + (width - 2 * corner_radius) * end_value, corner_radius, + corner_radius + (width - 2 * corner_radius) * end_value, height - corner_radius, + corner_radius + (width - 2 * corner_radius) * start_value, height - corner_radius,) + + self._canvas.itemconfig("scrollbar_polygon_1", width=inner_corner_radius * 2) + + return requires_recoloring + + def __draw_rounded_scrollbar_font_shapes(self, width: int, height: int, corner_radius: int, inner_corner_radius: int, + start_value: float, end_value: float, orientation: str) -> bool: + requires_recoloring = False + + if not self._canvas.find_withtag("border_parts"): + self._canvas.create_rectangle(0, 0, 0, 0, tags=("border_rectangle_1", "border_parts"), width=0) + requires_recoloring = True + self._canvas.coords("border_rectangle_1", 0, 0, width, height) + + if inner_corner_radius > 0: + if not self._canvas.find_withtag("scrollbar_oval_1_a"): + self._canvas.create_aa_circle(0, 0, 0, tags=("scrollbar_oval_1_a", "scrollbar_corner_part", "scrollbar_parts"), anchor=tkinter.CENTER) + self._canvas.create_aa_circle(0, 0, 0, tags=("scrollbar_oval_1_b", "scrollbar_corner_part", "scrollbar_parts"), anchor=tkinter.CENTER, angle=180) + requires_recoloring = True + + if not self._canvas.find_withtag("scrollbar_oval_2_a") and width > 2 * corner_radius: + self._canvas.create_aa_circle(0, 0, 0, tags=("scrollbar_oval_2_a", "scrollbar_corner_part", "scrollbar_parts"), anchor=tkinter.CENTER) + self._canvas.create_aa_circle(0, 0, 0, tags=("scrollbar_oval_2_b", "scrollbar_corner_part", "scrollbar_parts"), anchor=tkinter.CENTER, angle=180) + requires_recoloring = True + elif self._canvas.find_withtag("scrollbar_oval_2_a") and not width > 2 * corner_radius: + self._canvas.delete("scrollbar_oval_2_a", "scrollbar_oval_2_b") + + if not self._canvas.find_withtag("scrollbar_oval_3_a") and height > 2 * corner_radius and width > 2 * corner_radius: + self._canvas.create_aa_circle(0, 0, 0, tags=("scrollbar_oval_3_a", "scrollbar_corner_part", "scrollbar_parts"), anchor=tkinter.CENTER) + self._canvas.create_aa_circle(0, 0, 0, tags=("scrollbar_oval_3_b", "scrollbar_corner_part", "scrollbar_parts"), anchor=tkinter.CENTER, angle=180) + requires_recoloring = True + elif self._canvas.find_withtag("scrollbar_oval_3_a") and not (height > 2 * corner_radius and width > 2 * corner_radius): + self._canvas.delete("scrollbar_oval_3_a", "scrollbar_oval_3_b") + + if not self._canvas.find_withtag("scrollbar_oval_4_a") and height > 2 * corner_radius: + self._canvas.create_aa_circle(0, 0, 0, tags=("scrollbar_oval_4_a", "scrollbar_corner_part", "scrollbar_parts"), anchor=tkinter.CENTER) + self._canvas.create_aa_circle(0, 0, 0, tags=("scrollbar_oval_4_b", "scrollbar_corner_part", "scrollbar_parts"), anchor=tkinter.CENTER, angle=180) + requires_recoloring = True + elif self._canvas.find_withtag("scrollbar_oval_4_a") and not height > 2 * corner_radius: + self._canvas.delete("scrollbar_oval_4_a", "scrollbar_oval_4_b") + else: + self._canvas.delete("scrollbar_corner_part") + + if not self._canvas.find_withtag("scrollbar_rectangle_1") and width > 2 * corner_radius: + self._canvas.create_rectangle(0, 0, 0, 0, tags=("scrollbar_rectangle_1", "scrollbar_rectangle_part", "scrollbar_parts"), width=0) + requires_recoloring = True + elif self._canvas.find_withtag("scrollbar_rectangle_1") and not width > 2 * corner_radius: + self._canvas.delete("scrollbar_rectangle_1") + + if not self._canvas.find_withtag("scrollbar_rectangle_2"): + self._canvas.create_rectangle(0, 0, 0, 0, tags=("scrollbar_rectangle_2", "scrollbar_rectangle_part", "scrollbar_parts"), width=0) + requires_recoloring = True + elif self._canvas.find_withtag("scrollbar_rectangle_2") and not height > 2 * corner_radius: + self._canvas.delete("scrollbar_rectangle_2") + + self._canvas.coords("scrollbar_rectangle_1", + corner_radius - inner_corner_radius, corner_radius + (height - 2 * corner_radius) * start_value, + width - (corner_radius - inner_corner_radius), corner_radius + (height - 2 * corner_radius) * end_value) + self._canvas.coords("scrollbar_rectangle_2", + corner_radius, corner_radius - inner_corner_radius + (height - 2 * corner_radius) * start_value, + width - (corner_radius), corner_radius + inner_corner_radius + (height - 2 * corner_radius) * end_value) + + if orientation == "vertical": + self._canvas.coords("scrollbar_oval_1_a", corner_radius, corner_radius + (height - 2 * corner_radius) * start_value, inner_corner_radius) + self._canvas.coords("scrollbar_oval_1_b", corner_radius, corner_radius + (height - 2 * corner_radius) * start_value, inner_corner_radius) + self._canvas.coords("scrollbar_oval_2_a", width - corner_radius, corner_radius + (height - 2 * corner_radius) * start_value, inner_corner_radius) + self._canvas.coords("scrollbar_oval_2_b", width - corner_radius, corner_radius + (height - 2 * corner_radius) * start_value, inner_corner_radius) + self._canvas.coords("scrollbar_oval_3_a", width - corner_radius, corner_radius + (height - 2 * corner_radius) * end_value, inner_corner_radius) + self._canvas.coords("scrollbar_oval_3_b", width - corner_radius, corner_radius + (height - 2 * corner_radius) * end_value, inner_corner_radius) + self._canvas.coords("scrollbar_oval_4_a", corner_radius, corner_radius + (height - 2 * corner_radius) * end_value, inner_corner_radius) + self._canvas.coords("scrollbar_oval_4_b", corner_radius, corner_radius + (height - 2 * corner_radius) * end_value, inner_corner_radius) + if orientation == "horizontal": + self._canvas.coords("scrollbar_oval_1_a", corner_radius + (width - 2 * corner_radius) * start_value, corner_radius, inner_corner_radius) + self._canvas.coords("scrollbar_oval_1_b", corner_radius + (width - 2 * corner_radius) * start_value, corner_radius, inner_corner_radius) + self._canvas.coords("scrollbar_oval_2_a", corner_radius + (width - 2 * corner_radius) * end_value, corner_radius, inner_corner_radius) + self._canvas.coords("scrollbar_oval_2_b", corner_radius + (width - 2 * corner_radius) * end_value, corner_radius, inner_corner_radius) + self._canvas.coords("scrollbar_oval_3_a", corner_radius + (width - 2 * corner_radius) * end_value, height - corner_radius, inner_corner_radius) + self._canvas.coords("scrollbar_oval_3_b", corner_radius + (width - 2 * corner_radius) * end_value, height - corner_radius, inner_corner_radius) + self._canvas.coords("scrollbar_oval_4_a", corner_radius + (width - 2 * corner_radius) * start_value, height - corner_radius, inner_corner_radius) + self._canvas.coords("scrollbar_oval_4_b", corner_radius + (width - 2 * corner_radius) * start_value, height - corner_radius, inner_corner_radius) + + return requires_recoloring + def draw_checkmark(self, width: Union[float, int], height: Union[float, int], size: Union[int, float]) -> bool: """ Draws a rounded rectangle with a corner_radius and border_width on the canvas. The border elements have a 'border_parts' tag, the main foreground elements have an 'inner_parts' tag to color the elements accordingly. diff --git a/customtkinter/widgets/ctk_scrollbar.py b/customtkinter/widgets/ctk_scrollbar.py new file mode 100644 index 0000000..4382d88 --- /dev/null +++ b/customtkinter/widgets/ctk_scrollbar.py @@ -0,0 +1,200 @@ +from .ctk_canvas import CTkCanvas +from ..theme_manager import ThemeManager +from ..draw_engine import DrawEngine +from .widget_base_class import CTkBaseClass + + +class CTkScrollbar(CTkBaseClass): + def __init__(self, *args, + bg_color=None, + fg_color=None, + scrollbar_color="default_theme", + scrollbar_hover_color="default_theme", + border_spacing="default_theme", + corner_radius="default_theme", + width=None, + height=None, + orientation="vertical", + command=None, + hover=True, + **kwargs): + + # set default dimensions according to orientation + if width is None: + if orientation.lower() == "vertical": + width = 16 + else: + width = 200 + if height is None: + if orientation.lower() == "vertical": + height = 200 + else: + height = 16 + + # transfer basic functionality (bg_color, size, _appearance_mode, scaling) to CTkBaseClass + super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs) + + # color + self.fg_color = fg_color + self.scrollbar_color = ThemeManager.theme["color"]["scrollbar"] if scrollbar_color == "default_theme" else scrollbar_color + self.scrollbar_hover_color = ThemeManager.theme["color"]["scrollbar_hover"] if scrollbar_hover_color == "default_theme" else scrollbar_hover_color + + # shape + self.corner_radius = ThemeManager.theme["shape"]["scrollbar_corner_radius"] if corner_radius == "default_theme" else corner_radius + self.border_spacing = ThemeManager.theme["shape"]["scrollbar_border_spacing"] if border_spacing == "default_theme" else border_spacing + + self.hover = hover + self.hover_state = False + self.command = command + self.orientation = orientation + self.start_value: float = 0 # 0 to 1 + self.end_value: float = 1 # 0 to 1 + + self.canvas = CTkCanvas(master=self, + highlightthickness=0, + width=self.apply_widget_scaling(self._current_width), + height=self.apply_widget_scaling(self._current_height)) + self.canvas.place(x=0, y=0, relwidth=1, relheight=1) + self.canvas.configure(bg="green") + self.draw_engine = DrawEngine(self.canvas) + + self.canvas.bind("", self.on_enter) + self.canvas.bind("", self.on_leave) + self.canvas.tag_bind("border_parts", "", self.clicked) + self.canvas.bind("", self.clicked) + self.canvas.bind("", self.mouse_scroll_event) + self.bind('', self.update_dimensions_event) + + self.draw() + + def set_scaling(self, *args, **kwargs): + super().set_scaling(*args, **kwargs) + + self.canvas.configure(width=self.apply_widget_scaling(self._desired_width), height=self.apply_widget_scaling(self._desired_height)) + self.draw(no_color_updates=True) + + def set_dimensions(self, width=None, height=None): + super().set_dimensions(width, height) + + self.canvas.configure(width=self.apply_widget_scaling(self._desired_width), + height=self.apply_widget_scaling(self._desired_height)) + self.draw(no_color_updates=True) + + def draw(self, no_color_updates=False): + requires_recoloring = self.draw_engine.draw_rounded_scrollbar(self.apply_widget_scaling(self._current_width), + self.apply_widget_scaling(self._current_height), + self.apply_widget_scaling(self.corner_radius), + self.apply_widget_scaling(self.border_spacing), + self.start_value, self.end_value, + self.orientation) + + if no_color_updates is False or requires_recoloring: + if self.hover_state is True: + self.canvas.itemconfig("scrollbar_parts", + fill=ThemeManager.single_color(self.scrollbar_hover_color, self._appearance_mode), + outline=ThemeManager.single_color(self.scrollbar_hover_color, self._appearance_mode)) + else: + self.canvas.itemconfig("scrollbar_parts", + fill=ThemeManager.single_color(self.scrollbar_color, self._appearance_mode), + outline=ThemeManager.single_color(self.scrollbar_color, self._appearance_mode)) + + if self.fg_color is None: + self.canvas.itemconfig("border_parts", + fill=ThemeManager.single_color(self.bg_color, self._appearance_mode), + outline=ThemeManager.single_color(self.bg_color, self._appearance_mode)) + else: + self.canvas.itemconfig("border_parts", + fill=ThemeManager.single_color(self.fg_color, self._appearance_mode), + outline=ThemeManager.single_color(self.fg_color, self._appearance_mode)) + + def set(self, start_value: float, end_value: float): + self.start_value = float(start_value) + self.end_value = float(end_value) + self.scrollbar_height = self.end_value - self.start_value + self.draw() + + def get(self): + return self.start_value, self.end_value + + def configure(self, *args, **kwargs): + require_redraw = False # some attribute changes require a call of self.draw() at the end + + if "bg_color" in kwargs: + if kwargs["bg_color"] is None: + self.bg_color = self.detect_color_of_master() + else: + self.bg_color = kwargs["bg_color"] + require_redraw = True + del kwargs["bg_color"] + + if "fg_color" in kwargs: + self.fg_color = kwargs["fg_color"] + require_redraw = True + del kwargs["fg_color"] + + if "scrollbar_color" in kwargs: + self.scrollbar_color = kwargs["scrollbar_color"] + require_redraw = True + del kwargs["scrollbar_color"] + + if "scrollbar_hover_color" in kwargs: + self.scrollbar_hover_color = kwargs["scrollbar_hover_color"] + require_redraw = True + del kwargs["scrollbar_hover_color"] + + if "command" in kwargs: + self.command = kwargs["command"] + del kwargs["command"] + + if "corner_radius" in kwargs: + self.corner_radius = kwargs["corner_radius"] + require_redraw = True + del kwargs["corner_radius"] + + if "border_spacing" in kwargs: + self.border_spacing = kwargs["border_spacing"] + require_redraw = True + del kwargs["border_spacing"] + + if "width" in kwargs: + self.set_dimensions(width=kwargs["width"]) + del kwargs["width"] + + if "height" in kwargs: + self.set_dimensions(height=kwargs["height"]) + del kwargs["height"] + + super().configure(*args, **kwargs) + + if require_redraw: + self.draw() + + def on_enter(self, event=0): + if self.hover is True: + self.hover_state = True + self.canvas.itemconfig("scrollbar_parts", + outline=ThemeManager.single_color(self.scrollbar_hover_color, self._appearance_mode), + fill=ThemeManager.single_color(self.scrollbar_hover_color, self._appearance_mode)) + + def on_leave(self, event=0): + self.hover_state = False + self.canvas.itemconfig("scrollbar_parts", + outline=ThemeManager.single_color(self.scrollbar_color, self._appearance_mode), + fill=ThemeManager.single_color(self.scrollbar_color, self._appearance_mode)) + + def clicked(self, event): + if self.orientation == "vertical": + value = ((event.y - self.border_spacing) / (self._current_height - 2 * self.border_spacing)) / self._widget_scaling + current_scrollbar_length = self.end_value - self.start_value + value = max(current_scrollbar_length / 2, min(value, 1 - (current_scrollbar_length / 2))) + self.start_value = value - (current_scrollbar_length / 2) + self.end_value = value + (current_scrollbar_length / 2) + self.draw() + + if self.command is not None: + self.command('moveto', self.start_value) + + def mouse_scroll_event(self, event=None): + if self.command is not None: + self.command('scroll', event.delta, 'units') + diff --git a/customtkinter/widgets/ctk_slider.py b/customtkinter/widgets/ctk_slider.py index 4712885..94889d3 100644 --- a/customtkinter/widgets/ctk_slider.py +++ b/customtkinter/widgets/ctk_slider.py @@ -61,7 +61,7 @@ class CTkSlider(CTkBaseClass): self.border_width = ThemeManager.theme["shape"]["slider_border_width"] if border_width == "default_theme" else border_width self.button_length = ThemeManager.theme["shape"]["slider_button_length"] if button_length == "default_theme" else button_length self.value = 0.5 # initial value of slider in percent - self.orient = orient + self.orientation = orient self.hover_state = False self.from_ = from_ self.to = to @@ -139,9 +139,9 @@ class CTkSlider(CTkBaseClass): self.configure(cursor="arrow") def draw(self, no_color_updates=False): - if self.orient.lower() == "horizontal": + if self.orientation.lower() == "horizontal": orientation = "w" - elif self.orient.lower() == "vertical": + elif self.orientation.lower() == "vertical": orientation = "s" else: orientation = "w" @@ -185,7 +185,7 @@ class CTkSlider(CTkBaseClass): def clicked(self, event=None): if self.state == "normal": - if self.orient.lower() == "horizontal": + if self.orientation.lower() == "horizontal": self.value = (event.x / self._current_width) / self._widget_scaling else: self.value = 1 - (event.y / self._current_height) / self._widget_scaling diff --git a/test/manual_integration_tests/test_scrollbar.py b/test/manual_integration_tests/test_scrollbar.py new file mode 100644 index 0000000..8b63c74 --- /dev/null +++ b/test/manual_integration_tests/test_scrollbar.py @@ -0,0 +1,37 @@ +import tkinter +import customtkinter + +# customtkinter.DrawEngine.preferred_drawing_method = "font_shapes" + + +def to_scollbar(*args, **kwargs): + tk_textbox_scrollbar.set(*args, **kwargs) + ctk_textbox_scrollbar.set(*args, **kwargs) + ctk_textbox_scrollbar.update_idletasks() + tk_textbox_scrollbar.update_idletasks() + print("to_scollbar:", args, **kwargs) + + +def from_scrollbar(*args, **kwargs): + tk_textbox.yview(*args, **kwargs) + print("from_scrollbar:", args, **kwargs) + + +app = customtkinter.CTk() +app.grid_rowconfigure(0, weight=1) +app.grid_columnconfigure(0, weight=1) + +tk_textbox = tkinter.Text(app) +tk_textbox.grid(row=0, column=0, sticky="nsew") + +ctk_textbox_scrollbar = customtkinter.CTkScrollbar(app, command=from_scrollbar, fg_color="red") +ctk_textbox_scrollbar.grid(row=0, column=1, padx=0, sticky="ns") + +tk_textbox_scrollbar = tkinter.Scrollbar(app, command=from_scrollbar) +tk_textbox_scrollbar.grid(row=0, column=2, padx=1, sticky="ns") + +tk_textbox.configure(yscrollcommand=to_scollbar) + +tk_textbox.insert("insert", "\n".join([str(i) for i in range(100)])) + +app.mainloop() From 43900c7fefe5703ac59b38f09ceea36e6c80844f Mon Sep 17 00:00:00 2001 From: TomSchimansky Date: Sun, 19 Jun 2022 22:12:19 +0200 Subject: [PATCH 16/33] fixed scrollbar for Windows --- customtkinter/assets/themes/blue.json | 6 +++--- customtkinter/draw_engine.py | 8 +++---- customtkinter/widgets/ctk_scrollbar.py | 7 ++++++- .../test_scrollbar.py | 21 ++++++++++++------- 4 files changed, 26 insertions(+), 16 deletions(-) diff --git a/customtkinter/assets/themes/blue.json b/customtkinter/assets/themes/blue.json index 24b2aab..a35eeb5 100644 --- a/customtkinter/assets/themes/blue.json +++ b/customtkinter/assets/themes/blue.json @@ -34,8 +34,8 @@ "dropdown_color": ["gray90", "gray20"], "dropdown_hover": ["gray75", "gray28"], "dropdown_text": ["gray10", "#DCE4EE"], - "scrollbar": ["gray75", "gray25"], - "scrollbar_hover": ["gray50", "gray40"] + "scrollbar": ["gray60", "gray38"], + "scrollbar_hover": ["gray45", "gray50"] }, "text": { "macOS": { @@ -73,7 +73,7 @@ "switch_corner_radius": 1000, "switch_button_corner_radius": 1000, "switch_button_length": 0, - "scrollbar_corner_radius": 6, + "scrollbar_corner_radius": 1000, "scrollbar_border_spacing": 4 } } diff --git a/customtkinter/draw_engine.py b/customtkinter/draw_engine.py index 9af3347..75726f1 100644 --- a/customtkinter/draw_engine.py +++ b/customtkinter/draw_engine.py @@ -1062,16 +1062,16 @@ class DrawEngine: else: self._canvas.delete("scrollbar_corner_part") - if not self._canvas.find_withtag("scrollbar_rectangle_1") and width > 2 * corner_radius: + if not self._canvas.find_withtag("scrollbar_rectangle_1") and height > 2 * corner_radius: self._canvas.create_rectangle(0, 0, 0, 0, tags=("scrollbar_rectangle_1", "scrollbar_rectangle_part", "scrollbar_parts"), width=0) requires_recoloring = True - elif self._canvas.find_withtag("scrollbar_rectangle_1") and not width > 2 * corner_radius: + elif self._canvas.find_withtag("scrollbar_rectangle_1") and not height > 2 * corner_radius: self._canvas.delete("scrollbar_rectangle_1") - if not self._canvas.find_withtag("scrollbar_rectangle_2"): + if not self._canvas.find_withtag("scrollbar_rectangle_2") and width > 2 * corner_radius: self._canvas.create_rectangle(0, 0, 0, 0, tags=("scrollbar_rectangle_2", "scrollbar_rectangle_part", "scrollbar_parts"), width=0) requires_recoloring = True - elif self._canvas.find_withtag("scrollbar_rectangle_2") and not height > 2 * corner_radius: + elif self._canvas.find_withtag("scrollbar_rectangle_2") and not width > 2 * corner_radius: self._canvas.delete("scrollbar_rectangle_2") self._canvas.coords("scrollbar_rectangle_1", diff --git a/customtkinter/widgets/ctk_scrollbar.py b/customtkinter/widgets/ctk_scrollbar.py index 4382d88..f5b6e58 100644 --- a/customtkinter/widgets/ctk_scrollbar.py +++ b/customtkinter/widgets/ctk_scrollbar.py @@ -1,3 +1,5 @@ +import sys + from .ctk_canvas import CTkCanvas from ..theme_manager import ThemeManager from ..draw_engine import DrawEngine @@ -196,5 +198,8 @@ class CTkScrollbar(CTkBaseClass): def mouse_scroll_event(self, event=None): if self.command is not None: - self.command('scroll', event.delta, 'units') + if sys.platform.startswith("win"): + self.command('scroll', -int(event.delta/40), 'units') + else: + self.command('scroll', -event.delta, 'units') diff --git a/test/manual_integration_tests/test_scrollbar.py b/test/manual_integration_tests/test_scrollbar.py index 8b63c74..417061c 100644 --- a/test/manual_integration_tests/test_scrollbar.py +++ b/test/manual_integration_tests/test_scrollbar.py @@ -2,19 +2,24 @@ import tkinter import customtkinter # customtkinter.DrawEngine.preferred_drawing_method = "font_shapes" +#customtkinter.set_widget_scaling(2) +#customtkinter.set_window_scaling(2) +#customtkinter.set_spacing_scaling(2) + +customtkinter.set_appearance_mode("light") def to_scollbar(*args, **kwargs): - tk_textbox_scrollbar.set(*args, **kwargs) + #tk_textbox_scrollbar.set(*args, **kwargs) ctk_textbox_scrollbar.set(*args, **kwargs) ctk_textbox_scrollbar.update_idletasks() - tk_textbox_scrollbar.update_idletasks() - print("to_scollbar:", args, **kwargs) + #tk_textbox_scrollbar.update_idletasks() + #print(*args) def from_scrollbar(*args, **kwargs): tk_textbox.yview(*args, **kwargs) - print("from_scrollbar:", args, **kwargs) + #print(*args) app = customtkinter.CTk() @@ -22,13 +27,13 @@ app.grid_rowconfigure(0, weight=1) app.grid_columnconfigure(0, weight=1) tk_textbox = tkinter.Text(app) -tk_textbox.grid(row=0, column=0, sticky="nsew") +tk_textbox.grid(row=0, column=0, sticky="ns") -ctk_textbox_scrollbar = customtkinter.CTkScrollbar(app, command=from_scrollbar, fg_color="red") +ctk_textbox_scrollbar = customtkinter.CTkScrollbar(app, command=from_scrollbar) ctk_textbox_scrollbar.grid(row=0, column=1, padx=0, sticky="ns") -tk_textbox_scrollbar = tkinter.Scrollbar(app, command=from_scrollbar) -tk_textbox_scrollbar.grid(row=0, column=2, padx=1, sticky="ns") +#tk_textbox_scrollbar = tkinter.Scrollbar(app, command=from_scrollbar) +#tk_textbox_scrollbar.grid(row=0, column=2, padx=1, sticky="ns") tk_textbox.configure(yscrollcommand=to_scollbar) From a7b175ae657d3f7ced232e437f56d788c36c772e Mon Sep 17 00:00:00 2001 From: Tom Schimansky Date: Mon, 20 Jun 2022 23:44:35 +0200 Subject: [PATCH 17/33] changed scrollbar colors, added custom exception message for pyinstaller file problem --- customtkinter/__init__.py | 10 +++- customtkinter/assets/themes/blue.json | 6 +-- customtkinter/assets/themes/dark-blue.json | 4 +- customtkinter/assets/themes/green.json | 4 +- customtkinter/assets/themes/sweetkind.json | 4 +- customtkinter/widgets/ctk_scrollbar.py | 13 +++-- .../test_scrollbar.py | 51 +++++++++++-------- 7 files changed, 58 insertions(+), 34 deletions(-) diff --git a/customtkinter/__init__.py b/customtkinter/__init__.py index bb59d8d..bb91fed 100644 --- a/customtkinter/__init__.py +++ b/customtkinter/__init__.py @@ -14,7 +14,15 @@ from .font_manager import FontManager from .draw_engine import DrawEngine AppearanceModeTracker.init_appearance_mode() -ThemeManager.load_theme("blue") # load default theme + +# load default blue theme +try: + ThemeManager.load_theme("blue") +except FileNotFoundError as err: + raise FileNotFoundError(f"{err}\n\nThe .json theme file for CustomTkinter could not be found.\n" + + f"If packaging with pyinstaller was used, have a look at the wiki:\n" + + f"https://github.com/TomSchimansky/CustomTkinter/wiki/Packaging#windows-pyinstaller-auto-py-to-exe") + FontManager.init_font_manager() # determine draw method based on current platform diff --git a/customtkinter/assets/themes/blue.json b/customtkinter/assets/themes/blue.json index a35eeb5..06b580f 100644 --- a/customtkinter/assets/themes/blue.json +++ b/customtkinter/assets/themes/blue.json @@ -11,7 +11,7 @@ "entry_placeholder_text": ["gray52", "gray62"], "frame_border": ["#979DA2", "#1F2122"], "frame_low": ["#D1D5D8", "#2A2D2E"], - "frame_high": ["#D7D8D9", "#343638"], + "frame_high": ["#C0C2C5", "#343638"], "label": [null, null], "text": ["gray10", "#DCE4EE"], "text_disabled": ["gray60", "#777B80"], @@ -34,8 +34,8 @@ "dropdown_color": ["gray90", "gray20"], "dropdown_hover": ["gray75", "gray28"], "dropdown_text": ["gray10", "#DCE4EE"], - "scrollbar": ["gray60", "gray38"], - "scrollbar_hover": ["gray45", "gray50"] + "scrollbar_button": ["gray55", "gray41"], + "scrollbar_button_hover": ["gray40", "gray53"] }, "text": { "macOS": { diff --git a/customtkinter/assets/themes/dark-blue.json b/customtkinter/assets/themes/dark-blue.json index 60abe7f..f2f2c9f 100644 --- a/customtkinter/assets/themes/dark-blue.json +++ b/customtkinter/assets/themes/dark-blue.json @@ -33,7 +33,9 @@ "combobox_button_hover": ["#6E7174", "#7A848D"], "dropdown_color": ["gray90", "gray20"], "dropdown_hover": ["gray75", "gray28"], - "dropdown_text": ["gray10", "#DCE4EE"] + "dropdown_text": ["gray10", "#DCE4EE"], + "scrollbar_button": ["gray55", "gray41"], + "scrollbar_button_hover": ["gray40", "gray53"] }, "text": { "macOS": { diff --git a/customtkinter/assets/themes/green.json b/customtkinter/assets/themes/green.json index 6032062..469f9fe 100644 --- a/customtkinter/assets/themes/green.json +++ b/customtkinter/assets/themes/green.json @@ -33,7 +33,9 @@ "combobox_button_hover": ["#6E7174", "#7A848D"], "dropdown_color": ["gray90", "gray20"], "dropdown_hover": ["gray75", "gray28"], - "dropdown_text": ["gray10", "#DCE4EE"] + "dropdown_text": ["gray10", "#DCE4EE"], + "scrollbar_button": ["gray55", "gray41"], + "scrollbar_button_hover": ["gray40", "gray53"] }, "text": { "macOS": { diff --git a/customtkinter/assets/themes/sweetkind.json b/customtkinter/assets/themes/sweetkind.json index 3f3b1a2..0c952b6 100644 --- a/customtkinter/assets/themes/sweetkind.json +++ b/customtkinter/assets/themes/sweetkind.json @@ -33,7 +33,9 @@ "combobox_button_hover": ["#6E7174", "#7A848D"], "dropdown_color": ["gray90", "gray20"], "dropdown_hover": ["gray75", "gray28"], - "dropdown_text": ["gray10", "#DCE4EE"] + "dropdown_text": ["gray10", "#DCE4EE"], + "scrollbar_button": ["gray55", "gray41"], + "scrollbar_button_hover": ["gray40", "gray53"] }, "text": { "macOS": { diff --git a/customtkinter/widgets/ctk_scrollbar.py b/customtkinter/widgets/ctk_scrollbar.py index f5b6e58..135fe20 100644 --- a/customtkinter/widgets/ctk_scrollbar.py +++ b/customtkinter/widgets/ctk_scrollbar.py @@ -1,5 +1,7 @@ import sys +import tkinter +from .ctk_frame import CTkFrame from .ctk_canvas import CTkCanvas from ..theme_manager import ThemeManager from ..draw_engine import DrawEngine @@ -9,7 +11,7 @@ from .widget_base_class import CTkBaseClass class CTkScrollbar(CTkBaseClass): def __init__(self, *args, bg_color=None, - fg_color=None, + fg_color="default_theme", scrollbar_color="default_theme", scrollbar_hover_color="default_theme", border_spacing="default_theme", @@ -37,9 +39,9 @@ class CTkScrollbar(CTkBaseClass): super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs) # color - self.fg_color = fg_color - self.scrollbar_color = ThemeManager.theme["color"]["scrollbar"] if scrollbar_color == "default_theme" else scrollbar_color - self.scrollbar_hover_color = ThemeManager.theme["color"]["scrollbar_hover"] if scrollbar_hover_color == "default_theme" else scrollbar_hover_color + self.fg_color = ThemeManager.theme["color"]["frame_high"] if fg_color == "default_theme" else fg_color + self.scrollbar_color = ThemeManager.theme["color"]["scrollbar_button"] if scrollbar_color == "default_theme" else scrollbar_color + self.scrollbar_hover_color = ThemeManager.theme["color"]["scrollbar_button_hover"] if scrollbar_hover_color == "default_theme" else scrollbar_hover_color # shape self.corner_radius = ThemeManager.theme["shape"]["scrollbar_corner_radius"] if corner_radius == "default_theme" else corner_radius @@ -57,7 +59,6 @@ class CTkScrollbar(CTkBaseClass): width=self.apply_widget_scaling(self._current_width), height=self.apply_widget_scaling(self._current_height)) self.canvas.place(x=0, y=0, relwidth=1, relheight=1) - self.canvas.configure(bg="green") self.draw_engine = DrawEngine(self.canvas) self.canvas.bind("", self.on_enter) @@ -109,6 +110,8 @@ class CTkScrollbar(CTkBaseClass): fill=ThemeManager.single_color(self.fg_color, self._appearance_mode), outline=ThemeManager.single_color(self.fg_color, self._appearance_mode)) + self.canvas.update_idletasks() + def set(self, start_value: float, end_value: float): self.start_value = float(start_value) self.end_value = float(end_value) diff --git a/test/manual_integration_tests/test_scrollbar.py b/test/manual_integration_tests/test_scrollbar.py index 417061c..8293a94 100644 --- a/test/manual_integration_tests/test_scrollbar.py +++ b/test/manual_integration_tests/test_scrollbar.py @@ -8,35 +8,42 @@ import customtkinter customtkinter.set_appearance_mode("light") - -def to_scollbar(*args, **kwargs): - #tk_textbox_scrollbar.set(*args, **kwargs) - ctk_textbox_scrollbar.set(*args, **kwargs) - ctk_textbox_scrollbar.update_idletasks() - #tk_textbox_scrollbar.update_idletasks() - #print(*args) - - -def from_scrollbar(*args, **kwargs): - tk_textbox.yview(*args, **kwargs) - #print(*args) - - app = customtkinter.CTk() app.grid_rowconfigure(0, weight=1) -app.grid_columnconfigure(0, weight=1) +app.grid_columnconfigure((0, 2), weight=1) -tk_textbox = tkinter.Text(app) -tk_textbox.grid(row=0, column=0, sticky="ns") - -ctk_textbox_scrollbar = customtkinter.CTkScrollbar(app, command=from_scrollbar) +tk_textbox = tkinter.Text(app, highlightthickness=0, padx=5, pady=5) +tk_textbox.grid(row=0, column=0, sticky="nsew") +ctk_textbox_scrollbar = customtkinter.CTkScrollbar(app, command=tk_textbox.yview) ctk_textbox_scrollbar.grid(row=0, column=1, padx=0, sticky="ns") +tk_textbox.configure(yscrollcommand=ctk_textbox_scrollbar.set) -#tk_textbox_scrollbar = tkinter.Scrollbar(app, command=from_scrollbar) -#tk_textbox_scrollbar.grid(row=0, column=2, padx=1, sticky="ns") +frame_1 = customtkinter.CTkFrame(app) +frame_1.grid(row=0, column=2, padx=10, pady=10, sticky="nsew") +frame_1.grid_rowconfigure((0, 1), weight=1) +frame_1.grid_columnconfigure((0, ), weight=1) +tk_textbox_1 = tkinter.Text(frame_1, highlightthickness=0, padx=5, pady=5) +tk_textbox_1.grid(row=0, column=0, sticky="nsew", padx=(5, 0), pady=5) +ctk_textbox_scrollbar_1 = customtkinter.CTkScrollbar(frame_1, command=tk_textbox_1.yview) +ctk_textbox_scrollbar_1.grid(row=0, column=1, sticky="ns", padx=(0, 5), pady=5) +tk_textbox_1.configure(yscrollcommand=ctk_textbox_scrollbar_1.set) -tk_textbox.configure(yscrollcommand=to_scollbar) +frame_2 = customtkinter.CTkFrame(frame_1) +frame_2.grid(row=1, column=0, columnspan=2, padx=20, pady=20, sticky="nsew") +frame_2.grid_rowconfigure((0, 1), weight=1) +frame_2.grid_columnconfigure((0, ), weight=1) +tk_textbox_2 = tkinter.Text(frame_2, highlightthickness=0, padx=5, pady=5) +tk_textbox_2.grid(row=0, column=0, sticky="nsew", padx=(5, 0), pady=5) +ctk_textbox_scrollbar_2 = customtkinter.CTkScrollbar(frame_2, command=tk_textbox_2.yview) +ctk_textbox_scrollbar_2.grid(row=0, column=1, sticky="ns", padx=(0, 5), pady=5) +tk_textbox_2.configure(yscrollcommand=ctk_textbox_scrollbar_2.set) + +tk_textbox.configure(font=(customtkinter.ThemeManager.theme["text"]["font"], customtkinter.ThemeManager.theme["text"]["size"])) +tk_textbox_1.configure(font=(customtkinter.ThemeManager.theme["text"]["font"], customtkinter.ThemeManager.theme["text"]["size"])) +tk_textbox_2.configure(font=(customtkinter.ThemeManager.theme["text"]["font"], customtkinter.ThemeManager.theme["text"]["size"])) tk_textbox.insert("insert", "\n".join([str(i) for i in range(100)])) +tk_textbox_1.insert("insert", "\n".join([str(i) for i in range(1000)])) +tk_textbox_2.insert("insert", "\n".join([str(i) for i in range(10000)])) app.mainloop() From ec3fdc40ff54ae8d80c285802a38358740505902 Mon Sep 17 00:00:00 2001 From: Tom Schimansky Date: Thu, 23 Jun 2022 17:41:12 +0200 Subject: [PATCH 18/33] fixed scrollbar for horizontal orientation --- customtkinter/widgets/ctk_scrollbar.py | 58 ++++++++++++++----- .../test_optionmenu_combobox.py | 2 +- .../test_scrollbar.py | 21 ++++--- 3 files changed, 58 insertions(+), 23 deletions(-) diff --git a/customtkinter/widgets/ctk_scrollbar.py b/customtkinter/widgets/ctk_scrollbar.py index 135fe20..a70908d 100644 --- a/customtkinter/widgets/ctk_scrollbar.py +++ b/customtkinter/widgets/ctk_scrollbar.py @@ -1,7 +1,5 @@ import sys -import tkinter -from .ctk_frame import CTkFrame from .ctk_canvas import CTkCanvas from ..theme_manager import ThemeManager from ..draw_engine import DrawEngine @@ -18,6 +16,7 @@ class CTkScrollbar(CTkBaseClass): corner_radius="default_theme", width=None, height=None, + minimum_pixel_length=20, orientation="vertical", command=None, hover=True, @@ -30,10 +29,10 @@ class CTkScrollbar(CTkBaseClass): else: width = 200 if height is None: - if orientation.lower() == "vertical": - height = 200 - else: + if orientation.lower() == "horizontal": height = 16 + else: + height = 200 # transfer basic functionality (bg_color, size, _appearance_mode, scaling) to CTkBaseClass super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs) @@ -53,6 +52,7 @@ class CTkScrollbar(CTkBaseClass): self.orientation = orientation self.start_value: float = 0 # 0 to 1 self.end_value: float = 1 # 0 to 1 + self.minimum_pixel_length = minimum_pixel_length self.canvas = CTkCanvas(master=self, highlightthickness=0, @@ -83,12 +83,38 @@ class CTkScrollbar(CTkBaseClass): height=self.apply_widget_scaling(self._desired_height)) self.draw(no_color_updates=True) + def get_scrollbar_values_for_minimum_pixel_size(self): + # correct scrollbar float values if scrollbar is too small + if self.orientation == "vertical": + scrollbar_pixel_length = (self.end_value - self.start_value) * self._current_height + if scrollbar_pixel_length < self.minimum_pixel_length and -scrollbar_pixel_length + self._current_height != 0: + # calculate how much to increase the float interval values so that the scrollbar width is self.minimum_pixel_length + interval_extend_factor = (-scrollbar_pixel_length + self.minimum_pixel_length) / (-scrollbar_pixel_length + self._current_height) + corrected_end_value = self.end_value + (1 - self.end_value) * interval_extend_factor + corrected_start_value = self.start_value - self.start_value * interval_extend_factor + return corrected_start_value, corrected_end_value + else: + return self.start_value, self.end_value + + else: + scrollbar_pixel_length = (self.end_value - self.start_value) * self._current_width + if scrollbar_pixel_length < self.minimum_pixel_length and -scrollbar_pixel_length + self._current_width != 0: + # calculate how much to increase the float interval values so that the scrollbar width is self.minimum_pixel_length + interval_extend_factor = (-scrollbar_pixel_length + self.minimum_pixel_length) / (-scrollbar_pixel_length + self._current_width) + corrected_end_value = self.end_value + (1 - self.end_value) * interval_extend_factor + corrected_start_value = self.start_value - self.start_value * interval_extend_factor + return corrected_start_value, corrected_end_value + else: + return self.start_value, self.end_value + def draw(self, no_color_updates=False): + corrected_start_value, corrected_end_value = self.get_scrollbar_values_for_minimum_pixel_size() requires_recoloring = self.draw_engine.draw_rounded_scrollbar(self.apply_widget_scaling(self._current_width), self.apply_widget_scaling(self._current_height), self.apply_widget_scaling(self.corner_radius), self.apply_widget_scaling(self.border_spacing), - self.start_value, self.end_value, + corrected_start_value, + corrected_end_value, self.orientation) if no_color_updates is False or requires_recoloring: @@ -102,10 +128,12 @@ class CTkScrollbar(CTkBaseClass): outline=ThemeManager.single_color(self.scrollbar_color, self._appearance_mode)) if self.fg_color is None: + self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode)) self.canvas.itemconfig("border_parts", fill=ThemeManager.single_color(self.bg_color, self._appearance_mode), outline=ThemeManager.single_color(self.bg_color, self._appearance_mode)) else: + self.canvas.configure(bg=ThemeManager.single_color(self.fg_color, self._appearance_mode)) self.canvas.itemconfig("border_parts", fill=ThemeManager.single_color(self.fg_color, self._appearance_mode), outline=ThemeManager.single_color(self.fg_color, self._appearance_mode)) @@ -115,7 +143,6 @@ class CTkScrollbar(CTkBaseClass): def set(self, start_value: float, end_value: float): self.start_value = float(start_value) self.end_value = float(end_value) - self.scrollbar_height = self.end_value - self.start_value self.draw() def get(self): @@ -190,14 +217,17 @@ class CTkScrollbar(CTkBaseClass): def clicked(self, event): if self.orientation == "vertical": value = ((event.y - self.border_spacing) / (self._current_height - 2 * self.border_spacing)) / self._widget_scaling - current_scrollbar_length = self.end_value - self.start_value - value = max(current_scrollbar_length / 2, min(value, 1 - (current_scrollbar_length / 2))) - self.start_value = value - (current_scrollbar_length / 2) - self.end_value = value + (current_scrollbar_length / 2) - self.draw() + else: + value = ((event.x - self.border_spacing) / (self._current_width - 2 * self.border_spacing)) / self._widget_scaling - if self.command is not None: - self.command('moveto', self.start_value) + current_scrollbar_length = self.end_value - self.start_value + value = max(current_scrollbar_length / 2, min(value, 1 - (current_scrollbar_length / 2))) + self.start_value = value - (current_scrollbar_length / 2) + self.end_value = value + (current_scrollbar_length / 2) + self.draw() + + if self.command is not None: + self.command('moveto', self.start_value) def mouse_scroll_event(self, event=None): if self.command is not None: diff --git a/test/manual_integration_tests/test_optionmenu_combobox.py b/test/manual_integration_tests/test_optionmenu_combobox.py index 32b29e8..91ac241 100644 --- a/test/manual_integration_tests/test_optionmenu_combobox.py +++ b/test/manual_integration_tests/test_optionmenu_combobox.py @@ -12,7 +12,7 @@ def select_callback(choice): print("display_selected", choice) -countries = ['Bahamas', 'Canada', 'Cuba', 'United States'] +countries = ['Bahamas', 'Canada', 'Cuba', 'United States', "long sdhfhjgdshjafghdgshfhjdsfj"] variable = tkinter.StringVar() variable.set("test") diff --git a/test/manual_integration_tests/test_scrollbar.py b/test/manual_integration_tests/test_scrollbar.py index 8293a94..5fcced0 100644 --- a/test/manual_integration_tests/test_scrollbar.py +++ b/test/manual_integration_tests/test_scrollbar.py @@ -1,14 +1,15 @@ import tkinter import customtkinter -# customtkinter.DrawEngine.preferred_drawing_method = "font_shapes" -#customtkinter.set_widget_scaling(2) -#customtkinter.set_window_scaling(2) -#customtkinter.set_spacing_scaling(2) +# test with scaling +# customtkinter.set_widget_scaling(2) +# customtkinter.set_window_scaling(2) +# customtkinter.set_spacing_scaling(2) -customtkinter.set_appearance_mode("light") +customtkinter.set_appearance_mode("dark") app = customtkinter.CTk() +app.title("test_scrollbar.py") app.grid_rowconfigure(0, weight=1) app.grid_columnconfigure((0, 2), weight=1) @@ -27,16 +28,20 @@ tk_textbox_1.grid(row=0, column=0, sticky="nsew", padx=(5, 0), pady=5) ctk_textbox_scrollbar_1 = customtkinter.CTkScrollbar(frame_1, command=tk_textbox_1.yview) ctk_textbox_scrollbar_1.grid(row=0, column=1, sticky="ns", padx=(0, 5), pady=5) tk_textbox_1.configure(yscrollcommand=ctk_textbox_scrollbar_1.set) +ctk_textbox_scrollbar_1.configure(scrollbar_color="red", scrollbar_hover_color="darkred", + border_spacing=0, width=12, fg_color="green", corner_radius=4) frame_2 = customtkinter.CTkFrame(frame_1) frame_2.grid(row=1, column=0, columnspan=2, padx=20, pady=20, sticky="nsew") frame_2.grid_rowconfigure((0, 1), weight=1) frame_2.grid_columnconfigure((0, ), weight=1) -tk_textbox_2 = tkinter.Text(frame_2, highlightthickness=0, padx=5, pady=5) +tk_textbox_2 = tkinter.Text(frame_2, highlightthickness=0, padx=5, pady=5, wrap="none") tk_textbox_2.grid(row=0, column=0, sticky="nsew", padx=(5, 0), pady=5) ctk_textbox_scrollbar_2 = customtkinter.CTkScrollbar(frame_2, command=tk_textbox_2.yview) ctk_textbox_scrollbar_2.grid(row=0, column=1, sticky="ns", padx=(0, 5), pady=5) -tk_textbox_2.configure(yscrollcommand=ctk_textbox_scrollbar_2.set) +ctk_textbox_scrollbar_2_horizontal = customtkinter.CTkScrollbar(frame_2, command=tk_textbox_2.xview, orientation="horizontal") +ctk_textbox_scrollbar_2_horizontal.grid(row=1, column=0, sticky="ew", padx=(5, 0), pady=(0, 5)) +tk_textbox_2.configure(yscrollcommand=ctk_textbox_scrollbar_2.set, xscrollcommand=ctk_textbox_scrollbar_2_horizontal.set) tk_textbox.configure(font=(customtkinter.ThemeManager.theme["text"]["font"], customtkinter.ThemeManager.theme["text"]["size"])) tk_textbox_1.configure(font=(customtkinter.ThemeManager.theme["text"]["font"], customtkinter.ThemeManager.theme["text"]["size"])) @@ -44,6 +49,6 @@ tk_textbox_2.configure(font=(customtkinter.ThemeManager.theme["text"]["font"], c tk_textbox.insert("insert", "\n".join([str(i) for i in range(100)])) tk_textbox_1.insert("insert", "\n".join([str(i) for i in range(1000)])) -tk_textbox_2.insert("insert", "\n".join([str(i) for i in range(10000)])) +tk_textbox_2.insert("insert", "\n".join([str(i) + " - "*30 for i in range(10000)])) app.mainloop() From d4ae8cab7dd84859ce2332af5124aed59efb24ad Mon Sep 17 00:00:00 2001 From: Tom Schimansky Date: Thu, 23 Jun 2022 21:05:44 +0200 Subject: [PATCH 19/33] fixed optionmenu and combobox bugs --- customtkinter/draw_engine.py | 4 +- customtkinter/widgets/ctk_combobox.py | 7 +- customtkinter/widgets/ctk_optionmenu.py | 66 +++++++++---------- .../test_optionmenu_combobox.py | 6 +- 4 files changed, 42 insertions(+), 41 deletions(-) diff --git a/customtkinter/draw_engine.py b/customtkinter/draw_engine.py index 75726f1..a117ccf 100644 --- a/customtkinter/draw_engine.py +++ b/customtkinter/draw_engine.py @@ -398,8 +398,8 @@ class DrawEngine: if not self._canvas.find_withtag("border_parts"): self._canvas.create_polygon((0, 0, 0, 0), tags=("border_line_left_1", "border_parts_left", "border_parts", "left_parts")) self._canvas.create_polygon((0, 0, 0, 0), tags=("border_line_right_1", "border_parts_right", "border_parts", "right_parts")) - self._canvas.create_rectangle((0, 0, 0, 0), tags=("border_rect_left_1", "border_parts_left", "border_parts", "left_parts")) - self._canvas.create_rectangle((0, 0, 0, 0), tags=("border_rect_right_1", "border_parts_right", "border_parts", "right_parts")) + self._canvas.create_rectangle((0, 0, 0, 0), tags=("border_rect_left_1", "border_parts_left", "border_parts", "left_parts"), width=0) + self._canvas.create_rectangle((0, 0, 0, 0), tags=("border_rect_right_1", "border_parts_right", "border_parts", "right_parts"), width=0) requires_recoloring = True self._canvas.coords("border_line_left_1", diff --git a/customtkinter/widgets/ctk_combobox.py b/customtkinter/widgets/ctk_combobox.py index 688bb13..fbe803f 100644 --- a/customtkinter/widgets/ctk_combobox.py +++ b/customtkinter/widgets/ctk_combobox.py @@ -100,6 +100,9 @@ class CTkComboBox(CTkBaseClass): 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)))) + self.entry.delete(0, tkinter.END) + self.entry.insert(0, self.current_value) + self.draw() # initial draw # event bindings @@ -140,10 +143,6 @@ class CTkComboBox(CTkBaseClass): self.apply_widget_scaling(self._current_height / 2), self.apply_widget_scaling(self._current_height / 3)) - if self.current_value is not None: - self.entry.delete(0, tkinter.END) - self.entry.insert(0, self.current_value) - if no_color_updates is False or requires_recoloring or requires_recoloring_2: self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode)) diff --git a/customtkinter/widgets/ctk_optionmenu.py b/customtkinter/widgets/ctk_optionmenu.py index 458ecc1..a10b3fd 100644 --- a/customtkinter/widgets/ctk_optionmenu.py +++ b/customtkinter/widgets/ctk_optionmenu.py @@ -1,6 +1,5 @@ import tkinter import sys -from typing import Union from .dropdown_menu import DropdownMenu @@ -32,6 +31,7 @@ class CTkOptionMenu(CTkBaseClass): dropdown_text_font="default_theme", hover=True, state=tkinter.NORMAL, + dynamic_resizing=True, **kwargs): # transfer basic functionality (bg_color, size, _appearance_mode, scaling) to CTkBaseClass @@ -58,6 +58,7 @@ class CTkOptionMenu(CTkBaseClass): self.variable_callback_name = None self.state = state self.hover = hover + self.dynamic_resizing = dynamic_resizing if values is None: self.values = ["CTkOptionMenu"] @@ -89,11 +90,14 @@ class CTkOptionMenu(CTkBaseClass): self.draw_engine = DrawEngine(self.canvas) left_section_width = self._current_width - self._current_height - self.text_label = tkinter.Label(master=self, font=self.apply_font_scaling(self.text_font)) + self.text_label = tkinter.Label(master=self, font=self.apply_font_scaling(self.text_font), anchor="w") self.text_label.grid(row=0, column=0, sticky="w", 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)))) + if not self.dynamic_resizing: + self.grid_propagate(0) + if Settings.cursor_manipulation_enabled: if sys.platform == "darwin": self.configure(cursor="pointinghand") @@ -176,6 +180,8 @@ class CTkOptionMenu(CTkBaseClass): self.text_label.configure(bg=ThemeManager.single_color(self.fg_color, self._appearance_mode)) + self.canvas.update_idletasks() + def open_dropdown_menu(self): self.dropdown_menu.open(self.winfo_rootx(), self.winfo_rooty() + self.apply_widget_scaling(self._current_height + 0)) @@ -184,47 +190,41 @@ class CTkOptionMenu(CTkBaseClass): require_redraw = False # some attribute changes require a call of self.draw() at the end if "state" in kwargs: - self.state = kwargs["state"] + self.state = kwargs.pop("state") require_redraw = True - del kwargs["state"] if "fg_color" in kwargs: - self.fg_color = kwargs["fg_color"] + self.fg_color = kwargs.pop("fg_color") require_redraw = True - del kwargs["fg_color"] if "bg_color" in kwargs: - if kwargs["bg_color"] is None: + new_bg_color = kwargs.pop("bg_color") + if new_bg_color is None: self.bg_color = self.detect_color_of_master() else: - self.bg_color = kwargs["bg_color"] + self.bg_color = new_bg_color require_redraw = True - del kwargs["bg_color"] if "button_color" in kwargs: - self.button_color = kwargs["button_color"] + self.button_color = kwargs.pop("button_color") require_redraw = True - del kwargs["button_color"] if "button_hover_color" in kwargs: - self.button_hover_color = kwargs["button_hover_color"] + self.button_hover_color = kwargs.pop("button_hover_color") require_redraw = True - del kwargs["button_hover_color"] if "text_color" in kwargs: - self.text_color = kwargs["text_color"] + self.text_color = kwargs.pop("text_color") require_redraw = True - del kwargs["text_color"] if "command" in kwargs: - self.function = kwargs["command"] - del kwargs["command"] + self.function = kwargs.pop("command") if "variable" in kwargs: if self.variable is not None: # remove old callback self.variable.trace_remove("write", self.variable_callback_name) - self.variable = kwargs["variable"] + self.variable = kwargs.pop("variable") if self.variable is not None and self.variable != "": self.variable_callback_name = self.variable.trace_add("write", self.variable_callback) @@ -232,36 +232,34 @@ class CTkOptionMenu(CTkBaseClass): else: self.variable = None - del kwargs["variable"] - if "width" in kwargs: - self.set_dimensions(width=kwargs["width"]) - del kwargs["width"] + self.set_dimensions(width=kwargs.pop("width")) if "height" in kwargs: - self.set_dimensions(height=kwargs["height"]) - del kwargs["height"] + self.set_dimensions(height=kwargs.pop("height")) if "values" in kwargs: - self.values = kwargs["values"] - del kwargs["values"] + self.values = kwargs.pop("values") self.dropdown_menu.configure(values=self.values) if "dropdown_color" in kwargs: - self.dropdown_menu.configure(fg_color=kwargs["dropdown_color"]) - del kwargs["dropdown_color"] + self.dropdown_menu.configure(fg_color=kwargs.pop("dropdown_color")) if "dropdown_hover_color" in kwargs: - self.dropdown_menu.configure(hover_color=kwargs["dropdown_hover_color"]) - del kwargs["dropdown_hover_color"] + self.dropdown_menu.configure(hover_color=kwargs.pop("dropdown_hover_color")) if "dropdown_text_color" in kwargs: - self.dropdown_menu.configure(text_color=kwargs["dropdown_text_color"]) - del kwargs["dropdown_text_color"] + self.dropdown_menu.configure(text_color=kwargs.pop("dropdown_text_color")) if "dropdown_text_font" in kwargs: - self.dropdown_menu.configure(text_font=kwargs["dropdown_text_font"]) - del kwargs["dropdown_text_font"] + self.dropdown_menu.configure(text_font=kwargs.pop("dropdown_text_font")) + + if "dynamic_resizing" in kwargs: + self.dynamic_resizing = kwargs.pop("dynamic_resizing") + if not self.dynamic_resizing: + self.grid_propagate(0) + else: + self.grid_propagate(1) super().configure(*args, **kwargs) diff --git a/test/manual_integration_tests/test_optionmenu_combobox.py b/test/manual_integration_tests/test_optionmenu_combobox.py index 91ac241..3c5d698 100644 --- a/test/manual_integration_tests/test_optionmenu_combobox.py +++ b/test/manual_integration_tests/test_optionmenu_combobox.py @@ -23,10 +23,14 @@ optionmenu_tk.pack(pady=10, padx=10) optionmenu_1 = customtkinter.CTkOptionMenu(app, variable=variable, values=countries, command=select_callback) optionmenu_1.pack(pady=20, padx=10) +optionmenu_2 = customtkinter.CTkOptionMenu(app, variable=variable, values=countries, command=select_callback, + dynamic_resizing=False) +optionmenu_2.pack(pady=20, padx=10) + combobox_tk = ttk.Combobox(app, values=countries) combobox_tk.pack(pady=10, padx=10) -combobox_1 = customtkinter.CTkComboBox(app, variable=variable, values=countries, command=select_callback) +combobox_1 = customtkinter.CTkComboBox(app, variable=None, values=countries, command=select_callback, width=300) combobox_1.pack(pady=20, padx=10) app.mainloop() From 28308065bc154b068536131fb141a447a717eb6e Mon Sep 17 00:00:00 2001 From: TomSchimansky Date: Thu, 23 Jun 2022 22:14:19 +0200 Subject: [PATCH 20/33] fixed horizontal scrollbar for Windows --- customtkinter/draw_engine.py | 22 +++++++++++++------ .../test_scrollbar.py | 2 +- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/customtkinter/draw_engine.py b/customtkinter/draw_engine.py index a117ccf..51148ee 100644 --- a/customtkinter/draw_engine.py +++ b/customtkinter/draw_engine.py @@ -1074,14 +1074,14 @@ class DrawEngine: elif self._canvas.find_withtag("scrollbar_rectangle_2") and not width > 2 * corner_radius: self._canvas.delete("scrollbar_rectangle_2") - self._canvas.coords("scrollbar_rectangle_1", - corner_radius - inner_corner_radius, corner_radius + (height - 2 * corner_radius) * start_value, - width - (corner_radius - inner_corner_radius), corner_radius + (height - 2 * corner_radius) * end_value) - self._canvas.coords("scrollbar_rectangle_2", - corner_radius, corner_radius - inner_corner_radius + (height - 2 * corner_radius) * start_value, - width - (corner_radius), corner_radius + inner_corner_radius + (height - 2 * corner_radius) * end_value) - if orientation == "vertical": + self._canvas.coords("scrollbar_rectangle_1", + corner_radius - inner_corner_radius, corner_radius + (height - 2 * corner_radius) * start_value, + width - (corner_radius - inner_corner_radius), corner_radius + (height - 2 * corner_radius) * end_value) + self._canvas.coords("scrollbar_rectangle_2", + corner_radius, corner_radius - inner_corner_radius + (height - 2 * corner_radius) * start_value, + width - (corner_radius), corner_radius + inner_corner_radius + (height - 2 * corner_radius) * end_value) + self._canvas.coords("scrollbar_oval_1_a", corner_radius, corner_radius + (height - 2 * corner_radius) * start_value, inner_corner_radius) self._canvas.coords("scrollbar_oval_1_b", corner_radius, corner_radius + (height - 2 * corner_radius) * start_value, inner_corner_radius) self._canvas.coords("scrollbar_oval_2_a", width - corner_radius, corner_radius + (height - 2 * corner_radius) * start_value, inner_corner_radius) @@ -1090,7 +1090,15 @@ class DrawEngine: self._canvas.coords("scrollbar_oval_3_b", width - corner_radius, corner_radius + (height - 2 * corner_radius) * end_value, inner_corner_radius) self._canvas.coords("scrollbar_oval_4_a", corner_radius, corner_radius + (height - 2 * corner_radius) * end_value, inner_corner_radius) self._canvas.coords("scrollbar_oval_4_b", corner_radius, corner_radius + (height - 2 * corner_radius) * end_value, inner_corner_radius) + if orientation == "horizontal": + self._canvas.coords("scrollbar_rectangle_1", + corner_radius - inner_corner_radius + (width - 2 * corner_radius) * start_value, corner_radius, + corner_radius + inner_corner_radius + (width - 2 * corner_radius) * end_value, height - corner_radius) + self._canvas.coords("scrollbar_rectangle_2", + corner_radius + (width - 2 * corner_radius) * start_value, corner_radius - inner_corner_radius, + corner_radius + (width - 2 * corner_radius) * end_value, height - (corner_radius - inner_corner_radius)) + self._canvas.coords("scrollbar_oval_1_a", corner_radius + (width - 2 * corner_radius) * start_value, corner_radius, inner_corner_radius) self._canvas.coords("scrollbar_oval_1_b", corner_radius + (width - 2 * corner_radius) * start_value, corner_radius, inner_corner_radius) self._canvas.coords("scrollbar_oval_2_a", corner_radius + (width - 2 * corner_radius) * end_value, corner_radius, inner_corner_radius) diff --git a/test/manual_integration_tests/test_scrollbar.py b/test/manual_integration_tests/test_scrollbar.py index 5fcced0..d579911 100644 --- a/test/manual_integration_tests/test_scrollbar.py +++ b/test/manual_integration_tests/test_scrollbar.py @@ -33,7 +33,7 @@ ctk_textbox_scrollbar_1.configure(scrollbar_color="red", scrollbar_hover_color=" frame_2 = customtkinter.CTkFrame(frame_1) frame_2.grid(row=1, column=0, columnspan=2, padx=20, pady=20, sticky="nsew") -frame_2.grid_rowconfigure((0, 1), weight=1) +frame_2.grid_rowconfigure((0, ), weight=1) frame_2.grid_columnconfigure((0, ), weight=1) tk_textbox_2 = tkinter.Text(frame_2, highlightthickness=0, padx=5, pady=5, wrap="none") tk_textbox_2.grid(row=0, column=0, sticky="nsew", padx=(5, 0), pady=5) From b891032e2ea0dd82f17eeed0df97ac1df3182f24 Mon Sep 17 00:00:00 2001 From: Tom Schimansky Date: Thu, 23 Jun 2022 22:18:06 +0200 Subject: [PATCH 21/33] Bump to 4.5.0 --- customtkinter/__init__.py | 2 +- pyproject.toml | 2 +- setup.cfg | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/customtkinter/__init__.py b/customtkinter/__init__.py index bb91fed..0fd9484 100644 --- a/customtkinter/__init__.py +++ b/customtkinter/__init__.py @@ -1,4 +1,4 @@ -__version__ = "4.4.0" +__version__ = "4.5.0" import os import sys diff --git a/pyproject.toml b/pyproject.toml index d8becea..adb72b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta" github_url = "https://github.com/TomSchimansky/CustomTkinter" [tool.tbump.version] -current = "4.4.0" +current = "4.5.0" # Example of a semver regexp. # Make sure this matches current_version before diff --git a/setup.cfg b/setup.cfg index ed583cd..7e4b3a8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = customtkinter -version = 4.4.0 +version = 4.5.0 description = Create modern looking GUIs with Python long_description = CustomTkinter UI-Library\n\n[](https://github.com/TomSchimansky/CustomTkinter/blob/master/documentation_images/Windows_dark.png)\n\nMore Information: https://github.com/TomSchimansky/CustomTkinter long_description_content_type = text/markdown From 11c7363d288f2a1c0bfd807c602c2a1df4c1ca1d Mon Sep 17 00:00:00 2001 From: Tom Schimansky Date: Thu, 23 Jun 2022 22:28:29 +0200 Subject: [PATCH 22/33] updated CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 592816d..8726190 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [4.5.0] - 2022-06-23 +### Added + - CTkScrollbar (vertical, horizontal) + ## [4.4.0] - 2022-06-14 ### Changed - Changed custom dropdown menu to normal tkinter.Menu because of multiple platform specific bugs From e15bc5933d226ed763d4954ac5f669b8e382ecb8 Mon Sep 17 00:00:00 2001 From: Tom Schimansky Date: Tue, 28 Jun 2022 11:16:28 +0200 Subject: [PATCH 23/33] fixed scaling issues for combobox and optionemnu, chatched error in appearance_mode_tracker --- customtkinter/__init__.py | 1 + customtkinter/appearance_mode_tracker.py | 5 +- customtkinter/widgets/ctk_combobox.py | 7 + customtkinter/widgets/ctk_optionmenu.py | 17 +- customtkinter/widgets/ctk_textbox.py | 159 ++++++++++++++++++ customtkinter/widgets/widget_base_class.py | 2 +- .../test_optionmenu_combobox.py | 10 +- test/manual_integration_tests/test_textbox.py | 11 ++ 8 files changed, 199 insertions(+), 13 deletions(-) create mode 100644 customtkinter/widgets/ctk_textbox.py create mode 100644 test/manual_integration_tests/test_textbox.py diff --git a/customtkinter/__init__.py b/customtkinter/__init__.py index 0fd9484..62253bd 100644 --- a/customtkinter/__init__.py +++ b/customtkinter/__init__.py @@ -62,6 +62,7 @@ from .widgets.ctk_switch import CTkSwitch from .widgets.ctk_optionmenu import CTkOptionMenu from .widgets.ctk_combobox import CTkComboBox from .widgets.ctk_scrollbar import CTkScrollbar +from .widgets.ctk_textbox import CTkTextbox # import windows from .windows.ctk_tk import CTk diff --git a/customtkinter/appearance_mode_tracker.py b/customtkinter/appearance_mode_tracker.py index f805f31..5301e31 100644 --- a/customtkinter/appearance_mode_tracker.py +++ b/customtkinter/appearance_mode_tracker.py @@ -50,7 +50,10 @@ class AppearanceModeTracker: @classmethod def remove(cls, callback: Callable): - cls.callback_list.remove(callback) + try: + cls.callback_list.remove(callback) + except ValueError: + return @staticmethod def detect_appearance_mode() -> int: diff --git a/customtkinter/widgets/ctk_combobox.py b/customtkinter/widgets/ctk_combobox.py index fbe803f..7266a19 100644 --- a/customtkinter/widgets/ctk_combobox.py +++ b/customtkinter/widgets/ctk_combobox.py @@ -120,6 +120,13 @@ class CTkComboBox(CTkBaseClass): def set_scaling(self, *args, **kwargs): super().set_scaling(*args, **kwargs) + # change entry font size and grid padding + left_section_width = self._current_width - self._current_height + self.entry.configure(font=self.apply_font_scaling(self.text_font)) + 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)))) + self.canvas.configure(width=self.apply_widget_scaling(self._desired_width), height=self.apply_widget_scaling(self._desired_height)) self.draw() diff --git a/customtkinter/widgets/ctk_optionmenu.py b/customtkinter/widgets/ctk_optionmenu.py index a10b3fd..d6df6fb 100644 --- a/customtkinter/widgets/ctk_optionmenu.py +++ b/customtkinter/widgets/ctk_optionmenu.py @@ -126,9 +126,12 @@ class CTkOptionMenu(CTkBaseClass): def set_scaling(self, *args, **kwargs): super().set_scaling(*args, **kwargs) - if self.text_label is not None: - self.text_label.destroy() - self.text_label = None + # change label text size and grid padding + left_section_width = self._current_width - self._current_height + self.text_label.configure(font=self.apply_font_scaling(self.text_font)) + self.text_label.grid(row=0, column=0, sticky="w", + 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)))) self.canvas.configure(width=self.apply_widget_scaling(self._desired_width), height=self.apply_widget_scaling(self._desired_height)) @@ -153,9 +156,6 @@ class CTkOptionMenu(CTkBaseClass): self.apply_widget_scaling(self._current_height / 2), self.apply_widget_scaling(self._current_height / 3)) - if self.current_value is not None: - self.text_label.configure(text=self.current_value) - if no_color_updates is False or requires_recoloring or requires_recoloring_2: self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode)) @@ -287,10 +287,7 @@ class CTkOptionMenu(CTkBaseClass): def set(self, value: str, from_variable_callback: bool = False): self.current_value = value - if self.text_label is not None: - self.text_label.configure(text=self.current_value) - else: - self.draw() + self.text_label.configure(text=self.current_value) if self.variable is not None and not from_variable_callback: self.variable_callback_blocked = True diff --git a/customtkinter/widgets/ctk_textbox.py b/customtkinter/widgets/ctk_textbox.py new file mode 100644 index 0000000..96e3410 --- /dev/null +++ b/customtkinter/widgets/ctk_textbox.py @@ -0,0 +1,159 @@ +import tkinter + +from .ctk_canvas import CTkCanvas +from ..theme_manager import ThemeManager +from ..draw_engine import DrawEngine +from .widget_base_class import CTkBaseClass + + +class CTkTextbox(CTkBaseClass): + def __init__(self, *args, + bg_color=None, + fg_color="default_theme", + border_color="default_theme", + border_width="default_theme", + corner_radius="default_theme", + text_font="default_theme", + text_color="default_theme", + width=200, + height=200, + **kwargs): + + # transfer basic functionality (bg_color, size, _appearance_mode, scaling) to CTkBaseClass + super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs) + + # color + self.fg_color = ThemeManager.theme["color"]["entry"] if fg_color == "default_theme" else fg_color + self.border_color = ThemeManager.theme["color"]["frame_border"] if border_color == "default_theme" else border_color + + # shape + self.corner_radius = ThemeManager.theme["shape"]["frame_corner_radius"] if corner_radius == "default_theme" else corner_radius + self.border_width = ThemeManager.theme["shape"]["frame_border_width"] if border_width == "default_theme" else border_width + + self.text_color = ThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color + self.text_font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font + + # configure 1x1 grid + self.grid_rowconfigure(0, weight=1) + self.grid_columnconfigure(0, weight=1) + + self.canvas = CTkCanvas(master=self, + highlightthickness=0, + 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, rowspan=1, columnspan=1, sticky="nsew") + self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode)) + self.draw_engine = DrawEngine(self.canvas) + + self.textbox = tkinter.Text(self, + fg=ThemeManager.single_color(self.text_color, self._appearance_mode), + width=0, + height=0, + font=self.text_font, + highlightthickness=0, + bg=ThemeManager.single_color(self.fg_color, self._appearance_mode), + **kwargs) + self.textbox.grid(row=0, column=0, padx=self.corner_radius, pady=self.corner_radius, rowspan=1, columnspan=1, sticky="nsew") + + self.bind('', self.update_dimensions_event) + + self.draw() + + def winfo_children(self): + """ winfo_children of CTkFrame without self.canvas widget, + because it's not a child but part of the CTkFrame itself """ + + child_widgets = super().winfo_children() + try: + child_widgets.remove(self.canvas) + return child_widgets + except ValueError: + return child_widgets + + def set_scaling(self, *args, **kwargs): + super().set_scaling(*args, **kwargs) + + self.canvas.configure(width=self.apply_widget_scaling(self._desired_width), height=self.apply_widget_scaling(self._desired_height)) + self.draw() + + def set_dimensions(self, width=None, height=None): + super().set_dimensions(width, height) + + self.canvas.configure(width=self.apply_widget_scaling(self._desired_width), + height=self.apply_widget_scaling(self._desired_height)) + self.draw() + + def draw(self, no_color_updates=False): + + requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.apply_widget_scaling(self._current_width), + self.apply_widget_scaling(self._current_height), + self.apply_widget_scaling(self.corner_radius), + self.apply_widget_scaling(self.border_width)) + + if no_color_updates is False or requires_recoloring: + if self.fg_color is None: + self.canvas.itemconfig("inner_parts", + fill=ThemeManager.single_color(self.bg_color, self._appearance_mode), + outline=ThemeManager.single_color(self.bg_color, self._appearance_mode)) + else: + self.canvas.itemconfig("inner_parts", + fill=ThemeManager.single_color(self.fg_color, self._appearance_mode), + outline=ThemeManager.single_color(self.fg_color, self._appearance_mode)) + + self.canvas.itemconfig("border_parts", + fill=ThemeManager.single_color(self.border_color, self._appearance_mode), + outline=ThemeManager.single_color(self.border_color, self._appearance_mode)) + self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode)) + + self.canvas.tag_lower("inner_parts") + self.canvas.tag_lower("border_parts") + + def configure(self, *args, **kwargs): + require_redraw = False # some attribute changes require a call of self.draw() at the end + + if "fg_color" in kwargs: + self.fg_color = kwargs["fg_color"] + require_redraw = True + del kwargs["fg_color"] + + # check if CTk widgets are children of the frame and change their bg_color to new frame fg_color + for child in self.winfo_children(): + if isinstance(child, CTkBaseClass): + child.configure(bg_color=self.fg_color) + + if "bg_color" in kwargs: + if kwargs["bg_color"] is None: + self.bg_color = self.detect_color_of_master() + else: + self.bg_color = kwargs["bg_color"] + require_redraw = True + + del kwargs["bg_color"] + + if "border_color" in kwargs: + self.border_color = kwargs["border_color"] + require_redraw = True + del kwargs["border_color"] + + if "corner_radius" in kwargs: + self.corner_radius = kwargs["corner_radius"] + require_redraw = True + del kwargs["corner_radius"] + + if "border_width" in kwargs: + self.border_width = kwargs["border_width"] + require_redraw = True + del kwargs["border_width"] + + if "width" in kwargs: + self.set_dimensions(width=kwargs["width"]) + del kwargs["width"] + + if "height" in kwargs: + self.set_dimensions(height=kwargs["height"]) + del kwargs["height"] + + self.textbox.configure(*args, **kwargs) + + if require_redraw: + self.draw() diff --git a/customtkinter/widgets/widget_base_class.py b/customtkinter/widgets/widget_base_class.py index 9988ce0..29a5edb 100644 --- a/customtkinter/widgets/widget_base_class.py +++ b/customtkinter/widgets/widget_base_class.py @@ -138,7 +138,7 @@ class CTkBaseClass(tkinter.Frame): self.draw() def update_dimensions_event(self, event): - # only redraw if dimensions changed (for performance) + # only redraw if dimensions changed (for performance), independent of scaling if round(self._current_width) != round(event.width / self._widget_scaling) or round(self._current_height) != round(event.height / self._widget_scaling): self._current_width = (event.width / self._widget_scaling) # adjust current size according to new size given by event self._current_height = (event.height / self._widget_scaling) # _current_width and _current_height are independent of the scale diff --git a/test/manual_integration_tests/test_optionmenu_combobox.py b/test/manual_integration_tests/test_optionmenu_combobox.py index 3c5d698..31bf6da 100644 --- a/test/manual_integration_tests/test_optionmenu_combobox.py +++ b/test/manual_integration_tests/test_optionmenu_combobox.py @@ -4,7 +4,7 @@ import customtkinter app = customtkinter.CTk() app.title('Test OptionMenu ComboBox.py') -app.geometry('400x300') +app.geometry('400x500') def select_callback(choice): @@ -33,4 +33,12 @@ combobox_tk.pack(pady=10, padx=10) combobox_1 = customtkinter.CTkComboBox(app, variable=None, values=countries, command=select_callback, width=300) combobox_1.pack(pady=20, padx=10) +def set_new_scaling(scaling): + customtkinter.set_spacing_scaling(scaling) + customtkinter.set_window_scaling(scaling) + customtkinter.set_widget_scaling(scaling) + +scaling_slider = customtkinter.CTkSlider(app, command=set_new_scaling, from_=0, to=2) +scaling_slider.pack(pady=20, padx=10) + app.mainloop() diff --git a/test/manual_integration_tests/test_textbox.py b/test/manual_integration_tests/test_textbox.py new file mode 100644 index 0000000..f48f6dc --- /dev/null +++ b/test/manual_integration_tests/test_textbox.py @@ -0,0 +1,11 @@ +import customtkinter + +app = customtkinter.CTk() +app.grid_rowconfigure(0, weight=1) +app.grid_columnconfigure(0, weight=1) + +textbox = customtkinter.CTkTextbox(app) +textbox.grid(row=0, column=0, padx=20, pady=20, sticky="nsew") + + +app.mainloop() From 16b9ce3c5fb446dd0119d2d25f51b723eb3b7eb7 Mon Sep 17 00:00:00 2001 From: Tom Schimansky Date: Tue, 28 Jun 2022 11:26:43 +0200 Subject: [PATCH 24/33] added .focus() for CTkEntry --- customtkinter/widgets/ctk_entry.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/customtkinter/widgets/ctk_entry.py b/customtkinter/widgets/ctk_entry.py index 00ed619..509c6cf 100644 --- a/customtkinter/widgets/ctk_entry.py +++ b/customtkinter/widgets/ctk_entry.py @@ -218,3 +218,9 @@ class CTkEntry(CTkBaseClass): return "" else: return self.entry.get() + + def focus(self): + self.entry.focus() + + def focus_force(self): + self.entry.focus_force() From 2a0ae06426f68f8f01157c089031fc16ed7f5574 Mon Sep 17 00:00:00 2001 From: Tom Schimansky Date: Tue, 28 Jun 2022 11:28:11 +0200 Subject: [PATCH 25/33] made CTkBaseClass public --- customtkinter/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/customtkinter/__init__.py b/customtkinter/__init__.py index 62253bd..acc7c12 100644 --- a/customtkinter/__init__.py +++ b/customtkinter/__init__.py @@ -49,6 +49,7 @@ if FontManager.load_font(os.path.join(script_directory, "assets", "fonts", "Cust DrawEngine.preferred_drawing_method = "circle_shapes" # import widgets +from .widgets.widget_base_class import CTkBaseClass from .widgets.ctk_button import CTkButton from .widgets.ctk_checkbox import CTkCheckBox from .widgets.ctk_entry import CTkEntry From f587109618f69d8cee65ab91ff657ed743898d16 Mon Sep 17 00:00:00 2001 From: Tom Schimansky Date: Tue, 28 Jun 2022 11:33:21 +0200 Subject: [PATCH 26/33] Bump to 4.5.1 --- customtkinter/__init__.py | 2 +- pyproject.toml | 2 +- setup.cfg | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/customtkinter/__init__.py b/customtkinter/__init__.py index acc7c12..4119deb 100644 --- a/customtkinter/__init__.py +++ b/customtkinter/__init__.py @@ -1,4 +1,4 @@ -__version__ = "4.5.0" +__version__ = "4.5.1" import os import sys diff --git a/pyproject.toml b/pyproject.toml index adb72b9..62a4df0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta" github_url = "https://github.com/TomSchimansky/CustomTkinter" [tool.tbump.version] -current = "4.5.0" +current = "4.5.1" # Example of a semver regexp. # Make sure this matches current_version before diff --git a/setup.cfg b/setup.cfg index 7e4b3a8..99fc76c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = customtkinter -version = 4.5.0 +version = 4.5.1 description = Create modern looking GUIs with Python long_description = CustomTkinter UI-Library\n\n[](https://github.com/TomSchimansky/CustomTkinter/blob/master/documentation_images/Windows_dark.png)\n\nMore Information: https://github.com/TomSchimansky/CustomTkinter long_description_content_type = text/markdown From c9653e7793baa12951ff9b85d672107e8e039e8c Mon Sep 17 00:00:00 2001 From: Tom Schimansky Date: Thu, 30 Jun 2022 15:53:32 +0200 Subject: [PATCH 27/33] fixed updating of bg_color with set_scaling in CTkBaseClass --- customtkinter/widgets/ctk_optionmenu.py | 5 +- customtkinter/widgets/ctk_switch.py | 2 +- customtkinter/widgets/ctk_textbox.py | 38 ++--- customtkinter/widgets/widget_base_class.py | 7 +- customtkinter/windows/ctk_input_dialog.py | 8 +- .../complex_example_new.py | 133 ++++++++++++++++++ test/manual_integration_tests/test_textbox.py | 49 ++++++- 7 files changed, 212 insertions(+), 30 deletions(-) create mode 100644 test/manual_integration_tests/complex_example_new.py diff --git a/customtkinter/widgets/ctk_optionmenu.py b/customtkinter/widgets/ctk_optionmenu.py index d6df6fb..6c5cdee 100644 --- a/customtkinter/widgets/ctk_optionmenu.py +++ b/customtkinter/widgets/ctk_optionmenu.py @@ -90,7 +90,10 @@ class CTkOptionMenu(CTkBaseClass): self.draw_engine = DrawEngine(self.canvas) left_section_width = self._current_width - self._current_height - self.text_label = tkinter.Label(master=self, font=self.apply_font_scaling(self.text_font), anchor="w") + self.text_label = tkinter.Label(master=self, + font=self.apply_font_scaling(self.text_font), + anchor="w", + text=self.current_value) self.text_label.grid(row=0, column=0, sticky="w", 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)))) diff --git a/customtkinter/widgets/ctk_switch.py b/customtkinter/widgets/ctk_switch.py index a7084d2..f8ec240 100644 --- a/customtkinter/widgets/ctk_switch.py +++ b/customtkinter/widgets/ctk_switch.py @@ -44,7 +44,7 @@ class CTkSwitch(CTkBaseClass): self.button_color = ThemeManager.theme["color"]["switch_button"] if button_color == "default_theme" else button_color self.button_hover_color = ThemeManager.theme["color"]["switch_button_hover"] if button_hover_color == "default_theme" else button_hover_color self.text_color = ThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color - self.text_color_disabled = ThemeManager.theme["color"]["text_button_disabled"] if text_color_disabled == "default_theme" else text_color_disabled + self.text_color_disabled = ThemeManager.theme["color"]["text_disabled"] if text_color_disabled == "default_theme" else text_color_disabled # text self.text = text diff --git a/customtkinter/widgets/ctk_textbox.py b/customtkinter/widgets/ctk_textbox.py index 96e3410..43c26c9 100644 --- a/customtkinter/widgets/ctk_textbox.py +++ b/customtkinter/widgets/ctk_textbox.py @@ -20,7 +20,7 @@ class CTkTextbox(CTkBaseClass): **kwargs): # transfer basic functionality (bg_color, size, _appearance_mode, scaling) to CTkBaseClass - super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs) + super().__init__(*args, bg_color=bg_color, width=width, height=height) # color self.fg_color = ThemeManager.theme["color"]["entry"] if fg_color == "default_theme" else fg_color @@ -45,6 +45,8 @@ class CTkTextbox(CTkBaseClass): self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode)) self.draw_engine = DrawEngine(self.canvas) + for arg in ["highlightthickness", "fg", "bg"]: + kwargs.pop(arg, None) self.textbox = tkinter.Text(self, fg=ThemeManager.single_color(self.text_color, self._appearance_mode), width=0, @@ -108,13 +110,21 @@ class CTkTextbox(CTkBaseClass): self.canvas.tag_lower("inner_parts") self.canvas.tag_lower("border_parts") + def yview(self, args, kwargs): + self.textbox.yview(args, kwargs) + + def xview(self, args, kwargs): + self.textbox.xview(args, kwargs) + + def insert(self, args, kwargs): + self.textbox.insert(args, kwargs) + def configure(self, *args, **kwargs): require_redraw = False # some attribute changes require a call of self.draw() at the end if "fg_color" in kwargs: - self.fg_color = kwargs["fg_color"] + self.fg_color = kwargs.pop("fg_color") require_redraw = True - del kwargs["fg_color"] # check if CTk widgets are children of the frame and change their bg_color to new frame fg_color for child in self.winfo_children(): @@ -122,36 +132,30 @@ class CTkTextbox(CTkBaseClass): child.configure(bg_color=self.fg_color) if "bg_color" in kwargs: - if kwargs["bg_color"] is None: + new_bg_color = kwargs.pop("bg_color") + if new_bg_color is None: self.bg_color = self.detect_color_of_master() else: - self.bg_color = kwargs["bg_color"] + self.bg_color = new_bg_color require_redraw = True - del kwargs["bg_color"] - if "border_color" in kwargs: - self.border_color = kwargs["border_color"] + self.border_color = kwargs.pop("border_color") require_redraw = True - del kwargs["border_color"] if "corner_radius" in kwargs: - self.corner_radius = kwargs["corner_radius"] + self.corner_radius = kwargs.pop("corner_radius") require_redraw = True - del kwargs["corner_radius"] if "border_width" in kwargs: - self.border_width = kwargs["border_width"] + self.border_width = kwargs.pop("border_width") require_redraw = True - del kwargs["border_width"] if "width" in kwargs: - self.set_dimensions(width=kwargs["width"]) - del kwargs["width"] + self.set_dimensions(width=kwargs.pop("width")) if "height" in kwargs: - self.set_dimensions(height=kwargs["height"]) - del kwargs["height"] + self.set_dimensions(height=kwargs.pop("height")) self.textbox.configure(*args, **kwargs) diff --git a/customtkinter/widgets/widget_base_class.py b/customtkinter/widgets/widget_base_class.py index 29a5edb..ff57c81 100644 --- a/customtkinter/widgets/widget_base_class.py +++ b/customtkinter/widgets/widget_base_class.py @@ -151,7 +151,7 @@ class CTkBaseClass(tkinter.Frame): if master_widget is None: master_widget = self.master - if isinstance(master_widget, CTkBaseClass) and hasattr(master_widget, "fg_color"): # master is CTkFrame + if isinstance(master_widget, (CTkBaseClass, CTk, CTkToplevel)) and hasattr(master_widget, "fg_color"): if master_widget.fg_color is not None: return master_widget.fg_color @@ -178,11 +178,6 @@ class CTkBaseClass(tkinter.Frame): elif mode_string.lower() == "light": self._appearance_mode = 0 - if isinstance(self.master, (CTkBaseClass, CTk)) and hasattr(self.master, "fg_color"): - self.bg_color = self.master.fg_color - else: - self.bg_color = self.master.cget("bg") - self.draw() def set_scaling(self, new_widget_scaling, new_spacing_scaling, new_window_scaling): diff --git a/customtkinter/windows/ctk_input_dialog.py b/customtkinter/windows/ctk_input_dialog.py index bf38ad3..0e3d198 100644 --- a/customtkinter/windows/ctk_input_dialog.py +++ b/customtkinter/windows/ctk_input_dialog.py @@ -102,8 +102,12 @@ class CTkInputDialog: self.running = True while self.running: - self.top.update() - time.sleep(0.01) + try: + self.top.update() + except Exception: + return self.user_input + finally: + time.sleep(0.01) time.sleep(0.05) self.top.destroy() diff --git a/test/manual_integration_tests/complex_example_new.py b/test/manual_integration_tests/complex_example_new.py new file mode 100644 index 0000000..08167ea --- /dev/null +++ b/test/manual_integration_tests/complex_example_new.py @@ -0,0 +1,133 @@ +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): + + def __init__(self): + super().__init__() + + self.title("CustomTkinter complex_example.py") + self.geometry(f"{920}x{500}") + self.protocol("WM_DELETE_WINDOW", self.on_closing) # call .on_closing() when app gets closed + + # configure grid layout (4x4) + self.grid_columnconfigure(1, weight=1) + self.grid_columnconfigure((2, 3), weight=0, minsize=200) + self.grid_rowconfigure((0, 1, 2), weight=1) + + # create sidebar frame and widgets + self.sidebar_frame = customtkinter.CTkFrame(self, width=140) + self.sidebar_frame.grid(row=0, column=0, rowspan=4, sticky="nsew") + self.sidebar_frame.grid_rowconfigure(4, weight=1) + self.logo_label = customtkinter.CTkLabel(self.sidebar_frame, text="CustomTkinter", text_font=("Roboto", -16)) + self.logo_label.grid(row=0, column=0, padx=20, pady=(20, 10)) + self.sidebar_button_1 = customtkinter.CTkButton(self.sidebar_frame, command=self.sidebar_button_callback) + self.sidebar_button_1.grid(row=1, column=0, padx=20, pady=10) + self.sidebar_button_2 = customtkinter.CTkButton(self.sidebar_frame, command=self.sidebar_button_callback) + self.sidebar_button_2.grid(row=2, column=0, padx=20, pady=10) + self.sidebar_button_3 = customtkinter.CTkButton(self.sidebar_frame, command=self.sidebar_button_callback) + self.sidebar_button_3.grid(row=3, column=0, padx=20, pady=10) + self.appearance_mode_label = customtkinter.CTkLabel(self.sidebar_frame, text="Appearance Mode:") + self.appearance_mode_label.grid(row=5, column=0, padx=20, pady=(10, 0)) + self.appearance_mode_optionemenu = customtkinter.CTkOptionMenu(self.sidebar_frame, values=["Light", "Dark", "System"], + command=self.change_appearance_mode) + self.appearance_mode_optionemenu.grid(row=6, column=0, padx=20, pady=(10, 20)) + + # create main entry and button + self.entry = customtkinter.CTkEntry(self, placeholder_text="CTkEntry") + self.entry.grid(row=3, column=1, columnspan=2, padx=(20, 10), pady=(10, 20), sticky="nsew") + + self.main_button_1 = customtkinter.CTkButton(self, fg_color=None, border_width=2) + self.main_button_1.grid(row=3, column=3, padx=(10, 20), pady=(10, 20), sticky="nsew") + + self.text_frame = customtkinter.CTkFrame(self) + self.text_frame.grid(row=0, column=1, padx=(20, 10), pady=(20, 10), sticky="nsew") + + # create radiobutton frame + self.radiobutton_frame = customtkinter.CTkFrame(self) + self.radiobutton_frame.grid(row=0, column=3, padx=(10, 20), pady=(20, 10), sticky="nsew") + self.radio_var = tkinter.IntVar(value=0) + self.label_radio_group = customtkinter.CTkLabel(master=self.radiobutton_frame, text="CTkRadioButton Group:") + self.label_radio_group.grid(row=0, column=2, columnspan=1, padx=10, pady=10, sticky="") + self.radio_button_1 = customtkinter.CTkRadioButton(master=self.radiobutton_frame, 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.radiobutton_frame, 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.radiobutton_frame, variable=self.radio_var, value=2) + self.radio_button_3.grid(row=3, column=2, pady=10, padx=20, sticky="n") + + # create optionemnu and combobox frame + self.optionemnu_combobox_frame = customtkinter.CTkFrame(self) + self.optionemnu_combobox_frame.grid(row=0, column=2, padx=(10, 10), pady=(20, 10), sticky="nsew") + self.optionmenu_1 = customtkinter.CTkOptionMenu(self.optionemnu_combobox_frame, + dynamic_resizing=False, + values=["Value 1", "Value 2", "Value Long Long Long"]) + self.optionmenu_1.grid(row=0, column=0, padx=20, pady=(20, 10), sticky="ew") + self.combobox_1 = customtkinter.CTkComboBox(self.optionemnu_combobox_frame, + values=["Value 1", "Value 2", "Value Long....."]) + self.combobox_1.grid(row=1, column=0, padx=20, pady=(10, 10), sticky="ew") + self.string_input_button = customtkinter.CTkButton(self.optionemnu_combobox_frame, text="Open CTkInputDialog", + command=self.open_input_dialog) + self.string_input_button.grid(row=2, column=0, padx=20, pady=(10, 10), sticky="ew") + + # create checkbox and switch frame + self.checkbox_slider_frame = customtkinter.CTkFrame(self) + self.checkbox_slider_frame.grid(row=1, column=3, padx=(10, 20), pady=(10, 10), sticky="nsew") + self.checkbox_1 = customtkinter.CTkCheckBox(master=self.checkbox_slider_frame) + self.checkbox_1.grid(row=1, column=0, pady=(20, 10), padx=20, sticky="n") + self.checkbox_2 = customtkinter.CTkCheckBox(master=self.checkbox_slider_frame) + self.checkbox_2.grid(row=2, column=0, pady=10, padx=20, sticky="n") + self.switch_1 = customtkinter.CTkSwitch(master=self.checkbox_slider_frame) + self.switch_1.grid(row=3, column=0, pady=10, padx=20, sticky="n") + self.switch_2 = customtkinter.CTkSwitch(master=self.checkbox_slider_frame) + self.switch_2.grid(row=4, column=0, pady=(10, 20), padx=20, sticky="n") + + # create slider and progressbar frame + self.slider_progressbar_frame = customtkinter.CTkFrame(self, fg_color=None) + self.slider_progressbar_frame.grid(row=1, column=1, columnspan=2, padx=(20, 10), pady=(10, 10), sticky="nsew") + self.slider_progressbar_frame.grid_columnconfigure(0, weight=1) + self.slider_progressbar_frame.grid_rowconfigure(3, weight=1) + self.progressbar_1 = customtkinter.CTkProgressBar(self.slider_progressbar_frame) + self.progressbar_1.grid(row=0, column=0, padx=(20, 10), pady=(10, 10), sticky="ew") + self.slider_1 = customtkinter.CTkSlider(self.slider_progressbar_frame) + self.slider_1.grid(row=1, column=0, padx=(20, 10), pady=(10, 10), sticky="ew") + self.slider_2 = customtkinter.CTkSlider(self.slider_progressbar_frame, from_=0, to=4, number_of_steps=4) + self.slider_2.grid(row=2, column=0, padx=(20, 10), pady=(10, 10), sticky="ew") + self.slider_3 = customtkinter.CTkSlider(self.slider_progressbar_frame, orient="vertical") + self.slider_3.grid(row=0, column=1, rowspan=4, padx=(10, 10), pady=(10, 10), sticky="ns") + self.progressbar_2 = customtkinter.CTkProgressBar(self.slider_progressbar_frame, orient="vertical") + self.progressbar_2.grid(row=0, column=2, rowspan=4, padx=(10, 20), pady=(10, 10), sticky="ns") + + # set default values + self.sidebar_button_3.configure(state="disabled", text="Disabled CTkButton") + self.checkbox_2.configure(state="disabled") + self.switch_2.configure(state="disabled") + self.checkbox_1.select() + self.switch_1.select() + self.radio_button_3.configure(state="disabled") + self.appearance_mode_optionemenu.set("Dark") + self.optionmenu_1.set("CTkOptionmenu") + self.combobox_1.set("CTkComboBox") + + def open_input_dialog(self): + dialog = customtkinter.CTkInputDialog(master=None, text="Type in a number:", title="CTkInputDialog") + print("CTkInputDialog:", dialog.get_input()) + + def change_appearance_mode(self, new_appearance_mode): + customtkinter.set_appearance_mode(new_appearance_mode) + + def sidebar_button_callback(self): + print("sidebar_button click") + + def on_closing(self, event=0): + self.destroy() + + +if __name__ == "__main__": + app = App() + app.mainloop() diff --git a/test/manual_integration_tests/test_textbox.py b/test/manual_integration_tests/test_textbox.py index f48f6dc..f9a5bd0 100644 --- a/test/manual_integration_tests/test_textbox.py +++ b/test/manual_integration_tests/test_textbox.py @@ -1,11 +1,54 @@ +import tkinter import customtkinter +# test with scaling +# customtkinter.set_widget_scaling(2) +# customtkinter.set_window_scaling(2) +# customtkinter.set_spacing_scaling(2) + +customtkinter.set_appearance_mode("dark") + app = customtkinter.CTk() +app.title("test_scrollbar.py") app.grid_rowconfigure(0, weight=1) -app.grid_columnconfigure(0, weight=1) +app.grid_columnconfigure((0, 2), weight=1) -textbox = customtkinter.CTkTextbox(app) -textbox.grid(row=0, column=0, padx=20, pady=20, sticky="nsew") +tk_textbox = customtkinter.CTkTextbox(app, highlightthickness=0, padx=5, pady=5) +tk_textbox.grid(row=0, column=0, sticky="nsew") +ctk_textbox_scrollbar = customtkinter.CTkScrollbar(app, command=tk_textbox.yview) +ctk_textbox_scrollbar.grid(row=0, column=1, padx=0, sticky="ns") +tk_textbox.configure(yscrollcommand=ctk_textbox_scrollbar.set) +frame_1 = customtkinter.CTkFrame(app) +frame_1.grid(row=0, column=2, padx=10, pady=10, sticky="nsew") +frame_1.grid_rowconfigure((0, 1), weight=1) +frame_1.grid_columnconfigure((0, ), weight=1) +tk_textbox_1 = customtkinter.CTkTextbox(frame_1, highlightthickness=0, padx=5, pady=5) +tk_textbox_1.grid(row=0, column=0, sticky="nsew", padx=(5, 0), pady=5) +ctk_textbox_scrollbar_1 = customtkinter.CTkScrollbar(frame_1, command=tk_textbox_1.yview) +ctk_textbox_scrollbar_1.grid(row=0, column=1, sticky="ns", padx=(0, 5), pady=5) +tk_textbox_1.configure(yscrollcommand=ctk_textbox_scrollbar_1.set) +ctk_textbox_scrollbar_1.configure(scrollbar_color="red", scrollbar_hover_color="darkred", + border_spacing=0, width=12, fg_color="green", corner_radius=4) + +frame_2 = customtkinter.CTkFrame(frame_1) +frame_2.grid(row=1, column=0, columnspan=2, padx=20, pady=20, sticky="nsew") +frame_2.grid_rowconfigure((0, ), weight=1) +frame_2.grid_columnconfigure((0, ), weight=1) +tk_textbox_2 = customtkinter.CTkTextbox(frame_2, highlightthickness=0, padx=5, pady=5, wrap="none") +tk_textbox_2.grid(row=0, column=0, sticky="nsew", padx=(5, 0), pady=5) +ctk_textbox_scrollbar_2 = customtkinter.CTkScrollbar(frame_2, command=tk_textbox_2.yview) +ctk_textbox_scrollbar_2.grid(row=0, column=1, sticky="ns", padx=(0, 5), pady=5) +ctk_textbox_scrollbar_2_horizontal = customtkinter.CTkScrollbar(frame_2, command=tk_textbox_2.xview, orientation="horizontal") +ctk_textbox_scrollbar_2_horizontal.grid(row=1, column=0, sticky="ew", padx=(5, 0), pady=(0, 5)) +tk_textbox_2.configure(yscrollcommand=ctk_textbox_scrollbar_2.set, xscrollcommand=ctk_textbox_scrollbar_2_horizontal.set) + +tk_textbox.configure(font=(customtkinter.ThemeManager.theme["text"]["font"], customtkinter.ThemeManager.theme["text"]["size"])) +tk_textbox_1.configure(font=(customtkinter.ThemeManager.theme["text"]["font"], customtkinter.ThemeManager.theme["text"]["size"])) +tk_textbox_2.configure(font=(customtkinter.ThemeManager.theme["text"]["font"], customtkinter.ThemeManager.theme["text"]["size"])) + +tk_textbox.insert("insert", "\n".join([str(i) for i in range(100)])) +tk_textbox_1.insert("insert", "\n".join([str(i) for i in range(1000)])) +tk_textbox_2.insert("insert", "\n".join([str(i) + " - "*30 for i in range(10000)])) app.mainloop() From 1f030f04f949304b5b7c04dab4cc35991881d4ea Mon Sep 17 00:00:00 2001 From: Tom Schimansky Date: Fri, 1 Jul 2022 21:30:31 +0200 Subject: [PATCH 28/33] fixed placeholder_text bug for CTkEntry --- customtkinter/widgets/ctk_entry.py | 100 +++++++++++++++++------------ 1 file changed, 60 insertions(+), 40 deletions(-) diff --git a/customtkinter/widgets/ctk_entry.py b/customtkinter/widgets/ctk_entry.py index 509c6cf..7038d64 100644 --- a/customtkinter/widgets/ctk_entry.py +++ b/customtkinter/widgets/ctk_entry.py @@ -66,11 +66,14 @@ class CTkEntry(CTkBaseClass): pady=(self.apply_widget_scaling(self.border_width), self.apply_widget_scaling(self.border_width + 1))) super().bind('', self.update_dimensions_event) - self.entry.bind('', self.set_placeholder) - self.entry.bind('', self.clear_placeholder) + self.entry.bind('', self.entry_focus_out) + self.entry.bind('', self.entry_focus_in) self.draw() - self.set_placeholder() + + if self.placeholder_text is not None: + self.placeholder_text_active = True + self.set_placeholder() def set_scaling(self, *args, **kwargs): super().set_scaling( *args, **kwargs) @@ -89,23 +92,6 @@ class CTkEntry(CTkBaseClass): height=self.apply_widget_scaling(self._desired_height)) self.draw() - def set_placeholder(self, event=None): - if self.placeholder_text is not None: - if not self.placeholder_text_active and self.entry.get() == "": - self.placeholder_text_active = True - self.pre_placeholder_arguments = {"show": self.entry.cget("show")} - self.entry.config(fg=ThemeManager.single_color(self.placeholder_text_color, self._appearance_mode), show="") - self.entry.delete(0, tkinter.END) - self.entry.insert(0, self.placeholder_text) - - def clear_placeholder(self, event=None): - if self.placeholder_text_active: - self.placeholder_text_active = False - self.entry.config(fg=ThemeManager.single_color(self.text_color, self._appearance_mode)) - self.entry.delete(0, tkinter.END) - for argument, value in self.pre_placeholder_arguments.items(): - self.entry[argument] = value - def draw(self, no_color_updates=False): self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode)) @@ -150,35 +136,31 @@ class CTkEntry(CTkBaseClass): require_redraw = False # some attribute changes require a call of self.draw() at the end if "state" in kwargs: - self.state = kwargs["state"] + self.state = kwargs.pop("state") self.entry.configure(state=self.state) - del kwargs["state"] if "bg_color" in kwargs: - if kwargs["bg_color"] is None: + new_bg_color = kwargs.pop("bg_color") + if new_bg_color is None: self.bg_color = self.detect_color_of_master() else: - self.bg_color = kwargs["bg_color"] + self.bg_color = new_bg_color require_redraw = True - del kwargs["bg_color"] if "fg_color" in kwargs: - self.fg_color = kwargs["fg_color"] - del kwargs["fg_color"] + self.fg_color = kwargs.pop("fg_color") require_redraw = True if "text_color" in kwargs: - self.text_color = kwargs["text_color"] - del kwargs["text_color"] + self.text_color = kwargs.pop("text_color") require_redraw = True if "border_color" in kwargs: - self.border_color = kwargs["border_color"] - del kwargs["border_color"] + self.border_color = kwargs.pop("border_color") require_redraw = True if "corner_radius" in kwargs: - self.corner_radius = kwargs["corner_radius"] + self.corner_radius = kwargs.pop("corner_radius") if self.corner_radius * 2 > self._current_height: self.corner_radius = self._current_height / 2 @@ -186,31 +168,69 @@ class CTkEntry(CTkBaseClass): self.corner_radius = self._current_width / 2 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)) - del kwargs["corner_radius"] require_redraw = True if "width" in kwargs: - self.set_dimensions(width=kwargs["width"]) - del kwargs["width"] + self.set_dimensions(width=kwargs.pop("width")) if "height" in kwargs: - self.set_dimensions(height=kwargs["height"]) - del kwargs["height"] + self.set_dimensions(height=kwargs.pop("height")) if "placeholder_text" in kwargs: - pass + self.placeholder_text = kwargs.pop("placeholder_text") + if self.placeholder_text_active: + self.entry.delete(0, tkinter.END) + self.entry.insert(0, self.placeholder_text) + + if "placeholder_text_color" in kwargs: + self.placeholder_text_color = kwargs.pop("placeholder_text_color") + require_redraw = True + + if "show" in kwargs: + if self.placeholder_text_active: + self.pre_placeholder_arguments["show"] = kwargs.pop("show") + else: + self.entry.configure(show=kwargs.pop("show")) self.entry.configure(*args, **kwargs) if require_redraw is True: self.draw() + def set_placeholder(self): + self.pre_placeholder_arguments = {"show": self.entry.cget("show")} + self.entry.config(fg=ThemeManager.single_color(self.placeholder_text_color, self._appearance_mode), show="") + self.entry.delete(0, tkinter.END) + self.entry.insert(0, self.placeholder_text) + + def clear_placeholder(self): + self.entry.config(fg=ThemeManager.single_color(self.text_color, self._appearance_mode)) + self.entry.delete(0, tkinter.END) + for argument, value in self.pre_placeholder_arguments.items(): + self.entry[argument] = value + + def entry_focus_out(self, event=None): + if self.entry.get() == "": + self.placeholder_text_active = True + self.set_placeholder() + + def entry_focus_in(self, event=None): + if self.placeholder_text_active: + self.placeholder_text_active = False + self.clear_placeholder() + def delete(self, *args, **kwargs): self.entry.delete(*args, **kwargs) - self.set_placeholder() + + if self.entry.get() == "": + self.placeholder_text_active = True + self.set_placeholder() def insert(self, *args, **kwargs): - self.clear_placeholder() + if self.placeholder_text_active: + self.placeholder_text_active = False + self.clear_placeholder() + return self.entry.insert(*args, **kwargs) def get(self): From b30692d1af67c983804ebdd304654424d8eaa11c Mon Sep 17 00:00:00 2001 From: Tom Schimansky Date: Fri, 1 Jul 2022 21:39:19 +0200 Subject: [PATCH 29/33] Bump to 4.5.2 --- customtkinter/__init__.py | 2 +- pyproject.toml | 2 +- setup.cfg | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/customtkinter/__init__.py b/customtkinter/__init__.py index 4119deb..bb3abf6 100644 --- a/customtkinter/__init__.py +++ b/customtkinter/__init__.py @@ -1,4 +1,4 @@ -__version__ = "4.5.1" +__version__ = "4.5.2" import os import sys diff --git a/pyproject.toml b/pyproject.toml index 62a4df0..6a76993 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta" github_url = "https://github.com/TomSchimansky/CustomTkinter" [tool.tbump.version] -current = "4.5.1" +current = "4.5.2" # Example of a semver regexp. # Make sure this matches current_version before diff --git a/setup.cfg b/setup.cfg index 99fc76c..bf47f64 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = customtkinter -version = 4.5.1 +version = 4.5.2 description = Create modern looking GUIs with Python long_description = CustomTkinter UI-Library\n\n[](https://github.com/TomSchimansky/CustomTkinter/blob/master/documentation_images/Windows_dark.png)\n\nMore Information: https://github.com/TomSchimansky/CustomTkinter long_description_content_type = text/markdown From bb6678ae15793d119b20c0d72eb95e3c50dd59ca Mon Sep 17 00:00:00 2001 From: Tom Schimansky Date: Fri, 1 Jul 2022 22:09:45 +0200 Subject: [PATCH 30/33] added support for anchor attribute in CTkLabel --- customtkinter/widgets/ctk_label.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/customtkinter/widgets/ctk_label.py b/customtkinter/widgets/ctk_label.py index fed2a48..31451fa 100644 --- a/customtkinter/widgets/ctk_label.py +++ b/customtkinter/widgets/ctk_label.py @@ -16,6 +16,7 @@ class CTkLabel(CTkBaseClass): height=28, text="CTkLabel", text_font="default_theme", + anchor="center", # label anchor: center, n, e, s, w **kwargs): # transfer basic functionality (bg_color, size, _appearance_mode, scaling) to CTkBaseClass @@ -35,6 +36,7 @@ class CTkLabel(CTkBaseClass): self.corner_radius = ThemeManager.theme["shape"]["label_corner_radius"] if corner_radius == "default_theme" else corner_radius # text + self.anchor = anchor self.text = text self.text_font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font @@ -52,10 +54,13 @@ class CTkLabel(CTkBaseClass): self.text_label = tkinter.Label(master=self, highlightthickness=0, bd=0, + anchor=self.anchor, text=self.text, font=self.apply_font_scaling(self.text_font), **kwargs) - self.text_label.grid(row=0, column=0, padx=self.apply_widget_scaling(self.corner_radius)) + text_label_grid_sticky = self.anchor if self.anchor != "center" else "" + self.text_label.grid(row=0, column=0, padx=self.apply_widget_scaling(self.corner_radius), + sticky=text_label_grid_sticky) self.bind('', self.update_dimensions_event) self.draw() @@ -65,7 +70,9 @@ class CTkLabel(CTkBaseClass): self.canvas.configure(width=self.apply_widget_scaling(self._desired_width), height=self.apply_widget_scaling(self._desired_height)) self.text_label.configure(font=self.apply_font_scaling(self.text_font)) - self.text_label.grid(row=0, column=0, padx=self.apply_widget_scaling(self.corner_radius)) + text_label_grid_sticky = self.anchor if self.anchor != "center" else "" + self.text_label.grid(row=0, column=0, padx=self.apply_widget_scaling(self.corner_radius), + sticky=text_label_grid_sticky) self.draw() @@ -103,6 +110,12 @@ class CTkLabel(CTkBaseClass): def configure(self, *args, **kwargs): require_redraw = False # some attribute changes require a call of self.draw() at the end + if "anchor" in kwargs: + self.anchor = kwargs.pop("anchor") + text_label_grid_sticky = self.anchor if self.anchor != "center" else "" + self.text_label.grid(row=0, column=0, padx=self.apply_widget_scaling(self.corner_radius), + sticky=text_label_grid_sticky) + if "text" in kwargs: self.set_text(kwargs["text"]) del kwargs["text"] From a3fb12f7cf1e293b3adf7997bb5480322c8f5112 Mon Sep 17 00:00:00 2001 From: Tom Schimansky Date: Sat, 2 Jul 2022 00:54:21 +0200 Subject: [PATCH 31/33] Fixed textvariable support for CTkCheckBox, CTkSwitch, CTkRadiobutton --- customtkinter/widgets/ctk_checkbox.py | 48 ++++----- customtkinter/widgets/ctk_radiobutton.py | 80 ++++++--------- customtkinter/widgets/ctk_slider.py | 3 - customtkinter/widgets/ctk_switch.py | 98 ++++++++----------- .../test_variables.py | 11 ++- 5 files changed, 100 insertions(+), 140 deletions(-) diff --git a/customtkinter/widgets/ctk_checkbox.py b/customtkinter/widgets/ctk_checkbox.py index 5471a3a..e4ed8a2 100644 --- a/customtkinter/widgets/ctk_checkbox.py +++ b/customtkinter/widgets/ctk_checkbox.py @@ -90,6 +90,19 @@ class CTkCheckBox(CTkBaseClass): self.canvas.bind("", self.on_leave) self.canvas.bind("", self.toggle) + self.text_label = tkinter.Label(master=self, + bd=0, + text=self.text, + justify=tkinter.LEFT, + font=self.apply_font_scaling(self.text_font), + textvariable=self.textvariable) + self.text_label.grid(row=0, column=2, padx=0, pady=0, sticky="w") + self.text_label["anchor"] = "w" + + self.text_label.bind("", self.on_enter) + self.text_label.bind("", self.on_leave) + self.text_label.bind("", self.toggle) + # set select state according to variable if self.variable is not None: self.variable_callback_name = self.variable.trace_add("write", self.variable_callback) @@ -153,32 +166,18 @@ class CTkCheckBox(CTkBaseClass): outline=ThemeManager.single_color(self.border_color, self._appearance_mode), fill=ThemeManager.single_color(self.border_color, self._appearance_mode)) - if self.text_label is None: - self.text_label = tkinter.Label(master=self, - bd=0, - text=self.text, - justify=tkinter.LEFT, - font=self.apply_font_scaling(self.text_font)) - self.text_label.grid(row=0, column=2, padx=0, pady=0, sticky="w") - self.text_label["anchor"] = "w" - - self.text_label.bind("", self.on_enter) - self.text_label.bind("", self.on_leave) - self.text_label.bind("", self.toggle) - if self.state == tkinter.DISABLED: self.text_label.configure(fg=(ThemeManager.single_color(self.text_color_disabled, self._appearance_mode))) else: self.text_label.configure(fg=ThemeManager.single_color(self.text_color, self._appearance_mode)) self.text_label.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode)) - self.set_text(self.text) - def configure(self, *args, **kwargs): require_redraw = False # some attribute changes require a call of self.draw() if "text" in kwargs: - self.set_text(kwargs["text"]) + self.text = kwargs["text"] + self.text_label.configure(text=self.text) del kwargs["text"] if "state" in kwargs: @@ -219,6 +218,11 @@ class CTkCheckBox(CTkBaseClass): self.function = kwargs["command"] del kwargs["command"] + if "textvariable" in kwargs: + self.textvariable = kwargs["textvariable"] + self.text_label.configure(textvariable=self.textvariable) + del kwargs["textvariable"] + if "variable" in kwargs: if self.variable is not None: self.variable.trace_remove("write", self.variable_callback_name) @@ -263,13 +267,6 @@ class CTkCheckBox(CTkBaseClass): if self.text_label is not None: self.text_label.configure(cursor="hand2") - def set_text(self, text): - self.text = text - if self.text_label is not None: - self.text_label.configure(text=self.text) - else: - sys.stderr.write("ERROR (CTkButton): Cant change text because checkbox has no text.") - def on_enter(self, event=0): if self.hover is True and self.state == tkinter.NORMAL: if self.check_state is True: @@ -347,10 +344,7 @@ class CTkCheckBox(CTkBaseClass): self.variable_callback_blocked = False if self.function is not None: - try: - self.function() - except: - pass + self.function() def get(self): return self.onvalue if self.check_state is True else self.offvalue diff --git a/customtkinter/widgets/ctk_radiobutton.py b/customtkinter/widgets/ctk_radiobutton.py index 779d9b8..1640551 100644 --- a/customtkinter/widgets/ctk_radiobutton.py +++ b/customtkinter/widgets/ctk_radiobutton.py @@ -1,6 +1,6 @@ import tkinter import sys -from typing import Callable, Union +from typing import Union from .ctk_canvas import CTkCanvas from ..theme_manager import ThemeManager @@ -86,6 +86,19 @@ class CTkRadioButton(CTkBaseClass): self.canvas.bind("", self.on_leave) self.canvas.bind("", self.invoke) + self.text_label = tkinter.Label(master=self, + bd=0, + text=self.text, + justify=tkinter.LEFT, + font=self.apply_font_scaling(self.text_font), + textvariable=self.textvariable) + self.text_label.grid(row=0, column=2, padx=0, pady=0, sticky="w") + self.text_label["anchor"] = "w" + + self.text_label.bind("", self.on_enter) + self.text_label.bind("", self.on_leave) + self.text_label.bind("", self.invoke) + self.draw() # initial draw self.set_cursor() @@ -134,19 +147,6 @@ class CTkRadioButton(CTkBaseClass): outline=ThemeManager.single_color(self.bg_color, self._appearance_mode), fill=ThemeManager.single_color(self.bg_color, self._appearance_mode)) - if self.text_label is None: - self.text_label = tkinter.Label(master=self, - bd=0, - text=self.text, - justify=tkinter.LEFT, - font=self.apply_font_scaling(self.text_font)) - self.text_label.grid(row=0, column=2, padx=0, pady=0, sticky="w") - self.text_label["anchor"] = "w" - - self.text_label.bind("", self.on_enter) - self.text_label.bind("", self.on_leave) - self.text_label.bind("", self.invoke) - if self.state == tkinter.DISABLED: self.text_label.configure(fg=ThemeManager.single_color(self.text_color_disabled, self._appearance_mode)) else: @@ -154,63 +154,58 @@ class CTkRadioButton(CTkBaseClass): self.text_label.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode)) - self.set_text(self.text) - def configure(self, *args, **kwargs): require_redraw = False # some attribute changes require a call of self.draw() if "text" in kwargs: - self.set_text(kwargs["text"]) - del kwargs["text"] + self.text = kwargs.pop("text") + self.text_label.configure(text=self.text) if "state" in kwargs: - self.state = kwargs["state"] + self.state = kwargs.pop("state") self.set_cursor() require_redraw = True - del kwargs["state"] if "fg_color" in kwargs: - self.fg_color = kwargs["fg_color"] + self.fg_color = kwargs.pop("fg_color") require_redraw = True - del kwargs["fg_color"] if "bg_color" in kwargs: - if kwargs["bg_color"] is None: + new_bg_color = kwargs.pop("bg_color") + if new_bg_color is None: self.bg_color = self.detect_color_of_master() else: - self.bg_color = kwargs["bg_color"] + self.bg_color = new_bg_color require_redraw = True - del kwargs["bg_color"] if "hover_color" in kwargs: - self.hover_color = kwargs["hover_color"] + self.hover_color = kwargs.pop("hover_color") require_redraw = True - del kwargs["hover_color"] if "text_color" in kwargs: - self.text_color = kwargs["text_color"] + self.text_color = kwargs.pop("text_color") require_redraw = True - del kwargs["text_color"] if "border_color" in kwargs: - self.border_color = kwargs["border_color"] + self.border_color = kwargs.pop("border_color") require_redraw = True - del kwargs["border_color"] if "border_width" in kwargs: - self.border_width = kwargs["border_width"] + self.border_width = kwargs.pop("border_width") require_redraw = True - del kwargs["border_width"] if "command" in kwargs: - self.function = kwargs["command"] - del kwargs["command"] + self.function = kwargs.pop("command") + + if "textvariable" in kwargs: + self.textvariable = kwargs.pop("textvariable") + self.text_label.configure(textvariable=self.textvariable) if "variable" in kwargs: if self.variable is not None: self.variable.trace_remove("write", self.variable_callback_name) - self.variable = kwargs["variable"] + self.variable = kwargs.pop("variable") if self.variable is not None and self.variable != "": self.variable_callback_name = self.variable.trace_add("write", self.variable_callback) @@ -221,8 +216,6 @@ class CTkRadioButton(CTkBaseClass): else: self.variable = None - del kwargs["variable"] - super().configure(*args, **kwargs) if require_redraw: @@ -250,13 +243,6 @@ class CTkRadioButton(CTkBaseClass): if self.text_label is not None: self.text_label.configure(cursor="hand2") - def set_text(self, text): - self.text = text - if self.text_label is not None: - self.text_label.configure(text=self.text) - else: - sys.stderr.write("ERROR (CTkButton): Cant change text because radiobutton has no text.") - def on_enter(self, event=0): if self.hover is True and self.state == tkinter.NORMAL: self.canvas.itemconfig("border_parts", @@ -288,10 +274,8 @@ class CTkRadioButton(CTkBaseClass): self.select() if self.function is not None: - try: + if self.function is not None: self.function() - except: - pass def select(self, from_variable_callback=False): self.check_state = True diff --git a/customtkinter/widgets/ctk_slider.py b/customtkinter/widgets/ctk_slider.py index 94889d3..91e9786 100644 --- a/customtkinter/widgets/ctk_slider.py +++ b/customtkinter/widgets/ctk_slider.py @@ -249,9 +249,6 @@ class CTkSlider(CTkBaseClass): self.draw(no_color_updates=False) - # if self.callback_function is not None and not from_variable_callback: - # self.callback_function(self.output_value) - if self.variable is not None and not from_variable_callback: self.variable_callback_blocked = True self.variable.set(round(self.output_value) if isinstance(self.variable, tkinter.IntVar) else self.output_value) diff --git a/customtkinter/widgets/ctk_switch.py b/customtkinter/widgets/ctk_switch.py index f8ec240..7a909be 100644 --- a/customtkinter/widgets/ctk_switch.py +++ b/customtkinter/widgets/ctk_switch.py @@ -62,11 +62,8 @@ class CTkSwitch(CTkBaseClass): self.onvalue = onvalue self.offvalue = offvalue - # if self.corner_radius < self.button_corner_radius: - # self.corner_radius = self.button_corner_radius - # callback and control variables - self.callback_function = command + self.function = command self.variable: tkinter.Variable = variable self.variable_callback_blocked = False self.variable_callback_name = None @@ -94,6 +91,19 @@ class CTkSwitch(CTkBaseClass): self.canvas.bind("", self.on_leave) self.canvas.bind("", self.toggle) + self.text_label = tkinter.Label(master=self, + bd=0, + text=self.text, + justify=tkinter.LEFT, + font=self.apply_font_scaling(self.text_font), + textvariable=self.textvariable) + self.text_label.grid(row=0, column=2, padx=0, pady=0, sticky="w") + self.text_label["anchor"] = "w" + + self.text_label.bind("", self.on_enter) + self.text_label.bind("", self.on_leave) + self.text_label.bind("", self.toggle) + self.draw() # initial draw self.set_cursor() @@ -186,22 +196,6 @@ class CTkSwitch(CTkBaseClass): self.canvas.itemconfig("slider_parts", fill=ThemeManager.single_color(self.button_color, self._appearance_mode), outline=ThemeManager.single_color(self.button_color, self._appearance_mode)) - if self.text_label is None: - self.text_label = tkinter.Label(master=self, - bd=0, - text=self.text, - justify=tkinter.LEFT, - font=self.apply_font_scaling(self.text_font)) - self.text_label.grid(row=0, column=2, padx=0, pady=0, sticky="w") - self.text_label["anchor"] = "w" - - self.text_label.bind("", self.on_enter) - self.text_label.bind("", self.on_leave) - self.text_label.bind("", self.toggle) - - if self.textvariable is not None: - self.text_label.configure(textvariable=self.textvariable) - if self.state == tkinter.DISABLED: self.text_label.configure(fg=(ThemeManager.single_color(self.text_color_disabled, self._appearance_mode))) else: @@ -209,13 +203,6 @@ class CTkSwitch(CTkBaseClass): self.text_label.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode)) - self.set_text(self.text) - - def set_text(self, text): - self.text = text - if self.text_label is not None: - self.text_label.configure(text=self.text) - def toggle(self, event=None): if self.state is not tkinter.DISABLED: if self.check_state is True: @@ -225,8 +212,8 @@ class CTkSwitch(CTkBaseClass): self.draw(no_color_updates=True) - if self.callback_function is not None: - self.callback_function() + if self.function is not None: + self.function() if self.variable is not None: self.variable_callback_blocked = True @@ -244,8 +231,8 @@ class CTkSwitch(CTkBaseClass): self.variable.set(self.onvalue) self.variable_callback_blocked = False - if self.callback_function is not None: - self.callback_function() + if self.function is not None: + self.function() def deselect(self, from_variable_callback=False): if self.state is not tkinter.DISABLED or from_variable_callback: @@ -258,8 +245,8 @@ class CTkSwitch(CTkBaseClass): self.variable.set(self.offvalue) self.variable_callback_blocked = False - if self.callback_function is not None: - self.callback_function() + if self.function is not None: + self.function() def get(self): return self.onvalue if self.check_state is True else self.offvalue @@ -286,66 +273,63 @@ class CTkSwitch(CTkBaseClass): def configure(self, *args, **kwargs): require_redraw = False # some attribute changes require a call of self.draw() at the end + if "text" in kwargs: + self.text = kwargs.pop("text") + self.text_label.configure(text=self.text) + if "state" in kwargs: - self.state = kwargs["state"] + self.state = kwargs.pop("state") self.set_cursor() require_redraw = True - del kwargs["state"] if "fg_color" in kwargs: - self.fg_color = kwargs["fg_color"] + self.fg_color = kwargs.pop("fg_color") require_redraw = True - del kwargs["fg_color"] if "bg_color" in kwargs: - if kwargs["bg_color"] is None: + new_bg_color = kwargs.pop("bg_color") + if new_bg_color is None: self.bg_color = self.detect_color_of_master() else: - self.bg_color = kwargs["bg_color"] + self.bg_color = new_bg_color require_redraw = True - del kwargs["bg_color"] if "progress_color" in kwargs: - if kwargs["progress_color"] is None: + new_progress_color = kwargs.pop("progress_color") + if new_progress_color is None: self.progress_color = self.fg_color else: - self.progress_color = kwargs["progress_color"] + self.progress_color = new_progress_color require_redraw = True - del kwargs["progress_color"] if "button_color" in kwargs: - self.button_color = kwargs["button_color"] + self.button_color = kwargs.pop("button_color") require_redraw = True - del kwargs["button_color"] if "button_hover_color" in kwargs: - self.button_hover_color = kwargs["button_hover_color"] + self.button_hover_color = kwargs.pop("button_hover_color") require_redraw = True - del kwargs["button_hover_color"] if "border_color" in kwargs: - self.border_color = kwargs["border_color"] + self.border_color = kwargs.pop("border_color") require_redraw = True - del kwargs["border_color"] if "border_width" in kwargs: - self.border_width = kwargs["border_width"] + self.border_width = kwargs.pop("border_width") require_redraw = True - del kwargs["border_width"] if "command" in kwargs: - self.callback_function = kwargs["command"] - del kwargs["command"] + self.function = kwargs.pop("command") if "textvariable" in kwargs: - self.text_label.configure(textvariable=kwargs["textvariable"]) - del kwargs["textvariable"] + self.textvariable = kwargs.pop("textvariable") + self.text_label.configure(textvariable=self.textvariable) if "variable" in kwargs: if self.variable is not None: self.variable.trace_remove("write", self.variable_callback_name) - self.variable = kwargs["variable"] + self.variable = kwargs.pop("variable") if self.variable is not None and self.variable != "": self.variable_callback_name = self.variable.trace_add("write", self.variable_callback) @@ -356,8 +340,6 @@ class CTkSwitch(CTkBaseClass): else: self.variable = None - del kwargs["variable"] - super().configure(*args, **kwargs) if require_redraw: diff --git a/test/manual_integration_tests/test_variables.py b/test/manual_integration_tests/test_variables.py index 528bd60..1e9d173 100644 --- a/test/manual_integration_tests/test_variables.py +++ b/test/manual_integration_tests/test_variables.py @@ -5,7 +5,7 @@ TEST_CONFIGURE = True TEST_REMOVING = False app = customtkinter.CTk() # create CTk window like you do with the Tk window (you can also use normal tkinter.Tk window) -app.geometry("400x800") +app.geometry("400x900") app.title("Tkinter Variable Test") txt_var = tkinter.StringVar(value="") @@ -46,7 +46,7 @@ if TEST_CONFIGURE: progress_1.configure(variable=int_var) if TEST_REMOVING: progress_1.configure(variable="") check_var = tkinter.StringVar(value="on") -check_1 = customtkinter.CTkCheckBox(app, text="check 1", variable=check_var, onvalue="on", offvalue="off") +check_1 = customtkinter.CTkCheckBox(app, text="check 1", variable=check_var, onvalue="on", offvalue="off", textvariable=txt_var) check_1.pack(pady=15) if TEST_CONFIGURE: check_1.configure(variable=check_var) if TEST_REMOVING: check_1.configure(variable="") @@ -67,8 +67,8 @@ def switch_event(): s_var = tkinter.StringVar(value="on") switch_1 = customtkinter.CTkSwitch(master=app, variable=s_var, textvariable=s_var, onvalue="on", offvalue="off", command=switch_event) switch_1.pack(pady=20, padx=10) -switch_1 = customtkinter.CTkSwitch(master=app, variable=s_var, textvariable=s_var, onvalue="on", offvalue="off") -switch_1.pack(pady=20, padx=10) +switch_2 = customtkinter.CTkSwitch(master=app, variable=s_var, textvariable=s_var, onvalue="on", offvalue="off") +switch_2.pack(pady=20, padx=10) optionmenu_var = tkinter.StringVar(value="test") optionmenu_1 = customtkinter.CTkOptionMenu(master=app, variable=optionmenu_var, values=["Option 1", "Option 2", "Option 3"]) @@ -77,4 +77,7 @@ combobox_1 = customtkinter.CTkComboBox(master=app, values=["Option 1", "Option 2 combobox_1.pack(pady=20, padx=10) combobox_1.configure(variable=optionmenu_var) +radio_1 = customtkinter.CTkRadioButton(app, textvariable=txt_var) +radio_1.pack(pady=20, padx=10) + app.mainloop() From 7e8bbf2968a59d0fc34320fb7b9f754172e17337 Mon Sep 17 00:00:00 2001 From: Tom Schimansky Date: Sat, 2 Jul 2022 01:11:54 +0200 Subject: [PATCH 32/33] Bump to 4.5.3 --- customtkinter/__init__.py | 2 +- pyproject.toml | 2 +- setup.cfg | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/customtkinter/__init__.py b/customtkinter/__init__.py index bb3abf6..0866ca0 100644 --- a/customtkinter/__init__.py +++ b/customtkinter/__init__.py @@ -1,4 +1,4 @@ -__version__ = "4.5.2" +__version__ = "4.5.3" import os import sys diff --git a/pyproject.toml b/pyproject.toml index 6a76993..83187b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta" github_url = "https://github.com/TomSchimansky/CustomTkinter" [tool.tbump.version] -current = "4.5.2" +current = "4.5.3" # Example of a semver regexp. # Make sure this matches current_version before diff --git a/setup.cfg b/setup.cfg index bf47f64..858afee 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = customtkinter -version = 4.5.2 +version = 4.5.3 description = Create modern looking GUIs with Python long_description = CustomTkinter UI-Library\n\n[](https://github.com/TomSchimansky/CustomTkinter/blob/master/documentation_images/Windows_dark.png)\n\nMore Information: https://github.com/TomSchimansky/CustomTkinter long_description_content_type = text/markdown From cdaf8f5f5cd91401300574e63494e527edef6a58 Mon Sep 17 00:00:00 2001 From: Tom Schimansky Date: Sat, 2 Jul 2022 14:10:41 +0200 Subject: [PATCH 33/33] changed default CTkLabel corner_radius for better positioning --- customtkinter/assets/themes/blue.json | 2 +- customtkinter/assets/themes/dark-blue.json | 2 +- customtkinter/assets/themes/green.json | 2 +- customtkinter/assets/themes/sweetkind.json | 2 +- .../complex_example_new.py | 19 +++++++++++++++---- 5 files changed, 19 insertions(+), 8 deletions(-) diff --git a/customtkinter/assets/themes/blue.json b/customtkinter/assets/themes/blue.json index 06b580f..b42f3d1 100644 --- a/customtkinter/assets/themes/blue.json +++ b/customtkinter/assets/themes/blue.json @@ -62,7 +62,7 @@ "entry_border_width": 2, "frame_corner_radius": 6, "frame_border_width": 0, - "label_corner_radius": 8, + "label_corner_radius": 0, "progressbar_border_width": 0, "progressbar_corner_radius": 1000, "slider_border_width": 6, diff --git a/customtkinter/assets/themes/dark-blue.json b/customtkinter/assets/themes/dark-blue.json index f2f2c9f..2868ec4 100644 --- a/customtkinter/assets/themes/dark-blue.json +++ b/customtkinter/assets/themes/dark-blue.json @@ -62,7 +62,7 @@ "entry_border_width": 2, "frame_corner_radius": 10, "frame_border_width": 0, - "label_corner_radius": 8, + "label_corner_radius": 0, "progressbar_border_width": 0, "progressbar_corner_radius": 1000, "slider_border_width": 6, diff --git a/customtkinter/assets/themes/green.json b/customtkinter/assets/themes/green.json index 469f9fe..a6608a1 100644 --- a/customtkinter/assets/themes/green.json +++ b/customtkinter/assets/themes/green.json @@ -62,7 +62,7 @@ "entry_border_width": 2, "frame_corner_radius": 10, "frame_border_width": 0, - "label_corner_radius": 8, + "label_corner_radius": 0, "progressbar_border_width": 0, "progressbar_corner_radius": 1000, "slider_border_width": 6, diff --git a/customtkinter/assets/themes/sweetkind.json b/customtkinter/assets/themes/sweetkind.json index 0c952b6..f9bec05 100644 --- a/customtkinter/assets/themes/sweetkind.json +++ b/customtkinter/assets/themes/sweetkind.json @@ -62,7 +62,7 @@ "entry_border_width": 2, "frame_corner_radius": 10, "frame_border_width": 2, - "label_corner_radius": 8, + "label_corner_radius": 0, "progressbar_border_width": 2, "progressbar_corner_radius": 1000, "slider_border_width": 6, diff --git a/test/manual_integration_tests/complex_example_new.py b/test/manual_integration_tests/complex_example_new.py index 08167ea..adff63c 100644 --- a/test/manual_integration_tests/complex_example_new.py +++ b/test/manual_integration_tests/complex_example_new.py @@ -20,7 +20,7 @@ class App(customtkinter.CTk): self.grid_columnconfigure((2, 3), weight=0, minsize=200) self.grid_rowconfigure((0, 1, 2), weight=1) - # create sidebar frame and widgets + # create sidebar frame with widgets self.sidebar_frame = customtkinter.CTkFrame(self, width=140) self.sidebar_frame.grid(row=0, column=0, rowspan=4, sticky="nsew") self.sidebar_frame.grid_rowconfigure(4, weight=1) @@ -32,11 +32,16 @@ class App(customtkinter.CTk): self.sidebar_button_2.grid(row=2, column=0, padx=20, pady=10) self.sidebar_button_3 = customtkinter.CTkButton(self.sidebar_frame, command=self.sidebar_button_callback) self.sidebar_button_3.grid(row=3, column=0, padx=20, pady=10) - self.appearance_mode_label = customtkinter.CTkLabel(self.sidebar_frame, text="Appearance Mode:") + self.appearance_mode_label = customtkinter.CTkLabel(self.sidebar_frame, text="Appearance Mode:", anchor="w") self.appearance_mode_label.grid(row=5, column=0, padx=20, pady=(10, 0)) self.appearance_mode_optionemenu = customtkinter.CTkOptionMenu(self.sidebar_frame, values=["Light", "Dark", "System"], command=self.change_appearance_mode) - self.appearance_mode_optionemenu.grid(row=6, column=0, padx=20, pady=(10, 20)) + self.appearance_mode_optionemenu.grid(row=6, column=0, padx=20, pady=(10, 10)) + self.scaling_label = customtkinter.CTkLabel(self.sidebar_frame, text="Widget Scaling:", anchor="w") + self.scaling_label.grid(row=7, column=0, padx=20, pady=(10, 0)) + self.scaling_optionemenu = customtkinter.CTkOptionMenu(self.sidebar_frame, values=["75%", "100%", "150%"], + command=self.change_scaling) + self.scaling_optionemenu.grid(row=8, column=0, padx=20, pady=(10, 20)) # create main entry and button self.entry = customtkinter.CTkEntry(self, placeholder_text="CTkEntry") @@ -111,6 +116,7 @@ class App(customtkinter.CTk): self.switch_1.select() self.radio_button_3.configure(state="disabled") self.appearance_mode_optionemenu.set("Dark") + self.scaling_optionemenu.set("100%") self.optionmenu_1.set("CTkOptionmenu") self.combobox_1.set("CTkComboBox") @@ -118,9 +124,14 @@ class App(customtkinter.CTk): dialog = customtkinter.CTkInputDialog(master=None, text="Type in a number:", title="CTkInputDialog") print("CTkInputDialog:", dialog.get_input()) - def change_appearance_mode(self, new_appearance_mode): + def change_appearance_mode(self, new_appearance_mode: str): customtkinter.set_appearance_mode(new_appearance_mode) + def change_scaling(self, new_scaling: str): + new_scaling_float = int(new_scaling.replace("%", "")) / 100 + customtkinter.set_spacing_scaling(new_scaling_float) + customtkinter.set_widget_scaling(new_scaling_float) + def sidebar_button_callback(self): print("sidebar_button click")