refined dropdown_menu_fallback.py

This commit is contained in:
TomSchimansky 2022-06-14 23:58:21 +02:00
parent 91e7e3077c
commit 413cedd093
3 changed files with 92 additions and 55 deletions

View File

@ -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": {

View File

@ -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)

View File

@ -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