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