mirror of
https://github.com/TomSchimansky/CustomTkinter.git
synced 2023-08-10 21:13:13 +03:00
refined dropdown_menu_fallback.py
This commit is contained in:
parent
91e7e3077c
commit
413cedd093
@ -31,8 +31,8 @@
|
|||||||
"optionmenu_button_hover": ["#27577D", "#203A4F"],
|
"optionmenu_button_hover": ["#27577D", "#203A4F"],
|
||||||
"combobox_border": ["#979DA2", "#565B5E"],
|
"combobox_border": ["#979DA2", "#565B5E"],
|
||||||
"combobox_button_hover": ["#6E7174", "#7A848D"],
|
"combobox_button_hover": ["#6E7174", "#7A848D"],
|
||||||
"dropdown_color": ["#A8ACB1", "#535353"],
|
"dropdown_color": ["gray90", "gray20"],
|
||||||
"dropdown_hover": ["#D6DCE2", "#393D40"],
|
"dropdown_hover": ["gray75", "gray28"],
|
||||||
"dropdown_text": ["gray10", "#DCE4EE"]
|
"dropdown_text": ["gray10", "#DCE4EE"]
|
||||||
},
|
},
|
||||||
"text": {
|
"text": {
|
||||||
|
@ -13,7 +13,6 @@ from .widget_base_class import CTkBaseClass
|
|||||||
|
|
||||||
|
|
||||||
class CTkOptionMenu(CTkBaseClass):
|
class CTkOptionMenu(CTkBaseClass):
|
||||||
|
|
||||||
def __init__(self, *args,
|
def __init__(self, *args,
|
||||||
bg_color=None,
|
bg_color=None,
|
||||||
fg_color="default_theme",
|
fg_color="default_theme",
|
||||||
@ -74,7 +73,9 @@ class CTkOptionMenu(CTkBaseClass):
|
|||||||
else:
|
else:
|
||||||
self.current_value = "CTkOptionMenu"
|
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)
|
# configure grid system (1x1)
|
||||||
self.grid_rowconfigure(0, weight=1)
|
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.canvas.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="nsew")
|
||||||
self.draw_engine = DrawEngine(self.canvas)
|
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
|
# event bindings
|
||||||
self.canvas.bind("<Enter>", self.on_enter)
|
self.canvas.bind("<Enter>", self.on_enter)
|
||||||
self.canvas.bind("<Leave>", self.on_leave)
|
self.canvas.bind("<Leave>", self.on_leave)
|
||||||
@ -132,9 +139,9 @@ class CTkOptionMenu(CTkBaseClass):
|
|||||||
if self.text_label is None:
|
if self.text_label is None:
|
||||||
self.text_label = tkinter.Label(master=self,
|
self.text_label = tkinter.Label(master=self,
|
||||||
font=self.apply_font_scaling(self.text_font))
|
font=self.apply_font_scaling(self.text_font))
|
||||||
self.text_label.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="w",
|
self.text_label.grid(row=0, column=0, sticky="w",
|
||||||
padx=(max(self.apply_widget_scaling(self.corner_radius), 3),
|
padx=(max(self.apply_widget_scaling(self.corner_radius), self.apply_widget_scaling(3)),
|
||||||
max(self._current_width - left_section_width + 3, 3)))
|
max(self.apply_widget_scaling(self._current_width - left_section_width + 3), self.apply_widget_scaling(3))))
|
||||||
|
|
||||||
self.text_label.bind("<Enter>", self.on_enter)
|
self.text_label.bind("<Enter>", self.on_enter)
|
||||||
self.text_label.bind("<Leave>", self.on_leave)
|
self.text_label.bind("<Leave>", self.on_leave)
|
||||||
@ -169,22 +176,8 @@ class CTkOptionMenu(CTkBaseClass):
|
|||||||
self.text_label.configure(bg=ThemeManager.single_color(self.fg_color, self._appearance_mode))
|
self.text_label.configure(bg=ThemeManager.single_color(self.fg_color, self._appearance_mode))
|
||||||
|
|
||||||
def open_dropdown_menu(self):
|
def open_dropdown_menu(self):
|
||||||
if not Settings.use_dropdown_fallback:
|
self.dropdown_menu.open(self.winfo_rootx(),
|
||||||
self.dropdown_menu = DropdownMenu(x_position=self.winfo_rootx(),
|
self.winfo_rooty() + self.apply_widget_scaling(self._current_height + 0))
|
||||||
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):
|
def configure(self, *args, **kwargs):
|
||||||
require_redraw = False # some attribute changes require a call of self.draw() at the end
|
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:
|
if "values" in kwargs:
|
||||||
self.values = kwargs["values"]
|
self.values = kwargs["values"]
|
||||||
del kwargs["values"]
|
del kwargs["values"]
|
||||||
|
self.dropdown_menu.configure(values=self.values)
|
||||||
|
|
||||||
super().configure(*args, **kwargs)
|
super().configure(*args, **kwargs)
|
||||||
|
|
||||||
@ -259,34 +253,18 @@ class CTkOptionMenu(CTkBaseClass):
|
|||||||
|
|
||||||
def on_enter(self, event=0):
|
def on_enter(self, event=0):
|
||||||
if self.hover is True and self.state == tkinter.NORMAL and len(self.values) > 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
|
# set color of inner button parts to hover color
|
||||||
self.canvas.itemconfig("inner_parts_right",
|
self.canvas.itemconfig("inner_parts_right",
|
||||||
outline=ThemeManager.single_color(self.button_hover_color, self._appearance_mode),
|
outline=ThemeManager.single_color(self.button_hover_color, self._appearance_mode),
|
||||||
fill=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):
|
def on_leave(self, event=0):
|
||||||
self.click_animation_running = False
|
|
||||||
|
|
||||||
if self.hover is True:
|
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
|
# set color of inner button parts
|
||||||
self.canvas.itemconfig("inner_parts_right",
|
self.canvas.itemconfig("inner_parts_right",
|
||||||
outline=ThemeManager.single_color(self.button_color, self._appearance_mode),
|
outline=ThemeManager.single_color(self.button_color, self._appearance_mode),
|
||||||
fill=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):
|
def variable_callback(self, var_name, index, mode):
|
||||||
if not self.variable_callback_blocked:
|
if not self.variable_callback_blocked:
|
||||||
self.set(self.variable.get(), from_variable_callback=True)
|
self.set(self.variable.get(), from_variable_callback=True)
|
||||||
@ -314,8 +292,3 @@ class CTkOptionMenu(CTkBaseClass):
|
|||||||
def clicked(self, event=0):
|
def clicked(self, event=0):
|
||||||
if self.state is not tkinter.DISABLED and len(self.values) > 0:
|
if self.state is not tkinter.DISABLED and len(self.values) > 0:
|
||||||
self.open_dropdown_menu()
|
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)
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import tkinter
|
import tkinter
|
||||||
import sys
|
import sys
|
||||||
|
import copy
|
||||||
from distutils.version import StrictVersion as Version
|
from distutils.version import StrictVersion as Version
|
||||||
import platform
|
import platform
|
||||||
from typing import Union
|
from typing import Union
|
||||||
@ -11,8 +12,9 @@ from ..scaling_tracker import ScalingTracker
|
|||||||
|
|
||||||
class DropdownMenuFallback(tkinter.Menu):
|
class DropdownMenuFallback(tkinter.Menu):
|
||||||
def __init__(self, *args,
|
def __init__(self, *args,
|
||||||
fg_color="#555555",
|
min_character_width=18,
|
||||||
button_hover_color="gray35",
|
fg_color="default_theme",
|
||||||
|
button_hover_color="default_theme",
|
||||||
text_color="default_theme",
|
text_color="default_theme",
|
||||||
text_font="default_theme",
|
text_font="default_theme",
|
||||||
command=None,
|
command=None,
|
||||||
@ -24,32 +26,94 @@ class DropdownMenuFallback(tkinter.Menu):
|
|||||||
self._widget_scaling = ScalingTracker.get_widget_scaling(self)
|
self._widget_scaling = ScalingTracker.get_widget_scaling(self)
|
||||||
self._spacing_scaling = ScalingTracker.get_spacing_scaling(self)
|
self._spacing_scaling = ScalingTracker.get_spacing_scaling(self)
|
||||||
|
|
||||||
self.fg_color = fg_color
|
AppearanceModeTracker.add(self.set_appearance_mode, self)
|
||||||
self.button_hover_color = button_hover_color
|
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_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.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"):
|
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.values = values
|
||||||
self.command = command
|
self.command = command
|
||||||
|
|
||||||
|
self.add_menu_commands()
|
||||||
|
|
||||||
|
def add_menu_commands(self):
|
||||||
for value in self.values:
|
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":
|
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):
|
def button_callback(self, value):
|
||||||
if self.command is not None:
|
if self.command is not None:
|
||||||
self.command(value)
|
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):
|
def set_scaling(self, new_widget_scaling, new_spacing_scaling, new_window_scaling):
|
||||||
self._widget_scaling = new_widget_scaling
|
self._widget_scaling = new_widget_scaling
|
||||||
self._spacing_scaling = new_spacing_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
|
||||||
|
Loading…
Reference in New Issue
Block a user