CustomTkinter/customtkinter/widgets/dropdown_menu.py

171 lines
7.2 KiB
Python

import tkinter
import sys
import copy
import re
from typing import Union
from ..theme_manager import ThemeManager
from ..appearance_mode_tracker import AppearanceModeTracker
from ..scaling_tracker import ScalingTracker
class DropdownMenu(tkinter.Menu):
def __init__(self, *args,
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):
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.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
self.configure_menu_for_platforms()
self.values = values
self.command = command
self.add_menu_commands()
def configure_menu_for_platforms(self):
""" apply platform specific appearance attributes """
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))
def add_menu_commands(self):
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":
y += self.apply_widget_scaling(8)
else:
y += self.apply_widget_scaling(3)
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:
self.command(value)
def configure(self, **kwargs):
if "values" in kwargs:
self.values = kwargs.pop("values")
self.delete(0, "end") # delete all old commands
self.add_menu_commands()
if "fg_color" in kwargs:
self.fg_color = kwargs.pop("fg_color")
self.configure(bg=ThemeManager.single_color(self.fg_color, self._appearance_mode))
if "hover_color" in kwargs:
self.hover_color = kwargs.pop("hover_color")
self.configure(activebackground=ThemeManager.single_color(self.hover_color, self._appearance_mode))
if "text_color" in kwargs:
self.text_color = kwargs.pop("text_color")
self.configure(fg=ThemeManager.single_color(self.text_color, self._appearance_mode))
if "text_font" in kwargs:
self.text_font = kwargs.pop("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)):
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):
""" 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
self.configure_menu_for_platforms()