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