mirror of
https://github.com/TomSchimansky/CustomTkinter.git
synced 2023-08-10 21:13:13 +03:00
added type hints and private hints to all widget classes, fixed #497, removed get and set methods from some widgets
This commit is contained in:
parent
5717fc68e2
commit
d6075ad544
@ -10,7 +10,10 @@ from .widget_base_class import CTkBaseClass
|
||||
|
||||
|
||||
class CTkButton(CTkBaseClass):
|
||||
""" button with border, rounded corners, hover effect, image support """
|
||||
"""
|
||||
Button with rounded corners, border, hover effect, image support, click command and textvariable.
|
||||
For detailed information check out the documentation.
|
||||
"""
|
||||
|
||||
def __init__(self, *args,
|
||||
bg_color: Union[str, Tuple[str, str], None] = None,
|
||||
@ -33,34 +36,34 @@ class CTkButton(CTkBaseClass):
|
||||
command: Callable = None,
|
||||
**kwargs):
|
||||
|
||||
# transfer basic functionality (bg_color, size, _appearance_mode, scaling) to CTkBaseClass
|
||||
# transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to CTkBaseClass
|
||||
super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs)
|
||||
|
||||
# color
|
||||
self.fg_color = ThemeManager.theme["color"]["button"] if fg_color == "default_theme" else fg_color
|
||||
self.hover_color = ThemeManager.theme["color"]["button_hover"] if hover_color == "default_theme" else hover_color
|
||||
self.border_color = ThemeManager.theme["color"]["button_border"] if border_color == "default_theme" else border_color
|
||||
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._fg_color = ThemeManager.theme["color"]["button"] if fg_color == "default_theme" else fg_color
|
||||
self._hover_color = ThemeManager.theme["color"]["button_hover"] if hover_color == "default_theme" else hover_color
|
||||
self._border_color = ThemeManager.theme["color"]["button_border"] if border_color == "default_theme" else border_color
|
||||
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
|
||||
|
||||
# shape
|
||||
self.corner_radius = ThemeManager.theme["shape"]["button_corner_radius"] if corner_radius == "default_theme" else corner_radius
|
||||
self.border_width = ThemeManager.theme["shape"]["button_border_width"] if border_width == "default_theme" else border_width
|
||||
self._corner_radius = ThemeManager.theme["shape"]["button_corner_radius"] if corner_radius == "default_theme" else corner_radius
|
||||
self._border_width = ThemeManager.theme["shape"]["button_border_width"] if border_width == "default_theme" else border_width
|
||||
|
||||
# text, font, image
|
||||
self.image = image
|
||||
self.image_label = None
|
||||
self.text = text
|
||||
self.text_label = None
|
||||
self.text_font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font
|
||||
self._image = image
|
||||
self._image_label = None
|
||||
self._text = text
|
||||
self._text_label = None
|
||||
self._text_font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font
|
||||
|
||||
# callback and hover functionality
|
||||
self.command = command
|
||||
self.textvariable = textvariable
|
||||
self.state = state
|
||||
self.hover = hover
|
||||
self.compound = compound
|
||||
self.click_animation_running = False
|
||||
self._command = command
|
||||
self._textvariable = textvariable
|
||||
self._state = state
|
||||
self._hover = hover
|
||||
self._compound = compound
|
||||
self._click_animation_running = False
|
||||
|
||||
# configure grid system (2x2)
|
||||
self.grid_rowconfigure(0, weight=1)
|
||||
@ -69,309 +72,301 @@ class CTkButton(CTkBaseClass):
|
||||
self.grid_columnconfigure(1, weight=1)
|
||||
|
||||
# canvas
|
||||
self.canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self.apply_widget_scaling(self._desired_width),
|
||||
height=self.apply_widget_scaling(self._desired_height))
|
||||
self.canvas.grid(row=0, column=0, rowspan=2, columnspan=2, sticky="nsew")
|
||||
self.draw_engine = DrawEngine(self.canvas)
|
||||
self._canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self._apply_widget_scaling(self._desired_width),
|
||||
height=self._apply_widget_scaling(self._desired_height))
|
||||
self._canvas.grid(row=0, column=0, rowspan=2, columnspan=2, sticky="nsew")
|
||||
self._draw_engine = DrawEngine(self._canvas)
|
||||
|
||||
# canvas event bindings
|
||||
self.canvas.bind("<Enter>", self.on_enter)
|
||||
self.canvas.bind("<Leave>", self.on_leave)
|
||||
self.canvas.bind("<Button-1>", self.clicked)
|
||||
self.canvas.bind("<Button-1>", self.clicked)
|
||||
self.bind('<Configure>', self.update_dimensions_event)
|
||||
self._canvas.bind("<Enter>", self._on_enter)
|
||||
self._canvas.bind("<Leave>", self._on_leave)
|
||||
self._canvas.bind("<Button-1>", self._clicked)
|
||||
self._canvas.bind("<Button-1>", self._clicked)
|
||||
self.bind('<Configure>', self._update_dimensions_event)
|
||||
|
||||
# configure cursor and initial draw
|
||||
self.set_cursor()
|
||||
self.draw()
|
||||
self._set_cursor()
|
||||
self._draw()
|
||||
|
||||
def set_scaling(self, *args, **kwargs):
|
||||
super().set_scaling(*args, **kwargs)
|
||||
def _set_scaling(self, *args, **kwargs):
|
||||
super()._set_scaling(*args, **kwargs)
|
||||
|
||||
if self.text_label is not None:
|
||||
self.text_label.destroy()
|
||||
self.text_label = None
|
||||
if self.image_label is not None:
|
||||
self.image_label.destroy()
|
||||
self.image_label = None
|
||||
if self._text_label is not None:
|
||||
self._text_label.destroy()
|
||||
self._text_label = None
|
||||
if self._image_label is not None:
|
||||
self._image_label.destroy()
|
||||
self._image_label = None
|
||||
|
||||
self.canvas.configure(width=self.apply_widget_scaling(self._desired_width),
|
||||
height=self.apply_widget_scaling(self._desired_height))
|
||||
self.draw()
|
||||
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
|
||||
height=self._apply_widget_scaling(self._desired_height))
|
||||
self._draw()
|
||||
|
||||
def set_dimensions(self, width: int = None, height: int = None):
|
||||
super().set_dimensions(width, height)
|
||||
def _set_dimensions(self, width: int = None, height: int = None):
|
||||
super()._set_dimensions(width, height)
|
||||
|
||||
self.canvas.configure(width=self.apply_widget_scaling(self._desired_width),
|
||||
height=self.apply_widget_scaling(self._desired_height))
|
||||
self.draw()
|
||||
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
|
||||
height=self._apply_widget_scaling(self._desired_height))
|
||||
self._draw()
|
||||
|
||||
def draw(self, no_color_updates=False):
|
||||
requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.apply_widget_scaling(self._current_width),
|
||||
self.apply_widget_scaling(self._current_height),
|
||||
self.apply_widget_scaling(self.corner_radius),
|
||||
self.apply_widget_scaling(self.border_width))
|
||||
def _draw(self, no_color_updates=False):
|
||||
requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._current_width),
|
||||
self._apply_widget_scaling(self._current_height),
|
||||
self._apply_widget_scaling(self._corner_radius),
|
||||
self._apply_widget_scaling(self._border_width))
|
||||
|
||||
if no_color_updates is False or requires_recoloring:
|
||||
|
||||
self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
|
||||
self._canvas.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode))
|
||||
|
||||
# set color for the button border parts (outline)
|
||||
self.canvas.itemconfig("border_parts",
|
||||
outline=ThemeManager.single_color(self.border_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(self.border_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("border_parts",
|
||||
outline=ThemeManager.single_color(self._border_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(self._border_color, self._appearance_mode))
|
||||
|
||||
# set color for inner button parts
|
||||
if self.fg_color is None:
|
||||
self.canvas.itemconfig("inner_parts",
|
||||
outline=ThemeManager.single_color(self.bg_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(self.bg_color, self._appearance_mode))
|
||||
if self._fg_color is None:
|
||||
self._canvas.itemconfig("inner_parts",
|
||||
outline=ThemeManager.single_color(self._bg_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(self._bg_color, self._appearance_mode))
|
||||
else:
|
||||
self.canvas.itemconfig("inner_parts",
|
||||
outline=ThemeManager.single_color(self.fg_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(self.fg_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("inner_parts",
|
||||
outline=ThemeManager.single_color(self._fg_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(self._fg_color, self._appearance_mode))
|
||||
|
||||
# create text label if text given
|
||||
if self.text is not None and self.text != "":
|
||||
if self._text is not None and self._text != "":
|
||||
|
||||
if self.text_label is None:
|
||||
self.text_label = tkinter.Label(master=self,
|
||||
font=self.apply_font_scaling(self.text_font),
|
||||
text=self.text,
|
||||
textvariable=self.textvariable)
|
||||
if self._text_label is None:
|
||||
self._text_label = tkinter.Label(master=self,
|
||||
font=self._apply_font_scaling(self._text_font),
|
||||
text=self._text,
|
||||
textvariable=self._textvariable)
|
||||
|
||||
self.text_label.bind("<Enter>", self.on_enter)
|
||||
self.text_label.bind("<Leave>", self.on_leave)
|
||||
self.text_label.bind("<Button-1>", self.clicked)
|
||||
self.text_label.bind("<Button-1>", self.clicked)
|
||||
self._text_label.bind("<Enter>", self._on_enter)
|
||||
self._text_label.bind("<Leave>", self._on_leave)
|
||||
self._text_label.bind("<Button-1>", self._clicked)
|
||||
self._text_label.bind("<Button-1>", self._clicked)
|
||||
|
||||
if no_color_updates is False:
|
||||
# set text_label fg color (text color)
|
||||
self.text_label.configure(fg=ThemeManager.single_color(self.text_color, self._appearance_mode))
|
||||
self._text_label.configure(fg=ThemeManager.single_color(self._text_color, self._appearance_mode))
|
||||
|
||||
if self.state == tkinter.DISABLED:
|
||||
self.text_label.configure(fg=(ThemeManager.single_color(self.text_color_disabled, self._appearance_mode)))
|
||||
if self._state == tkinter.DISABLED:
|
||||
self._text_label.configure(fg=(ThemeManager.single_color(self._text_color_disabled, self._appearance_mode)))
|
||||
else:
|
||||
self.text_label.configure(fg=ThemeManager.single_color(self.text_color, self._appearance_mode))
|
||||
self._text_label.configure(fg=ThemeManager.single_color(self._text_color, self._appearance_mode))
|
||||
|
||||
if self.fg_color is None:
|
||||
self.text_label.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
|
||||
if self._fg_color is None:
|
||||
self._text_label.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode))
|
||||
else:
|
||||
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))
|
||||
|
||||
else:
|
||||
# delete text_label if no text given
|
||||
if self.text_label is not None:
|
||||
self.text_label.destroy()
|
||||
self.text_label = None
|
||||
if self._text_label is not None:
|
||||
self._text_label.destroy()
|
||||
self._text_label = None
|
||||
|
||||
# create image label if image given
|
||||
if self.image is not None:
|
||||
if self._image is not None:
|
||||
|
||||
if self.image_label is None:
|
||||
self.image_label = tkinter.Label(master=self)
|
||||
if self._image_label is None:
|
||||
self._image_label = tkinter.Label(master=self)
|
||||
|
||||
self.image_label.bind("<Enter>", self.on_enter)
|
||||
self.image_label.bind("<Leave>", self.on_leave)
|
||||
self.image_label.bind("<Button-1>", self.clicked)
|
||||
self.image_label.bind("<Button-1>", self.clicked)
|
||||
self._image_label.bind("<Enter>", self._on_enter)
|
||||
self._image_label.bind("<Leave>", self._on_leave)
|
||||
self._image_label.bind("<Button-1>", self._clicked)
|
||||
self._image_label.bind("<Button-1>", self._clicked)
|
||||
|
||||
if no_color_updates is False:
|
||||
# set image_label bg color (background color of label)
|
||||
if self.fg_color is None:
|
||||
self.image_label.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
|
||||
if self._fg_color is None:
|
||||
self._image_label.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode))
|
||||
else:
|
||||
self.image_label.configure(bg=ThemeManager.single_color(self.fg_color, self._appearance_mode))
|
||||
self._image_label.configure(bg=ThemeManager.single_color(self._fg_color, self._appearance_mode))
|
||||
|
||||
self.image_label.configure(image=self.image) # set image
|
||||
self._image_label.configure(image=self._image) # set image
|
||||
|
||||
else:
|
||||
# delete text_label if no text given
|
||||
if self.image_label is not None:
|
||||
self.image_label.destroy()
|
||||
self.image_label = None
|
||||
if self._image_label is not None:
|
||||
self._image_label.destroy()
|
||||
self._image_label = None
|
||||
|
||||
# create grid layout with just an image given
|
||||
if self.image_label is not None and self.text_label is None:
|
||||
self.image_label.grid(row=0, column=0, rowspan=2, columnspan=2, sticky="",
|
||||
pady=(self.apply_widget_scaling(self.border_width), self.apply_widget_scaling(self.border_width) + 1)) # bottom pady with +1 for rounding to even
|
||||
if self._image_label is not None and self._text_label is None:
|
||||
self._image_label.grid(row=0, column=0, rowspan=2, columnspan=2, sticky="",
|
||||
pady=(self._apply_widget_scaling(self._border_width), self._apply_widget_scaling(self._border_width) + 1)) # bottom pady with +1 for rounding to even
|
||||
|
||||
# create grid layout with just text given
|
||||
if self.image_label is None and self.text_label is not None:
|
||||
self.text_label.grid(row=0, column=0, rowspan=2, columnspan=2, sticky="",
|
||||
padx=self.apply_widget_scaling(self.corner_radius),
|
||||
pady=(self.apply_widget_scaling(self.border_width), self.apply_widget_scaling(self.border_width) + 1)) # bottom pady with +1 for rounding to even
|
||||
if self._image_label is None and self._text_label is not None:
|
||||
self._text_label.grid(row=0, column=0, rowspan=2, columnspan=2, sticky="",
|
||||
padx=self._apply_widget_scaling(self._corner_radius),
|
||||
pady=(self._apply_widget_scaling(self._border_width), self._apply_widget_scaling(self._border_width) + 1)) # bottom pady with +1 for rounding to even
|
||||
|
||||
# create grid layout of image and text label in 2x2 grid system with given compound
|
||||
if self.image_label is not None and self.text_label is not None:
|
||||
if self.compound == tkinter.LEFT or self.compound == "left":
|
||||
self.image_label.grid(row=0, column=0, sticky="e", rowspan=2, columnspan=1,
|
||||
padx=(max(self.apply_widget_scaling(self.corner_radius), self.apply_widget_scaling(self.border_width)), 2),
|
||||
pady=(self.apply_widget_scaling(self.border_width), self.apply_widget_scaling(self.border_width) + 1))
|
||||
self.text_label.grid(row=0, column=1, sticky="w", rowspan=2, columnspan=1,
|
||||
padx=(2, max(self.apply_widget_scaling(self.corner_radius), self.apply_widget_scaling(self.border_width))),
|
||||
pady=(self.apply_widget_scaling(self.border_width), self.apply_widget_scaling(self.border_width) + 1))
|
||||
elif self.compound == tkinter.TOP or self.compound == "top":
|
||||
self.image_label.grid(row=0, column=0, sticky="s", columnspan=2, rowspan=1,
|
||||
padx=max(self.apply_widget_scaling(self.corner_radius), self.apply_widget_scaling(self.border_width)),
|
||||
pady=(self.apply_widget_scaling(self.border_width), 2))
|
||||
self.text_label.grid(row=1, column=0, sticky="n", columnspan=2, rowspan=1,
|
||||
padx=max(self.apply_widget_scaling(self.corner_radius), self.apply_widget_scaling(self.border_width)),
|
||||
pady=(2, self.apply_widget_scaling(self.border_width)))
|
||||
elif self.compound == tkinter.RIGHT or self.compound == "right":
|
||||
self.image_label.grid(row=0, column=1, sticky="w", rowspan=2, columnspan=1,
|
||||
padx=(2, max(self.apply_widget_scaling(self.corner_radius), self.apply_widget_scaling(self.border_width))),
|
||||
pady=(self.apply_widget_scaling(self.border_width), self.apply_widget_scaling(self.border_width) + 1))
|
||||
self.text_label.grid(row=0, column=0, sticky="e", rowspan=2, columnspan=1,
|
||||
padx=(max(self.apply_widget_scaling(self.corner_radius), self.apply_widget_scaling(self.border_width)), 2),
|
||||
pady=(self.apply_widget_scaling(self.border_width), self.apply_widget_scaling(self.border_width) + 1))
|
||||
elif self.compound == tkinter.BOTTOM or self.compound == "bottom":
|
||||
self.image_label.grid(row=1, column=0, sticky="n", columnspan=2, rowspan=1,
|
||||
padx=max(self.apply_widget_scaling(self.corner_radius), self.apply_widget_scaling(self.border_width)),
|
||||
pady=(2, self.apply_widget_scaling(self.border_width)))
|
||||
self.text_label.grid(row=0, column=0, sticky="s", columnspan=2, rowspan=1,
|
||||
padx=max(self.apply_widget_scaling(self.corner_radius), self.apply_widget_scaling(self.border_width)),
|
||||
pady=(self.apply_widget_scaling(self.border_width), 2))
|
||||
if self._image_label is not None and self._text_label is not None:
|
||||
if self._compound == tkinter.LEFT or self._compound == "left":
|
||||
self._image_label.grid(row=0, column=0, sticky="e", rowspan=2, columnspan=1,
|
||||
padx=(max(self._apply_widget_scaling(self._corner_radius), self._apply_widget_scaling(self._border_width)), 2),
|
||||
pady=(self._apply_widget_scaling(self._border_width), self._apply_widget_scaling(self._border_width) + 1))
|
||||
self._text_label.grid(row=0, column=1, sticky="w", rowspan=2, columnspan=1,
|
||||
padx=(2, max(self._apply_widget_scaling(self._corner_radius), self._apply_widget_scaling(self._border_width))),
|
||||
pady=(self._apply_widget_scaling(self._border_width), self._apply_widget_scaling(self._border_width) + 1))
|
||||
elif self._compound == tkinter.TOP or self._compound == "top":
|
||||
self._image_label.grid(row=0, column=0, sticky="s", columnspan=2, rowspan=1,
|
||||
padx=max(self._apply_widget_scaling(self._corner_radius), self._apply_widget_scaling(self._border_width)),
|
||||
pady=(self._apply_widget_scaling(self._border_width), 2))
|
||||
self._text_label.grid(row=1, column=0, sticky="n", columnspan=2, rowspan=1,
|
||||
padx=max(self._apply_widget_scaling(self._corner_radius), self._apply_widget_scaling(self._border_width)),
|
||||
pady=(2, self._apply_widget_scaling(self._border_width)))
|
||||
elif self._compound == tkinter.RIGHT or self._compound == "right":
|
||||
self._image_label.grid(row=0, column=1, sticky="w", rowspan=2, columnspan=1,
|
||||
padx=(2, max(self._apply_widget_scaling(self._corner_radius), self._apply_widget_scaling(self._border_width))),
|
||||
pady=(self._apply_widget_scaling(self._border_width), self._apply_widget_scaling(self._border_width) + 1))
|
||||
self._text_label.grid(row=0, column=0, sticky="e", rowspan=2, columnspan=1,
|
||||
padx=(max(self._apply_widget_scaling(self._corner_radius), self._apply_widget_scaling(self._border_width)), 2),
|
||||
pady=(self._apply_widget_scaling(self._border_width), self._apply_widget_scaling(self._border_width) + 1))
|
||||
elif self._compound == tkinter.BOTTOM or self._compound == "bottom":
|
||||
self._image_label.grid(row=1, column=0, sticky="n", columnspan=2, rowspan=1,
|
||||
padx=max(self._apply_widget_scaling(self._corner_radius), self._apply_widget_scaling(self._border_width)),
|
||||
pady=(2, self._apply_widget_scaling(self._border_width)))
|
||||
self._text_label.grid(row=0, column=0, sticky="s", columnspan=2, rowspan=1,
|
||||
padx=max(self._apply_widget_scaling(self._corner_radius), self._apply_widget_scaling(self._border_width)),
|
||||
pady=(self._apply_widget_scaling(self._border_width), 2))
|
||||
|
||||
def configure(self, require_redraw=False, **kwargs):
|
||||
if "text" in kwargs:
|
||||
self.text = kwargs.pop("text")
|
||||
if self.text_label is None:
|
||||
self._text = kwargs.pop("text")
|
||||
if self._text_label is None:
|
||||
require_redraw = True # text_label will be created in .draw()
|
||||
else:
|
||||
self.text_label.configure(text=self.text)
|
||||
self._text_label.configure(text=self._text)
|
||||
|
||||
if "text_font" in kwargs:
|
||||
self.text_font = kwargs.pop("text_font")
|
||||
if self.text_label is not None:
|
||||
self.text_label.configure(font=self.apply_font_scaling(self.text_font))
|
||||
self._text_font = kwargs.pop("text_font")
|
||||
if self._text_label is not None:
|
||||
self._text_label.configure(font=self._apply_font_scaling(self._text_font))
|
||||
|
||||
if "state" in kwargs:
|
||||
self.state = kwargs.pop("state")
|
||||
self.set_cursor()
|
||||
self._state = kwargs.pop("state")
|
||||
self._set_cursor()
|
||||
require_redraw = True
|
||||
|
||||
if "image" in kwargs:
|
||||
self.image = kwargs.pop("image")
|
||||
self._image = kwargs.pop("image")
|
||||
require_redraw = True
|
||||
|
||||
if "corner_radius" in kwargs:
|
||||
self.corner_radius = kwargs.pop("corner_radius")
|
||||
self._corner_radius = kwargs.pop("corner_radius")
|
||||
require_redraw = True
|
||||
|
||||
if "compound" in kwargs:
|
||||
self.compound = kwargs.pop("compound")
|
||||
self._compound = kwargs.pop("compound")
|
||||
require_redraw = True
|
||||
|
||||
if "fg_color" in kwargs:
|
||||
self.fg_color = kwargs.pop("fg_color")
|
||||
self._fg_color = kwargs.pop("fg_color")
|
||||
require_redraw = True
|
||||
|
||||
if "border_color" in kwargs:
|
||||
self.border_color = kwargs.pop("border_color")
|
||||
self._border_color = kwargs.pop("border_color")
|
||||
require_redraw = True
|
||||
|
||||
if "hover_color" in kwargs:
|
||||
self.hover_color = kwargs.pop("hover_color")
|
||||
self._hover_color = kwargs.pop("hover_color")
|
||||
require_redraw = True
|
||||
|
||||
if "text_color" in kwargs:
|
||||
self.text_color = kwargs.pop("text_color")
|
||||
self._text_color = kwargs.pop("text_color")
|
||||
require_redraw = True
|
||||
|
||||
if "command" in kwargs:
|
||||
self.command = kwargs.pop("command")
|
||||
self._command = kwargs.pop("command")
|
||||
|
||||
if "textvariable" in kwargs:
|
||||
self.textvariable = kwargs.pop("textvariable")
|
||||
if self.text_label is not None:
|
||||
self.text_label.configure(textvariable=self.textvariable)
|
||||
self._textvariable = kwargs.pop("textvariable")
|
||||
if self._text_label is not None:
|
||||
self._text_label.configure(textvariable=self._textvariable)
|
||||
|
||||
if "width" in kwargs:
|
||||
self.set_dimensions(width=kwargs.pop("width"))
|
||||
self._set_dimensions(width=kwargs.pop("width"))
|
||||
|
||||
if "height" in kwargs:
|
||||
self.set_dimensions(height=kwargs.pop("height"))
|
||||
self._set_dimensions(height=kwargs.pop("height"))
|
||||
|
||||
super().configure(require_redraw=require_redraw, **kwargs)
|
||||
|
||||
def set_cursor(self):
|
||||
def _set_cursor(self):
|
||||
if Settings.cursor_manipulation_enabled:
|
||||
if self.state == tkinter.DISABLED:
|
||||
if sys.platform == "darwin" and self.command is not None and Settings.cursor_manipulation_enabled:
|
||||
if self._state == tkinter.DISABLED:
|
||||
if sys.platform == "darwin" and self._command is not None and Settings.cursor_manipulation_enabled:
|
||||
self.configure(cursor="arrow")
|
||||
elif sys.platform.startswith("win") and self.command is not None and Settings.cursor_manipulation_enabled:
|
||||
elif sys.platform.startswith("win") and self._command is not None and Settings.cursor_manipulation_enabled:
|
||||
self.configure(cursor="arrow")
|
||||
|
||||
elif self.state == tkinter.NORMAL:
|
||||
if sys.platform == "darwin" and self.command is not None and Settings.cursor_manipulation_enabled:
|
||||
elif self._state == tkinter.NORMAL:
|
||||
if sys.platform == "darwin" and self._command is not None and Settings.cursor_manipulation_enabled:
|
||||
self.configure(cursor="pointinghand")
|
||||
elif sys.platform.startswith("win") and self.command is not None and Settings.cursor_manipulation_enabled:
|
||||
elif sys.platform.startswith("win") and self._command is not None and Settings.cursor_manipulation_enabled:
|
||||
self.configure(cursor="hand2")
|
||||
|
||||
def set_image(self, image):
|
||||
""" will be removed in next major """
|
||||
self.configure(image=image)
|
||||
|
||||
def set_text(self, text):
|
||||
""" will be removed in next major """
|
||||
self.configure(text=text)
|
||||
|
||||
def on_enter(self, event=None):
|
||||
if self.hover is True and self.state == tkinter.NORMAL:
|
||||
if self.hover_color is None:
|
||||
inner_parts_color = self.fg_color
|
||||
def _on_enter(self, event=None):
|
||||
if self._hover is True and self._state == tkinter.NORMAL:
|
||||
if self._hover_color is None:
|
||||
inner_parts_color = self._fg_color
|
||||
else:
|
||||
inner_parts_color = self.hover_color
|
||||
inner_parts_color = self._hover_color
|
||||
|
||||
# set color of inner button parts to hover color
|
||||
self.canvas.itemconfig("inner_parts",
|
||||
outline=ThemeManager.single_color(inner_parts_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(inner_parts_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("inner_parts",
|
||||
outline=ThemeManager.single_color(inner_parts_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(inner_parts_color, self._appearance_mode))
|
||||
|
||||
# set text_label bg color to button hover color
|
||||
if self.text_label is not None:
|
||||
self.text_label.configure(bg=ThemeManager.single_color(inner_parts_color, self._appearance_mode))
|
||||
if self._text_label is not None:
|
||||
self._text_label.configure(bg=ThemeManager.single_color(inner_parts_color, self._appearance_mode))
|
||||
|
||||
# set image_label bg color to button hover color
|
||||
if self.image_label is not None:
|
||||
self.image_label.configure(bg=ThemeManager.single_color(inner_parts_color, self._appearance_mode))
|
||||
if self._image_label is not None:
|
||||
self._image_label.configure(bg=ThemeManager.single_color(inner_parts_color, self._appearance_mode))
|
||||
|
||||
def on_leave(self, event=None):
|
||||
self.click_animation_running = False
|
||||
def _on_leave(self, event=None):
|
||||
self._click_animation_running = False
|
||||
|
||||
if self.hover is True:
|
||||
if self.fg_color is None:
|
||||
inner_parts_color = self.bg_color
|
||||
if self._hover is True:
|
||||
if self._fg_color is None:
|
||||
inner_parts_color = self._bg_color
|
||||
else:
|
||||
inner_parts_color = self.fg_color
|
||||
inner_parts_color = self._fg_color
|
||||
|
||||
# set color of inner button parts
|
||||
self.canvas.itemconfig("inner_parts",
|
||||
outline=ThemeManager.single_color(inner_parts_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(inner_parts_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("inner_parts",
|
||||
outline=ThemeManager.single_color(inner_parts_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(inner_parts_color, self._appearance_mode))
|
||||
|
||||
# set text_label bg color (label color)
|
||||
if self.text_label is not None:
|
||||
self.text_label.configure(bg=ThemeManager.single_color(inner_parts_color, self._appearance_mode))
|
||||
if self._text_label is not None:
|
||||
self._text_label.configure(bg=ThemeManager.single_color(inner_parts_color, self._appearance_mode))
|
||||
|
||||
# set image_label bg color (image bg color)
|
||||
if self.image_label is not None:
|
||||
self.image_label.configure(bg=ThemeManager.single_color(inner_parts_color, self._appearance_mode))
|
||||
if self._image_label is not None:
|
||||
self._image_label.configure(bg=ThemeManager.single_color(inner_parts_color, self._appearance_mode))
|
||||
|
||||
def click_animation(self):
|
||||
if self.click_animation_running:
|
||||
self.on_enter()
|
||||
def _click_animation(self):
|
||||
if self._click_animation_running:
|
||||
self._on_enter()
|
||||
|
||||
def clicked(self, event=None):
|
||||
if self.command is not None:
|
||||
if self.state != tkinter.DISABLED:
|
||||
def _clicked(self, event=None):
|
||||
if self._command is not None:
|
||||
if self._state != tkinter.DISABLED:
|
||||
|
||||
# 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)
|
||||
self._on_leave()
|
||||
self._click_animation_running = True
|
||||
self.after(100, self._click_animation)
|
||||
|
||||
self.command()
|
||||
self._command()
|
||||
|
@ -4,11 +4,32 @@ from typing import Union, Tuple
|
||||
|
||||
|
||||
class CTkCanvas(tkinter.Canvas):
|
||||
"""
|
||||
Canvas with additional functionality to draw antialiased circles on Windows/Linux.
|
||||
|
||||
Call .init_font_character_mapping() at program start to load the correct character
|
||||
dictionary according to the operating system. Characters (circle sizes) are optimised
|
||||
to look best for rendering CustomTkinter shapes on the different operating systems.
|
||||
|
||||
- .create_aa_circle() creates antialiased circle and returns int identifier.
|
||||
- .coords() is modified to support the aa-circle shapes correctly like you would expect.
|
||||
- .itemconfig() is also modified to support aa-cricle shapes.
|
||||
|
||||
The aa-circles are created by choosing a character from the custom created and loaded
|
||||
font 'CustomTkinter_shapes_font'. It contains circle shapes with different sizes filling
|
||||
either the whole character space or just pert of it (characters A to R). Circles with a smaller
|
||||
radius need a smaller circle character to look correct when rendered on the canvas.
|
||||
|
||||
For an optimal result, the draw-engine creates two aa-circles on top of each other, while
|
||||
one is rotated by 90 degrees. This helps to make the circle look more symetric, which is
|
||||
not can be a problem when using only a single circle character.
|
||||
"""
|
||||
|
||||
radius_to_char_fine: dict = None # dict to map radius to font circle character
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.aa_circle_canvas_ids = set()
|
||||
self._aa_circle_canvas_ids = set()
|
||||
|
||||
@classmethod
|
||||
def init_font_character_mapping(cls):
|
||||
@ -43,7 +64,7 @@ class CTkCanvas(tkinter.Canvas):
|
||||
else:
|
||||
cls.radius_to_char_fine = radius_to_char_fine_windows_10
|
||||
|
||||
def get_char_from_radius(self, radius: int) -> str:
|
||||
def _get_char_from_radius(self, radius: int) -> str:
|
||||
if radius >= 20:
|
||||
return "A"
|
||||
else:
|
||||
@ -52,10 +73,10 @@ class CTkCanvas(tkinter.Canvas):
|
||||
def create_aa_circle(self, x_pos: int, y_pos: int, radius: int, angle: int = 0, fill: str = "white",
|
||||
tags: Union[str, Tuple[str, ...]] = "", anchor: str = tkinter.CENTER) -> int:
|
||||
# create a circle with a font element
|
||||
circle_1 = self.create_text(x_pos, y_pos, text=self.get_char_from_radius(radius), anchor=anchor, fill=fill,
|
||||
circle_1 = self.create_text(x_pos, y_pos, text=self._get_char_from_radius(radius), anchor=anchor, fill=fill,
|
||||
font=("CustomTkinter_shapes_font", -radius * 2), tags=tags, angle=angle)
|
||||
self.addtag_withtag("ctk_aa_circle_font_element", circle_1)
|
||||
self.aa_circle_canvas_ids.add(circle_1)
|
||||
self._aa_circle_canvas_ids.add(circle_1)
|
||||
|
||||
return circle_1
|
||||
|
||||
@ -66,13 +87,13 @@ class CTkCanvas(tkinter.Canvas):
|
||||
super().coords(coords_id, *args[:2])
|
||||
|
||||
if len(args) == 3:
|
||||
super().itemconfigure(coords_id, font=("CustomTkinter_shapes_font", -int(args[2]) * 2), text=self.get_char_from_radius(args[2]))
|
||||
super().itemconfigure(coords_id, font=("CustomTkinter_shapes_font", -int(args[2]) * 2), text=self._get_char_from_radius(args[2]))
|
||||
|
||||
elif type(tag_or_id) == int and tag_or_id in self.aa_circle_canvas_ids:
|
||||
elif type(tag_or_id) == int and tag_or_id in self._aa_circle_canvas_ids:
|
||||
super().coords(tag_or_id, *args[:2])
|
||||
|
||||
if len(args) == 3:
|
||||
super().itemconfigure(tag_or_id, font=("CustomTkinter_shapes_font", -args[2] * 2), text=self.get_char_from_radius(args[2]))
|
||||
super().itemconfigure(tag_or_id, font=("CustomTkinter_shapes_font", -args[2] * 2), text=self._get_char_from_radius(args[2]))
|
||||
|
||||
else:
|
||||
super().coords(tag_or_id, *args)
|
||||
@ -83,14 +104,14 @@ class CTkCanvas(tkinter.Canvas):
|
||||
del kwargs_except_outline["outline"]
|
||||
|
||||
if type(tag_or_id) == int:
|
||||
if tag_or_id in self.aa_circle_canvas_ids:
|
||||
if tag_or_id in self._aa_circle_canvas_ids:
|
||||
super().itemconfigure(tag_or_id, *args, **kwargs_except_outline)
|
||||
else:
|
||||
super().itemconfigure(tag_or_id, *args, **kwargs)
|
||||
else:
|
||||
configure_ids = self.find_withtag(tag_or_id)
|
||||
for configure_id in configure_ids:
|
||||
if configure_id in self.aa_circle_canvas_ids:
|
||||
if configure_id in self._aa_circle_canvas_ids:
|
||||
super().itemconfigure(configure_id, *args, **kwargs_except_outline)
|
||||
else:
|
||||
super().itemconfigure(configure_id, *args, **kwargs)
|
||||
|
@ -1,6 +1,6 @@
|
||||
import tkinter
|
||||
import sys
|
||||
from typing import Union
|
||||
from typing import Union, Tuple, Callable
|
||||
|
||||
from .ctk_canvas import CTkCanvas
|
||||
from ..theme_manager import ThemeManager
|
||||
@ -10,313 +10,316 @@ from .widget_base_class import CTkBaseClass
|
||||
|
||||
|
||||
class CTkCheckBox(CTkBaseClass):
|
||||
""" tkinter custom checkbox with border, rounded corners and hover effect """
|
||||
"""
|
||||
Checkbox with rounded corners, border, variable support and hover effect.
|
||||
For detailed information check out the documentation.
|
||||
"""
|
||||
|
||||
def __init__(self, *args,
|
||||
bg_color=None,
|
||||
fg_color="default_theme",
|
||||
hover_color="default_theme",
|
||||
border_color="default_theme",
|
||||
border_width="default_theme",
|
||||
checkmark_color="default_theme",
|
||||
width=24,
|
||||
height=24,
|
||||
corner_radius="default_theme",
|
||||
text_font="default_theme",
|
||||
text_color="default_theme",
|
||||
text="CTkCheckBox",
|
||||
text_color_disabled="default_theme",
|
||||
hover=True,
|
||||
command=None,
|
||||
state=tkinter.NORMAL,
|
||||
onvalue=1,
|
||||
offvalue=0,
|
||||
variable=None,
|
||||
textvariable=None,
|
||||
bg_color: Union[str, Tuple[str, str], None] = None,
|
||||
fg_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
hover_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
border_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
border_width: Union[int, str] = "default_theme",
|
||||
checkmark_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
width: int = 24,
|
||||
height: int = 24,
|
||||
corner_radius: Union[int, str] = "default_theme",
|
||||
text_font: any = "default_theme",
|
||||
text_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
text: str = "CTkCheckBox",
|
||||
text_color_disabled: Union[str, Tuple[str, str]] = "default_theme",
|
||||
hover: bool = True,
|
||||
command: Callable = None,
|
||||
state: str = tkinter.NORMAL,
|
||||
onvalue: Union[int, str] = 1,
|
||||
offvalue: Union[int, str] = 0,
|
||||
variable: tkinter.Variable = None,
|
||||
textvariable: tkinter.Variable = None,
|
||||
**kwargs):
|
||||
|
||||
# transfer basic functionality (bg_color, size, _appearance_mode, scaling) to CTkBaseClass
|
||||
# transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to CTkBaseClass
|
||||
super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs)
|
||||
|
||||
# color
|
||||
self.fg_color = ThemeManager.theme["color"]["button"] if fg_color == "default_theme" else fg_color
|
||||
self.hover_color = ThemeManager.theme["color"]["button_hover"] if hover_color == "default_theme" else hover_color
|
||||
self.border_color = ThemeManager.theme["color"]["checkbox_border"] if border_color == "default_theme" else border_color
|
||||
self.checkmark_color = ThemeManager.theme["color"]["checkmark"] if checkmark_color == "default_theme" else checkmark_color
|
||||
self._fg_color = ThemeManager.theme["color"]["button"] if fg_color == "default_theme" else fg_color
|
||||
self._hover_color = ThemeManager.theme["color"]["button_hover"] if hover_color == "default_theme" else hover_color
|
||||
self._border_color = ThemeManager.theme["color"]["checkbox_border"] if border_color == "default_theme" else border_color
|
||||
self._checkmark_color = ThemeManager.theme["color"]["checkmark"] if checkmark_color == "default_theme" else checkmark_color
|
||||
|
||||
# shape
|
||||
self.corner_radius = ThemeManager.theme["shape"]["checkbox_corner_radius"] if corner_radius == "default_theme" else corner_radius
|
||||
self.border_width = ThemeManager.theme["shape"]["checkbox_border_width"] if border_width == "default_theme" else border_width
|
||||
self._corner_radius = ThemeManager.theme["shape"]["checkbox_corner_radius"] if corner_radius == "default_theme" else corner_radius
|
||||
self._border_width = ThemeManager.theme["shape"]["checkbox_border_width"] if border_width == "default_theme" else border_width
|
||||
|
||||
# text
|
||||
self.text = text
|
||||
self.text_label: Union[tkinter.Label, None] = None
|
||||
self.text_color = ThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color
|
||||
self.text_color_disabled = ThemeManager.theme["color"]["text_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._text = text
|
||||
self._text_label: Union[tkinter.Label, None] = None
|
||||
self._text_color = ThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color
|
||||
self._text_color_disabled = ThemeManager.theme["color"]["text_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
|
||||
|
||||
# callback and hover functionality
|
||||
self.command = command
|
||||
self.state = state
|
||||
self.hover = hover
|
||||
self.check_state = False
|
||||
self._command = command
|
||||
self._state = state
|
||||
self._hover = hover
|
||||
self._check_state = False
|
||||
|
||||
self.onvalue = onvalue
|
||||
self.offvalue = offvalue
|
||||
self.variable: tkinter.Variable = variable
|
||||
self.variable_callback_blocked = False
|
||||
self.textvariable: tkinter.Variable = textvariable
|
||||
self.variable_callback_name = None
|
||||
self._onvalue = onvalue
|
||||
self._offvalue = offvalue
|
||||
self._variable: tkinter.Variable = variable
|
||||
self._variable_callback_blocked = False
|
||||
self._textvariable: tkinter.Variable = textvariable
|
||||
self._variable_callback_name = None
|
||||
|
||||
# configure grid system (1x3)
|
||||
self.grid_columnconfigure(0, weight=0)
|
||||
self.grid_columnconfigure(1, weight=0, minsize=self.apply_widget_scaling(6))
|
||||
self.grid_columnconfigure(1, weight=0, minsize=self._apply_widget_scaling(6))
|
||||
self.grid_columnconfigure(2, weight=1)
|
||||
self.grid_rowconfigure(0, weight=1)
|
||||
|
||||
self.bg_canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self.apply_widget_scaling(self._desired_width),
|
||||
height=self.apply_widget_scaling(self._desired_height))
|
||||
self.bg_canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=3, rowspan=1, sticky="nswe")
|
||||
self._bg_canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self._apply_widget_scaling(self._desired_width),
|
||||
height=self._apply_widget_scaling(self._desired_height))
|
||||
self._bg_canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=3, rowspan=1, sticky="nswe")
|
||||
|
||||
self.canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self.apply_widget_scaling(self._desired_width),
|
||||
height=self.apply_widget_scaling(self._desired_height))
|
||||
self.canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=1, rowspan=1)
|
||||
self.draw_engine = DrawEngine(self.canvas)
|
||||
self._canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self._apply_widget_scaling(self._desired_width),
|
||||
height=self._apply_widget_scaling(self._desired_height))
|
||||
self._canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=1, rowspan=1)
|
||||
self._draw_engine = DrawEngine(self._canvas)
|
||||
|
||||
self.canvas.bind("<Enter>", self.on_enter)
|
||||
self.canvas.bind("<Leave>", self.on_leave)
|
||||
self.canvas.bind("<Button-1>", self.toggle)
|
||||
self._canvas.bind("<Enter>", self._on_enter)
|
||||
self._canvas.bind("<Leave>", self._on_leave)
|
||||
self._canvas.bind("<Button-1>", self.toggle)
|
||||
|
||||
self.text_label = tkinter.Label(master=self,
|
||||
bd=0,
|
||||
text=self.text,
|
||||
justify=tkinter.LEFT,
|
||||
font=self.apply_font_scaling(self.text_font),
|
||||
textvariable=self.textvariable)
|
||||
self.text_label.grid(row=0, column=2, padx=0, pady=0, sticky="w")
|
||||
self.text_label["anchor"] = "w"
|
||||
self._text_label = tkinter.Label(master=self,
|
||||
bd=0,
|
||||
text=self._text,
|
||||
justify=tkinter.LEFT,
|
||||
font=self._apply_font_scaling(self._text_font),
|
||||
textvariable=self._textvariable)
|
||||
self._text_label.grid(row=0, column=2, padx=0, pady=0, sticky="w")
|
||||
self._text_label["anchor"] = "w"
|
||||
|
||||
self.text_label.bind("<Enter>", self.on_enter)
|
||||
self.text_label.bind("<Leave>", self.on_leave)
|
||||
self.text_label.bind("<Button-1>", self.toggle)
|
||||
self._text_label.bind("<Enter>", self._on_enter)
|
||||
self._text_label.bind("<Leave>", self._on_leave)
|
||||
self._text_label.bind("<Button-1>", self.toggle)
|
||||
|
||||
# register variable callback and set state according to variable
|
||||
if self.variable is not None and self.variable != "":
|
||||
self.variable_callback_name = self.variable.trace_add("write", self.variable_callback)
|
||||
self.check_state = True if variable.get() == self.onvalue else False
|
||||
if self._variable is not None and self._variable != "":
|
||||
self._variable_callback_name = self._variable.trace_add("write", self._variable_callback)
|
||||
self._check_state = True if self._variable.get() == self._onvalue else False
|
||||
|
||||
self.draw() # initial draw
|
||||
self.set_cursor()
|
||||
self._draw() # initial draw
|
||||
self._set_cursor()
|
||||
|
||||
def set_scaling(self, *args, **kwargs):
|
||||
super().set_scaling(*args, **kwargs)
|
||||
def _set_scaling(self, *args, **kwargs):
|
||||
super()._set_scaling(*args, **kwargs)
|
||||
|
||||
self.grid_columnconfigure(1, weight=0, minsize=self.apply_widget_scaling(6))
|
||||
self.text_label.configure(font=self.apply_font_scaling(self.text_font))
|
||||
self.grid_columnconfigure(1, weight=0, minsize=self._apply_widget_scaling(6))
|
||||
self._text_label.configure(font=self._apply_font_scaling(self._text_font))
|
||||
|
||||
self.canvas.delete("checkmark")
|
||||
self.bg_canvas.configure(width=self.apply_widget_scaling(self._desired_width), height=self.apply_widget_scaling(self._desired_height))
|
||||
self.canvas.configure(width=self.apply_widget_scaling(self._desired_width), height=self.apply_widget_scaling(self._desired_height))
|
||||
self.draw()
|
||||
self._canvas.delete("checkmark")
|
||||
self._bg_canvas.configure(width=self._apply_widget_scaling(self._desired_width), height=self._apply_widget_scaling(self._desired_height))
|
||||
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), height=self._apply_widget_scaling(self._desired_height))
|
||||
self._draw()
|
||||
|
||||
def destroy(self):
|
||||
if self.variable is not None:
|
||||
self.variable.trace_remove("write", self.variable_callback_name)
|
||||
if self._variable is not None:
|
||||
self._variable.trace_remove("write", self._variable_callback_name)
|
||||
|
||||
super().destroy()
|
||||
|
||||
def draw(self, no_color_updates=False):
|
||||
requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.apply_widget_scaling(self._current_width),
|
||||
self.apply_widget_scaling(self._current_height),
|
||||
self.apply_widget_scaling(self.corner_radius),
|
||||
self.apply_widget_scaling(self.border_width))
|
||||
def _draw(self, no_color_updates=False):
|
||||
requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._current_width),
|
||||
self._apply_widget_scaling(self._current_height),
|
||||
self._apply_widget_scaling(self._corner_radius),
|
||||
self._apply_widget_scaling(self._border_width))
|
||||
|
||||
if self.check_state is True:
|
||||
self.draw_engine.draw_checkmark(self.apply_widget_scaling(self._current_width),
|
||||
self.apply_widget_scaling(self._current_height),
|
||||
self.apply_widget_scaling(self._current_height * 0.58))
|
||||
if self._check_state is True:
|
||||
self._draw_engine.draw_checkmark(self._apply_widget_scaling(self._current_width),
|
||||
self._apply_widget_scaling(self._current_height),
|
||||
self._apply_widget_scaling(self._current_height * 0.58))
|
||||
else:
|
||||
self.canvas.delete("checkmark")
|
||||
self._canvas.delete("checkmark")
|
||||
|
||||
self.bg_canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
|
||||
self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
|
||||
self._bg_canvas.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode))
|
||||
self._canvas.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode))
|
||||
|
||||
if self.check_state is True:
|
||||
self.canvas.itemconfig("inner_parts",
|
||||
outline=ThemeManager.single_color(self.fg_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(self.fg_color, self._appearance_mode))
|
||||
self.canvas.itemconfig("border_parts",
|
||||
outline=ThemeManager.single_color(self.fg_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(self.fg_color, self._appearance_mode))
|
||||
if self._check_state is True:
|
||||
self._canvas.itemconfig("inner_parts",
|
||||
outline=ThemeManager.single_color(self._fg_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(self._fg_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("border_parts",
|
||||
outline=ThemeManager.single_color(self._fg_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(self._fg_color, self._appearance_mode))
|
||||
|
||||
if "create_line" in self.canvas.gettags("checkmark"):
|
||||
self.canvas.itemconfig("checkmark", fill=ThemeManager.single_color(self.checkmark_color, self._appearance_mode))
|
||||
if "create_line" in self._canvas.gettags("checkmark"):
|
||||
self._canvas.itemconfig("checkmark", fill=ThemeManager.single_color(self._checkmark_color, self._appearance_mode))
|
||||
else:
|
||||
self.canvas.itemconfig("checkmark", fill=ThemeManager.single_color(self.checkmark_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("checkmark", fill=ThemeManager.single_color(self._checkmark_color, self._appearance_mode))
|
||||
else:
|
||||
self.canvas.itemconfig("inner_parts",
|
||||
outline=ThemeManager.single_color(self.bg_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(self.bg_color, self._appearance_mode))
|
||||
self.canvas.itemconfig("border_parts",
|
||||
outline=ThemeManager.single_color(self.border_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(self.border_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("inner_parts",
|
||||
outline=ThemeManager.single_color(self._bg_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(self._bg_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("border_parts",
|
||||
outline=ThemeManager.single_color(self._border_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(self._border_color, self._appearance_mode))
|
||||
|
||||
if self.state == tkinter.DISABLED:
|
||||
self.text_label.configure(fg=(ThemeManager.single_color(self.text_color_disabled, self._appearance_mode)))
|
||||
if self._state == tkinter.DISABLED:
|
||||
self._text_label.configure(fg=(ThemeManager.single_color(self._text_color_disabled, self._appearance_mode)))
|
||||
else:
|
||||
self.text_label.configure(fg=ThemeManager.single_color(self.text_color, self._appearance_mode))
|
||||
self._text_label.configure(fg=ThemeManager.single_color(self._text_color, self._appearance_mode))
|
||||
|
||||
self.text_label.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
|
||||
self._text_label.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode))
|
||||
|
||||
def configure(self, require_redraw=False, **kwargs):
|
||||
if "text" in kwargs:
|
||||
self.text = kwargs.pop("text")
|
||||
self.text_label.configure(text=self.text)
|
||||
self._text = kwargs.pop("text")
|
||||
self._text_label.configure(text=self._text)
|
||||
|
||||
if "text_font" in kwargs:
|
||||
self.text_font = kwargs.pop("text_font")
|
||||
if self.text_label is not None:
|
||||
self.text_label.configure(font=self.apply_font_scaling(self.text_font))
|
||||
self._text_font = kwargs.pop("text_font")
|
||||
if self._text_label is not None:
|
||||
self._text_label.configure(font=self._apply_font_scaling(self._text_font))
|
||||
|
||||
if "state" in kwargs:
|
||||
self.state = kwargs.pop("state")
|
||||
self.set_cursor()
|
||||
self._state = kwargs.pop("state")
|
||||
self._set_cursor()
|
||||
require_redraw = True
|
||||
|
||||
if "fg_color" in kwargs:
|
||||
self.fg_color = kwargs.pop("fg_color")
|
||||
self._fg_color = kwargs.pop("fg_color")
|
||||
require_redraw = True
|
||||
|
||||
if "hover_color" in kwargs:
|
||||
self.hover_color = kwargs.pop("hover_color")
|
||||
self._hover_color = kwargs.pop("hover_color")
|
||||
require_redraw = True
|
||||
|
||||
if "text_color" in kwargs:
|
||||
self.text_color = kwargs.pop("text_color")
|
||||
self._text_color = kwargs.pop("text_color")
|
||||
require_redraw = True
|
||||
|
||||
if "border_color" in kwargs:
|
||||
self.border_color = kwargs.pop("border_color")
|
||||
self._border_color = kwargs.pop("border_color")
|
||||
require_redraw = True
|
||||
|
||||
if "command" in kwargs:
|
||||
self.command = kwargs.pop("command")
|
||||
self._command = kwargs.pop("command")
|
||||
|
||||
if "textvariable" in kwargs:
|
||||
self.textvariable = kwargs.pop("textvariable")
|
||||
self.text_label.configure(textvariable=self.textvariable)
|
||||
self._textvariable = kwargs.pop("textvariable")
|
||||
self._text_label.configure(textvariable=self._textvariable)
|
||||
|
||||
if "variable" in kwargs:
|
||||
if self.variable is not None and self.variable != "":
|
||||
self.variable.trace_remove("write", self.variable_callback_name) # remove old variable callback
|
||||
if self._variable is not None and self._variable != "":
|
||||
self._variable.trace_remove("write", self._variable_callback_name) # remove old variable callback
|
||||
|
||||
self.variable = kwargs.pop("variable")
|
||||
self._variable = kwargs.pop("variable")
|
||||
|
||||
if self.variable is not None and self.variable != "":
|
||||
self.variable_callback_name = self.variable.trace_add("write", self.variable_callback)
|
||||
self.check_state = True if self.variable.get() == self.onvalue else False
|
||||
if self._variable is not None and self._variable != "":
|
||||
self._variable_callback_name = self._variable.trace_add("write", self._variable_callback)
|
||||
self._check_state = True if self._variable.get() == self._onvalue else False
|
||||
require_redraw = True
|
||||
|
||||
super().configure(require_redraw=require_redraw, **kwargs)
|
||||
|
||||
def set_cursor(self):
|
||||
def _set_cursor(self):
|
||||
if Settings.cursor_manipulation_enabled:
|
||||
if self.state == tkinter.DISABLED:
|
||||
if self._state == tkinter.DISABLED:
|
||||
if sys.platform == "darwin" and Settings.cursor_manipulation_enabled:
|
||||
self.canvas.configure(cursor="arrow")
|
||||
if self.text_label is not None:
|
||||
self.text_label.configure(cursor="arrow")
|
||||
self._canvas.configure(cursor="arrow")
|
||||
if self._text_label is not None:
|
||||
self._text_label.configure(cursor="arrow")
|
||||
elif sys.platform.startswith("win") and Settings.cursor_manipulation_enabled:
|
||||
self.canvas.configure(cursor="arrow")
|
||||
if self.text_label is not None:
|
||||
self.text_label.configure(cursor="arrow")
|
||||
self._canvas.configure(cursor="arrow")
|
||||
if self._text_label is not None:
|
||||
self._text_label.configure(cursor="arrow")
|
||||
|
||||
elif self.state == tkinter.NORMAL:
|
||||
elif self._state == tkinter.NORMAL:
|
||||
if sys.platform == "darwin" and Settings.cursor_manipulation_enabled:
|
||||
self.canvas.configure(cursor="pointinghand")
|
||||
if self.text_label is not None:
|
||||
self.text_label.configure(cursor="pointinghand")
|
||||
self._canvas.configure(cursor="pointinghand")
|
||||
if self._text_label is not None:
|
||||
self._text_label.configure(cursor="pointinghand")
|
||||
elif sys.platform.startswith("win") and Settings.cursor_manipulation_enabled:
|
||||
self.canvas.configure(cursor="hand2")
|
||||
if self.text_label is not None:
|
||||
self.text_label.configure(cursor="hand2")
|
||||
self._canvas.configure(cursor="hand2")
|
||||
if self._text_label is not None:
|
||||
self._text_label.configure(cursor="hand2")
|
||||
|
||||
def on_enter(self, event=0):
|
||||
if self.hover is True and self.state == tkinter.NORMAL:
|
||||
if self.check_state is True:
|
||||
self.canvas.itemconfig("inner_parts",
|
||||
fill=ThemeManager.single_color(self.hover_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.hover_color, self._appearance_mode))
|
||||
self.canvas.itemconfig("border_parts",
|
||||
fill=ThemeManager.single_color(self.hover_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.hover_color, self._appearance_mode))
|
||||
def _on_enter(self, event=0):
|
||||
if self._hover is True and self._state == tkinter.NORMAL:
|
||||
if self._check_state is True:
|
||||
self._canvas.itemconfig("inner_parts",
|
||||
fill=ThemeManager.single_color(self._hover_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._hover_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("border_parts",
|
||||
fill=ThemeManager.single_color(self._hover_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._hover_color, self._appearance_mode))
|
||||
else:
|
||||
self.canvas.itemconfig("inner_parts",
|
||||
fill=ThemeManager.single_color(self.hover_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.hover_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("inner_parts",
|
||||
fill=ThemeManager.single_color(self._hover_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._hover_color, self._appearance_mode))
|
||||
|
||||
def on_leave(self, event=0):
|
||||
if self.hover is True:
|
||||
if self.check_state is True:
|
||||
self.canvas.itemconfig("inner_parts",
|
||||
fill=ThemeManager.single_color(self.fg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.fg_color, self._appearance_mode))
|
||||
self.canvas.itemconfig("border_parts",
|
||||
fill=ThemeManager.single_color(self.fg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.fg_color, self._appearance_mode))
|
||||
def _on_leave(self, event=0):
|
||||
if self._hover is True:
|
||||
if self._check_state is True:
|
||||
self._canvas.itemconfig("inner_parts",
|
||||
fill=ThemeManager.single_color(self._fg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._fg_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("border_parts",
|
||||
fill=ThemeManager.single_color(self._fg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._fg_color, self._appearance_mode))
|
||||
else:
|
||||
self.canvas.itemconfig("inner_parts",
|
||||
fill=ThemeManager.single_color(self.bg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.bg_color, self._appearance_mode))
|
||||
self.canvas.itemconfig("border_parts",
|
||||
fill=ThemeManager.single_color(self.border_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.border_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("inner_parts",
|
||||
fill=ThemeManager.single_color(self._bg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._bg_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("border_parts",
|
||||
fill=ThemeManager.single_color(self._border_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._border_color, self._appearance_mode))
|
||||
|
||||
def variable_callback(self, var_name, index, mode):
|
||||
if not self.variable_callback_blocked:
|
||||
if self.variable.get() == self.onvalue:
|
||||
def _variable_callback(self, var_name, index, mode):
|
||||
if not self._variable_callback_blocked:
|
||||
if self._variable.get() == self._onvalue:
|
||||
self.select(from_variable_callback=True)
|
||||
elif self.variable.get() == self.offvalue:
|
||||
elif self._variable.get() == self._offvalue:
|
||||
self.deselect(from_variable_callback=True)
|
||||
|
||||
def toggle(self, event=0):
|
||||
if self.state == tkinter.NORMAL:
|
||||
if self.check_state is True:
|
||||
self.check_state = False
|
||||
self.draw()
|
||||
if self._state == tkinter.NORMAL:
|
||||
if self._check_state is True:
|
||||
self._check_state = False
|
||||
self._draw()
|
||||
else:
|
||||
self.check_state = True
|
||||
self.draw()
|
||||
self._check_state = True
|
||||
self._draw()
|
||||
|
||||
if self.variable is not None:
|
||||
self.variable_callback_blocked = True
|
||||
self.variable.set(self.onvalue if self.check_state is True else self.offvalue)
|
||||
self.variable_callback_blocked = False
|
||||
if self._variable is not None:
|
||||
self._variable_callback_blocked = True
|
||||
self._variable.set(self._onvalue if self._check_state is True else self._offvalue)
|
||||
self._variable_callback_blocked = False
|
||||
|
||||
if self.command is not None:
|
||||
self.command()
|
||||
if self._command is not None:
|
||||
self._command()
|
||||
|
||||
def select(self, from_variable_callback=False):
|
||||
self.check_state = True
|
||||
self.draw()
|
||||
self._check_state = True
|
||||
self._draw()
|
||||
|
||||
if self.variable is not None and not from_variable_callback:
|
||||
self.variable_callback_blocked = True
|
||||
self.variable.set(self.onvalue)
|
||||
self.variable_callback_blocked = False
|
||||
if self._variable is not None and not from_variable_callback:
|
||||
self._variable_callback_blocked = True
|
||||
self._variable.set(self._onvalue)
|
||||
self._variable_callback_blocked = False
|
||||
|
||||
def deselect(self, from_variable_callback=False):
|
||||
self.check_state = False
|
||||
self.draw()
|
||||
self._check_state = False
|
||||
self._draw()
|
||||
|
||||
if self.variable is not None and not from_variable_callback:
|
||||
self.variable_callback_blocked = True
|
||||
self.variable.set(self.offvalue)
|
||||
self.variable_callback_blocked = False
|
||||
if self._variable is not None and not from_variable_callback:
|
||||
self._variable_callback_blocked = True
|
||||
self._variable.set(self._offvalue)
|
||||
self._variable_callback_blocked = False
|
||||
|
||||
def get(self):
|
||||
return self.onvalue if self.check_state is True else self.offvalue
|
||||
def get(self) -> Union[int, str]:
|
||||
return self._onvalue if self._check_state is True else self._offvalue
|
||||
|
@ -1,5 +1,6 @@
|
||||
import tkinter
|
||||
import sys
|
||||
from typing import Union, Tuple, Callable, List
|
||||
|
||||
from .dropdown_menu import DropdownMenu
|
||||
from .ctk_canvas import CTkCanvas
|
||||
@ -10,288 +11,293 @@ from .widget_base_class import CTkBaseClass
|
||||
|
||||
|
||||
class CTkComboBox(CTkBaseClass):
|
||||
"""
|
||||
Combobox with dropdown menu, rounded corners, border, variable support.
|
||||
For detailed information check out the documentation.
|
||||
"""
|
||||
|
||||
def __init__(self, *args,
|
||||
bg_color=None,
|
||||
fg_color="default_theme",
|
||||
border_color="default_theme",
|
||||
button_color="default_theme",
|
||||
button_hover_color="default_theme",
|
||||
dropdown_color="default_theme",
|
||||
dropdown_hover_color="default_theme",
|
||||
dropdown_text_color="default_theme",
|
||||
variable=None,
|
||||
values=None,
|
||||
command=None,
|
||||
width=140,
|
||||
height=28,
|
||||
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,
|
||||
state=tkinter.NORMAL,
|
||||
bg_color: Union[str, Tuple[str, str], None] = None,
|
||||
fg_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
border_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
button_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
button_hover_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
dropdown_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
dropdown_hover_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
dropdown_text_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
variable: tkinter.Variable = None,
|
||||
values: List[str] = None,
|
||||
command: Callable = None,
|
||||
width: int = 140,
|
||||
height: int = 28,
|
||||
corner_radius: int = "default_theme",
|
||||
border_width: int = "default_theme",
|
||||
text_font: any = "default_theme",
|
||||
dropdown_text_font: any = "default_theme",
|
||||
text_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
text_color_disabled: Union[str, Tuple[str, str]] = "default_theme",
|
||||
hover: bool = True,
|
||||
state: str = tkinter.NORMAL,
|
||||
**kwargs):
|
||||
|
||||
# transfer basic functionality (bg_color, size, _appearance_mode, scaling) to CTkBaseClass
|
||||
# transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to CTkBaseClass
|
||||
super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs)
|
||||
|
||||
# color variables
|
||||
self.fg_color = ThemeManager.theme["color"]["entry"] if fg_color == "default_theme" else fg_color
|
||||
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._fg_color = ThemeManager.theme["color"]["entry"] if fg_color == "default_theme" else fg_color
|
||||
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
|
||||
|
||||
# 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
|
||||
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_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._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
|
||||
|
||||
# callback and hover functionality
|
||||
self.command = command
|
||||
self.textvariable = variable
|
||||
self.state = state
|
||||
self.hover = hover
|
||||
self._command = command
|
||||
self._textvariable = variable
|
||||
self._state = state
|
||||
self._hover = hover
|
||||
|
||||
if values is None:
|
||||
self.values = ["CTkComboBox"]
|
||||
self._values = ["CTkComboBox"]
|
||||
else:
|
||||
self.values = values
|
||||
self._values = values
|
||||
|
||||
self.dropdown_menu = DropdownMenu(master=self,
|
||||
values=self.values,
|
||||
command=self.dropdown_callback,
|
||||
fg_color=dropdown_color,
|
||||
hover_color=dropdown_hover_color,
|
||||
text_color=dropdown_text_color,
|
||||
text_font=dropdown_text_font)
|
||||
self._dropdown_menu = DropdownMenu(master=self,
|
||||
values=self._values,
|
||||
command=self._dropdown_callback,
|
||||
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)
|
||||
self.grid_columnconfigure(0, weight=1)
|
||||
|
||||
self.canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self.apply_widget_scaling(self._desired_width),
|
||||
height=self.apply_widget_scaling(self._desired_height))
|
||||
self.canvas.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="nsew")
|
||||
self.draw_engine = DrawEngine(self.canvas)
|
||||
self._canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self._apply_widget_scaling(self._desired_width),
|
||||
height=self._apply_widget_scaling(self._desired_height))
|
||||
self._canvas.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="nsew")
|
||||
self.draw_engine = DrawEngine(self._canvas)
|
||||
|
||||
self.entry = tkinter.Entry(master=self,
|
||||
state=self.state,
|
||||
width=1,
|
||||
bd=0,
|
||||
highlightthickness=0,
|
||||
font=self.apply_font_scaling(self.text_font))
|
||||
self._entry = tkinter.Entry(master=self,
|
||||
state=self._state,
|
||||
width=1,
|
||||
bd=0,
|
||||
highlightthickness=0,
|
||||
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=(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._entry.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="ew",
|
||||
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))))
|
||||
|
||||
# insert default value
|
||||
if len(self.values) > 0:
|
||||
self.entry.insert(0, self.values[0])
|
||||
if len(self._values) > 0:
|
||||
self._entry.insert(0, self._values[0])
|
||||
else:
|
||||
self.entry.insert(0, "CTkComboBox")
|
||||
self._entry.insert(0, "CTkComboBox")
|
||||
|
||||
self.draw() # initial draw
|
||||
self._draw() # initial draw
|
||||
|
||||
# event bindings
|
||||
self.canvas.tag_bind("right_parts", "<Enter>", self.on_enter)
|
||||
self.canvas.tag_bind("dropdown_arrow", "<Enter>", self.on_enter)
|
||||
self.canvas.tag_bind("right_parts", "<Leave>", self.on_leave)
|
||||
self.canvas.tag_bind("dropdown_arrow", "<Leave>", self.on_leave)
|
||||
self.canvas.tag_bind("right_parts", "<Button-1>", self.clicked)
|
||||
self.canvas.tag_bind("dropdown_arrow", "<Button-1>", self.clicked)
|
||||
self.bind('<Configure>', self.update_dimensions_event)
|
||||
self._canvas.tag_bind("right_parts", "<Enter>", self._on_enter)
|
||||
self._canvas.tag_bind("dropdown_arrow", "<Enter>", self._on_enter)
|
||||
self._canvas.tag_bind("right_parts", "<Leave>", self._on_leave)
|
||||
self._canvas.tag_bind("dropdown_arrow", "<Leave>", self._on_leave)
|
||||
self._canvas.tag_bind("right_parts", "<Button-1>", self._clicked)
|
||||
self._canvas.tag_bind("dropdown_arrow", "<Button-1>", self._clicked)
|
||||
self.bind('<Configure>', self._update_dimensions_event)
|
||||
|
||||
if self.textvariable is not None:
|
||||
self.entry.configure(textvariable=self.textvariable)
|
||||
if self._textvariable is not None:
|
||||
self._entry.configure(textvariable=self._textvariable)
|
||||
|
||||
def set_scaling(self, *args, **kwargs):
|
||||
super().set_scaling(*args, **kwargs)
|
||||
def _set_scaling(self, *args, **kwargs):
|
||||
super()._set_scaling(*args, **kwargs)
|
||||
|
||||
# change entry font size and grid padding
|
||||
left_section_width = self._current_width - self._current_height
|
||||
self.entry.configure(font=self.apply_font_scaling(self.text_font))
|
||||
self.entry.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="ew",
|
||||
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._entry.configure(font=self._apply_font_scaling(self._text_font))
|
||||
self._entry.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="ew",
|
||||
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.canvas.configure(width=self.apply_widget_scaling(self._desired_width),
|
||||
height=self.apply_widget_scaling(self._desired_height))
|
||||
self.draw()
|
||||
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
|
||||
height=self._apply_widget_scaling(self._desired_height))
|
||||
self._draw()
|
||||
|
||||
def set_dimensions(self, width: int = None, height: int = None):
|
||||
super().set_dimensions(width, height)
|
||||
def _set_dimensions(self, width: int = None, height: int = None):
|
||||
super()._set_dimensions(width, height)
|
||||
|
||||
self.canvas.configure(width=self.apply_widget_scaling(self._desired_width),
|
||||
height=self.apply_widget_scaling(self._desired_height))
|
||||
self.draw()
|
||||
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
|
||||
height=self._apply_widget_scaling(self._desired_height))
|
||||
self._draw()
|
||||
|
||||
def draw(self, no_color_updates=False):
|
||||
def _draw(self, no_color_updates=False):
|
||||
left_section_width = self._current_width - self._current_height
|
||||
requires_recoloring = self.draw_engine.draw_rounded_rect_with_border_vertical_split(self.apply_widget_scaling(self._current_width),
|
||||
self.apply_widget_scaling(self._current_height),
|
||||
self.apply_widget_scaling(self.corner_radius),
|
||||
self.apply_widget_scaling(self.border_width),
|
||||
self.apply_widget_scaling(left_section_width))
|
||||
requires_recoloring = self.draw_engine.draw_rounded_rect_with_border_vertical_split(self._apply_widget_scaling(self._current_width),
|
||||
self._apply_widget_scaling(self._current_height),
|
||||
self._apply_widget_scaling(self._corner_radius),
|
||||
self._apply_widget_scaling(self._border_width),
|
||||
self._apply_widget_scaling(left_section_width))
|
||||
|
||||
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))
|
||||
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 no_color_updates is False or requires_recoloring or requires_recoloring_2:
|
||||
|
||||
self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
|
||||
self._canvas.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode))
|
||||
|
||||
self.canvas.itemconfig("inner_parts_left",
|
||||
outline=ThemeManager.single_color(self.fg_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(self.fg_color, self._appearance_mode))
|
||||
self.canvas.itemconfig("border_parts_left",
|
||||
outline=ThemeManager.single_color(self.border_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(self.border_color, self._appearance_mode))
|
||||
self.canvas.itemconfig("inner_parts_right",
|
||||
outline=ThemeManager.single_color(self.border_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(self.border_color, self._appearance_mode))
|
||||
self.canvas.itemconfig("border_parts_right",
|
||||
outline=ThemeManager.single_color(self.border_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(self.border_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("inner_parts_left",
|
||||
outline=ThemeManager.single_color(self._fg_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(self._fg_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("border_parts_left",
|
||||
outline=ThemeManager.single_color(self._border_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(self._border_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("inner_parts_right",
|
||||
outline=ThemeManager.single_color(self._border_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(self._border_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("border_parts_right",
|
||||
outline=ThemeManager.single_color(self._border_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(self._border_color, self._appearance_mode))
|
||||
|
||||
self.entry.configure(bg=ThemeManager.single_color(self.fg_color, self._appearance_mode),
|
||||
fg=ThemeManager.single_color(self.text_color, self._appearance_mode),
|
||||
disabledforeground=ThemeManager.single_color(self.text_color_disabled, self._appearance_mode),
|
||||
disabledbackground=ThemeManager.single_color(self.fg_color, self._appearance_mode))
|
||||
self._entry.configure(bg=ThemeManager.single_color(self._fg_color, self._appearance_mode),
|
||||
fg=ThemeManager.single_color(self._text_color, self._appearance_mode),
|
||||
disabledforeground=ThemeManager.single_color(self._text_color_disabled, self._appearance_mode),
|
||||
disabledbackground=ThemeManager.single_color(self._fg_color, self._appearance_mode))
|
||||
|
||||
if self.state == tkinter.DISABLED:
|
||||
self.canvas.itemconfig("dropdown_arrow",
|
||||
fill=ThemeManager.single_color(self.text_color_disabled, self._appearance_mode))
|
||||
if self._state == tkinter.DISABLED:
|
||||
self._canvas.itemconfig("dropdown_arrow",
|
||||
fill=ThemeManager.single_color(self._text_color_disabled, self._appearance_mode))
|
||||
else:
|
||||
self.canvas.itemconfig("dropdown_arrow",
|
||||
fill=ThemeManager.single_color(self.text_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("dropdown_arrow",
|
||||
fill=ThemeManager.single_color(self._text_color, self._appearance_mode))
|
||||
|
||||
def open_dropdown_menu(self):
|
||||
self.dropdown_menu.open(self.winfo_rootx(),
|
||||
self.winfo_rooty() + self.apply_widget_scaling(self._current_height + 0))
|
||||
def _open_dropdown_menu(self):
|
||||
self._dropdown_menu.open(self.winfo_rootx(),
|
||||
self.winfo_rooty() + self._apply_widget_scaling(self._current_height + 0))
|
||||
|
||||
def configure(self, require_redraw=False, **kwargs):
|
||||
if "state" in kwargs:
|
||||
self.state = kwargs.pop("state")
|
||||
self.entry.configure(state=self.state)
|
||||
self._state = kwargs.pop("state")
|
||||
self._entry.configure(state=self._state)
|
||||
require_redraw = True
|
||||
|
||||
if "fg_color" in kwargs:
|
||||
self.fg_color = kwargs.pop("fg_color")
|
||||
self._fg_color = kwargs.pop("fg_color")
|
||||
require_redraw = True
|
||||
|
||||
if "button_color" in kwargs:
|
||||
self.button_color = kwargs.pop("button_color")
|
||||
self._button_color = kwargs.pop("button_color")
|
||||
require_redraw = True
|
||||
|
||||
if "button_hover_color" in kwargs:
|
||||
self.button_hover_color = kwargs.pop("button_hover_color")
|
||||
self._button_hover_color = kwargs.pop("button_hover_color")
|
||||
require_redraw = True
|
||||
|
||||
if "text_color" in kwargs:
|
||||
self.text_color = kwargs.pop("text_color")
|
||||
self._text_color = kwargs.pop("text_color")
|
||||
require_redraw = True
|
||||
|
||||
if "text_font" in kwargs:
|
||||
self.text_font = kwargs.pop("text_font")
|
||||
self.entry.configure(font=self.apply_font_scaling(self.text_font))
|
||||
self._text_font = kwargs.pop("text_font")
|
||||
self._entry.configure(font=self._apply_font_scaling(self._text_font))
|
||||
|
||||
if "command" in kwargs:
|
||||
self.command = kwargs.pop("command")
|
||||
self._command = kwargs.pop("command")
|
||||
|
||||
if "variable" in kwargs:
|
||||
self.textvariable = kwargs.pop("variable")
|
||||
self.entry.configure(textvariable=self.textvariable)
|
||||
self._textvariable = kwargs.pop("variable")
|
||||
self._entry.configure(textvariable=self._textvariable)
|
||||
|
||||
if "width" in kwargs:
|
||||
self.set_dimensions(width=kwargs.pop("width"))
|
||||
self._set_dimensions(width=kwargs.pop("width"))
|
||||
|
||||
if "height" in kwargs:
|
||||
self.set_dimensions(height=kwargs.pop("height"))
|
||||
self._set_dimensions(height=kwargs.pop("height"))
|
||||
|
||||
if "values" in kwargs:
|
||||
self.values = kwargs.pop("values")
|
||||
self.dropdown_menu.configure(values=self.values)
|
||||
self._values = kwargs.pop("values")
|
||||
self._dropdown_menu.configure(values=self._values)
|
||||
|
||||
if "dropdown_color" in kwargs:
|
||||
self.dropdown_menu.configure(fg_color=kwargs.pop("dropdown_color"))
|
||||
self._dropdown_menu.configure(fg_color=kwargs.pop("dropdown_color"))
|
||||
|
||||
if "dropdown_hover_color" in kwargs:
|
||||
self.dropdown_menu.configure(hover_color=kwargs.pop("dropdown_hover_color"))
|
||||
self._dropdown_menu.configure(hover_color=kwargs.pop("dropdown_hover_color"))
|
||||
|
||||
if "dropdown_text_color" in kwargs:
|
||||
self.dropdown_menu.configure(text_color=kwargs.pop("dropdown_text_color"))
|
||||
self._dropdown_menu.configure(text_color=kwargs.pop("dropdown_text_color"))
|
||||
|
||||
if "dropdown_text_font" in kwargs:
|
||||
self.dropdown_menu.configure(text_font=kwargs.pop("dropdown_text_font"))
|
||||
self._dropdown_menu.configure(text_font=kwargs.pop("dropdown_text_font"))
|
||||
|
||||
super().configure(require_redraw=require_redraw, **kwargs)
|
||||
|
||||
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.canvas.configure(cursor="pointinghand")
|
||||
elif sys.platform.startswith("win") and len(self.values) > 0 and Settings.cursor_manipulation_enabled:
|
||||
self.canvas.configure(cursor="hand2")
|
||||
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._canvas.configure(cursor="pointinghand")
|
||||
elif sys.platform.startswith("win") and len(self._values) > 0 and Settings.cursor_manipulation_enabled:
|
||||
self._canvas.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))
|
||||
self.canvas.itemconfig("border_parts_right",
|
||||
outline=ThemeManager.single_color(self.button_hover_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(self.button_hover_color, self._appearance_mode))
|
||||
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))
|
||||
self._canvas.itemconfig("border_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):
|
||||
if self.hover is True:
|
||||
if sys.platform == "darwin" and len(self.values) > 0 and Settings.cursor_manipulation_enabled:
|
||||
self.canvas.configure(cursor="arrow")
|
||||
elif sys.platform.startswith("win") and len(self.values) > 0 and Settings.cursor_manipulation_enabled:
|
||||
self.canvas.configure(cursor="arrow")
|
||||
def _on_leave(self, event=0):
|
||||
if self._hover is True:
|
||||
if sys.platform == "darwin" and len(self._values) > 0 and Settings.cursor_manipulation_enabled:
|
||||
self._canvas.configure(cursor="arrow")
|
||||
elif sys.platform.startswith("win") and len(self._values) > 0 and Settings.cursor_manipulation_enabled:
|
||||
self._canvas.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))
|
||||
self.canvas.itemconfig("border_parts_right",
|
||||
outline=ThemeManager.single_color(self.button_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(self.button_color, self._appearance_mode))
|
||||
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))
|
||||
self._canvas.itemconfig("border_parts_right",
|
||||
outline=ThemeManager.single_color(self._button_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(self._button_color, self._appearance_mode))
|
||||
|
||||
def dropdown_callback(self, value: str):
|
||||
if self.state == "readonly":
|
||||
self.entry.configure(state="normal")
|
||||
self.entry.delete(0, tkinter.END)
|
||||
self.entry.insert(0, value)
|
||||
self.entry.configure(state="readonly")
|
||||
def _dropdown_callback(self, value: str):
|
||||
if self._state == "readonly":
|
||||
self._entry.configure(state="normal")
|
||||
self._entry.delete(0, tkinter.END)
|
||||
self._entry.insert(0, value)
|
||||
self._entry.configure(state="readonly")
|
||||
else:
|
||||
self.entry.delete(0, tkinter.END)
|
||||
self.entry.insert(0, value)
|
||||
self._entry.delete(0, tkinter.END)
|
||||
self._entry.insert(0, value)
|
||||
|
||||
if self.command is not None:
|
||||
self.command(value)
|
||||
if self._command is not None:
|
||||
self._command(value)
|
||||
|
||||
def set(self, value: str):
|
||||
if self.state == "readonly":
|
||||
self.entry.configure(state="normal")
|
||||
self.entry.delete(0, tkinter.END)
|
||||
self.entry.insert(0, value)
|
||||
self.entry.configure(state="readonly")
|
||||
if self._state == "readonly":
|
||||
self._entry.configure(state="normal")
|
||||
self._entry.delete(0, tkinter.END)
|
||||
self._entry.insert(0, value)
|
||||
self._entry.configure(state="readonly")
|
||||
else:
|
||||
self.entry.delete(0, tkinter.END)
|
||||
self.entry.insert(0, value)
|
||||
self._entry.delete(0, tkinter.END)
|
||||
self._entry.insert(0, value)
|
||||
|
||||
def get(self) -> str:
|
||||
return self.entry.get()
|
||||
return self._entry.get()
|
||||
|
||||
def clicked(self, event=0):
|
||||
if self.state is not tkinter.DISABLED and len(self.values) > 0:
|
||||
self.open_dropdown_menu()
|
||||
def _clicked(self, event=0):
|
||||
if self._state is not tkinter.DISABLED and len(self._values) > 0:
|
||||
self._open_dropdown_menu()
|
||||
|
@ -1,4 +1,5 @@
|
||||
import tkinter
|
||||
from typing import Union, Tuple
|
||||
|
||||
from .ctk_canvas import CTkCanvas
|
||||
from ..theme_manager import ThemeManager
|
||||
@ -7,23 +8,28 @@ from .widget_base_class import CTkBaseClass
|
||||
|
||||
|
||||
class CTkEntry(CTkBaseClass):
|
||||
"""
|
||||
Entry with rounded corners, border, textvariable support, focus and placeholder.
|
||||
For detailed information check out the documentation.
|
||||
"""
|
||||
|
||||
def __init__(self, *args,
|
||||
bg_color=None,
|
||||
fg_color="default_theme",
|
||||
text_color="default_theme",
|
||||
placeholder_text_color="default_theme",
|
||||
text_font="default_theme",
|
||||
placeholder_text=None,
|
||||
corner_radius="default_theme",
|
||||
border_width="default_theme",
|
||||
border_color="default_theme",
|
||||
width=140,
|
||||
height=28,
|
||||
state=tkinter.NORMAL,
|
||||
bg_color: Union[str, Tuple[str, str], None] = None,
|
||||
fg_color: Union[str, Tuple[str, str], None] = "default_theme",
|
||||
text_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
placeholder_text_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
text_font: Union[str, Tuple[str, str]] = "default_theme",
|
||||
placeholder_text: str = None,
|
||||
corner_radius: int = "default_theme",
|
||||
border_width: int = "default_theme",
|
||||
border_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
width: int = 140,
|
||||
height: int = 28,
|
||||
state: str = tkinter.NORMAL,
|
||||
textvariable: tkinter.Variable = None,
|
||||
**kwargs):
|
||||
|
||||
# transfer basic functionality (bg_color, size, _appearance_mode, scaling) to CTkBaseClass
|
||||
# transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to CTkBaseClass
|
||||
if "master" in kwargs:
|
||||
super().__init__(*args, bg_color=bg_color, width=width, height=height, master=kwargs.pop("master"))
|
||||
else:
|
||||
@ -34,219 +40,221 @@ class CTkEntry(CTkBaseClass):
|
||||
self.grid_columnconfigure(0, weight=1)
|
||||
|
||||
# color
|
||||
self.fg_color = ThemeManager.theme["color"]["entry"] if fg_color == "default_theme" else fg_color
|
||||
self.text_color = ThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color
|
||||
self.placeholder_text_color = ThemeManager.theme["color"]["entry_placeholder_text"] if placeholder_text_color == "default_theme" else placeholder_text_color
|
||||
self.text_font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font
|
||||
self.border_color = ThemeManager.theme["color"]["entry_border"] if border_color == "default_theme" else border_color
|
||||
self._fg_color = ThemeManager.theme["color"]["entry"] if fg_color == "default_theme" else fg_color
|
||||
self._text_color = ThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color
|
||||
self._placeholder_text_color = ThemeManager.theme["color"]["entry_placeholder_text"] if placeholder_text_color == "default_theme" else placeholder_text_color
|
||||
self._text_font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font
|
||||
self._border_color = ThemeManager.theme["color"]["entry_border"] if border_color == "default_theme" else border_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
|
||||
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
|
||||
|
||||
# placeholder text
|
||||
self.placeholder_text = placeholder_text
|
||||
self.placeholder_text_active = False
|
||||
self.pre_placeholder_arguments = {} # some set arguments of the entry will be changed for placeholder and then set back
|
||||
self._is_focused: bool = True
|
||||
self._placeholder_text = placeholder_text
|
||||
self._placeholder_text_active = False
|
||||
self._pre_placeholder_arguments = {} # some set arguments of the entry will be changed for placeholder and then set back
|
||||
|
||||
# textvariable
|
||||
self.textvariable = textvariable
|
||||
self._textvariable = textvariable
|
||||
self._state = state
|
||||
|
||||
self.state = state
|
||||
self._canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self._apply_widget_scaling(self._current_width),
|
||||
height=self._apply_widget_scaling(self._current_height))
|
||||
self._canvas.grid(column=0, row=0, sticky="nswe")
|
||||
self._draw_engine = DrawEngine(self._canvas)
|
||||
|
||||
self.canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self.apply_widget_scaling(self._current_width),
|
||||
height=self.apply_widget_scaling(self._current_height))
|
||||
self.canvas.grid(column=0, row=0, sticky="nswe")
|
||||
self.draw_engine = DrawEngine(self.canvas)
|
||||
self._entry = tkinter.Entry(master=self,
|
||||
bd=0,
|
||||
width=1,
|
||||
highlightthickness=0,
|
||||
font=self._apply_font_scaling(self._text_font),
|
||||
state=self._state,
|
||||
textvariable=self._textvariable,
|
||||
**kwargs)
|
||||
self._entry.grid(column=0, row=0, sticky="nswe",
|
||||
padx=self._apply_widget_scaling(self._corner_radius) if self._corner_radius >= 6 else self._apply_widget_scaling(6),
|
||||
pady=(self._apply_widget_scaling(self._border_width), self._apply_widget_scaling(self._border_width + 1)))
|
||||
|
||||
self.entry = tkinter.Entry(master=self,
|
||||
bd=0,
|
||||
width=1,
|
||||
highlightthickness=0,
|
||||
font=self.apply_font_scaling(self.text_font),
|
||||
state=self.state,
|
||||
textvariable=self.textvariable,
|
||||
**kwargs)
|
||||
self.entry.grid(column=0, row=0, sticky="nswe",
|
||||
padx=self.apply_widget_scaling(self.corner_radius) if self.corner_radius >= 6 else self.apply_widget_scaling(6),
|
||||
pady=(self.apply_widget_scaling(self.border_width), self.apply_widget_scaling(self.border_width + 1)))
|
||||
super().bind('<Configure>', self._update_dimensions_event)
|
||||
self._entry.bind('<FocusOut>', self._entry_focus_out)
|
||||
self._entry.bind('<FocusIn>', self._entry_focus_in)
|
||||
|
||||
super().bind('<Configure>', self.update_dimensions_event)
|
||||
self.entry.bind('<FocusOut>', self.entry_focus_out)
|
||||
self.entry.bind('<FocusIn>', self.entry_focus_in)
|
||||
self._activate_placeholder()
|
||||
self._draw()
|
||||
|
||||
self.activate_placeholder()
|
||||
self.draw()
|
||||
def _set_scaling(self, *args, **kwargs):
|
||||
super()._set_scaling(*args, **kwargs)
|
||||
|
||||
def set_scaling(self, *args, **kwargs):
|
||||
super().set_scaling( *args, **kwargs)
|
||||
self._entry.configure(font=self._apply_font_scaling(self._text_font))
|
||||
self._entry.grid(column=0, row=0, sticky="we",
|
||||
padx=self._apply_widget_scaling(self._corner_radius) if self._corner_radius >= 6 else self._apply_widget_scaling(6))
|
||||
|
||||
self.entry.configure(font=self.apply_font_scaling(self.text_font))
|
||||
self.entry.grid(column=0, row=0, sticky="we",
|
||||
padx=self.apply_widget_scaling(self.corner_radius) if self.corner_radius >= 6 else self.apply_widget_scaling(6))
|
||||
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), height=self._apply_widget_scaling(self._desired_height))
|
||||
self._draw()
|
||||
|
||||
self.canvas.configure(width=self.apply_widget_scaling(self._desired_width), height=self.apply_widget_scaling(self._desired_height))
|
||||
self.draw()
|
||||
def _set_dimensions(self, width=None, height=None):
|
||||
super()._set_dimensions(width, height)
|
||||
|
||||
def set_dimensions(self, width=None, height=None):
|
||||
super().set_dimensions(width, height)
|
||||
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
|
||||
height=self._apply_widget_scaling(self._desired_height))
|
||||
self._draw()
|
||||
|
||||
self.canvas.configure(width=self.apply_widget_scaling(self._desired_width),
|
||||
height=self.apply_widget_scaling(self._desired_height))
|
||||
self.draw()
|
||||
def _draw(self, no_color_updates=False):
|
||||
self._canvas.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode))
|
||||
|
||||
def draw(self, no_color_updates=False):
|
||||
self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
|
||||
|
||||
requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.apply_widget_scaling(self._current_width),
|
||||
self.apply_widget_scaling(self._current_height),
|
||||
self.apply_widget_scaling(self.corner_radius),
|
||||
self.apply_widget_scaling(self.border_width))
|
||||
requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._current_width),
|
||||
self._apply_widget_scaling(self._current_height),
|
||||
self._apply_widget_scaling(self._corner_radius),
|
||||
self._apply_widget_scaling(self._border_width))
|
||||
|
||||
if requires_recoloring or no_color_updates is False:
|
||||
if ThemeManager.single_color(self.fg_color, self._appearance_mode) is not None:
|
||||
self.canvas.itemconfig("inner_parts",
|
||||
fill=ThemeManager.single_color(self.fg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.fg_color, self._appearance_mode))
|
||||
self.entry.configure(bg=ThemeManager.single_color(self.fg_color, self._appearance_mode),
|
||||
disabledbackground=ThemeManager.single_color(self.fg_color, self._appearance_mode),
|
||||
highlightcolor=ThemeManager.single_color(self.fg_color, self._appearance_mode),
|
||||
fg=ThemeManager.single_color(self.text_color, self._appearance_mode),
|
||||
disabledforeground=ThemeManager.single_color(self.text_color, self._appearance_mode),
|
||||
insertbackground=ThemeManager.single_color(self.text_color, self._appearance_mode))
|
||||
if ThemeManager.single_color(self._fg_color, self._appearance_mode) is not None:
|
||||
self._canvas.itemconfig("inner_parts",
|
||||
fill=ThemeManager.single_color(self._fg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._fg_color, self._appearance_mode))
|
||||
self._entry.configure(bg=ThemeManager.single_color(self._fg_color, self._appearance_mode),
|
||||
disabledbackground=ThemeManager.single_color(self._fg_color, self._appearance_mode),
|
||||
highlightcolor=ThemeManager.single_color(self._fg_color, self._appearance_mode),
|
||||
fg=ThemeManager.single_color(self._text_color, self._appearance_mode),
|
||||
disabledforeground=ThemeManager.single_color(self._text_color, self._appearance_mode),
|
||||
insertbackground=ThemeManager.single_color(self._text_color, self._appearance_mode))
|
||||
else:
|
||||
self.canvas.itemconfig("inner_parts",
|
||||
fill=ThemeManager.single_color(self.bg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.bg_color, self._appearance_mode))
|
||||
self.entry.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode),
|
||||
disabledbackground=ThemeManager.single_color(self.bg_color, self._appearance_mode),
|
||||
highlightcolor=ThemeManager.single_color(self.bg_color, self._appearance_mode),
|
||||
fg=ThemeManager.single_color(self.text_color, self._appearance_mode),
|
||||
disabledforeground=ThemeManager.single_color(self.text_color, self._appearance_mode),
|
||||
insertbackground=ThemeManager.single_color(self.text_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("inner_parts",
|
||||
fill=ThemeManager.single_color(self._bg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._bg_color, self._appearance_mode))
|
||||
self._entry.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode),
|
||||
disabledbackground=ThemeManager.single_color(self._bg_color, self._appearance_mode),
|
||||
highlightcolor=ThemeManager.single_color(self._bg_color, self._appearance_mode),
|
||||
fg=ThemeManager.single_color(self._text_color, self._appearance_mode),
|
||||
disabledforeground=ThemeManager.single_color(self._text_color, self._appearance_mode),
|
||||
insertbackground=ThemeManager.single_color(self._text_color, self._appearance_mode))
|
||||
|
||||
self.canvas.itemconfig("border_parts",
|
||||
fill=ThemeManager.single_color(self.border_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.border_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("border_parts",
|
||||
fill=ThemeManager.single_color(self._border_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._border_color, self._appearance_mode))
|
||||
|
||||
if self.placeholder_text_active:
|
||||
self.entry.config(fg=ThemeManager.single_color(self.placeholder_text_color, self._appearance_mode))
|
||||
if self._placeholder_text_active:
|
||||
self._entry.config(fg=ThemeManager.single_color(self._placeholder_text_color, self._appearance_mode))
|
||||
|
||||
def bind(self, *args, **kwargs):
|
||||
self.entry.bind(*args, **kwargs)
|
||||
self._entry.bind(*args, **kwargs)
|
||||
|
||||
def configure(self, require_redraw=False, **kwargs):
|
||||
if "state" in kwargs:
|
||||
self.state = kwargs.pop("state")
|
||||
self.entry.configure(state=self.state)
|
||||
self._state = kwargs.pop("state")
|
||||
self._entry.configure(state=self._state)
|
||||
|
||||
if "fg_color" in kwargs:
|
||||
self.fg_color = kwargs.pop("fg_color")
|
||||
self._fg_color = kwargs.pop("fg_color")
|
||||
require_redraw = True
|
||||
|
||||
if "text_color" in kwargs:
|
||||
self.text_color = kwargs.pop("text_color")
|
||||
self._text_color = kwargs.pop("text_color")
|
||||
require_redraw = True
|
||||
|
||||
if "border_color" in kwargs:
|
||||
self.border_color = kwargs.pop("border_color")
|
||||
self._border_color = kwargs.pop("border_color")
|
||||
require_redraw = True
|
||||
|
||||
if "corner_radius" in kwargs:
|
||||
self.corner_radius = kwargs.pop("corner_radius")
|
||||
self._corner_radius = kwargs.pop("corner_radius")
|
||||
|
||||
if self.corner_radius * 2 > self._current_height:
|
||||
self.corner_radius = self._current_height / 2
|
||||
elif self.corner_radius * 2 > self._current_width:
|
||||
self.corner_radius = self._current_width / 2
|
||||
if self._corner_radius * 2 > self._current_height:
|
||||
self._corner_radius = self._current_height / 2
|
||||
elif self._corner_radius * 2 > self._current_width:
|
||||
self._corner_radius = self._current_width / 2
|
||||
|
||||
self.entry.grid(column=0, row=0, sticky="we", padx=self.apply_widget_scaling(self.corner_radius) if self.corner_radius >= 6 else self.apply_widget_scaling(6))
|
||||
self._entry.grid(column=0, row=0, sticky="we", padx=self._apply_widget_scaling(self._corner_radius) if self._corner_radius >= 6 else self._apply_widget_scaling(6))
|
||||
require_redraw = True
|
||||
|
||||
if "width" in kwargs:
|
||||
self.set_dimensions(width=kwargs.pop("width"))
|
||||
self._set_dimensions(width=kwargs.pop("width"))
|
||||
|
||||
if "height" in kwargs:
|
||||
self.set_dimensions(height=kwargs.pop("height"))
|
||||
self._set_dimensions(height=kwargs.pop("height"))
|
||||
|
||||
if "placeholder_text" in kwargs:
|
||||
self.placeholder_text = kwargs.pop("placeholder_text")
|
||||
if self.placeholder_text_active:
|
||||
self.entry.delete(0, tkinter.END)
|
||||
self.entry.insert(0, self.placeholder_text)
|
||||
self._placeholder_text = kwargs.pop("placeholder_text")
|
||||
if self._placeholder_text_active:
|
||||
self._entry.delete(0, tkinter.END)
|
||||
self._entry.insert(0, self._placeholder_text)
|
||||
else:
|
||||
self.activate_placeholder()
|
||||
self._activate_placeholder()
|
||||
|
||||
if "placeholder_text_color" in kwargs:
|
||||
self.placeholder_text_color = kwargs.pop("placeholder_text_color")
|
||||
self._placeholder_text_color = kwargs.pop("placeholder_text_color")
|
||||
require_redraw = True
|
||||
|
||||
if "textvariable" in kwargs:
|
||||
self.textvariable = kwargs.pop("textvariable")
|
||||
self.entry.configure(textvariable=self.textvariable)
|
||||
self._textvariable = kwargs.pop("textvariable")
|
||||
self._entry.configure(textvariable=self._textvariable)
|
||||
|
||||
if "text_font" in kwargs:
|
||||
self.text_font = kwargs.pop("text_font")
|
||||
self.entry.configure(font=self.apply_font_scaling(self.text_font))
|
||||
self._text_font = kwargs.pop("text_font")
|
||||
self._entry.configure(font=self._apply_font_scaling(self._text_font))
|
||||
|
||||
if "show" in kwargs:
|
||||
if self.placeholder_text_active:
|
||||
self.pre_placeholder_arguments["show"] = kwargs.pop("show")
|
||||
if self._placeholder_text_active:
|
||||
self._pre_placeholder_arguments["show"] = kwargs.pop("show")
|
||||
else:
|
||||
self.entry.configure(show=kwargs.pop("show"))
|
||||
self._entry.configure(show=kwargs.pop("show"))
|
||||
|
||||
if "bg_color" in kwargs:
|
||||
super().configure(bg_color=kwargs.pop("bg_color"), require_redraw=require_redraw)
|
||||
if "_bg_color" in kwargs:
|
||||
super().configure(bg_color=kwargs.pop("_bg_color"), require_redraw=require_redraw)
|
||||
else:
|
||||
super().configure(require_redraw=require_redraw)
|
||||
|
||||
self.entry.configure(**kwargs) # pass remaining kwargs to entry
|
||||
self._entry.configure(**kwargs) # pass remaining kwargs to entry
|
||||
|
||||
def activate_placeholder(self):
|
||||
if self.entry.get() == "" and self.placeholder_text is not None and (self.textvariable is None or self.textvariable == ""):
|
||||
self.placeholder_text_active = True
|
||||
def _activate_placeholder(self):
|
||||
if self._entry.get() == "" and self._placeholder_text is not None and (self._textvariable is None or self._textvariable == ""):
|
||||
self._placeholder_text_active = True
|
||||
|
||||
self.pre_placeholder_arguments = {"show": self.entry.cget("show")}
|
||||
self.entry.config(fg=ThemeManager.single_color(self.placeholder_text_color, self._appearance_mode), show="")
|
||||
self.entry.delete(0, tkinter.END)
|
||||
self.entry.insert(0, self.placeholder_text)
|
||||
self._pre_placeholder_arguments = {"show": self._entry.cget("show")}
|
||||
self._entry.config(fg=ThemeManager.single_color(self._placeholder_text_color, self._appearance_mode), show="")
|
||||
self._entry.delete(0, tkinter.END)
|
||||
self._entry.insert(0, self._placeholder_text)
|
||||
|
||||
def deactivate_placeholder(self):
|
||||
if self.placeholder_text_active:
|
||||
self.placeholder_text_active = False
|
||||
def _deactivate_placeholder(self):
|
||||
if self._placeholder_text_active:
|
||||
self._placeholder_text_active = False
|
||||
|
||||
self.entry.config(fg=ThemeManager.single_color(self.text_color, self._appearance_mode))
|
||||
self.entry.delete(0, tkinter.END)
|
||||
for argument, value in self.pre_placeholder_arguments.items():
|
||||
self.entry[argument] = value
|
||||
self._entry.config(fg=ThemeManager.single_color(self._text_color, self._appearance_mode))
|
||||
self._entry.delete(0, tkinter.END)
|
||||
for argument, value in self._pre_placeholder_arguments.items():
|
||||
self._entry[argument] = value
|
||||
|
||||
def entry_focus_out(self, event=None):
|
||||
self.activate_placeholder()
|
||||
def _entry_focus_out(self, event=None):
|
||||
self._activate_placeholder()
|
||||
self._is_focused = False
|
||||
|
||||
def entry_focus_in(self, event=None):
|
||||
self.deactivate_placeholder()
|
||||
def _entry_focus_in(self, event=None):
|
||||
self._deactivate_placeholder()
|
||||
self._is_focused = True
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
self.entry.delete(*args, **kwargs)
|
||||
self._entry.delete(*args, **kwargs)
|
||||
|
||||
if self.entry.get() == "":
|
||||
self.activate_placeholder()
|
||||
if not self._is_focused and self._entry.get() == "":
|
||||
self._activate_placeholder()
|
||||
|
||||
def insert(self, *args, **kwargs):
|
||||
self.deactivate_placeholder()
|
||||
self._deactivate_placeholder()
|
||||
|
||||
return self.entry.insert(*args, **kwargs)
|
||||
return self._entry.insert(*args, **kwargs)
|
||||
|
||||
def get(self):
|
||||
if self.placeholder_text_active:
|
||||
if self._placeholder_text_active:
|
||||
return ""
|
||||
else:
|
||||
return self.entry.get()
|
||||
return self._entry.get()
|
||||
|
||||
def focus(self):
|
||||
self.entry.focus()
|
||||
self._entry.focus()
|
||||
|
||||
def focus_force(self):
|
||||
self.entry.focus_force()
|
||||
self._entry.focus_force()
|
||||
|
@ -1,3 +1,5 @@
|
||||
from typing import Union, Tuple, List
|
||||
|
||||
from .ctk_canvas import CTkCanvas
|
||||
from ..theme_manager import ThemeManager
|
||||
from ..draw_engine import DrawEngine
|
||||
@ -5,128 +7,137 @@ from .widget_base_class import CTkBaseClass
|
||||
|
||||
|
||||
class CTkFrame(CTkBaseClass):
|
||||
"""
|
||||
Frame with rounded corners and border.
|
||||
Default foreground colors are set according to theme.
|
||||
To make the frame transparent set fg_color=None.
|
||||
For detailed information check out the documentation.
|
||||
"""
|
||||
|
||||
def __init__(self, *args,
|
||||
bg_color=None,
|
||||
fg_color="default_theme",
|
||||
border_color="default_theme",
|
||||
border_width="default_theme",
|
||||
corner_radius="default_theme",
|
||||
width=200,
|
||||
height=200,
|
||||
bg_color: Union[str, Tuple[str, str], None] = None,
|
||||
fg_color: Union[str, Tuple[str, str], None] = "default_theme",
|
||||
border_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
border_width: Union[int, str] = "default_theme",
|
||||
corner_radius: Union[int, str] = "default_theme",
|
||||
width: int = 200,
|
||||
height: int = 200,
|
||||
overwrite_preferred_drawing_method: str = None,
|
||||
**kwargs):
|
||||
|
||||
# transfer basic functionality (bg_color, size, _appearance_mode, scaling) to CTkBaseClass
|
||||
# transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to CTkBaseClass
|
||||
super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs)
|
||||
|
||||
# color
|
||||
self.border_color = ThemeManager.theme["color"]["frame_border"] if border_color == "default_theme" else border_color
|
||||
self._border_color = ThemeManager.theme["color"]["frame_border"] if border_color == "default_theme" else border_color
|
||||
|
||||
# determine fg_color of frame
|
||||
if fg_color == "default_theme":
|
||||
if isinstance(self.master, CTkFrame):
|
||||
if self.master.fg_color == ThemeManager.theme["color"]["frame_low"]:
|
||||
self.fg_color = ThemeManager.theme["color"]["frame_high"]
|
||||
if self.master._fg_color == ThemeManager.theme["color"]["frame_low"]:
|
||||
self._fg_color = ThemeManager.theme["color"]["frame_high"]
|
||||
else:
|
||||
self.fg_color = ThemeManager.theme["color"]["frame_low"]
|
||||
self._fg_color = ThemeManager.theme["color"]["frame_low"]
|
||||
else:
|
||||
self.fg_color = ThemeManager.theme["color"]["frame_low"]
|
||||
self._fg_color = ThemeManager.theme["color"]["frame_low"]
|
||||
else:
|
||||
self.fg_color = fg_color
|
||||
self._fg_color = fg_color
|
||||
|
||||
# shape
|
||||
self.corner_radius = ThemeManager.theme["shape"]["frame_corner_radius"] if corner_radius == "default_theme" else corner_radius
|
||||
self.border_width = ThemeManager.theme["shape"]["frame_border_width"] if border_width == "default_theme" else border_width
|
||||
self._corner_radius = ThemeManager.theme["shape"]["frame_corner_radius"] if corner_radius == "default_theme" else corner_radius
|
||||
self._border_width = ThemeManager.theme["shape"]["frame_border_width"] if border_width == "default_theme" else border_width
|
||||
|
||||
self.canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self.apply_widget_scaling(self._current_width),
|
||||
height=self.apply_widget_scaling(self._current_height))
|
||||
self.canvas.place(x=0, y=0, relwidth=1, relheight=1)
|
||||
self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
|
||||
self.draw_engine = DrawEngine(self.canvas)
|
||||
self._canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self._apply_widget_scaling(self._current_width),
|
||||
height=self._apply_widget_scaling(self._current_height))
|
||||
self._canvas.place(x=0, y=0, relwidth=1, relheight=1)
|
||||
self._canvas.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode))
|
||||
self._draw_engine = DrawEngine(self._canvas)
|
||||
self._overwrite_preferred_drawing_method = overwrite_preferred_drawing_method
|
||||
|
||||
self.bind('<Configure>', self.update_dimensions_event)
|
||||
self.bind('<Configure>', self._update_dimensions_event)
|
||||
|
||||
self.draw()
|
||||
self._draw()
|
||||
|
||||
def winfo_children(self):
|
||||
""" winfo_children of CTkFrame without self.canvas widget,
|
||||
because it's not a child but part of the CTkFrame itself """
|
||||
def winfo_children(self) -> List[any]:
|
||||
"""
|
||||
winfo_children of CTkFrame without self.canvas widget,
|
||||
because it's not a child but part of the CTkFrame itself
|
||||
"""
|
||||
|
||||
child_widgets = super().winfo_children()
|
||||
try:
|
||||
child_widgets.remove(self.canvas)
|
||||
child_widgets.remove(self._canvas)
|
||||
return child_widgets
|
||||
except ValueError:
|
||||
return child_widgets
|
||||
|
||||
def set_scaling(self, *args, **kwargs):
|
||||
super().set_scaling(*args, **kwargs)
|
||||
def _set_scaling(self, *args, **kwargs):
|
||||
super()._set_scaling(*args, **kwargs)
|
||||
|
||||
self.canvas.configure(width=self.apply_widget_scaling(self._desired_width), height=self.apply_widget_scaling(self._desired_height))
|
||||
self.draw()
|
||||
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), height=self._apply_widget_scaling(self._desired_height))
|
||||
self._draw()
|
||||
|
||||
def set_dimensions(self, width=None, height=None):
|
||||
super().set_dimensions(width, height)
|
||||
def _set_dimensions(self, width=None, height=None):
|
||||
super()._set_dimensions(width, height)
|
||||
|
||||
self.canvas.configure(width=self.apply_widget_scaling(self._desired_width),
|
||||
height=self.apply_widget_scaling(self._desired_height))
|
||||
self.draw()
|
||||
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
|
||||
height=self._apply_widget_scaling(self._desired_height))
|
||||
self._draw()
|
||||
|
||||
def draw(self, no_color_updates=False):
|
||||
def _draw(self, no_color_updates=False):
|
||||
|
||||
requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.apply_widget_scaling(self._current_width),
|
||||
self.apply_widget_scaling(self._current_height),
|
||||
self.apply_widget_scaling(self.corner_radius),
|
||||
self.apply_widget_scaling(self.border_width),
|
||||
overwrite_preferred_drawing_method=self._overwrite_preferred_drawing_method)
|
||||
requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._current_width),
|
||||
self._apply_widget_scaling(self._current_height),
|
||||
self._apply_widget_scaling(self._corner_radius),
|
||||
self._apply_widget_scaling(self._border_width),
|
||||
overwrite_preferred_drawing_method=self._overwrite_preferred_drawing_method)
|
||||
|
||||
if no_color_updates is False or requires_recoloring:
|
||||
if self.fg_color is None:
|
||||
self.canvas.itemconfig("inner_parts",
|
||||
fill=ThemeManager.single_color(self.bg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.bg_color, self._appearance_mode))
|
||||
if self._fg_color is None:
|
||||
self._canvas.itemconfig("inner_parts",
|
||||
fill=ThemeManager.single_color(self._bg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._bg_color, self._appearance_mode))
|
||||
else:
|
||||
self.canvas.itemconfig("inner_parts",
|
||||
fill=ThemeManager.single_color(self.fg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.fg_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("inner_parts",
|
||||
fill=ThemeManager.single_color(self._fg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._fg_color, self._appearance_mode))
|
||||
|
||||
self.canvas.itemconfig("border_parts",
|
||||
fill=ThemeManager.single_color(self.border_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.border_color, self._appearance_mode))
|
||||
self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("border_parts",
|
||||
fill=ThemeManager.single_color(self._border_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._border_color, self._appearance_mode))
|
||||
self._canvas.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode))
|
||||
|
||||
self.canvas.tag_lower("inner_parts")
|
||||
self.canvas.tag_lower("border_parts")
|
||||
self._canvas.tag_lower("inner_parts")
|
||||
self._canvas.tag_lower("border_parts")
|
||||
|
||||
def configure(self, require_redraw=False, **kwargs):
|
||||
if "fg_color" in kwargs:
|
||||
self.fg_color = kwargs.pop("fg_color")
|
||||
self._fg_color = kwargs.pop("fg_color")
|
||||
require_redraw = True
|
||||
|
||||
# check if CTk widgets are children of the frame and change their bg_color to new frame fg_color
|
||||
# check if CTk widgets are children of the frame and change their _bg_color to new frame fg_color
|
||||
for child in self.winfo_children():
|
||||
if isinstance(child, CTkBaseClass):
|
||||
child.configure(bg_color=self.fg_color)
|
||||
child.configure(bg_color=self._fg_color)
|
||||
|
||||
if "border_color" in kwargs:
|
||||
self.border_color = kwargs.pop("border_color")
|
||||
self._border_color = kwargs.pop("border_color")
|
||||
require_redraw = True
|
||||
|
||||
if "corner_radius" in kwargs:
|
||||
self.corner_radius = kwargs.pop("corner_radius")
|
||||
self._corner_radius = kwargs.pop("corner_radius")
|
||||
require_redraw = True
|
||||
|
||||
if "border_width" in kwargs:
|
||||
self.border_width = kwargs.pop("border_width")
|
||||
self._border_width = kwargs.pop("border_width")
|
||||
require_redraw = True
|
||||
|
||||
if "width" in kwargs:
|
||||
self.set_dimensions(width=kwargs.pop("width"))
|
||||
self._set_dimensions(width=kwargs.pop("width"))
|
||||
|
||||
if "height" in kwargs:
|
||||
self.set_dimensions(height=kwargs.pop("height"))
|
||||
self._set_dimensions(height=kwargs.pop("height"))
|
||||
|
||||
super().configure(require_redraw=require_redraw, **kwargs)
|
||||
|
@ -1,5 +1,5 @@
|
||||
import sys
|
||||
import tkinter
|
||||
from typing import Union, Tuple
|
||||
|
||||
from .ctk_canvas import CTkCanvas
|
||||
from ..theme_manager import ThemeManager
|
||||
@ -8,152 +8,143 @@ from .widget_base_class import CTkBaseClass
|
||||
|
||||
|
||||
class CTkLabel(CTkBaseClass):
|
||||
"""
|
||||
Label with rounded corners. Default is fg_color=None (transparent fg_color).
|
||||
For detailed information check out the documentation.
|
||||
"""
|
||||
|
||||
def __init__(self, *args,
|
||||
bg_color=None,
|
||||
fg_color="default_theme",
|
||||
text_color="default_theme",
|
||||
corner_radius="default_theme",
|
||||
width=140,
|
||||
height=28,
|
||||
text="CTkLabel",
|
||||
text_font="default_theme",
|
||||
anchor="center", # label anchor: center, n, e, s, w
|
||||
bg_color: Union[str, Tuple[str, str], None] = None,
|
||||
fg_color: Union[str, Tuple[str, str], None] = "default_theme",
|
||||
text_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
corner_radius: Union[int, str] = "default_theme",
|
||||
width: int = 140,
|
||||
height: int = 28,
|
||||
text: str = "CTkLabel",
|
||||
text_font: any = "default_theme",
|
||||
anchor: str = "center", # label anchor: center, n, e, s, w
|
||||
**kwargs):
|
||||
|
||||
# transfer basic functionality (bg_color, size, _appearance_mode, scaling) to CTkBaseClass
|
||||
# transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to CTkBaseClass
|
||||
if "master" in kwargs:
|
||||
super().__init__(*args, bg_color=bg_color, width=width, height=height, master=kwargs.pop("master"))
|
||||
else:
|
||||
super().__init__(*args, bg_color=bg_color, width=width, height=height)
|
||||
|
||||
# color
|
||||
self.fg_color = ThemeManager.theme["color"]["label"] if fg_color == "default_theme" else fg_color
|
||||
if self.fg_color is None:
|
||||
self.fg_color = self.bg_color
|
||||
self.text_color = ThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color
|
||||
self._fg_color = ThemeManager.theme["color"]["label"] if fg_color == "default_theme" else fg_color
|
||||
self._text_color = ThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color
|
||||
|
||||
# shape
|
||||
self.corner_radius = ThemeManager.theme["shape"]["label_corner_radius"] if corner_radius == "default_theme" else corner_radius
|
||||
self._corner_radius = ThemeManager.theme["shape"]["label_corner_radius"] if corner_radius == "default_theme" else corner_radius
|
||||
|
||||
# text
|
||||
self.anchor = anchor
|
||||
self.text = text
|
||||
self.text_font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font
|
||||
self._anchor = anchor
|
||||
self._text = text
|
||||
self._text_font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font
|
||||
|
||||
# configure grid system (1x1)
|
||||
self.grid_rowconfigure(0, weight=1)
|
||||
self.grid_columnconfigure(0, weight=1)
|
||||
|
||||
self.canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self.apply_widget_scaling(self._desired_width),
|
||||
height=self.apply_widget_scaling(self._desired_height))
|
||||
self.canvas.grid(row=0, column=0, sticky="nswe")
|
||||
self.draw_engine = DrawEngine(self.canvas)
|
||||
self._canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self._apply_widget_scaling(self._desired_width),
|
||||
height=self._apply_widget_scaling(self._desired_height))
|
||||
self._canvas.grid(row=0, column=0, sticky="nswe")
|
||||
self._draw_engine = DrawEngine(self._canvas)
|
||||
|
||||
self.text_label = tkinter.Label(master=self,
|
||||
highlightthickness=0,
|
||||
bd=0,
|
||||
anchor=self.anchor,
|
||||
text=self.text,
|
||||
font=self.apply_font_scaling(self.text_font),
|
||||
**kwargs)
|
||||
text_label_grid_sticky = self.anchor if self.anchor != "center" else ""
|
||||
self.text_label.grid(row=0, column=0, padx=self.apply_widget_scaling(self.corner_radius),
|
||||
sticky=text_label_grid_sticky)
|
||||
self._text_label = tkinter.Label(master=self,
|
||||
highlightthickness=0,
|
||||
bd=0,
|
||||
anchor=self._anchor,
|
||||
text=self._text,
|
||||
font=self._apply_font_scaling(self._text_font),
|
||||
**kwargs)
|
||||
text_label_grid_sticky = self._anchor if self._anchor != "center" else ""
|
||||
self._text_label.grid(row=0, column=0, padx=self._apply_widget_scaling(self._corner_radius),
|
||||
sticky=text_label_grid_sticky)
|
||||
|
||||
self.bind('<Configure>', self.update_dimensions_event)
|
||||
self.draw()
|
||||
self.bind('<Configure>', self._update_dimensions_event)
|
||||
self._draw()
|
||||
|
||||
def set_scaling(self, *args, **kwargs):
|
||||
super().set_scaling(*args, **kwargs)
|
||||
def _set_scaling(self, *args, **kwargs):
|
||||
super()._set_scaling(*args, **kwargs)
|
||||
|
||||
self.canvas.configure(width=self.apply_widget_scaling(self._desired_width), height=self.apply_widget_scaling(self._desired_height))
|
||||
self.text_label.configure(font=self.apply_font_scaling(self.text_font))
|
||||
text_label_grid_sticky = self.anchor if self.anchor != "center" else ""
|
||||
self.text_label.grid(row=0, column=0, padx=self.apply_widget_scaling(self.corner_radius),
|
||||
sticky=text_label_grid_sticky)
|
||||
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), height=self._apply_widget_scaling(self._desired_height))
|
||||
self._text_label.configure(font=self._apply_font_scaling(self._text_font))
|
||||
text_label_grid_sticky = self._anchor if self._anchor != "center" else ""
|
||||
self._text_label.grid(row=0, column=0, padx=self._apply_widget_scaling(self._corner_radius),
|
||||
sticky=text_label_grid_sticky)
|
||||
|
||||
self.draw()
|
||||
self._draw()
|
||||
|
||||
def set_dimensions(self, width=None, height=None):
|
||||
super().set_dimensions(width, height)
|
||||
def _set_dimensions(self, width=None, height=None):
|
||||
super()._set_dimensions(width, height)
|
||||
|
||||
self.canvas.configure(width=self.apply_widget_scaling(self._desired_width),
|
||||
height=self.apply_widget_scaling(self._desired_height))
|
||||
self.draw()
|
||||
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
|
||||
height=self._apply_widget_scaling(self._desired_height))
|
||||
self._draw()
|
||||
|
||||
def draw(self, no_color_updates=False):
|
||||
requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.apply_widget_scaling(self._current_width),
|
||||
self.apply_widget_scaling(self._current_height),
|
||||
self.apply_widget_scaling(self.corner_radius),
|
||||
0)
|
||||
def _draw(self, no_color_updates=False):
|
||||
requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._current_width),
|
||||
self._apply_widget_scaling(self._current_height),
|
||||
self._apply_widget_scaling(self._corner_radius),
|
||||
0)
|
||||
|
||||
if no_color_updates is False or requires_recoloring:
|
||||
if ThemeManager.single_color(self.fg_color, self._appearance_mode) is not None:
|
||||
self.canvas.itemconfig("inner_parts",
|
||||
fill=ThemeManager.single_color(self.fg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.fg_color, self._appearance_mode))
|
||||
if ThemeManager.single_color(self._fg_color, self._appearance_mode) is not None:
|
||||
self._canvas.itemconfig("inner_parts",
|
||||
fill=ThemeManager.single_color(self._fg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._fg_color, self._appearance_mode))
|
||||
|
||||
self.text_label.configure(fg=ThemeManager.single_color(self.text_color, self._appearance_mode),
|
||||
bg=ThemeManager.single_color(self.fg_color, self._appearance_mode))
|
||||
self._text_label.configure(fg=ThemeManager.single_color(self._text_color, self._appearance_mode),
|
||||
bg=ThemeManager.single_color(self._fg_color, self._appearance_mode))
|
||||
else:
|
||||
self.canvas.itemconfig("inner_parts",
|
||||
fill=ThemeManager.single_color(self.bg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.bg_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("inner_parts",
|
||||
fill=ThemeManager.single_color(self._bg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._bg_color, self._appearance_mode))
|
||||
|
||||
self.text_label.configure(fg=ThemeManager.single_color(self.text_color, self._appearance_mode),
|
||||
bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
|
||||
self._text_label.configure(fg=ThemeManager.single_color(self._text_color, self._appearance_mode),
|
||||
bg=ThemeManager.single_color(self._bg_color, self._appearance_mode))
|
||||
|
||||
self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
|
||||
self._canvas.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode))
|
||||
|
||||
def config(self, **kwargs):
|
||||
sys.stderr.write("Warning: Use .configure() instead of .config()")
|
||||
self.configure(**kwargs)
|
||||
return self.configure(**kwargs)
|
||||
|
||||
def configure(self, require_redraw=False, **kwargs):
|
||||
if "anchor" in kwargs:
|
||||
self.anchor = kwargs.pop("anchor")
|
||||
text_label_grid_sticky = self.anchor if self.anchor != "center" else ""
|
||||
self.text_label.grid(row=0, column=0, padx=self.apply_widget_scaling(self.corner_radius),
|
||||
sticky=text_label_grid_sticky)
|
||||
self._anchor = kwargs.pop("anchor")
|
||||
text_label_grid_sticky = self._anchor if self._anchor != "center" else ""
|
||||
self._text_label.grid(row=0, column=0, padx=self._apply_widget_scaling(self._corner_radius),
|
||||
sticky=text_label_grid_sticky)
|
||||
|
||||
if "text" in kwargs:
|
||||
self.text = kwargs["text"]
|
||||
self.text_label.configure(text=self.text)
|
||||
del kwargs["text"]
|
||||
self._text = kwargs.pop("text")
|
||||
self._text_label.configure(text=self._text)
|
||||
|
||||
if "text_font" in kwargs:
|
||||
self.text_font = kwargs.pop("text_font")
|
||||
self.text_label.configure(font=self.apply_font_scaling(self.text_font))
|
||||
self._text_font = kwargs.pop("text_font")
|
||||
self._text_label.configure(font=self._apply_font_scaling(self._text_font))
|
||||
|
||||
if "fg_color" in kwargs:
|
||||
self.fg_color = kwargs["fg_color"]
|
||||
self._fg_color = kwargs.pop("fg_color")
|
||||
require_redraw = True
|
||||
del kwargs["fg_color"]
|
||||
|
||||
if "text_color" in kwargs:
|
||||
self.text_color = kwargs["text_color"]
|
||||
self._text_color = kwargs.pop("text_color")
|
||||
require_redraw = True
|
||||
del kwargs["text_color"]
|
||||
|
||||
if "width" in kwargs:
|
||||
self.set_dimensions(width=kwargs["width"])
|
||||
del kwargs["width"]
|
||||
self._set_dimensions(width=kwargs.pop("width"))
|
||||
|
||||
if "height" in kwargs:
|
||||
self.set_dimensions(height=kwargs["height"])
|
||||
del kwargs["height"]
|
||||
self._set_dimensions(height=kwargs.pop("height"))
|
||||
|
||||
if "bg_color" in kwargs:
|
||||
super().configure(bg_color=kwargs.pop("bg_color"), require_redraw=require_redraw)
|
||||
else:
|
||||
super().configure(require_redraw=require_redraw)
|
||||
if "_bg_color" in kwargs:
|
||||
super().configure(bg_color=kwargs.pop("_bg_color"), require_redraw=require_redraw)
|
||||
else:
|
||||
super().configure(require_redraw=require_redraw)
|
||||
|
||||
self.text_label.configure(**kwargs) # pass remaining kwargs to label
|
||||
|
||||
def set_text(self, text):
|
||||
""" Will be removed in the next major release """
|
||||
|
||||
self.text = text
|
||||
self.text_label.configure(text=self.text)
|
||||
self._text_label.configure(**kwargs) # pass remaining kwargs to label
|
||||
|
@ -1,104 +1,109 @@
|
||||
import tkinter
|
||||
import sys
|
||||
|
||||
from .dropdown_menu import DropdownMenu
|
||||
from typing import Union, Tuple, Callable
|
||||
|
||||
from .ctk_canvas import CTkCanvas
|
||||
from ..theme_manager import ThemeManager
|
||||
from ..settings import Settings
|
||||
from ..draw_engine import DrawEngine
|
||||
from .widget_base_class import CTkBaseClass
|
||||
from .dropdown_menu import DropdownMenu
|
||||
|
||||
|
||||
class CTkOptionMenu(CTkBaseClass):
|
||||
"""
|
||||
Optionemnu with rounded corners, dropdown menu, variable support, command.
|
||||
For detailed information check out the documentation.
|
||||
"""
|
||||
|
||||
def __init__(self, *args,
|
||||
bg_color=None,
|
||||
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",
|
||||
variable=None,
|
||||
values=None,
|
||||
command=None,
|
||||
width=140,
|
||||
height=28,
|
||||
corner_radius="default_theme",
|
||||
text_font="default_theme",
|
||||
dropdown_text_font="default_theme",
|
||||
hover=True,
|
||||
state=tkinter.NORMAL,
|
||||
dynamic_resizing=True,
|
||||
bg_color: Union[str, Tuple[str, str], None] = None,
|
||||
fg_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
button_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
button_hover_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
text_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
text_color_disabled: Union[str, Tuple[str, str]] = "default_theme",
|
||||
dropdown_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
dropdown_hover_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
dropdown_text_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
variable: tkinter.Variable = None,
|
||||
values: list = None,
|
||||
command: Callable = None,
|
||||
width: int = 140,
|
||||
height: int = 28,
|
||||
corner_radius: Union[int, str] = "default_theme",
|
||||
text_font: any = "default_theme",
|
||||
dropdown_text_font: any = "default_theme",
|
||||
hover: bool = True,
|
||||
state: str = tkinter.NORMAL,
|
||||
dynamic_resizing: bool = True,
|
||||
**kwargs):
|
||||
|
||||
# transfer basic functionality (bg_color, size, _appearance_mode, scaling) to CTkBaseClass
|
||||
# transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to CTkBaseClass
|
||||
super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs)
|
||||
|
||||
# color variables
|
||||
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._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
|
||||
|
||||
# shape
|
||||
self.corner_radius = ThemeManager.theme["shape"]["button_corner_radius"] if corner_radius == "default_theme" else corner_radius
|
||||
self._corner_radius = ThemeManager.theme["shape"]["button_corner_radius"] if corner_radius == "default_theme" else corner_radius
|
||||
|
||||
# text and font
|
||||
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
|
||||
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.command = command
|
||||
self.variable = variable
|
||||
self.variable_callback_blocked = False
|
||||
self.variable_callback_name = None
|
||||
self.state = state
|
||||
self.hover = hover
|
||||
self.dynamic_resizing = dynamic_resizing
|
||||
self._command = command
|
||||
self._variable = variable
|
||||
self._variable_callback_blocked: bool = False
|
||||
self._variable_callback_name: Union[str, None] = None
|
||||
self._state = state
|
||||
self._hover = hover
|
||||
self._dynamic_resizing = dynamic_resizing
|
||||
|
||||
if values is None:
|
||||
self.values = ["CTkOptionMenu"]
|
||||
self._values = ["CTkOptionMenu"]
|
||||
else:
|
||||
self.values = values
|
||||
self._values = values
|
||||
|
||||
if len(self.values) > 0:
|
||||
self.current_value = self.values[0]
|
||||
if len(self._values) > 0:
|
||||
self._current_value = self._values[0]
|
||||
else:
|
||||
self.current_value = "CTkOptionMenu"
|
||||
self._current_value = "CTkOptionMenu"
|
||||
|
||||
self.dropdown_menu = DropdownMenu(master=self,
|
||||
values=self.values,
|
||||
command=self.dropdown_callback,
|
||||
fg_color=dropdown_color,
|
||||
hover_color=dropdown_hover_color,
|
||||
text_color=dropdown_text_color,
|
||||
text_font=dropdown_text_font)
|
||||
self._dropdown_menu = DropdownMenu(master=self,
|
||||
values=self._values,
|
||||
command=self._dropdown_callback,
|
||||
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)
|
||||
self.grid_columnconfigure(0, weight=1)
|
||||
|
||||
self.canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self.apply_widget_scaling(self._desired_width),
|
||||
height=self.apply_widget_scaling(self._desired_height))
|
||||
self.canvas.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="nsew")
|
||||
self.draw_engine = DrawEngine(self.canvas)
|
||||
self._canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self._apply_widget_scaling(self._desired_width),
|
||||
height=self._apply_widget_scaling(self._desired_height))
|
||||
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),
|
||||
anchor="w",
|
||||
text=self.current_value)
|
||||
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 = tkinter.Label(master=self,
|
||||
font=self._apply_font_scaling(self._text_font),
|
||||
anchor="w",
|
||||
text=self._current_value)
|
||||
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 not self.dynamic_resizing:
|
||||
if not self._dynamic_resizing:
|
||||
self.grid_propagate(0)
|
||||
|
||||
if Settings.cursor_manipulation_enabled:
|
||||
@ -108,204 +113,204 @@ class CTkOptionMenu(CTkBaseClass):
|
||||
self.configure(cursor="hand2")
|
||||
|
||||
# event bindings
|
||||
self.canvas.bind("<Enter>", self.on_enter)
|
||||
self.canvas.bind("<Leave>", self.on_leave)
|
||||
self.canvas.bind("<Button-1>", self.clicked)
|
||||
self.canvas.bind("<Button-1>", self.clicked)
|
||||
self._canvas.bind("<Enter>", self._on_enter)
|
||||
self._canvas.bind("<Leave>", self._on_leave)
|
||||
self._canvas.bind("<Button-1>", self._clicked)
|
||||
self._canvas.bind("<Button-1>", self._clicked)
|
||||
|
||||
self.text_label.bind("<Enter>", self.on_enter)
|
||||
self.text_label.bind("<Leave>", self.on_leave)
|
||||
self.text_label.bind("<Button-1>", self.clicked)
|
||||
self.text_label.bind("<Button-1>", self.clicked)
|
||||
self._text_label.bind("<Enter>", self._on_enter)
|
||||
self._text_label.bind("<Leave>", self._on_leave)
|
||||
self._text_label.bind("<Button-1>", self._clicked)
|
||||
self._text_label.bind("<Button-1>", self._clicked)
|
||||
|
||||
self.bind('<Configure>', self.update_dimensions_event)
|
||||
self.bind('<Configure>', self._update_dimensions_event)
|
||||
|
||||
self.draw() # initial draw
|
||||
self._draw() # initial draw
|
||||
|
||||
if self.variable is not None:
|
||||
self.variable_callback_name = self.variable.trace_add("write", self.variable_callback)
|
||||
self.current_value = self.variable.get()
|
||||
self.text_label.configure(text=self.current_value)
|
||||
if self._variable is not None:
|
||||
self._variable_callback_name = self._variable.trace_add("write", self._variable_callback)
|
||||
self._current_value = self._variable.get()
|
||||
self._text_label.configure(text=self._current_value)
|
||||
|
||||
def set_scaling(self, *args, **kwargs):
|
||||
super().set_scaling(*args, **kwargs)
|
||||
def _set_scaling(self, *args, **kwargs):
|
||||
super()._set_scaling(*args, **kwargs)
|
||||
|
||||
# change label text size and grid padding
|
||||
left_section_width = self._current_width - self._current_height
|
||||
self.text_label.configure(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.configure(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.canvas.configure(width=self.apply_widget_scaling(self._desired_width),
|
||||
height=self.apply_widget_scaling(self._desired_height))
|
||||
self.draw()
|
||||
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
|
||||
height=self._apply_widget_scaling(self._desired_height))
|
||||
self._draw()
|
||||
|
||||
def set_dimensions(self, width: int = None, height: int = None):
|
||||
super().set_dimensions(width, height)
|
||||
def _set_dimensions(self, width: int = None, height: int = None):
|
||||
super()._set_dimensions(width, height)
|
||||
|
||||
self.canvas.configure(width=self.apply_widget_scaling(self._desired_width),
|
||||
height=self.apply_widget_scaling(self._desired_height))
|
||||
self.draw()
|
||||
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
|
||||
height=self._apply_widget_scaling(self._desired_height))
|
||||
self._draw()
|
||||
|
||||
def draw(self, no_color_updates=False):
|
||||
def _draw(self, no_color_updates=False):
|
||||
left_section_width = self._current_width - self._current_height
|
||||
requires_recoloring = self.draw_engine.draw_rounded_rect_with_border_vertical_split(self.apply_widget_scaling(self._current_width),
|
||||
self.apply_widget_scaling(self._current_height),
|
||||
self.apply_widget_scaling(self.corner_radius),
|
||||
0,
|
||||
self.apply_widget_scaling(left_section_width))
|
||||
requires_recoloring = self._draw_engine.draw_rounded_rect_with_border_vertical_split(self._apply_widget_scaling(self._current_width),
|
||||
self._apply_widget_scaling(self._current_height),
|
||||
self._apply_widget_scaling(self._corner_radius),
|
||||
0,
|
||||
self._apply_widget_scaling(left_section_width))
|
||||
|
||||
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))
|
||||
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 no_color_updates is False or requires_recoloring or requires_recoloring_2:
|
||||
|
||||
self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
|
||||
self._canvas.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode))
|
||||
|
||||
self.canvas.itemconfig("inner_parts_left",
|
||||
outline=ThemeManager.single_color(self.fg_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(self.fg_color, self._appearance_mode))
|
||||
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))
|
||||
self._canvas.itemconfig("inner_parts_left",
|
||||
outline=ThemeManager.single_color(self._fg_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(self._fg_color, self._appearance_mode))
|
||||
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))
|
||||
|
||||
self.text_label.configure(fg=ThemeManager.single_color(self.text_color, self._appearance_mode))
|
||||
self._text_label.configure(fg=ThemeManager.single_color(self._text_color, self._appearance_mode))
|
||||
|
||||
if self.state == tkinter.DISABLED:
|
||||
self.text_label.configure(fg=(ThemeManager.single_color(self.text_color_disabled, self._appearance_mode)))
|
||||
self.canvas.itemconfig("dropdown_arrow",
|
||||
fill=ThemeManager.single_color(self.text_color_disabled, self._appearance_mode))
|
||||
if self._state == tkinter.DISABLED:
|
||||
self._text_label.configure(fg=(ThemeManager.single_color(self._text_color_disabled, self._appearance_mode)))
|
||||
self._canvas.itemconfig("dropdown_arrow",
|
||||
fill=ThemeManager.single_color(self._text_color_disabled, self._appearance_mode))
|
||||
else:
|
||||
self.text_label.configure(fg=ThemeManager.single_color(self.text_color, self._appearance_mode))
|
||||
self.canvas.itemconfig("dropdown_arrow",
|
||||
fill=ThemeManager.single_color(self.text_color, self._appearance_mode))
|
||||
self._text_label.configure(fg=ThemeManager.single_color(self._text_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("dropdown_arrow",
|
||||
fill=ThemeManager.single_color(self._text_color, self._appearance_mode))
|
||||
|
||||
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))
|
||||
|
||||
self.canvas.update_idletasks()
|
||||
self._canvas.update_idletasks()
|
||||
|
||||
def open_dropdown_menu(self):
|
||||
self.dropdown_menu.open(self.winfo_rootx(),
|
||||
self.winfo_rooty() + self.apply_widget_scaling(self._current_height + 0))
|
||||
def _open_dropdown_menu(self):
|
||||
self._dropdown_menu.open(self.winfo_rootx(),
|
||||
self.winfo_rooty() + self._apply_widget_scaling(self._current_height + 0))
|
||||
|
||||
def configure(self, require_redraw=False, **kwargs):
|
||||
if "state" in kwargs:
|
||||
self.state = kwargs.pop("state")
|
||||
self._state = kwargs.pop("state")
|
||||
require_redraw = True
|
||||
|
||||
if "fg_color" in kwargs:
|
||||
self.fg_color = kwargs.pop("fg_color")
|
||||
self._fg_color = kwargs.pop("fg_color")
|
||||
require_redraw = True
|
||||
|
||||
if "button_color" in kwargs:
|
||||
self.button_color = kwargs.pop("button_color")
|
||||
self._button_color = kwargs.pop("button_color")
|
||||
require_redraw = True
|
||||
|
||||
if "button_hover_color" in kwargs:
|
||||
self.button_hover_color = kwargs.pop("button_hover_color")
|
||||
self._button_hover_color = kwargs.pop("button_hover_color")
|
||||
require_redraw = True
|
||||
|
||||
if "text_color" in kwargs:
|
||||
self.text_color = kwargs.pop("text_color")
|
||||
self._text_color = kwargs.pop("text_color")
|
||||
require_redraw = True
|
||||
|
||||
if "text_font" in kwargs:
|
||||
self.text_font = kwargs.pop("text_font")
|
||||
self.text_label.configure(font=self.apply_font_scaling(self.text_font))
|
||||
self._text_font = kwargs.pop("text_font")
|
||||
self._text_label.configure(font=self._apply_font_scaling(self._text_font))
|
||||
|
||||
if "command" in kwargs:
|
||||
self.command = kwargs.pop("command")
|
||||
self._command = kwargs.pop("command")
|
||||
|
||||
if "variable" in kwargs:
|
||||
if self.variable is not None: # remove old callback
|
||||
self.variable.trace_remove("write", self.variable_callback_name)
|
||||
if self._variable is not None: # remove old callback
|
||||
self._variable.trace_remove("write", self._variable_callback_name)
|
||||
|
||||
self.variable = kwargs.pop("variable")
|
||||
self._variable = kwargs.pop("variable")
|
||||
|
||||
if self.variable is not None and self.variable != "":
|
||||
self.variable_callback_name = self.variable.trace_add("write", self.variable_callback)
|
||||
self.current_value = self.variable.get()
|
||||
self.text_label.configure(text=self.current_value)
|
||||
if self._variable is not None and self._variable != "":
|
||||
self._variable_callback_name = self._variable.trace_add("write", self._variable_callback)
|
||||
self._current_value = self._variable.get()
|
||||
self._text_label.configure(text=self._current_value)
|
||||
else:
|
||||
self.variable = None
|
||||
self._variable = None
|
||||
|
||||
if "width" in kwargs:
|
||||
self.set_dimensions(width=kwargs.pop("width"))
|
||||
self._set_dimensions(width=kwargs.pop("width"))
|
||||
|
||||
if "height" in kwargs:
|
||||
self.set_dimensions(height=kwargs.pop("height"))
|
||||
self._set_dimensions(height=kwargs.pop("height"))
|
||||
|
||||
if "values" in kwargs:
|
||||
self.values = kwargs.pop("values")
|
||||
self.dropdown_menu.configure(values=self.values)
|
||||
self._values = kwargs.pop("values")
|
||||
self._dropdown_menu.configure(values=self._values)
|
||||
|
||||
if "dropdown_color" in kwargs:
|
||||
self.dropdown_menu.configure(fg_color=kwargs.pop("dropdown_color"))
|
||||
self._dropdown_menu.configure(fg_color=kwargs.pop("dropdown_color"))
|
||||
|
||||
if "dropdown_hover_color" in kwargs:
|
||||
self.dropdown_menu.configure(hover_color=kwargs.pop("dropdown_hover_color"))
|
||||
self._dropdown_menu.configure(hover_color=kwargs.pop("dropdown_hover_color"))
|
||||
|
||||
if "dropdown_text_color" in kwargs:
|
||||
self.dropdown_menu.configure(text_color=kwargs.pop("dropdown_text_color"))
|
||||
self._dropdown_menu.configure(text_color=kwargs.pop("dropdown_text_color"))
|
||||
|
||||
if "dropdown_text_font" in kwargs:
|
||||
self.dropdown_text_font = kwargs.pop("dropdown_text_font")
|
||||
self.dropdown_menu.configure(text_font=self.dropdown_text_font)
|
||||
self._dropdown_text_font = kwargs.pop("dropdown_text_font")
|
||||
self._dropdown_menu.configure(text_font=self._dropdown_text_font)
|
||||
|
||||
if "dynamic_resizing" in kwargs:
|
||||
self.dynamic_resizing = kwargs.pop("dynamic_resizing")
|
||||
if not self.dynamic_resizing:
|
||||
self._dynamic_resizing = kwargs.pop("dynamic_resizing")
|
||||
if not self._dynamic_resizing:
|
||||
self.grid_propagate(0)
|
||||
else:
|
||||
self.grid_propagate(1)
|
||||
|
||||
super().configure(require_redraw=require_redraw, **kwargs)
|
||||
|
||||
def on_enter(self, event=0):
|
||||
if self.hover is True and self.state == tkinter.NORMAL and len(self.values) > 0:
|
||||
def _on_enter(self, event=0):
|
||||
if self._hover is True and self._state == tkinter.NORMAL and len(self._values) > 0:
|
||||
# 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))
|
||||
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):
|
||||
if self.hover is True:
|
||||
def _on_leave(self, event=0):
|
||||
if self._hover is True:
|
||||
# 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))
|
||||
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 variable_callback(self, var_name, index, mode):
|
||||
if not self.variable_callback_blocked:
|
||||
self.current_value = self.variable.get()
|
||||
self.text_label.configure(text=self.current_value)
|
||||
def _variable_callback(self, var_name, index, mode):
|
||||
if not self._variable_callback_blocked:
|
||||
self._current_value = self._variable.get()
|
||||
self._text_label.configure(text=self._current_value)
|
||||
|
||||
def dropdown_callback(self, value: str):
|
||||
self.current_value = value
|
||||
self.text_label.configure(text=self.current_value)
|
||||
def _dropdown_callback(self, value: str):
|
||||
self._current_value = value
|
||||
self._text_label.configure(text=self._current_value)
|
||||
|
||||
if self.variable is not None:
|
||||
self.variable_callback_blocked = True
|
||||
self.variable.set(self.current_value)
|
||||
self.variable_callback_blocked = False
|
||||
if self._variable is not None:
|
||||
self._variable_callback_blocked = True
|
||||
self._variable.set(self._current_value)
|
||||
self._variable_callback_blocked = False
|
||||
|
||||
if self.command is not None:
|
||||
self.command(self.current_value)
|
||||
if self._command is not None:
|
||||
self._command(self._current_value)
|
||||
|
||||
def set(self, value: str):
|
||||
self.current_value = value
|
||||
self.text_label.configure(text=self.current_value)
|
||||
self._current_value = value
|
||||
self._text_label.configure(text=self._current_value)
|
||||
|
||||
if self.variable is not None:
|
||||
self.variable_callback_blocked = True
|
||||
self.variable.set(self.current_value)
|
||||
self.variable_callback_blocked = False
|
||||
if self._variable is not None:
|
||||
self._variable_callback_blocked = True
|
||||
self._variable.set(self._current_value)
|
||||
self._variable_callback_blocked = False
|
||||
|
||||
def get(self) -> str:
|
||||
return self.current_value
|
||||
return self._current_value
|
||||
|
||||
def clicked(self, event=0):
|
||||
if self.state is not tkinter.DISABLED and len(self.values) > 0:
|
||||
self.open_dropdown_menu()
|
||||
def _clicked(self, event=0):
|
||||
if self._state is not tkinter.DISABLED and len(self._values) > 0:
|
||||
self._open_dropdown_menu()
|
||||
|
@ -1,5 +1,6 @@
|
||||
import tkinter
|
||||
import math
|
||||
from typing import Union, Tuple
|
||||
|
||||
from .ctk_canvas import CTkCanvas
|
||||
from ..theme_manager import ThemeManager
|
||||
@ -8,250 +9,247 @@ from .widget_base_class import CTkBaseClass
|
||||
|
||||
|
||||
class CTkProgressBar(CTkBaseClass):
|
||||
""" tkinter custom progressbar, values from 0 to 1 """
|
||||
"""
|
||||
Progressbar with rounded corners, border, variable support,
|
||||
indeterminate mode, vertical orientation.
|
||||
For detailed information check out the documentation.
|
||||
"""
|
||||
|
||||
def __init__(self, *args,
|
||||
variable=None,
|
||||
bg_color=None,
|
||||
border_color="default_theme",
|
||||
fg_color="default_theme",
|
||||
progress_color="default_theme",
|
||||
corner_radius="default_theme",
|
||||
width=None,
|
||||
height=None,
|
||||
border_width="default_theme",
|
||||
orient="horizontal",
|
||||
mode="determinate",
|
||||
determinate_speed=1,
|
||||
indeterminate_speed=1,
|
||||
bg_color: Union[str, Tuple[str, str], None] = None,
|
||||
border_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
fg_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
progress_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
corner_radius: Union[str, Tuple[str, str]] = "default_theme",
|
||||
width: Union[int, str] = "default_init",
|
||||
height: Union[int, str] = "default_init",
|
||||
border_width: Union[int, str] = "default_theme",
|
||||
variable: tkinter.Variable = None,
|
||||
orient: str = "horizontal",
|
||||
mode: str = "determinate",
|
||||
determinate_speed: float = 1,
|
||||
indeterminate_speed: float = 1,
|
||||
**kwargs):
|
||||
|
||||
# set default dimensions according to orientation
|
||||
if width is None:
|
||||
if width == "default_init":
|
||||
if orient.lower() == "vertical":
|
||||
width = 8
|
||||
else:
|
||||
width = 200
|
||||
if height is None:
|
||||
if height == "default_init":
|
||||
if orient.lower() == "vertical":
|
||||
height = 200
|
||||
else:
|
||||
height = 8
|
||||
|
||||
# transfer basic functionality (bg_color, size, _appearance_mode, scaling) to CTkBaseClass
|
||||
# transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to CTkBaseClass
|
||||
super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs)
|
||||
|
||||
# color
|
||||
self.border_color = ThemeManager.theme["color"]["progressbar_border"] if border_color == "default_theme" else border_color
|
||||
self.fg_color = ThemeManager.theme["color"]["progressbar"] if fg_color == "default_theme" else fg_color
|
||||
self.progress_color = ThemeManager.theme["color"]["progressbar_progress"] if progress_color == "default_theme" else progress_color
|
||||
self._border_color = ThemeManager.theme["color"]["progressbar_border"] if border_color == "default_theme" else border_color
|
||||
self._fg_color = ThemeManager.theme["color"]["progressbar"] if fg_color == "default_theme" else fg_color
|
||||
self._progress_color = ThemeManager.theme["color"]["progressbar_progress"] if progress_color == "default_theme" else progress_color
|
||||
|
||||
# control variable
|
||||
self.variable = variable
|
||||
self.variable_callback_blocked = False
|
||||
self.variable_callback_name = None
|
||||
self._variable = variable
|
||||
self._variable_callback_blocked = False
|
||||
self._variable_callback_name = None
|
||||
|
||||
# shape
|
||||
self.corner_radius = ThemeManager.theme["shape"]["progressbar_corner_radius"] if corner_radius == "default_theme" else corner_radius
|
||||
self.border_width = ThemeManager.theme["shape"]["progressbar_border_width"] if border_width == "default_theme" else border_width
|
||||
self.determinate_value = 0.5 # range 0-1
|
||||
self.determinate_speed = determinate_speed # range 0-1
|
||||
self.indeterminate_value = 0 # range 0-inf
|
||||
self.indeterminate_width = 0.4 # range 0-1
|
||||
self.indeterminate_speed = indeterminate_speed # range 0-1 to travel in 50ms
|
||||
self.loop_running = False
|
||||
self.orient = orient
|
||||
self.mode = mode # "determinate" or "indeterminate"
|
||||
self._corner_radius = ThemeManager.theme["shape"]["progressbar_corner_radius"] if corner_radius == "default_theme" else corner_radius
|
||||
self._border_width = ThemeManager.theme["shape"]["progressbar_border_width"] if border_width == "default_theme" else border_width
|
||||
self._determinate_value: float = 0.5 # range 0-1
|
||||
self._determinate_speed = determinate_speed # range 0-1
|
||||
self._indeterminate_value: float = 0 # range 0-inf
|
||||
self._indeterminate_width: float = 0.4 # range 0-1
|
||||
self._indeterminate_speed = indeterminate_speed # range 0-1 to travel in 50ms
|
||||
self._loop_running: bool = False
|
||||
self._orient = orient
|
||||
self._mode = mode # "determinate" or "indeterminate"
|
||||
|
||||
self.grid_rowconfigure(0, weight=1)
|
||||
self.grid_columnconfigure(0, weight=1)
|
||||
|
||||
self.canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self.apply_widget_scaling(self._desired_width),
|
||||
height=self.apply_widget_scaling(self._desired_height))
|
||||
self.canvas.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="nswe")
|
||||
self.draw_engine = DrawEngine(self.canvas)
|
||||
self._canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self._apply_widget_scaling(self._desired_width),
|
||||
height=self._apply_widget_scaling(self._desired_height))
|
||||
self._canvas.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="nswe")
|
||||
self._draw_engine = DrawEngine(self._canvas)
|
||||
|
||||
# Each time an item is resized due to pack position mode, the binding Configure is called on the widget
|
||||
self.bind('<Configure>', self.update_dimensions_event)
|
||||
self.bind('<Configure>', self._update_dimensions_event)
|
||||
|
||||
self.draw() # initial draw
|
||||
self._draw() # initial draw
|
||||
|
||||
if self.variable is not None:
|
||||
self.variable_callback_name = self.variable.trace_add("write", self.variable_callback)
|
||||
self.variable_callback_blocked = True
|
||||
self.set(self.variable.get(), from_variable_callback=True)
|
||||
self.variable_callback_blocked = False
|
||||
if self._variable is not None:
|
||||
self._variable_callback_name = self._variable.trace_add("write", self._variable_callback)
|
||||
self._variable_callback_blocked = True
|
||||
self.set(self._variable.get(), from_variable_callback=True)
|
||||
self._variable_callback_blocked = False
|
||||
|
||||
def set_scaling(self, *args, **kwargs):
|
||||
super().set_scaling(*args, **kwargs)
|
||||
def _set_scaling(self, *args, **kwargs):
|
||||
super()._set_scaling(*args, **kwargs)
|
||||
|
||||
self.canvas.configure(width=self.apply_widget_scaling(self._desired_width), height=self.apply_widget_scaling(self._desired_height))
|
||||
self.draw()
|
||||
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
|
||||
height=self._apply_widget_scaling(self._desired_height))
|
||||
self._draw()
|
||||
|
||||
def set_dimensions(self, width=None, height=None):
|
||||
super().set_dimensions(width, height)
|
||||
def _set_dimensions(self, width=None, height=None):
|
||||
super()._set_dimensions(width, height)
|
||||
|
||||
self.canvas.configure(width=self.apply_widget_scaling(self._desired_width),
|
||||
height=self.apply_widget_scaling(self._desired_height))
|
||||
self.draw()
|
||||
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
|
||||
height=self._apply_widget_scaling(self._desired_height))
|
||||
self._draw()
|
||||
|
||||
def destroy(self):
|
||||
if self.variable is not None:
|
||||
self.variable.trace_remove("write", self.variable_callback_name)
|
||||
if self._variable is not None:
|
||||
self._variable.trace_remove("write", self._variable_callback_name)
|
||||
|
||||
super().destroy()
|
||||
|
||||
def draw(self, no_color_updates=False):
|
||||
if self.orient.lower() == "horizontal":
|
||||
def _draw(self, no_color_updates=False):
|
||||
if self._orient.lower() == "horizontal":
|
||||
orientation = "w"
|
||||
elif self.orient.lower() == "vertical":
|
||||
elif self._orient.lower() == "vertical":
|
||||
orientation = "s"
|
||||
else:
|
||||
orientation = "w"
|
||||
|
||||
if self.mode == "determinate":
|
||||
requires_recoloring = self.draw_engine.draw_rounded_progress_bar_with_border(self.apply_widget_scaling(self._current_width),
|
||||
self.apply_widget_scaling(self._current_height),
|
||||
self.apply_widget_scaling(self.corner_radius),
|
||||
self.apply_widget_scaling(self.border_width),
|
||||
0,
|
||||
self.determinate_value,
|
||||
orientation)
|
||||
if self._mode == "determinate":
|
||||
requires_recoloring = self._draw_engine.draw_rounded_progress_bar_with_border(self._apply_widget_scaling(self._current_width),
|
||||
self._apply_widget_scaling(self._current_height),
|
||||
self._apply_widget_scaling(self._corner_radius),
|
||||
self._apply_widget_scaling(self._border_width),
|
||||
0,
|
||||
self._determinate_value,
|
||||
orientation)
|
||||
else: # indeterminate mode
|
||||
progress_value = (math.sin(self.indeterminate_value * math.pi / 40) + 1) / 2
|
||||
progress_value_1 = min(1.0, progress_value + (self.indeterminate_width / 2))
|
||||
progress_value_2 = max(0.0, progress_value - (self.indeterminate_width / 2))
|
||||
progress_value = (math.sin(self._indeterminate_value * math.pi / 40) + 1) / 2
|
||||
progress_value_1 = min(1.0, progress_value + (self._indeterminate_width / 2))
|
||||
progress_value_2 = max(0.0, progress_value - (self._indeterminate_width / 2))
|
||||
|
||||
requires_recoloring = self.draw_engine.draw_rounded_progress_bar_with_border(self.apply_widget_scaling(self._current_width),
|
||||
self.apply_widget_scaling(self._current_height),
|
||||
self.apply_widget_scaling(self.corner_radius),
|
||||
self.apply_widget_scaling(self.border_width),
|
||||
progress_value_1,
|
||||
progress_value_2,
|
||||
orientation)
|
||||
requires_recoloring = self._draw_engine.draw_rounded_progress_bar_with_border(self._apply_widget_scaling(self._current_width),
|
||||
self._apply_widget_scaling(self._current_height),
|
||||
self._apply_widget_scaling(self._corner_radius),
|
||||
self._apply_widget_scaling(self._border_width),
|
||||
progress_value_1,
|
||||
progress_value_2,
|
||||
orientation)
|
||||
|
||||
if no_color_updates is False or requires_recoloring:
|
||||
self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
|
||||
self.canvas.itemconfig("border_parts",
|
||||
fill=ThemeManager.single_color(self.border_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.border_color, self._appearance_mode))
|
||||
self.canvas.itemconfig("inner_parts",
|
||||
fill=ThemeManager.single_color(self.fg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.fg_color, self._appearance_mode))
|
||||
self.canvas.itemconfig("progress_parts",
|
||||
fill=ThemeManager.single_color(self.progress_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.progress_color, self._appearance_mode))
|
||||
self._canvas.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("border_parts",
|
||||
fill=ThemeManager.single_color(self._border_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._border_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("inner_parts",
|
||||
fill=ThemeManager.single_color(self._fg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._fg_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("progress_parts",
|
||||
fill=ThemeManager.single_color(self._progress_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._progress_color, self._appearance_mode))
|
||||
|
||||
def configure(self, require_redraw=False, **kwargs):
|
||||
if "fg_color" in kwargs:
|
||||
self.fg_color = kwargs["fg_color"]
|
||||
del kwargs["fg_color"]
|
||||
self._fg_color = kwargs.pop("fg_color")
|
||||
require_redraw = True
|
||||
|
||||
if "border_color" in kwargs:
|
||||
self.border_color = kwargs["border_color"]
|
||||
del kwargs["border_color"]
|
||||
self._border_color = kwargs.pop("border_color")
|
||||
require_redraw = True
|
||||
|
||||
if "progress_color" in kwargs:
|
||||
self.progress_color = kwargs["progress_color"]
|
||||
del kwargs["progress_color"]
|
||||
self._progress_color = kwargs.pop("progress_color")
|
||||
require_redraw = True
|
||||
|
||||
if "border_width" in kwargs:
|
||||
self.border_width = kwargs["border_width"]
|
||||
del kwargs["border_width"]
|
||||
self._border_width = kwargs.pop("border_width")
|
||||
require_redraw = True
|
||||
|
||||
if "variable" in kwargs:
|
||||
if self.variable is not None:
|
||||
self.variable.trace_remove("write", self.variable_callback_name)
|
||||
if self._variable is not None:
|
||||
self._variable.trace_remove("write", self._variable_callback_name)
|
||||
|
||||
self.variable = kwargs["variable"]
|
||||
self._variable = kwargs.pop("variable")
|
||||
|
||||
if self.variable is not None and self.variable != "":
|
||||
self.variable_callback_name = self.variable.trace_add("write", self.variable_callback)
|
||||
self.set(self.variable.get(), from_variable_callback=True)
|
||||
if self._variable is not None and self._variable != "":
|
||||
self._variable_callback_name = self._variable.trace_add("write", self._variable_callback)
|
||||
self.set(self._variable.get(), from_variable_callback=True)
|
||||
else:
|
||||
self.variable = None
|
||||
|
||||
del kwargs["variable"]
|
||||
self._variable = None
|
||||
|
||||
if "mode" in kwargs:
|
||||
self.mode = kwargs.pop("mode")
|
||||
self._mode = kwargs.pop("mode")
|
||||
require_redraw = True
|
||||
|
||||
if "determinate_speed" in kwargs:
|
||||
self.determinate_speed = kwargs.pop("determinate_speed")
|
||||
self._determinate_speed = kwargs.pop("determinate_speed")
|
||||
|
||||
if "indeterminate_speed" in kwargs:
|
||||
self.indeterminate_speed = kwargs.pop("indeterminate_speed")
|
||||
self._indeterminate_speed = kwargs.pop("indeterminate_speed")
|
||||
|
||||
if "width" in kwargs:
|
||||
self.set_dimensions(width=kwargs["width"])
|
||||
del kwargs["width"]
|
||||
self._set_dimensions(width=kwargs.pop("width"))
|
||||
|
||||
if "height" in kwargs:
|
||||
self.set_dimensions(height=kwargs["height"])
|
||||
del kwargs["height"]
|
||||
self._set_dimensions(height=kwargs.pop("height"))
|
||||
|
||||
super().configure(require_redraw=require_redraw, **kwargs)
|
||||
|
||||
def variable_callback(self, var_name, index, mode):
|
||||
if not self.variable_callback_blocked:
|
||||
self.set(self.variable.get(), from_variable_callback=True)
|
||||
def _variable_callback(self, var_name, index, mode):
|
||||
if not self._variable_callback_blocked:
|
||||
self.set(self._variable.get(), from_variable_callback=True)
|
||||
|
||||
def set(self, value, from_variable_callback=False):
|
||||
""" set determinate value """
|
||||
self.determinate_value = value
|
||||
self._determinate_value = value
|
||||
|
||||
if self.determinate_value > 1:
|
||||
self.determinate_value = 1
|
||||
elif self.determinate_value < 0:
|
||||
self.determinate_value = 0
|
||||
if self._determinate_value > 1:
|
||||
self._determinate_value = 1
|
||||
elif self._determinate_value < 0:
|
||||
self._determinate_value = 0
|
||||
|
||||
self.draw(no_color_updates=True)
|
||||
self._draw(no_color_updates=True)
|
||||
|
||||
if self.variable is not None and not from_variable_callback:
|
||||
self.variable_callback_blocked = True
|
||||
self.variable.set(round(self.determinate_value) if isinstance(self.variable, tkinter.IntVar) else self.determinate_value)
|
||||
self.variable_callback_blocked = False
|
||||
if self._variable is not None and not from_variable_callback:
|
||||
self._variable_callback_blocked = True
|
||||
self._variable.set(round(self._determinate_value) if isinstance(self._variable, tkinter.IntVar) else self._determinate_value)
|
||||
self._variable_callback_blocked = False
|
||||
|
||||
def get(self):
|
||||
def get(self) -> float:
|
||||
""" get determinate value """
|
||||
return self.determinate_value
|
||||
return self._determinate_value
|
||||
|
||||
def start(self):
|
||||
""" start indeterminate mode """
|
||||
if not self.loop_running:
|
||||
self.loop_running = True
|
||||
self.internal_loop()
|
||||
if not self._loop_running:
|
||||
self._loop_running = True
|
||||
self._internal_loop()
|
||||
|
||||
def stop(self):
|
||||
""" stop indeterminate mode """
|
||||
self.loop_running = False
|
||||
self._loop_running = False
|
||||
|
||||
def internal_loop(self):
|
||||
if self.loop_running:
|
||||
if self.mode == "determinate":
|
||||
self.determinate_value += self.determinate_speed / 50
|
||||
if self.determinate_value > 1:
|
||||
self.determinate_value -= 1
|
||||
self.draw()
|
||||
self.after(20, self.internal_loop)
|
||||
def _internal_loop(self):
|
||||
if self._loop_running:
|
||||
if self._mode == "determinate":
|
||||
self._determinate_value += self._determinate_speed / 50
|
||||
if self._determinate_value > 1:
|
||||
self._determinate_value -= 1
|
||||
self._draw()
|
||||
self.after(20, self._internal_loop)
|
||||
else:
|
||||
self.indeterminate_value += self.indeterminate_speed
|
||||
self.draw()
|
||||
self.after(20, self.internal_loop)
|
||||
self._indeterminate_value += self._indeterminate_speed
|
||||
self._draw()
|
||||
self.after(20, self._internal_loop)
|
||||
|
||||
def step(self):
|
||||
if self.mode == "determinate":
|
||||
self.determinate_value += self.determinate_speed / 50
|
||||
if self.determinate_value > 1:
|
||||
self.determinate_value -= 1
|
||||
self.draw()
|
||||
if self._mode == "determinate":
|
||||
self._determinate_value += self._determinate_speed / 50
|
||||
if self._determinate_value > 1:
|
||||
self._determinate_value -= 1
|
||||
self._draw()
|
||||
else:
|
||||
self.indeterminate_value += self.indeterminate_speed
|
||||
self.draw()
|
||||
self._indeterminate_value += self._indeterminate_speed
|
||||
self._draw()
|
||||
|
@ -1,6 +1,6 @@
|
||||
import tkinter
|
||||
import sys
|
||||
from typing import Union
|
||||
from typing import Union, Tuple, Callable
|
||||
|
||||
from .ctk_canvas import CTkCanvas
|
||||
from ..theme_manager import ThemeManager
|
||||
@ -10,272 +10,277 @@ from .widget_base_class import CTkBaseClass
|
||||
|
||||
|
||||
class CTkRadioButton(CTkBaseClass):
|
||||
"""
|
||||
Radiobutton with rounded corners, border, label, variable support, command.
|
||||
For detailed information check out the documentation.
|
||||
"""
|
||||
|
||||
def __init__(self, *args,
|
||||
bg_color=None,
|
||||
fg_color="default_theme",
|
||||
hover_color="default_theme",
|
||||
border_color="default_theme",
|
||||
border_width_unchecked="default_theme",
|
||||
border_width_checked="default_theme",
|
||||
width=22,
|
||||
height=22,
|
||||
corner_radius="default_theme",
|
||||
text_font="default_theme",
|
||||
text_color="default_theme",
|
||||
text="CTkRadioButton",
|
||||
text_color_disabled="default_theme",
|
||||
hover=True,
|
||||
command=None,
|
||||
state=tkinter.NORMAL,
|
||||
value=0,
|
||||
variable=None,
|
||||
textvariable=None,
|
||||
bg_color: Union[str, Tuple[str, str], None] = None,
|
||||
fg_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
hover_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
border_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
border_width_unchecked: Union[int, str] = "default_theme",
|
||||
border_width_checked: Union[int, str] = "default_theme",
|
||||
width: int = 22,
|
||||
height: int = 22,
|
||||
corner_radius: Union[int, str] = "default_theme",
|
||||
text_font: any = "default_theme",
|
||||
text_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
text: str = "CTkRadioButton",
|
||||
text_color_disabled: Union[str, Tuple[str, str]] = "default_theme",
|
||||
hover: bool = True,
|
||||
command: Callable = None,
|
||||
state: str = tkinter.NORMAL,
|
||||
value: Union[int, str] = 0,
|
||||
variable: tkinter.Variable = None,
|
||||
textvariable: tkinter.Variable = None,
|
||||
**kwargs):
|
||||
|
||||
# transfer basic functionality (bg_color, size, _appearance_mode, scaling) to CTkBaseClass
|
||||
# transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to CTkBaseClass
|
||||
super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs)
|
||||
|
||||
# color
|
||||
self.fg_color = ThemeManager.theme["color"]["button"] if fg_color == "default_theme" else fg_color
|
||||
self.hover_color = ThemeManager.theme["color"]["button_hover"] if hover_color == "default_theme" else hover_color
|
||||
self.border_color = ThemeManager.theme["color"]["checkbox_border"] if border_color == "default_theme" else border_color
|
||||
self._fg_color = ThemeManager.theme["color"]["button"] if fg_color == "default_theme" else fg_color
|
||||
self._hover_color = ThemeManager.theme["color"]["button_hover"] if hover_color == "default_theme" else hover_color
|
||||
self._border_color = ThemeManager.theme["color"]["checkbox_border"] if border_color == "default_theme" else border_color
|
||||
|
||||
# shape
|
||||
self.corner_radius = ThemeManager.theme["shape"]["radiobutton_corner_radius"] if corner_radius == "default_theme" else corner_radius
|
||||
self.border_width_unchecked = ThemeManager.theme["shape"]["radiobutton_border_width_unchecked"] if border_width_unchecked == "default_theme" else border_width_unchecked
|
||||
self.border_width_checked = ThemeManager.theme["shape"]["radiobutton_border_width_checked"] if border_width_checked == "default_theme" else border_width_checked
|
||||
self.border_width = self.border_width_unchecked
|
||||
self._corner_radius = ThemeManager.theme["shape"]["radiobutton_corner_radius"] if corner_radius == "default_theme" else corner_radius
|
||||
self._border_width_unchecked = ThemeManager.theme["shape"]["radiobutton_border_width_unchecked"] if border_width_unchecked == "default_theme" else border_width_unchecked
|
||||
self._border_width_checked = ThemeManager.theme["shape"]["radiobutton_border_width_checked"] if border_width_checked == "default_theme" else border_width_checked
|
||||
self._border_width = self._border_width_unchecked
|
||||
|
||||
# text
|
||||
self.text = text
|
||||
self.text_label: Union[tkinter.Label, None] = None
|
||||
self.text_color = ThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color
|
||||
self.text_color_disabled = ThemeManager.theme["color"]["text_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._text = text
|
||||
self._text_label: Union[tkinter.Label, None] = None
|
||||
self._text_color = ThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color
|
||||
self._text_color_disabled = ThemeManager.theme["color"]["text_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
|
||||
|
||||
# callback and control variables
|
||||
self.command = command
|
||||
self.state = state
|
||||
self.hover = hover
|
||||
self.check_state = False
|
||||
self.value = value
|
||||
self.variable: tkinter.Variable = variable
|
||||
self.variable_callback_blocked = False
|
||||
self.textvariable = textvariable
|
||||
self.variable_callback_name = None
|
||||
self._command = command
|
||||
self._state = state
|
||||
self._hover = hover
|
||||
self._check_state: bool = False
|
||||
self._value = value
|
||||
self._variable: tkinter.Variable = variable
|
||||
self._variable_callback_blocked: bool = False
|
||||
self._textvariable = textvariable
|
||||
self._variable_callback_name: Union[str, None] = None
|
||||
|
||||
# configure grid system (3x1)
|
||||
self.grid_columnconfigure(0, weight=0)
|
||||
self.grid_columnconfigure(1, weight=0, minsize=self.apply_widget_scaling(6))
|
||||
self.grid_columnconfigure(1, weight=0, minsize=self._apply_widget_scaling(6))
|
||||
self.grid_columnconfigure(2, weight=1)
|
||||
|
||||
self.bg_canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self.apply_widget_scaling(self._current_width),
|
||||
height=self.apply_widget_scaling(self._current_height))
|
||||
self.bg_canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=3, rowspan=1, sticky="nswe")
|
||||
self._bg_canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self._apply_widget_scaling(self._current_width),
|
||||
height=self._apply_widget_scaling(self._current_height))
|
||||
self._bg_canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=3, rowspan=1, sticky="nswe")
|
||||
|
||||
self.canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self.apply_widget_scaling(self._current_width),
|
||||
height=self.apply_widget_scaling(self._current_height))
|
||||
self.canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=1)
|
||||
self.draw_engine = DrawEngine(self.canvas)
|
||||
self._canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self._apply_widget_scaling(self._current_width),
|
||||
height=self._apply_widget_scaling(self._current_height))
|
||||
self._canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=1)
|
||||
self._draw_engine = DrawEngine(self._canvas)
|
||||
|
||||
self.canvas.bind("<Enter>", self.on_enter)
|
||||
self.canvas.bind("<Leave>", self.on_leave)
|
||||
self.canvas.bind("<Button-1>", self.invoke)
|
||||
self._canvas.bind("<Enter>", self._on_enter)
|
||||
self._canvas.bind("<Leave>", self._on_leave)
|
||||
self._canvas.bind("<Button-1>", self.invoke)
|
||||
|
||||
self.text_label = tkinter.Label(master=self,
|
||||
bd=0,
|
||||
text=self.text,
|
||||
justify=tkinter.LEFT,
|
||||
font=self.apply_font_scaling(self.text_font),
|
||||
textvariable=self.textvariable)
|
||||
self.text_label.grid(row=0, column=2, padx=0, pady=0, sticky="w")
|
||||
self.text_label["anchor"] = "w"
|
||||
self._text_label = tkinter.Label(master=self,
|
||||
bd=0,
|
||||
text=self._text,
|
||||
justify=tkinter.LEFT,
|
||||
font=self._apply_font_scaling(self._text_font),
|
||||
textvariable=self._textvariable)
|
||||
self._text_label.grid(row=0, column=2, padx=0, pady=0, sticky="w")
|
||||
self._text_label["anchor"] = "w"
|
||||
|
||||
self.text_label.bind("<Enter>", self.on_enter)
|
||||
self.text_label.bind("<Leave>", self.on_leave)
|
||||
self.text_label.bind("<Button-1>", self.invoke)
|
||||
self._text_label.bind("<Enter>", self._on_enter)
|
||||
self._text_label.bind("<Leave>", self._on_leave)
|
||||
self._text_label.bind("<Button-1>", self.invoke)
|
||||
|
||||
if self.variable is not None:
|
||||
self.variable_callback_name = self.variable.trace_add("write", self.variable_callback)
|
||||
self.check_state = True if self.variable.get() == self.value else False
|
||||
if self._variable is not None:
|
||||
self._variable_callback_name = self._variable.trace_add("write", self._variable_callback)
|
||||
self._check_state = True if self._variable.get() == self._value else False
|
||||
|
||||
self.draw() # initial draw
|
||||
self.set_cursor()
|
||||
self._draw() # initial draw
|
||||
self._set_cursor()
|
||||
|
||||
def set_scaling(self, *args, **kwargs):
|
||||
super().set_scaling(*args, **kwargs)
|
||||
def _set_scaling(self, *args, **kwargs):
|
||||
super()._set_scaling(*args, **kwargs)
|
||||
|
||||
self.grid_columnconfigure(1, weight=0, minsize=self.apply_widget_scaling(6))
|
||||
self.text_label.configure(font=self.apply_font_scaling(self.text_font))
|
||||
self.grid_columnconfigure(1, weight=0, minsize=self._apply_widget_scaling(6))
|
||||
self._text_label.configure(font=self._apply_font_scaling(self._text_font))
|
||||
|
||||
self.bg_canvas.configure(width=self.apply_widget_scaling(self._desired_width), height=self.apply_widget_scaling(self._desired_height))
|
||||
self.canvas.configure(width=self.apply_widget_scaling(self._desired_width), height=self.apply_widget_scaling(self._desired_height))
|
||||
self.draw()
|
||||
self._bg_canvas.configure(width=self._apply_widget_scaling(self._desired_width), height=self._apply_widget_scaling(self._desired_height))
|
||||
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), height=self._apply_widget_scaling(self._desired_height))
|
||||
self._draw()
|
||||
|
||||
def destroy(self):
|
||||
if self.variable is not None:
|
||||
self.variable.trace_remove("write", self.variable_callback_name)
|
||||
if self._variable is not None:
|
||||
self._variable.trace_remove("write", self._variable_callback_name)
|
||||
|
||||
super().destroy()
|
||||
|
||||
def draw(self, no_color_updates=False):
|
||||
requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.apply_widget_scaling(self._current_width),
|
||||
self.apply_widget_scaling(self._current_height),
|
||||
self.apply_widget_scaling(self.corner_radius),
|
||||
self.apply_widget_scaling(self.border_width))
|
||||
def _draw(self, no_color_updates=False):
|
||||
requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._current_width),
|
||||
self._apply_widget_scaling(self._current_height),
|
||||
self._apply_widget_scaling(self._corner_radius),
|
||||
self._apply_widget_scaling(self._border_width))
|
||||
|
||||
self.bg_canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
|
||||
self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
|
||||
self._bg_canvas.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode))
|
||||
self._canvas.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode))
|
||||
|
||||
if self.check_state is False:
|
||||
self.canvas.itemconfig("border_parts",
|
||||
outline=ThemeManager.single_color(self.border_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(self.border_color, self._appearance_mode))
|
||||
if self._check_state is False:
|
||||
self._canvas.itemconfig("border_parts",
|
||||
outline=ThemeManager.single_color(self._border_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(self._border_color, self._appearance_mode))
|
||||
else:
|
||||
self.canvas.itemconfig("border_parts",
|
||||
outline=ThemeManager.single_color(self.fg_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(self.fg_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("border_parts",
|
||||
outline=ThemeManager.single_color(self._fg_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(self._fg_color, self._appearance_mode))
|
||||
|
||||
self.canvas.itemconfig("inner_parts",
|
||||
outline=ThemeManager.single_color(self.bg_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(self.bg_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("inner_parts",
|
||||
outline=ThemeManager.single_color(self._bg_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(self._bg_color, self._appearance_mode))
|
||||
|
||||
if self.state == tkinter.DISABLED:
|
||||
self.text_label.configure(fg=ThemeManager.single_color(self.text_color_disabled, self._appearance_mode))
|
||||
if self._state == tkinter.DISABLED:
|
||||
self._text_label.configure(fg=ThemeManager.single_color(self._text_color_disabled, self._appearance_mode))
|
||||
else:
|
||||
self.text_label.configure(fg=ThemeManager.single_color(self.text_color, self._appearance_mode))
|
||||
self._text_label.configure(fg=ThemeManager.single_color(self._text_color, self._appearance_mode))
|
||||
|
||||
self.text_label.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
|
||||
self._text_label.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode))
|
||||
|
||||
def configure(self, require_redraw=False, **kwargs):
|
||||
if "text" in kwargs:
|
||||
self.text = kwargs.pop("text")
|
||||
self.text_label.configure(text=self.text)
|
||||
self._text = kwargs.pop("text")
|
||||
self._text_label.configure(text=self._text)
|
||||
|
||||
if "text_font" in kwargs:
|
||||
self.text_font = kwargs.pop("text_font")
|
||||
self.text_label.configure(font=self.apply_font_scaling(self.text_font))
|
||||
self._text_font = kwargs.pop("text_font")
|
||||
self._text_label.configure(font=self._apply_font_scaling(self._text_font))
|
||||
|
||||
if "state" in kwargs:
|
||||
self.state = kwargs.pop("state")
|
||||
self.set_cursor()
|
||||
self._state = kwargs.pop("state")
|
||||
self._set_cursor()
|
||||
require_redraw = True
|
||||
|
||||
if "fg_color" in kwargs:
|
||||
self.fg_color = kwargs.pop("fg_color")
|
||||
self._fg_color = kwargs.pop("fg_color")
|
||||
require_redraw = True
|
||||
|
||||
if "hover_color" in kwargs:
|
||||
self.hover_color = kwargs.pop("hover_color")
|
||||
self._hover_color = kwargs.pop("hover_color")
|
||||
require_redraw = True
|
||||
|
||||
if "text_color" in kwargs:
|
||||
self.text_color = kwargs.pop("text_color")
|
||||
self._text_color = kwargs.pop("text_color")
|
||||
require_redraw = True
|
||||
|
||||
if "border_color" in kwargs:
|
||||
self.border_color = kwargs.pop("border_color")
|
||||
self._border_color = kwargs.pop("border_color")
|
||||
require_redraw = True
|
||||
|
||||
if "border_width" in kwargs:
|
||||
self.border_width = kwargs.pop("border_width")
|
||||
self._border_width = kwargs.pop("border_width")
|
||||
require_redraw = True
|
||||
|
||||
if "command" in kwargs:
|
||||
self.command = kwargs.pop("command")
|
||||
self._command = kwargs.pop("command")
|
||||
|
||||
if "textvariable" in kwargs:
|
||||
self.textvariable = kwargs.pop("textvariable")
|
||||
self.text_label.configure(textvariable=self.textvariable)
|
||||
self._textvariable = kwargs.pop("textvariable")
|
||||
self._text_label.configure(textvariable=self._textvariable)
|
||||
|
||||
if "variable" in kwargs:
|
||||
if self.variable is not None:
|
||||
self.variable.trace_remove("write", self.variable_callback_name)
|
||||
if self._variable is not None:
|
||||
self._variable.trace_remove("write", self._variable_callback_name)
|
||||
|
||||
self.variable = kwargs.pop("variable")
|
||||
self._variable = kwargs.pop("variable")
|
||||
|
||||
if self.variable is not None and self.variable != "":
|
||||
self.variable_callback_name = self.variable.trace_add("write", self.variable_callback)
|
||||
self.check_state = True if self.variable.get() == self.value else False
|
||||
if self._variable is not None and self._variable != "":
|
||||
self._variable_callback_name = self._variable.trace_add("write", self._variable_callback)
|
||||
self._check_state = True if self._variable.get() == self._value else False
|
||||
require_redraw = True
|
||||
|
||||
super().configure(require_redraw=require_redraw, **kwargs)
|
||||
|
||||
def set_cursor(self):
|
||||
def _set_cursor(self):
|
||||
if Settings.cursor_manipulation_enabled:
|
||||
if self.state == tkinter.DISABLED:
|
||||
if self._state == tkinter.DISABLED:
|
||||
if sys.platform == "darwin" and Settings.cursor_manipulation_enabled:
|
||||
self.canvas.configure(cursor="arrow")
|
||||
if self.text_label is not None:
|
||||
self.text_label.configure(cursor="arrow")
|
||||
self._canvas.configure(cursor="arrow")
|
||||
if self._text_label is not None:
|
||||
self._text_label.configure(cursor="arrow")
|
||||
elif sys.platform.startswith("win") and Settings.cursor_manipulation_enabled:
|
||||
self.canvas.configure(cursor="arrow")
|
||||
if self.text_label is not None:
|
||||
self.text_label.configure(cursor="arrow")
|
||||
self._canvas.configure(cursor="arrow")
|
||||
if self._text_label is not None:
|
||||
self._text_label.configure(cursor="arrow")
|
||||
|
||||
elif self.state == tkinter.NORMAL:
|
||||
elif self._state == tkinter.NORMAL:
|
||||
if sys.platform == "darwin" and Settings.cursor_manipulation_enabled:
|
||||
self.canvas.configure(cursor="pointinghand")
|
||||
if self.text_label is not None:
|
||||
self.text_label.configure(cursor="pointinghand")
|
||||
self._canvas.configure(cursor="pointinghand")
|
||||
if self._text_label is not None:
|
||||
self._text_label.configure(cursor="pointinghand")
|
||||
elif sys.platform.startswith("win") and Settings.cursor_manipulation_enabled:
|
||||
self.canvas.configure(cursor="hand2")
|
||||
if self.text_label is not None:
|
||||
self.text_label.configure(cursor="hand2")
|
||||
self._canvas.configure(cursor="hand2")
|
||||
if self._text_label is not None:
|
||||
self._text_label.configure(cursor="hand2")
|
||||
|
||||
def on_enter(self, event=0):
|
||||
if self.hover is True and self.state == tkinter.NORMAL:
|
||||
self.canvas.itemconfig("border_parts",
|
||||
fill=ThemeManager.single_color(self.hover_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.hover_color, self._appearance_mode))
|
||||
def _on_enter(self, event=0):
|
||||
if self._hover is True and self._state == tkinter.NORMAL:
|
||||
self._canvas.itemconfig("border_parts",
|
||||
fill=ThemeManager.single_color(self._hover_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._hover_color, self._appearance_mode))
|
||||
|
||||
def on_leave(self, event=0):
|
||||
if self.hover is True:
|
||||
if self.check_state is True:
|
||||
self.canvas.itemconfig("border_parts",
|
||||
fill=ThemeManager.single_color(self.fg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.fg_color, self._appearance_mode))
|
||||
def _on_leave(self, event=0):
|
||||
if self._hover is True:
|
||||
if self._check_state is True:
|
||||
self._canvas.itemconfig("border_parts",
|
||||
fill=ThemeManager.single_color(self._fg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._fg_color, self._appearance_mode))
|
||||
else:
|
||||
self.canvas.itemconfig("border_parts",
|
||||
fill=ThemeManager.single_color(self.border_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.border_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("border_parts",
|
||||
fill=ThemeManager.single_color(self._border_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._border_color, self._appearance_mode))
|
||||
|
||||
def variable_callback(self, var_name, index, mode):
|
||||
if not self.variable_callback_blocked:
|
||||
if self.variable.get() == self.value:
|
||||
def _variable_callback(self, var_name, index, mode):
|
||||
if not self._variable_callback_blocked:
|
||||
if self._variable.get() == self._value:
|
||||
self.select(from_variable_callback=True)
|
||||
else:
|
||||
self.deselect(from_variable_callback=True)
|
||||
|
||||
def invoke(self, event=0):
|
||||
if self.state == tkinter.NORMAL:
|
||||
if self.check_state is False:
|
||||
self.check_state = True
|
||||
if self._state == tkinter.NORMAL:
|
||||
if self._check_state is False:
|
||||
self._check_state = True
|
||||
self.select()
|
||||
|
||||
if self.command is not None:
|
||||
self.command()
|
||||
if self._command is not None:
|
||||
self._command()
|
||||
|
||||
def select(self, from_variable_callback=False):
|
||||
self.check_state = True
|
||||
self.border_width = self.border_width_checked
|
||||
self.draw()
|
||||
self._check_state = True
|
||||
self._border_width = self._border_width_checked
|
||||
self._draw()
|
||||
|
||||
if self.variable is not None and not from_variable_callback:
|
||||
self.variable_callback_blocked = True
|
||||
self.variable.set(self.value)
|
||||
self.variable_callback_blocked = False
|
||||
if self._variable is not None and not from_variable_callback:
|
||||
self._variable_callback_blocked = True
|
||||
self._variable.set(self._value)
|
||||
self._variable_callback_blocked = False
|
||||
|
||||
def deselect(self, from_variable_callback=False):
|
||||
self.check_state = False
|
||||
self.border_width = self.border_width_unchecked
|
||||
self.draw()
|
||||
self._check_state = False
|
||||
self._border_width = self._border_width_unchecked
|
||||
self._draw()
|
||||
|
||||
if self.variable is not None and not from_variable_callback:
|
||||
self.variable_callback_blocked = True
|
||||
self.variable.set("")
|
||||
self.variable_callback_blocked = False
|
||||
if self._variable is not None and not from_variable_callback:
|
||||
self._variable_callback_blocked = True
|
||||
self._variable.set("")
|
||||
self._variable_callback_blocked = False
|
||||
|
@ -1,4 +1,5 @@
|
||||
import sys
|
||||
from typing import Union, Tuple, Callable
|
||||
|
||||
from .ctk_canvas import CTkCanvas
|
||||
from ..theme_manager import ThemeManager
|
||||
@ -7,219 +8,217 @@ from .widget_base_class import CTkBaseClass
|
||||
|
||||
|
||||
class CTkScrollbar(CTkBaseClass):
|
||||
"""
|
||||
Scrollbar with rounded corners, configurable spacing.
|
||||
Connect to scrollable widget by passing .set() method and set command attribute.
|
||||
For detailed information check out the documentation.
|
||||
"""
|
||||
|
||||
def __init__(self, *args,
|
||||
bg_color=None,
|
||||
fg_color="default_theme",
|
||||
scrollbar_color="default_theme",
|
||||
scrollbar_hover_color="default_theme",
|
||||
border_spacing="default_theme",
|
||||
corner_radius="default_theme",
|
||||
width=None,
|
||||
height=None,
|
||||
minimum_pixel_length=20,
|
||||
orientation="vertical",
|
||||
command=None,
|
||||
hover=True,
|
||||
bg_color: Union[str, Tuple[str, str], None] = None,
|
||||
fg_color: Union[str, Tuple[str, str], None] = "default_theme",
|
||||
scrollbar_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
scrollbar_hover_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
border_spacing: Union[int, str] = "default_theme",
|
||||
corner_radius: Union[int, str] = "default_theme",
|
||||
width: Union[int, str] = "default_init",
|
||||
height: Union[int, str] = "default_init",
|
||||
minimum_pixel_length: int = 20,
|
||||
orientation: str = "vertical",
|
||||
command: Callable = None,
|
||||
hover: bool = True,
|
||||
**kwargs):
|
||||
|
||||
# set default dimensions according to orientation
|
||||
if width is None:
|
||||
if width == "default_init":
|
||||
if orientation.lower() == "vertical":
|
||||
width = 16
|
||||
else:
|
||||
width = 200
|
||||
if height is None:
|
||||
if height == "default_init":
|
||||
if orientation.lower() == "horizontal":
|
||||
height = 16
|
||||
else:
|
||||
height = 200
|
||||
|
||||
# transfer basic functionality (bg_color, size, _appearance_mode, scaling) to CTkBaseClass
|
||||
# transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to CTkBaseClass
|
||||
super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs)
|
||||
|
||||
# color
|
||||
self.fg_color = ThemeManager.theme["color"]["frame_high"] if fg_color == "default_theme" else fg_color
|
||||
self.scrollbar_color = ThemeManager.theme["color"]["scrollbar_button"] if scrollbar_color == "default_theme" else scrollbar_color
|
||||
self.scrollbar_hover_color = ThemeManager.theme["color"]["scrollbar_button_hover"] if scrollbar_hover_color == "default_theme" else scrollbar_hover_color
|
||||
self._fg_color = ThemeManager.theme["color"]["frame_high"] if fg_color == "default_theme" else fg_color
|
||||
self._scrollbar_color = ThemeManager.theme["color"]["scrollbar_button"] if scrollbar_color == "default_theme" else scrollbar_color
|
||||
self._scrollbar_hover_color = ThemeManager.theme["color"]["scrollbar_button_hover"] if scrollbar_hover_color == "default_theme" else scrollbar_hover_color
|
||||
|
||||
# shape
|
||||
self.corner_radius = ThemeManager.theme["shape"]["scrollbar_corner_radius"] if corner_radius == "default_theme" else corner_radius
|
||||
self.border_spacing = ThemeManager.theme["shape"]["scrollbar_border_spacing"] if border_spacing == "default_theme" else border_spacing
|
||||
self._corner_radius = ThemeManager.theme["shape"]["scrollbar_corner_radius"] if corner_radius == "default_theme" else corner_radius
|
||||
self._border_spacing = ThemeManager.theme["shape"]["scrollbar_border_spacing"] if border_spacing == "default_theme" else border_spacing
|
||||
|
||||
self.hover = hover
|
||||
self.hover_state = False
|
||||
self.command = command
|
||||
self.orientation = orientation
|
||||
self.start_value: float = 0 # 0 to 1
|
||||
self.end_value: float = 1 # 0 to 1
|
||||
self.minimum_pixel_length = minimum_pixel_length
|
||||
self._hover = hover
|
||||
self._hover_state: bool = False
|
||||
self._command = command
|
||||
self._orientation = orientation
|
||||
self._start_value: float = 0 # 0 to 1
|
||||
self._end_value: float = 1 # 0 to 1
|
||||
self._minimum_pixel_length = minimum_pixel_length
|
||||
|
||||
self.canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self.apply_widget_scaling(self._current_width),
|
||||
height=self.apply_widget_scaling(self._current_height))
|
||||
self.canvas.place(x=0, y=0, relwidth=1, relheight=1)
|
||||
self.draw_engine = DrawEngine(self.canvas)
|
||||
self._canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self._apply_widget_scaling(self._current_width),
|
||||
height=self._apply_widget_scaling(self._current_height))
|
||||
self._canvas.place(x=0, y=0, relwidth=1, relheight=1)
|
||||
self._draw_engine = DrawEngine(self._canvas)
|
||||
|
||||
self.canvas.bind("<Enter>", self.on_enter)
|
||||
self.canvas.bind("<Leave>", self.on_leave)
|
||||
self.canvas.tag_bind("border_parts", "<Button-1>", self.clicked)
|
||||
self.canvas.bind("<B1-Motion>", self.clicked)
|
||||
self.canvas.bind("<MouseWheel>", self.mouse_scroll_event)
|
||||
self.bind('<Configure>', self.update_dimensions_event)
|
||||
self._canvas.bind("<Enter>", self._on_enter)
|
||||
self._canvas.bind("<Leave>", self._on_leave)
|
||||
self._canvas.tag_bind("border_parts", "<Button-1>", self._clicked)
|
||||
self._canvas.bind("<B1-Motion>", self._clicked)
|
||||
self._canvas.bind("<MouseWheel>", self._mouse_scroll_event)
|
||||
self.bind('<Configure>', self._update_dimensions_event)
|
||||
|
||||
self.draw()
|
||||
self._draw()
|
||||
|
||||
def set_scaling(self, *args, **kwargs):
|
||||
super().set_scaling(*args, **kwargs)
|
||||
def _set_scaling(self, *args, **kwargs):
|
||||
super()._set_scaling(*args, **kwargs)
|
||||
|
||||
self.canvas.configure(width=self.apply_widget_scaling(self._desired_width), height=self.apply_widget_scaling(self._desired_height))
|
||||
self.draw(no_color_updates=True)
|
||||
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
|
||||
height=self._apply_widget_scaling(self._desired_height))
|
||||
self._draw(no_color_updates=True)
|
||||
|
||||
def set_dimensions(self, width=None, height=None):
|
||||
super().set_dimensions(width, height)
|
||||
def _set_dimensions(self, width=None, height=None):
|
||||
super()._set_dimensions(width, height)
|
||||
|
||||
self.canvas.configure(width=self.apply_widget_scaling(self._desired_width),
|
||||
height=self.apply_widget_scaling(self._desired_height))
|
||||
self.draw(no_color_updates=True)
|
||||
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
|
||||
height=self._apply_widget_scaling(self._desired_height))
|
||||
self._draw(no_color_updates=True)
|
||||
|
||||
def get_scrollbar_values_for_minimum_pixel_size(self):
|
||||
def _get_scrollbar_values_for_minimum_pixel_size(self):
|
||||
# correct scrollbar float values if scrollbar is too small
|
||||
if self.orientation == "vertical":
|
||||
scrollbar_pixel_length = (self.end_value - self.start_value) * self._current_height
|
||||
if scrollbar_pixel_length < self.minimum_pixel_length and -scrollbar_pixel_length + self._current_height != 0:
|
||||
if self._orientation == "vertical":
|
||||
scrollbar_pixel_length = (self._end_value - self._start_value) * self._current_height
|
||||
if scrollbar_pixel_length < self._minimum_pixel_length and -scrollbar_pixel_length + self._current_height != 0:
|
||||
# calculate how much to increase the float interval values so that the scrollbar width is self.minimum_pixel_length
|
||||
interval_extend_factor = (-scrollbar_pixel_length + self.minimum_pixel_length) / (-scrollbar_pixel_length + self._current_height)
|
||||
corrected_end_value = self.end_value + (1 - self.end_value) * interval_extend_factor
|
||||
corrected_start_value = self.start_value - self.start_value * interval_extend_factor
|
||||
interval_extend_factor = (-scrollbar_pixel_length + self._minimum_pixel_length) / (-scrollbar_pixel_length + self._current_height)
|
||||
corrected_end_value = self._end_value + (1 - self._end_value) * interval_extend_factor
|
||||
corrected_start_value = self._start_value - self._start_value * interval_extend_factor
|
||||
return corrected_start_value, corrected_end_value
|
||||
else:
|
||||
return self.start_value, self.end_value
|
||||
return self._start_value, self._end_value
|
||||
|
||||
else:
|
||||
scrollbar_pixel_length = (self.end_value - self.start_value) * self._current_width
|
||||
if scrollbar_pixel_length < self.minimum_pixel_length and -scrollbar_pixel_length + self._current_width != 0:
|
||||
scrollbar_pixel_length = (self._end_value - self._start_value) * self._current_width
|
||||
if scrollbar_pixel_length < self._minimum_pixel_length and -scrollbar_pixel_length + self._current_width != 0:
|
||||
# calculate how much to increase the float interval values so that the scrollbar width is self.minimum_pixel_length
|
||||
interval_extend_factor = (-scrollbar_pixel_length + self.minimum_pixel_length) / (-scrollbar_pixel_length + self._current_width)
|
||||
corrected_end_value = self.end_value + (1 - self.end_value) * interval_extend_factor
|
||||
corrected_start_value = self.start_value - self.start_value * interval_extend_factor
|
||||
interval_extend_factor = (-scrollbar_pixel_length + self._minimum_pixel_length) / (-scrollbar_pixel_length + self._current_width)
|
||||
corrected_end_value = self._end_value + (1 - self._end_value) * interval_extend_factor
|
||||
corrected_start_value = self._start_value - self._start_value * interval_extend_factor
|
||||
return corrected_start_value, corrected_end_value
|
||||
else:
|
||||
return self.start_value, self.end_value
|
||||
return self._start_value, self._end_value
|
||||
|
||||
def draw(self, no_color_updates=False):
|
||||
corrected_start_value, corrected_end_value = self.get_scrollbar_values_for_minimum_pixel_size()
|
||||
requires_recoloring = self.draw_engine.draw_rounded_scrollbar(self.apply_widget_scaling(self._current_width),
|
||||
self.apply_widget_scaling(self._current_height),
|
||||
self.apply_widget_scaling(self.corner_radius),
|
||||
self.apply_widget_scaling(self.border_spacing),
|
||||
corrected_start_value,
|
||||
corrected_end_value,
|
||||
self.orientation)
|
||||
def _draw(self, no_color_updates=False):
|
||||
corrected_start_value, corrected_end_value = self._get_scrollbar_values_for_minimum_pixel_size()
|
||||
requires_recoloring = self._draw_engine.draw_rounded_scrollbar(self._apply_widget_scaling(self._current_width),
|
||||
self._apply_widget_scaling(self._current_height),
|
||||
self._apply_widget_scaling(self._corner_radius),
|
||||
self._apply_widget_scaling(self._border_spacing),
|
||||
corrected_start_value,
|
||||
corrected_end_value,
|
||||
self._orientation)
|
||||
|
||||
if no_color_updates is False or requires_recoloring:
|
||||
if self.hover_state is True:
|
||||
self.canvas.itemconfig("scrollbar_parts",
|
||||
fill=ThemeManager.single_color(self.scrollbar_hover_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.scrollbar_hover_color, self._appearance_mode))
|
||||
if self._hover_state is True:
|
||||
self._canvas.itemconfig("scrollbar_parts",
|
||||
fill=ThemeManager.single_color(self._scrollbar_hover_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._scrollbar_hover_color, self._appearance_mode))
|
||||
else:
|
||||
self.canvas.itemconfig("scrollbar_parts",
|
||||
fill=ThemeManager.single_color(self.scrollbar_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.scrollbar_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("scrollbar_parts",
|
||||
fill=ThemeManager.single_color(self._scrollbar_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._scrollbar_color, self._appearance_mode))
|
||||
|
||||
if self.fg_color is None:
|
||||
self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
|
||||
self.canvas.itemconfig("border_parts",
|
||||
fill=ThemeManager.single_color(self.bg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.bg_color, self._appearance_mode))
|
||||
if self._fg_color is None:
|
||||
self._canvas.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("border_parts",
|
||||
fill=ThemeManager.single_color(self._bg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._bg_color, self._appearance_mode))
|
||||
else:
|
||||
self.canvas.configure(bg=ThemeManager.single_color(self.fg_color, self._appearance_mode))
|
||||
self.canvas.itemconfig("border_parts",
|
||||
fill=ThemeManager.single_color(self.fg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.fg_color, self._appearance_mode))
|
||||
self._canvas.configure(bg=ThemeManager.single_color(self._fg_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("border_parts",
|
||||
fill=ThemeManager.single_color(self._fg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._fg_color, self._appearance_mode))
|
||||
|
||||
self.canvas.update_idletasks()
|
||||
self._canvas.update_idletasks()
|
||||
|
||||
def set(self, start_value: float, end_value: float):
|
||||
self.start_value = float(start_value)
|
||||
self.end_value = float(end_value)
|
||||
self.draw()
|
||||
self._start_value = float(start_value)
|
||||
self._end_value = float(end_value)
|
||||
self._draw()
|
||||
|
||||
def get(self):
|
||||
return self.start_value, self.end_value
|
||||
return self._start_value, self._end_value
|
||||
|
||||
def configure(self, require_redraw=False, **kwargs):
|
||||
if "fg_color" in kwargs:
|
||||
self.fg_color = kwargs["fg_color"]
|
||||
self._fg_color = kwargs.pop("fg_color")
|
||||
require_redraw = True
|
||||
del kwargs["fg_color"]
|
||||
|
||||
if "scrollbar_color" in kwargs:
|
||||
self.scrollbar_color = kwargs["scrollbar_color"]
|
||||
self._scrollbar_color = kwargs.pop("scrollbar_color")
|
||||
require_redraw = True
|
||||
del kwargs["scrollbar_color"]
|
||||
|
||||
if "scrollbar_hover_color" in kwargs:
|
||||
self.scrollbar_hover_color = kwargs["scrollbar_hover_color"]
|
||||
self._scrollbar_hover_color = kwargs.pop("scrollbar_hover_color")
|
||||
require_redraw = True
|
||||
del kwargs["scrollbar_hover_color"]
|
||||
|
||||
if "command" in kwargs:
|
||||
self.command = kwargs["command"]
|
||||
del kwargs["command"]
|
||||
self._command = kwargs.pop("command")
|
||||
|
||||
if "corner_radius" in kwargs:
|
||||
self.corner_radius = kwargs["corner_radius"]
|
||||
self._corner_radius = kwargs.pop("corner_radius")
|
||||
require_redraw = True
|
||||
del kwargs["corner_radius"]
|
||||
|
||||
if "border_spacing" in kwargs:
|
||||
self.border_spacing = kwargs["border_spacing"]
|
||||
self._border_spacing = kwargs.pop("border_spacing")
|
||||
require_redraw = True
|
||||
del kwargs["border_spacing"]
|
||||
|
||||
if "width" in kwargs:
|
||||
self.set_dimensions(width=kwargs["width"])
|
||||
del kwargs["width"]
|
||||
self._set_dimensions(width=kwargs.pop("width"))
|
||||
|
||||
if "height" in kwargs:
|
||||
self.set_dimensions(height=kwargs["height"])
|
||||
del kwargs["height"]
|
||||
self._set_dimensions(height=kwargs.pop("height"))
|
||||
|
||||
super().configure(require_redraw=require_redraw, **kwargs)
|
||||
|
||||
def on_enter(self, event=0):
|
||||
if self.hover is True:
|
||||
self.hover_state = True
|
||||
self.canvas.itemconfig("scrollbar_parts",
|
||||
outline=ThemeManager.single_color(self.scrollbar_hover_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(self.scrollbar_hover_color, self._appearance_mode))
|
||||
def _on_enter(self, event=0):
|
||||
if self._hover is True:
|
||||
self._hover_state = True
|
||||
self._canvas.itemconfig("scrollbar_parts",
|
||||
outline=ThemeManager.single_color(self._scrollbar_hover_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(self._scrollbar_hover_color, self._appearance_mode))
|
||||
|
||||
def on_leave(self, event=0):
|
||||
self.hover_state = False
|
||||
self.canvas.itemconfig("scrollbar_parts",
|
||||
outline=ThemeManager.single_color(self.scrollbar_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(self.scrollbar_color, self._appearance_mode))
|
||||
def _on_leave(self, event=0):
|
||||
self._hover_state = False
|
||||
self._canvas.itemconfig("scrollbar_parts",
|
||||
outline=ThemeManager.single_color(self._scrollbar_color, self._appearance_mode),
|
||||
fill=ThemeManager.single_color(self._scrollbar_color, self._appearance_mode))
|
||||
|
||||
def clicked(self, event):
|
||||
if self.orientation == "vertical":
|
||||
value = ((event.y - self.border_spacing) / (self._current_height - 2 * self.border_spacing)) / self._widget_scaling
|
||||
def _clicked(self, event):
|
||||
if self._orientation == "vertical":
|
||||
value = ((event.y - self._border_spacing) / (self._current_height - 2 * self._border_spacing)) / self._widget_scaling
|
||||
else:
|
||||
value = ((event.x - self.border_spacing) / (self._current_width - 2 * self.border_spacing)) / self._widget_scaling
|
||||
value = ((event.x - self._border_spacing) / (self._current_width - 2 * self._border_spacing)) / self._widget_scaling
|
||||
|
||||
current_scrollbar_length = self.end_value - self.start_value
|
||||
current_scrollbar_length = self._end_value - self._start_value
|
||||
value = max(current_scrollbar_length / 2, min(value, 1 - (current_scrollbar_length / 2)))
|
||||
self.start_value = value - (current_scrollbar_length / 2)
|
||||
self.end_value = value + (current_scrollbar_length / 2)
|
||||
self.draw()
|
||||
self._start_value = value - (current_scrollbar_length / 2)
|
||||
self._end_value = value + (current_scrollbar_length / 2)
|
||||
self._draw()
|
||||
|
||||
if self.command is not None:
|
||||
self.command('moveto', self.start_value)
|
||||
if self._command is not None:
|
||||
self._command('moveto', self._start_value)
|
||||
|
||||
def mouse_scroll_event(self, event=None):
|
||||
if self.command is not None:
|
||||
def _mouse_scroll_event(self, event=None):
|
||||
if self._command is not None:
|
||||
if sys.platform.startswith("win"):
|
||||
self.command('scroll', -int(event.delta/40), 'units')
|
||||
self._command('scroll', -int(event.delta/40), 'units')
|
||||
else:
|
||||
self.command('scroll', -event.delta, 'units')
|
||||
|
||||
self._command('scroll', -event.delta, 'units')
|
||||
|
@ -1,5 +1,6 @@
|
||||
import tkinter
|
||||
import sys
|
||||
from typing import Union, Tuple, Callable
|
||||
|
||||
from .ctk_canvas import CTkCanvas
|
||||
from ..theme_manager import ThemeManager
|
||||
@ -9,331 +10,317 @@ from .widget_base_class import CTkBaseClass
|
||||
|
||||
|
||||
class CTkSlider(CTkBaseClass):
|
||||
""" tkinter custom slider"""
|
||||
"""
|
||||
Slider with rounded corners, border, number of steps, variable support, vertical orientation.
|
||||
For detailed information check out the documentation.
|
||||
"""
|
||||
|
||||
def __init__(self, *args,
|
||||
bg_color=None,
|
||||
border_color=None,
|
||||
fg_color="default_theme",
|
||||
progress_color="default_theme",
|
||||
button_color="default_theme",
|
||||
button_hover_color="default_theme",
|
||||
from_=0,
|
||||
to=1,
|
||||
number_of_steps=None,
|
||||
width=None,
|
||||
height=None,
|
||||
corner_radius="default_theme",
|
||||
button_corner_radius="default_theme",
|
||||
border_width="default_theme",
|
||||
button_length="default_theme",
|
||||
command=None,
|
||||
variable=None,
|
||||
orient="horizontal",
|
||||
state="normal",
|
||||
bg_color: Union[str, Tuple[str, str], None] = None,
|
||||
border_color: Union[str, Tuple[str, str], None] = None,
|
||||
fg_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
progress_color: Union[str, Tuple[str, str], None] = "default_theme",
|
||||
button_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
button_hover_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
from_: int = 0,
|
||||
to: int = 1,
|
||||
number_of_steps: Union[int, None] = None,
|
||||
width: Union[int, str] = "default_init",
|
||||
height: Union[int, str] = "default_init",
|
||||
corner_radius: Union[int, str] = "default_theme",
|
||||
button_corner_radius: Union[int, str] = "default_theme",
|
||||
border_width: Union[int, str] = "default_theme",
|
||||
button_length: Union[int, str] = "default_theme",
|
||||
command: Callable = None,
|
||||
variable: tkinter.Variable = None,
|
||||
orient: str = "horizontal",
|
||||
state: str = "normal",
|
||||
**kwargs):
|
||||
|
||||
# set default dimensions according to orientation
|
||||
if width is None:
|
||||
if width == "default_init":
|
||||
if orient.lower() == "vertical":
|
||||
width = 16
|
||||
else:
|
||||
width = 200
|
||||
if height is None:
|
||||
if height == "default_init":
|
||||
if orient.lower() == "vertical":
|
||||
height = 200
|
||||
else:
|
||||
height = 16
|
||||
|
||||
# transfer basic functionality (bg_color, size, _appearance_mode, scaling) to CTkBaseClass
|
||||
# transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to CTkBaseClass
|
||||
super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs)
|
||||
|
||||
# color
|
||||
self.border_color = border_color
|
||||
self.fg_color = ThemeManager.theme["color"]["slider"] if fg_color == "default_theme" else fg_color
|
||||
self.progress_color = ThemeManager.theme["color"]["slider_progress"] if progress_color == "default_theme" else progress_color
|
||||
self.button_color = ThemeManager.theme["color"]["slider_button"] if button_color == "default_theme" else button_color
|
||||
self.button_hover_color = ThemeManager.theme["color"]["slider_button_hover"] if button_hover_color == "default_theme" else button_hover_color
|
||||
self._border_color = border_color
|
||||
self._fg_color = ThemeManager.theme["color"]["slider"] if fg_color == "default_theme" else fg_color
|
||||
self._progress_color = ThemeManager.theme["color"]["slider_progress"] if progress_color == "default_theme" else progress_color
|
||||
self._button_color = ThemeManager.theme["color"]["slider_button"] if button_color == "default_theme" else button_color
|
||||
self._button_hover_color = ThemeManager.theme["color"]["slider_button_hover"] if button_hover_color == "default_theme" else button_hover_color
|
||||
|
||||
# shape
|
||||
self.corner_radius = ThemeManager.theme["shape"]["slider_corner_radius"] if corner_radius == "default_theme" else corner_radius
|
||||
self.button_corner_radius = ThemeManager.theme["shape"]["slider_button_corner_radius"] if button_corner_radius == "default_theme" else button_corner_radius
|
||||
self.border_width = ThemeManager.theme["shape"]["slider_border_width"] if border_width == "default_theme" else border_width
|
||||
self.button_length = ThemeManager.theme["shape"]["slider_button_length"] if button_length == "default_theme" else button_length
|
||||
self.value = 0.5 # initial value of slider in percent
|
||||
self.orientation = orient
|
||||
self.hover_state = False
|
||||
self.from_ = from_
|
||||
self.to = to
|
||||
self.number_of_steps = number_of_steps
|
||||
self.output_value = self.from_ + (self.value * (self.to - self.from_))
|
||||
self._corner_radius = ThemeManager.theme["shape"]["slider_corner_radius"] if corner_radius == "default_theme" else corner_radius
|
||||
self._button_corner_radius = ThemeManager.theme["shape"]["slider_button_corner_radius"] if button_corner_radius == "default_theme" else button_corner_radius
|
||||
self._border_width = ThemeManager.theme["shape"]["slider_border_width"] if border_width == "default_theme" else border_width
|
||||
self._button_length = ThemeManager.theme["shape"]["slider_button_length"] if button_length == "default_theme" else button_length
|
||||
self._value: float = 0.5 # initial value of slider in percent
|
||||
self._orientation = orient
|
||||
self._hover_state: bool = False
|
||||
self._from_ = from_
|
||||
self._to = to
|
||||
self._number_of_steps = number_of_steps
|
||||
self._output_value = self._from_ + (self._value * (self._to - self._from_))
|
||||
|
||||
if self.corner_radius < self.button_corner_radius:
|
||||
self.corner_radius = self.button_corner_radius
|
||||
if self._corner_radius < self._button_corner_radius:
|
||||
self._corner_radius = self._button_corner_radius
|
||||
|
||||
# callback and control variables
|
||||
self.command = command
|
||||
self.variable: tkinter.Variable = variable
|
||||
self.variable_callback_blocked = False
|
||||
self.variable_callback_name = None
|
||||
self.state = state
|
||||
self._command = command
|
||||
self._variable: tkinter.Variable = variable
|
||||
self._variable_callback_blocked: bool = False
|
||||
self._variable_callback_name: Union[bool, None] = None
|
||||
self._state = state
|
||||
|
||||
self.grid_rowconfigure(0, weight=1)
|
||||
self.grid_columnconfigure(0, weight=1)
|
||||
|
||||
self.canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self.apply_widget_scaling(self._desired_width),
|
||||
height=self.apply_widget_scaling(self._desired_height))
|
||||
self.canvas.grid(column=0, row=0, rowspan=1, columnspan=1, sticky="nswe")
|
||||
self.draw_engine = DrawEngine(self.canvas)
|
||||
self._canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self._apply_widget_scaling(self._desired_width),
|
||||
height=self._apply_widget_scaling(self._desired_height))
|
||||
self._canvas.grid(column=0, row=0, rowspan=1, columnspan=1, sticky="nswe")
|
||||
self._draw_engine = DrawEngine(self._canvas)
|
||||
|
||||
self.canvas.bind("<Enter>", self.on_enter)
|
||||
self.canvas.bind("<Leave>", self.on_leave)
|
||||
self.canvas.bind("<Button-1>", self.clicked)
|
||||
self.canvas.bind("<B1-Motion>", self.clicked)
|
||||
self._canvas.bind("<Enter>", self._on_enter)
|
||||
self._canvas.bind("<Leave>", self._on_leave)
|
||||
self._canvas.bind("<Button-1>", self._clicked)
|
||||
self._canvas.bind("<B1-Motion>", self._clicked)
|
||||
|
||||
# Each time an item is resized due to pack position mode, the binding Configure is called on the widget
|
||||
self.bind('<Configure>', self.update_dimensions_event)
|
||||
self.bind('<Configure>', self._update_dimensions_event)
|
||||
|
||||
self.set_cursor()
|
||||
self.draw() # initial draw
|
||||
self._set_cursor()
|
||||
self._draw() # initial draw
|
||||
|
||||
if self.variable is not None:
|
||||
self.variable_callback_name = self.variable.trace_add("write", self.variable_callback)
|
||||
self.variable_callback_blocked = True
|
||||
self.set(self.variable.get(), from_variable_callback=True)
|
||||
self.variable_callback_blocked = False
|
||||
if self._variable is not None:
|
||||
self._variable_callback_name = self._variable.trace_add("write", self._variable_callback)
|
||||
self._variable_callback_blocked = True
|
||||
self.set(self._variable.get(), from_variable_callback=True)
|
||||
self._variable_callback_blocked = False
|
||||
|
||||
def set_scaling(self, *args, **kwargs):
|
||||
super().set_scaling(*args, **kwargs)
|
||||
def _set_scaling(self, *args, **kwargs):
|
||||
super()._set_scaling(*args, **kwargs)
|
||||
|
||||
self.canvas.configure(width=self.apply_widget_scaling(self._desired_width), height=self.apply_widget_scaling(self._desired_height))
|
||||
self.draw()
|
||||
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
|
||||
height=self._apply_widget_scaling(self._desired_height))
|
||||
self._draw()
|
||||
|
||||
def set_dimensions(self, width=None, height=None):
|
||||
super().set_dimensions(width, height)
|
||||
def _set_dimensions(self, width=None, height=None):
|
||||
super()._set_dimensions(width, height)
|
||||
|
||||
self.canvas.configure(width=self.apply_widget_scaling(self._desired_width),
|
||||
height=self.apply_widget_scaling(self._desired_height))
|
||||
self.draw()
|
||||
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
|
||||
height=self._apply_widget_scaling(self._desired_height))
|
||||
self._draw()
|
||||
|
||||
def destroy(self):
|
||||
# remove variable_callback from variable callbacks if variable exists
|
||||
if self.variable is not None:
|
||||
self.variable.trace_remove("write", self.variable_callback_name)
|
||||
if self._variable is not None:
|
||||
self._variable.trace_remove("write", self._variable_callback_name)
|
||||
|
||||
super().destroy()
|
||||
|
||||
def set_cursor(self):
|
||||
if self.state == "normal" and Settings.cursor_manipulation_enabled:
|
||||
def _set_cursor(self):
|
||||
if self._state == "normal" and Settings.cursor_manipulation_enabled:
|
||||
if sys.platform == "darwin":
|
||||
self.configure(cursor="pointinghand")
|
||||
elif sys.platform.startswith("win"):
|
||||
self.configure(cursor="hand2")
|
||||
|
||||
elif self.state == "disabled" and Settings.cursor_manipulation_enabled:
|
||||
elif self._state == "disabled" and Settings.cursor_manipulation_enabled:
|
||||
if sys.platform == "darwin":
|
||||
self.configure(cursor="arrow")
|
||||
elif sys.platform.startswith("win"):
|
||||
self.configure(cursor="arrow")
|
||||
|
||||
def draw(self, no_color_updates=False):
|
||||
if self.orientation.lower() == "horizontal":
|
||||
def _draw(self, no_color_updates=False):
|
||||
if self._orientation.lower() == "horizontal":
|
||||
orientation = "w"
|
||||
elif self.orientation.lower() == "vertical":
|
||||
elif self._orientation.lower() == "vertical":
|
||||
orientation = "s"
|
||||
else:
|
||||
orientation = "w"
|
||||
|
||||
requires_recoloring = self.draw_engine.draw_rounded_slider_with_border_and_button(self.apply_widget_scaling(self._current_width),
|
||||
self.apply_widget_scaling(self._current_height),
|
||||
self.apply_widget_scaling(self.corner_radius),
|
||||
self.apply_widget_scaling(self.border_width),
|
||||
self.apply_widget_scaling(self.button_length),
|
||||
self.apply_widget_scaling(self.button_corner_radius),
|
||||
self.value, orientation)
|
||||
requires_recoloring = self._draw_engine.draw_rounded_slider_with_border_and_button(self._apply_widget_scaling(self._current_width),
|
||||
self._apply_widget_scaling(self._current_height),
|
||||
self._apply_widget_scaling(self._corner_radius),
|
||||
self._apply_widget_scaling(self._border_width),
|
||||
self._apply_widget_scaling(self._button_length),
|
||||
self._apply_widget_scaling(self._button_corner_radius),
|
||||
self._value, orientation)
|
||||
|
||||
if no_color_updates is False or requires_recoloring:
|
||||
self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
|
||||
self._canvas.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode))
|
||||
|
||||
if self.border_color is None:
|
||||
self.canvas.itemconfig("border_parts", fill=ThemeManager.single_color(self.bg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.bg_color, self._appearance_mode))
|
||||
if self._border_color is None:
|
||||
self._canvas.itemconfig("border_parts", fill=ThemeManager.single_color(self._bg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._bg_color, self._appearance_mode))
|
||||
else:
|
||||
self.canvas.itemconfig("border_parts", fill=ThemeManager.single_color(self.border_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.border_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("border_parts", fill=ThemeManager.single_color(self._border_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._border_color, self._appearance_mode))
|
||||
|
||||
self.canvas.itemconfig("inner_parts", fill=ThemeManager.single_color(self.fg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.fg_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("inner_parts", fill=ThemeManager.single_color(self._fg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._fg_color, self._appearance_mode))
|
||||
|
||||
if self.progress_color is None:
|
||||
self.canvas.itemconfig("progress_parts", fill=ThemeManager.single_color(self.fg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.fg_color, self._appearance_mode))
|
||||
if self._progress_color is None:
|
||||
self._canvas.itemconfig("progress_parts", fill=ThemeManager.single_color(self._fg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._fg_color, self._appearance_mode))
|
||||
else:
|
||||
self.canvas.itemconfig("progress_parts", fill=ThemeManager.single_color(self.progress_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.progress_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("progress_parts", fill=ThemeManager.single_color(self._progress_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._progress_color, self._appearance_mode))
|
||||
|
||||
if self.hover_state is True:
|
||||
self.canvas.itemconfig("slider_parts",
|
||||
fill=ThemeManager.single_color(self.button_hover_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.button_hover_color, self._appearance_mode))
|
||||
if self._hover_state is True:
|
||||
self._canvas.itemconfig("slider_parts",
|
||||
fill=ThemeManager.single_color(self._button_hover_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._button_hover_color, self._appearance_mode))
|
||||
else:
|
||||
self.canvas.itemconfig("slider_parts",
|
||||
fill=ThemeManager.single_color(self.button_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.button_color, self._appearance_mode))
|
||||
|
||||
def clicked(self, event=None):
|
||||
if self.state == "normal":
|
||||
if self.orientation.lower() == "horizontal":
|
||||
self.value = (event.x / self._current_width) / self._widget_scaling
|
||||
else:
|
||||
self.value = 1 - (event.y / self._current_height) / self._widget_scaling
|
||||
|
||||
if self.value > 1:
|
||||
self.value = 1
|
||||
if self.value < 0:
|
||||
self.value = 0
|
||||
|
||||
self.output_value = self.round_to_step_size(self.from_ + (self.value * (self.to - self.from_)))
|
||||
self.value = (self.output_value - self.from_) / (self.to - self.from_)
|
||||
|
||||
self.draw(no_color_updates=False)
|
||||
|
||||
if self.variable is not None:
|
||||
self.variable_callback_blocked = True
|
||||
self.variable.set(round(self.output_value) if isinstance(self.variable, tkinter.IntVar) else self.output_value)
|
||||
self.variable_callback_blocked = False
|
||||
|
||||
if self.command is not None:
|
||||
self.command(self.output_value)
|
||||
|
||||
def on_enter(self, event=0):
|
||||
if self.state == "normal":
|
||||
self.hover_state = True
|
||||
self.canvas.itemconfig("slider_parts",
|
||||
fill=ThemeManager.single_color(self.button_hover_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.button_hover_color, self._appearance_mode))
|
||||
|
||||
def on_leave(self, event=0):
|
||||
self.hover_state = False
|
||||
self.canvas.itemconfig("slider_parts",
|
||||
fill=ThemeManager.single_color(self.button_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.button_color, self._appearance_mode))
|
||||
|
||||
def round_to_step_size(self, value):
|
||||
if self.number_of_steps is not None:
|
||||
step_size = (self.to - self.from_) / self.number_of_steps
|
||||
value = self.to - (round((self.to - value) / step_size) * step_size)
|
||||
return value
|
||||
else:
|
||||
return value
|
||||
|
||||
def get(self):
|
||||
return self.output_value
|
||||
|
||||
def set(self, output_value, from_variable_callback=False):
|
||||
if self.from_ < self.to:
|
||||
if output_value > self.to:
|
||||
output_value = self.to
|
||||
elif output_value < self.from_:
|
||||
output_value = self.from_
|
||||
else:
|
||||
if output_value < self.to:
|
||||
output_value = self.to
|
||||
elif output_value > self.from_:
|
||||
output_value = self.from_
|
||||
|
||||
self.output_value = self.round_to_step_size(output_value)
|
||||
self.value = (self.output_value - self.from_) / (self.to - self.from_)
|
||||
|
||||
self.draw(no_color_updates=False)
|
||||
|
||||
if self.variable is not None and not from_variable_callback:
|
||||
self.variable_callback_blocked = True
|
||||
self.variable.set(round(self.output_value) if isinstance(self.variable, tkinter.IntVar) else self.output_value)
|
||||
self.variable_callback_blocked = False
|
||||
|
||||
def variable_callback(self, var_name, index, mode):
|
||||
if not self.variable_callback_blocked:
|
||||
self.set(self.variable.get(), from_variable_callback=True)
|
||||
self._canvas.itemconfig("slider_parts",
|
||||
fill=ThemeManager.single_color(self._button_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._button_color, self._appearance_mode))
|
||||
|
||||
def configure(self, require_redraw=False, **kwargs):
|
||||
if "state" in kwargs:
|
||||
self.state = kwargs["state"]
|
||||
self.set_cursor()
|
||||
self._state = kwargs.pop("state")
|
||||
self._set_cursor()
|
||||
require_redraw = True
|
||||
del kwargs["state"]
|
||||
|
||||
if "fg_color" in kwargs:
|
||||
self.fg_color = kwargs["fg_color"]
|
||||
self._fg_color = kwargs.pop("fg_color")
|
||||
require_redraw = True
|
||||
del kwargs["fg_color"]
|
||||
|
||||
if "progress_color" in kwargs:
|
||||
if kwargs["progress_color"] is None:
|
||||
self.progress_color = self.fg_color
|
||||
else:
|
||||
self.progress_color = kwargs["progress_color"]
|
||||
self._progress_color = kwargs.pop("progress_color")
|
||||
require_redraw = True
|
||||
del kwargs["progress_color"]
|
||||
|
||||
if "button_color" in kwargs:
|
||||
self.button_color = kwargs["button_color"]
|
||||
self._button_color = kwargs.pop("button_color")
|
||||
require_redraw = True
|
||||
del kwargs["button_color"]
|
||||
|
||||
if "button_hover_color" in kwargs:
|
||||
self.button_hover_color = kwargs["button_hover_color"]
|
||||
self._button_hover_color = kwargs.pop("button_hover_color")
|
||||
require_redraw = True
|
||||
del kwargs["button_hover_color"]
|
||||
|
||||
if "border_color" in kwargs:
|
||||
self.border_color = kwargs["border_color"]
|
||||
self._border_color = kwargs.pop("border_color")
|
||||
require_redraw = True
|
||||
del kwargs["border_color"]
|
||||
|
||||
if "border_width" in kwargs:
|
||||
self.border_width = kwargs["border_width"]
|
||||
self._border_width = kwargs.pop("border_width")
|
||||
require_redraw = True
|
||||
del kwargs["border_width"]
|
||||
|
||||
if "from_" in kwargs:
|
||||
self.from_ = kwargs["from_"]
|
||||
del kwargs["from_"]
|
||||
self._from_ = kwargs.pop("from_")
|
||||
|
||||
if "to" in kwargs:
|
||||
self.to = kwargs["to"]
|
||||
del kwargs["to"]
|
||||
self._to = kwargs.pop("to")
|
||||
|
||||
if "number_of_steps" in kwargs:
|
||||
self.number_of_steps = kwargs["number_of_steps"]
|
||||
del kwargs["number_of_steps"]
|
||||
self._number_of_steps = kwargs.pop("number_of_steps")
|
||||
|
||||
if "command" in kwargs:
|
||||
self.command = kwargs["command"]
|
||||
del kwargs["command"]
|
||||
self._command = kwargs.pop("command")
|
||||
|
||||
if "variable" in kwargs:
|
||||
if self.variable is not None:
|
||||
self.variable.trace_remove("write", self.variable_callback_name)
|
||||
if self._variable is not None:
|
||||
self._variable.trace_remove("write", self._variable_callback_name)
|
||||
|
||||
self.variable = kwargs["variable"]
|
||||
self._variable = kwargs.pop("variable")
|
||||
|
||||
if self.variable is not None and self.variable != "":
|
||||
self.variable_callback_name = self.variable.trace_add("write", self.variable_callback)
|
||||
self.set(self.variable.get(), from_variable_callback=True)
|
||||
if self._variable is not None and self._variable != "":
|
||||
self._variable_callback_name = self._variable.trace_add("write", self._variable_callback)
|
||||
self.set(self._variable.get(), from_variable_callback=True)
|
||||
else:
|
||||
self.variable = None
|
||||
|
||||
del kwargs["variable"]
|
||||
self._variable = None
|
||||
|
||||
if "width" in kwargs:
|
||||
self.set_dimensions(width=kwargs["width"])
|
||||
del kwargs["width"]
|
||||
self._set_dimensions(width=kwargs.pop("width"))
|
||||
|
||||
if "height" in kwargs:
|
||||
self.set_dimensions(height=kwargs["height"])
|
||||
del kwargs["height"]
|
||||
self._set_dimensions(height=kwargs.pop("height"))
|
||||
|
||||
super().configure(require_redraw=require_redraw, **kwargs)
|
||||
|
||||
def _clicked(self, event=None):
|
||||
if self._state == "normal":
|
||||
if self._orientation.lower() == "horizontal":
|
||||
self._value = (event.x / self._current_width) / self._widget_scaling
|
||||
else:
|
||||
self._value = 1 - (event.y / self._current_height) / self._widget_scaling
|
||||
|
||||
if self._value > 1:
|
||||
self._value = 1
|
||||
if self._value < 0:
|
||||
self._value = 0
|
||||
|
||||
self._output_value = self._round_to_step_size(self._from_ + (self._value * (self._to - self._from_)))
|
||||
self._value = (self._output_value - self._from_) / (self._to - self._from_)
|
||||
|
||||
self._draw(no_color_updates=False)
|
||||
|
||||
if self._variable is not None:
|
||||
self._variable_callback_blocked = True
|
||||
self._variable.set(round(self._output_value) if isinstance(self._variable, tkinter.IntVar) else self._output_value)
|
||||
self._variable_callback_blocked = False
|
||||
|
||||
if self._command is not None:
|
||||
self._command(self._output_value)
|
||||
|
||||
def _on_enter(self, event=0):
|
||||
if self._state == "normal":
|
||||
self._hover_state = True
|
||||
self._canvas.itemconfig("slider_parts",
|
||||
fill=ThemeManager.single_color(self._button_hover_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._button_hover_color, self._appearance_mode))
|
||||
|
||||
def _on_leave(self, event=0):
|
||||
self._hover_state = False
|
||||
self._canvas.itemconfig("slider_parts",
|
||||
fill=ThemeManager.single_color(self._button_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._button_color, self._appearance_mode))
|
||||
|
||||
def _round_to_step_size(self, value) -> float:
|
||||
if self._number_of_steps is not None:
|
||||
step_size = (self._to - self._from_) / self._number_of_steps
|
||||
value = self._to - (round((self._to - value) / step_size) * step_size)
|
||||
return value
|
||||
else:
|
||||
return value
|
||||
|
||||
def get(self) -> float:
|
||||
return self._output_value
|
||||
|
||||
def set(self, output_value, from_variable_callback=False):
|
||||
if self._from_ < self._to:
|
||||
if output_value > self._to:
|
||||
output_value = self._to
|
||||
elif output_value < self._from_:
|
||||
output_value = self._from_
|
||||
else:
|
||||
if output_value < self._to:
|
||||
output_value = self._to
|
||||
elif output_value > self._from_:
|
||||
output_value = self._from_
|
||||
|
||||
self._output_value = self._round_to_step_size(output_value)
|
||||
self._value = (self._output_value - self._from_) / (self._to - self._from_)
|
||||
|
||||
self._draw(no_color_updates=False)
|
||||
|
||||
if self._variable is not None and not from_variable_callback:
|
||||
self._variable_callback_blocked = True
|
||||
self._variable.set(round(self._output_value) if isinstance(self._variable, tkinter.IntVar) else self._output_value)
|
||||
self._variable_callback_blocked = False
|
||||
|
||||
def _variable_callback(self, var_name, index, mode):
|
||||
if not self._variable_callback_blocked:
|
||||
self.set(self._variable.get(), from_variable_callback=True)
|
||||
|
@ -1,5 +1,6 @@
|
||||
import tkinter
|
||||
import sys
|
||||
from typing import Union, Tuple, Callable
|
||||
|
||||
from .ctk_canvas import CTkCanvas
|
||||
from ..theme_manager import ThemeManager
|
||||
@ -9,316 +10,322 @@ from .widget_base_class import CTkBaseClass
|
||||
|
||||
|
||||
class CTkSwitch(CTkBaseClass):
|
||||
"""
|
||||
Switch with rounded corners, border, label, command, variable support.
|
||||
For detailed information check out the documentation.
|
||||
"""
|
||||
|
||||
def __init__(self, *args,
|
||||
text="CTkSwitch",
|
||||
text_font="default_theme",
|
||||
text_color="default_theme",
|
||||
text_color_disabled="default_theme",
|
||||
bg_color=None,
|
||||
border_color=None,
|
||||
fg_color="default_theme",
|
||||
progress_color="default_theme",
|
||||
button_color="default_theme",
|
||||
button_hover_color="default_theme",
|
||||
width=36,
|
||||
height=18,
|
||||
corner_radius="default_theme",
|
||||
# button_corner_radius="default_theme",
|
||||
border_width="default_theme",
|
||||
button_length="default_theme",
|
||||
command=None,
|
||||
onvalue=1,
|
||||
offvalue=0,
|
||||
variable=None,
|
||||
textvariable=None,
|
||||
state=tkinter.NORMAL,
|
||||
bg_color: Union[str, Tuple[str, str], None] = None,
|
||||
border_color: Union[str, Tuple[str, str], None] = None,
|
||||
fg_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
progress_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
button_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
button_hover_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
width: int = 36,
|
||||
height: int = 18,
|
||||
text: str = "CTkSwitch",
|
||||
text_font: any = "default_theme",
|
||||
text_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
text_color_disabled: Union[str, Tuple[str, str]] = "default_theme",
|
||||
corner_radius: Union[int, str] = "default_theme",
|
||||
border_width: Union[int, str] = "default_theme",
|
||||
button_length: Union[int, str] = "default_theme",
|
||||
command: Callable = None,
|
||||
onvalue: Union[int, str] = 1,
|
||||
offvalue: Union[int, str] = 0,
|
||||
variable: tkinter.Variable = None,
|
||||
textvariable: tkinter.Variable = None,
|
||||
state: str = tkinter.NORMAL,
|
||||
**kwargs):
|
||||
|
||||
# transfer basic functionality (bg_color, size, _appearance_mode, scaling) to CTkBaseClass
|
||||
# transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to CTkBaseClass
|
||||
super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs)
|
||||
|
||||
# color
|
||||
self.border_color = border_color
|
||||
self.fg_color = ThemeManager.theme["color"]["switch"] if fg_color == "default_theme" else fg_color
|
||||
self.progress_color = ThemeManager.theme["color"]["switch_progress"] if progress_color == "default_theme" else progress_color
|
||||
self.button_color = ThemeManager.theme["color"]["switch_button"] if button_color == "default_theme" else button_color
|
||||
self.button_hover_color = ThemeManager.theme["color"]["switch_button_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_disabled = ThemeManager.theme["color"]["text_disabled"] if text_color_disabled == "default_theme" else text_color_disabled
|
||||
self._border_color = border_color
|
||||
self._fg_color = ThemeManager.theme["color"]["switch"] if fg_color == "default_theme" else fg_color
|
||||
self._progress_color = ThemeManager.theme["color"]["switch_progress"] if progress_color == "default_theme" else progress_color
|
||||
self._button_color = ThemeManager.theme["color"]["switch_button"] if button_color == "default_theme" else button_color
|
||||
self._button_hover_color = ThemeManager.theme["color"]["switch_button_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_disabled = ThemeManager.theme["color"]["text_disabled"] if text_color_disabled == "default_theme" else text_color_disabled
|
||||
|
||||
# text
|
||||
self.text = text
|
||||
self.text_label = None
|
||||
self.text_font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font
|
||||
self._text = text
|
||||
self._text_label = None
|
||||
self._text_font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font
|
||||
|
||||
# shape
|
||||
self.corner_radius = ThemeManager.theme["shape"]["switch_corner_radius"] if corner_radius == "default_theme" else corner_radius
|
||||
self._corner_radius = ThemeManager.theme["shape"]["switch_corner_radius"] if corner_radius == "default_theme" else corner_radius
|
||||
# self.button_corner_radius = ThemeManager.theme["shape"]["switch_button_corner_radius"] if button_corner_radius == "default_theme" else button_corner_radius
|
||||
self.border_width = ThemeManager.theme["shape"]["switch_border_width"] if border_width == "default_theme" else border_width
|
||||
self.button_length = ThemeManager.theme["shape"]["switch_button_length"] if button_length == "default_theme" else button_length
|
||||
self.hover_state = False
|
||||
self.check_state = False # True if switch is activated
|
||||
self.state = state
|
||||
self.onvalue = onvalue
|
||||
self.offvalue = offvalue
|
||||
self._border_width = ThemeManager.theme["shape"]["switch_border_width"] if border_width == "default_theme" else border_width
|
||||
self._button_length = ThemeManager.theme["shape"]["switch_button_length"] if button_length == "default_theme" else button_length
|
||||
self._hover_state: bool = False
|
||||
self._check_state: bool = False # True if switch is activated
|
||||
self._state = state
|
||||
self._onvalue = onvalue
|
||||
self._offvalue = offvalue
|
||||
|
||||
# callback and control variables
|
||||
self.command = command
|
||||
self.variable: tkinter.Variable = variable
|
||||
self.variable_callback_blocked = False
|
||||
self.variable_callback_name = None
|
||||
self.textvariable = textvariable
|
||||
self._command = command
|
||||
self._variable: tkinter.Variable = variable
|
||||
self._variable_callback_blocked = False
|
||||
self._variable_callback_name = None
|
||||
self._textvariable = textvariable
|
||||
|
||||
# configure grid system (3x1)
|
||||
self.grid_columnconfigure(0, weight=1)
|
||||
self.grid_columnconfigure(1, weight=0, minsize=self.apply_widget_scaling(6))
|
||||
self.grid_columnconfigure(1, weight=0, minsize=self._apply_widget_scaling(6))
|
||||
self.grid_columnconfigure(2, weight=0)
|
||||
|
||||
self.bg_canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self.apply_widget_scaling(self._current_width),
|
||||
height=self.apply_widget_scaling(self._current_height))
|
||||
self.bg_canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=3, rowspan=1, sticky="nswe")
|
||||
self._bg_canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self._apply_widget_scaling(self._current_width),
|
||||
height=self._apply_widget_scaling(self._current_height))
|
||||
self._bg_canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=3, rowspan=1, sticky="nswe")
|
||||
|
||||
self.canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self.apply_widget_scaling(self._current_width),
|
||||
height=self.apply_widget_scaling(self._current_height))
|
||||
self.canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=1, sticky="nswe")
|
||||
self.draw_engine = DrawEngine(self.canvas)
|
||||
self._canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self._apply_widget_scaling(self._current_width),
|
||||
height=self._apply_widget_scaling(self._current_height))
|
||||
self._canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=1, sticky="nswe")
|
||||
self._draw_engine = DrawEngine(self._canvas)
|
||||
|
||||
self.canvas.bind("<Enter>", self.on_enter)
|
||||
self.canvas.bind("<Leave>", self.on_leave)
|
||||
self.canvas.bind("<Button-1>", self.toggle)
|
||||
self._canvas.bind("<Enter>", self._on_enter)
|
||||
self._canvas.bind("<Leave>", self._on_leave)
|
||||
self._canvas.bind("<Button-1>", self.toggle)
|
||||
|
||||
self.text_label = tkinter.Label(master=self,
|
||||
bd=0,
|
||||
text=self.text,
|
||||
justify=tkinter.LEFT,
|
||||
font=self.apply_font_scaling(self.text_font),
|
||||
textvariable=self.textvariable)
|
||||
self.text_label.grid(row=0, column=2, padx=0, pady=0, sticky="w")
|
||||
self.text_label["anchor"] = "w"
|
||||
self._text_label = tkinter.Label(master=self,
|
||||
bd=0,
|
||||
text=self._text,
|
||||
justify=tkinter.LEFT,
|
||||
font=self._apply_font_scaling(self._text_font),
|
||||
textvariable=self._textvariable)
|
||||
self._text_label.grid(row=0, column=2, padx=0, pady=0, sticky="w")
|
||||
self._text_label["anchor"] = "w"
|
||||
|
||||
self.text_label.bind("<Enter>", self.on_enter)
|
||||
self.text_label.bind("<Leave>", self.on_leave)
|
||||
self.text_label.bind("<Button-1>", self.toggle)
|
||||
self._text_label.bind("<Enter>", self._on_enter)
|
||||
self._text_label.bind("<Leave>", self._on_leave)
|
||||
self._text_label.bind("<Button-1>", self.toggle)
|
||||
|
||||
if self.variable is not None and self.variable != "":
|
||||
self.variable_callback_name = self.variable.trace_add("write", self.variable_callback)
|
||||
self.check_state = True if self.variable.get() == self.onvalue else False
|
||||
if self._variable is not None and self._variable != "":
|
||||
self._variable_callback_name = self._variable.trace_add("write", self._variable_callback)
|
||||
self.c_heck_state = True if self._variable.get() == self._onvalue else False
|
||||
|
||||
self.draw() # initial draw
|
||||
self.set_cursor()
|
||||
self._draw() # initial draw
|
||||
self._set_cursor()
|
||||
|
||||
def set_scaling(self, *args, **kwargs):
|
||||
super().set_scaling(*args, **kwargs)
|
||||
def _set_scaling(self, *args, **kwargs):
|
||||
super()._set_scaling(*args, **kwargs)
|
||||
|
||||
self.grid_columnconfigure(1, weight=0, minsize=self.apply_widget_scaling(6))
|
||||
self.text_label.configure(font=self.apply_font_scaling(self.text_font))
|
||||
self.grid_columnconfigure(1, weight=0, minsize=self._apply_widget_scaling(6))
|
||||
self._text_label.configure(font=self._apply_font_scaling(self._text_font))
|
||||
|
||||
self.bg_canvas.configure(width=self.apply_widget_scaling(self._desired_width), height=self.apply_widget_scaling(self._desired_height))
|
||||
self.canvas.configure(width=self.apply_widget_scaling(self._desired_width), height=self.apply_widget_scaling(self._desired_height))
|
||||
self.draw()
|
||||
self._bg_canvas.configure(width=self._apply_widget_scaling(self._desired_width), height=self._apply_widget_scaling(self._desired_height))
|
||||
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), height=self._apply_widget_scaling(self._desired_height))
|
||||
self._draw()
|
||||
|
||||
def destroy(self):
|
||||
# remove variable_callback from variable callbacks if variable exists
|
||||
if self.variable is not None:
|
||||
self.variable.trace_remove("write", self.variable_callback_name)
|
||||
if self._variable is not None:
|
||||
self._variable.trace_remove("write", self._variable_callback_name)
|
||||
|
||||
super().destroy()
|
||||
|
||||
def set_cursor(self):
|
||||
def _set_cursor(self):
|
||||
if Settings.cursor_manipulation_enabled:
|
||||
if self.state == tkinter.DISABLED:
|
||||
if self._state == tkinter.DISABLED:
|
||||
if sys.platform == "darwin" and Settings.cursor_manipulation_enabled:
|
||||
self.canvas.configure(cursor="arrow")
|
||||
if self.text_label is not None:
|
||||
self.text_label.configure(cursor="arrow")
|
||||
self._canvas.configure(cursor="arrow")
|
||||
if self._text_label is not None:
|
||||
self._text_label.configure(cursor="arrow")
|
||||
elif sys.platform.startswith("win") and Settings.cursor_manipulation_enabled:
|
||||
self.canvas.configure(cursor="arrow")
|
||||
if self.text_label is not None:
|
||||
self.text_label.configure(cursor="arrow")
|
||||
self._canvas.configure(cursor="arrow")
|
||||
if self._text_label is not None:
|
||||
self._text_label.configure(cursor="arrow")
|
||||
|
||||
elif self.state == tkinter.NORMAL:
|
||||
elif self._state == tkinter.NORMAL:
|
||||
if sys.platform == "darwin" and Settings.cursor_manipulation_enabled:
|
||||
self.canvas.configure(cursor="pointinghand")
|
||||
if self.text_label is not None:
|
||||
self.text_label.configure(cursor="pointinghand")
|
||||
self._canvas.configure(cursor="pointinghand")
|
||||
if self._text_label is not None:
|
||||
self._text_label.configure(cursor="pointinghand")
|
||||
elif sys.platform.startswith("win") and Settings.cursor_manipulation_enabled:
|
||||
self.canvas.configure(cursor="hand2")
|
||||
if self.text_label is not None:
|
||||
self.text_label.configure(cursor="hand2")
|
||||
self._canvas.configure(cursor="hand2")
|
||||
if self._text_label is not None:
|
||||
self._text_label.configure(cursor="hand2")
|
||||
|
||||
def draw(self, no_color_updates=False):
|
||||
def _draw(self, no_color_updates=False):
|
||||
|
||||
if self.check_state is True:
|
||||
requires_recoloring = self.draw_engine.draw_rounded_slider_with_border_and_button(self.apply_widget_scaling(self._current_width),
|
||||
self.apply_widget_scaling(self._current_height),
|
||||
self.apply_widget_scaling(self.corner_radius),
|
||||
self.apply_widget_scaling(self.border_width),
|
||||
self.apply_widget_scaling(self.button_length),
|
||||
self.apply_widget_scaling(self.corner_radius),
|
||||
1, "w")
|
||||
if self._check_state is True:
|
||||
requires_recoloring = self._draw_engine.draw_rounded_slider_with_border_and_button(self._apply_widget_scaling(self._current_width),
|
||||
self._apply_widget_scaling(self._current_height),
|
||||
self._apply_widget_scaling(self._corner_radius),
|
||||
self._apply_widget_scaling(self._border_width),
|
||||
self._apply_widget_scaling(self._button_length),
|
||||
self._apply_widget_scaling(self._corner_radius),
|
||||
1, "w")
|
||||
else:
|
||||
requires_recoloring = self.draw_engine.draw_rounded_slider_with_border_and_button(self.apply_widget_scaling(self._current_width),
|
||||
self.apply_widget_scaling(self._current_height),
|
||||
self.apply_widget_scaling(self.corner_radius),
|
||||
self.apply_widget_scaling(self.border_width),
|
||||
self.apply_widget_scaling(self.button_length),
|
||||
self.apply_widget_scaling(self.corner_radius),
|
||||
0, "w")
|
||||
requires_recoloring = self._draw_engine.draw_rounded_slider_with_border_and_button(self._apply_widget_scaling(self._current_width),
|
||||
self._apply_widget_scaling(self._current_height),
|
||||
self._apply_widget_scaling(self._corner_radius),
|
||||
self._apply_widget_scaling(self._border_width),
|
||||
self._apply_widget_scaling(self._button_length),
|
||||
self._apply_widget_scaling(self._corner_radius),
|
||||
0, "w")
|
||||
|
||||
if no_color_updates is False or requires_recoloring:
|
||||
self.bg_canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
|
||||
self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
|
||||
self._bg_canvas.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode))
|
||||
self._canvas.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode))
|
||||
|
||||
if self.border_color is None:
|
||||
self.canvas.itemconfig("border_parts", fill=ThemeManager.single_color(self.bg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.bg_color, self._appearance_mode))
|
||||
if self._border_color is None:
|
||||
self._canvas.itemconfig("border_parts", fill=ThemeManager.single_color(self._bg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._bg_color, self._appearance_mode))
|
||||
else:
|
||||
self.canvas.itemconfig("border_parts", fill=ThemeManager.single_color(self.border_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.border_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("border_parts", fill=ThemeManager.single_color(self._border_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._border_color, self._appearance_mode))
|
||||
|
||||
self.canvas.itemconfig("inner_parts", fill=ThemeManager.single_color(self.fg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.fg_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("inner_parts", fill=ThemeManager.single_color(self._fg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._fg_color, self._appearance_mode))
|
||||
|
||||
if self.progress_color is None:
|
||||
self.canvas.itemconfig("progress_parts", fill=ThemeManager.single_color(self.fg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.fg_color, self._appearance_mode))
|
||||
if self._progress_color is None:
|
||||
self._canvas.itemconfig("progress_parts", fill=ThemeManager.single_color(self._fg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._fg_color, self._appearance_mode))
|
||||
else:
|
||||
self.canvas.itemconfig("progress_parts", fill=ThemeManager.single_color(self.progress_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.progress_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("progress_parts", fill=ThemeManager.single_color(self._progress_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._progress_color, self._appearance_mode))
|
||||
|
||||
self.canvas.itemconfig("slider_parts", fill=ThemeManager.single_color(self.button_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.button_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("slider_parts", fill=ThemeManager.single_color(self._button_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._button_color, self._appearance_mode))
|
||||
|
||||
if self.state == tkinter.DISABLED:
|
||||
self.text_label.configure(fg=(ThemeManager.single_color(self.text_color_disabled, self._appearance_mode)))
|
||||
if self._state == tkinter.DISABLED:
|
||||
self._text_label.configure(fg=(ThemeManager.single_color(self._text_color_disabled, self._appearance_mode)))
|
||||
else:
|
||||
self.text_label.configure(fg=ThemeManager.single_color(self.text_color, self._appearance_mode))
|
||||
self._text_label.configure(fg=ThemeManager.single_color(self._text_color, self._appearance_mode))
|
||||
|
||||
self.text_label.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
|
||||
self._text_label.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode))
|
||||
|
||||
def toggle(self, event=None):
|
||||
if self.state is not tkinter.DISABLED:
|
||||
if self.check_state is True:
|
||||
self.check_state = False
|
||||
if self._state is not tkinter.DISABLED:
|
||||
if self._check_state is True:
|
||||
self._check_state = False
|
||||
else:
|
||||
self.check_state = True
|
||||
self._check_state = True
|
||||
|
||||
self.draw(no_color_updates=True)
|
||||
self._draw(no_color_updates=True)
|
||||
|
||||
if self.variable is not None:
|
||||
self.variable_callback_blocked = True
|
||||
self.variable.set(self.onvalue if self.check_state is True else self.offvalue)
|
||||
self.variable_callback_blocked = False
|
||||
if self._variable is not None:
|
||||
self._variable_callback_blocked = True
|
||||
self._variable.set(self._onvalue if self._check_state is True else self._offvalue)
|
||||
self._variable_callback_blocked = False
|
||||
|
||||
if self.command is not None:
|
||||
self.command()
|
||||
if self._command is not None:
|
||||
self._command()
|
||||
|
||||
def select(self, from_variable_callback=False):
|
||||
if self.state is not tkinter.DISABLED or from_variable_callback:
|
||||
self.check_state = True
|
||||
if self._state is not tkinter.DISABLED or from_variable_callback:
|
||||
self._check_state = True
|
||||
|
||||
self.draw(no_color_updates=True)
|
||||
self._draw(no_color_updates=True)
|
||||
|
||||
if self.variable is not None and not from_variable_callback:
|
||||
self.variable_callback_blocked = True
|
||||
self.variable.set(self.onvalue)
|
||||
self.variable_callback_blocked = False
|
||||
if self._variable is not None and not from_variable_callback:
|
||||
self._variable_callback_blocked = True
|
||||
self._variable.set(self._onvalue)
|
||||
self._variable_callback_blocked = False
|
||||
|
||||
def deselect(self, from_variable_callback=False):
|
||||
if self.state is not tkinter.DISABLED or from_variable_callback:
|
||||
self.check_state = False
|
||||
if self._state is not tkinter.DISABLED or from_variable_callback:
|
||||
self._check_state = False
|
||||
|
||||
self.draw(no_color_updates=True)
|
||||
self._draw(no_color_updates=True)
|
||||
|
||||
if self.variable is not None and not from_variable_callback:
|
||||
self.variable_callback_blocked = True
|
||||
self.variable.set(self.offvalue)
|
||||
self.variable_callback_blocked = False
|
||||
if self._variable is not None and not from_variable_callback:
|
||||
self._variable_callback_blocked = True
|
||||
self._variable.set(self._offvalue)
|
||||
self._variable_callback_blocked = False
|
||||
|
||||
def get(self):
|
||||
return self.onvalue if self.check_state is True else self.offvalue
|
||||
def get(self) -> Union[int, str]:
|
||||
return self._onvalue if self._check_state is True else self._offvalue
|
||||
|
||||
def on_enter(self, event=0):
|
||||
self.hover_state = True
|
||||
def _on_enter(self, event=0):
|
||||
self._hover_state = True
|
||||
|
||||
if self.state is not tkinter.DISABLED:
|
||||
self.canvas.itemconfig("slider_parts", fill=ThemeManager.single_color(self.button_hover_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.button_hover_color, self._appearance_mode))
|
||||
if self._state is not tkinter.DISABLED:
|
||||
self._canvas.itemconfig("slider_parts",
|
||||
fill=ThemeManager.single_color(self._button_hover_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._button_hover_color, self._appearance_mode))
|
||||
|
||||
def on_leave(self, event=0):
|
||||
self.hover_state = False
|
||||
self.canvas.itemconfig("slider_parts", fill=ThemeManager.single_color(self.button_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.button_color, self._appearance_mode))
|
||||
def _on_leave(self, event=0):
|
||||
self._hover_state = False
|
||||
self._canvas.itemconfig("slider_parts",
|
||||
fill=ThemeManager.single_color(self._button_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._button_color, self._appearance_mode))
|
||||
|
||||
def variable_callback(self, var_name, index, mode):
|
||||
if not self.variable_callback_blocked:
|
||||
if self.variable.get() == self.onvalue:
|
||||
def _variable_callback(self, var_name, index, mode):
|
||||
if not self._variable_callback_blocked:
|
||||
if self._variable.get() == self._onvalue:
|
||||
self.select(from_variable_callback=True)
|
||||
elif self.variable.get() == self.offvalue:
|
||||
elif self._variable.get() == self._offvalue:
|
||||
self.deselect(from_variable_callback=True)
|
||||
|
||||
def configure(self, require_redraw=False, **kwargs):
|
||||
if "text" in kwargs:
|
||||
self.text = kwargs.pop("text")
|
||||
self.text_label.configure(text=self.text)
|
||||
self._text = kwargs.pop("text")
|
||||
self._text_label.configure(text=self._text)
|
||||
|
||||
if "text_font" in kwargs:
|
||||
self.text_font = kwargs.pop("text_font")
|
||||
self.text_label.configure(font=self.apply_font_scaling(self.text_font))
|
||||
self._text_font = kwargs.pop("text_font")
|
||||
self._text_label.configure(font=self._apply_font_scaling(self._text_font))
|
||||
|
||||
if "state" in kwargs:
|
||||
self.state = kwargs.pop("state")
|
||||
self.set_cursor()
|
||||
self._state = kwargs.pop("state")
|
||||
self._set_cursor()
|
||||
require_redraw = True
|
||||
|
||||
if "fg_color" in kwargs:
|
||||
self.fg_color = kwargs.pop("fg_color")
|
||||
self._fg_color = kwargs.pop("fg_color")
|
||||
require_redraw = True
|
||||
|
||||
if "progress_color" in kwargs:
|
||||
new_progress_color = kwargs.pop("progress_color")
|
||||
if new_progress_color is None:
|
||||
self.progress_color = self.fg_color
|
||||
self._progress_color = self._fg_color
|
||||
else:
|
||||
self.progress_color = new_progress_color
|
||||
self._progress_color = new_progress_color
|
||||
require_redraw = True
|
||||
|
||||
if "button_color" in kwargs:
|
||||
self.button_color = kwargs.pop("button_color")
|
||||
self._button_color = kwargs.pop("button_color")
|
||||
require_redraw = True
|
||||
|
||||
if "button_hover_color" in kwargs:
|
||||
self.button_hover_color = kwargs.pop("button_hover_color")
|
||||
self._button_hover_color = kwargs.pop("button_hover_color")
|
||||
require_redraw = True
|
||||
|
||||
if "border_color" in kwargs:
|
||||
self.border_color = kwargs.pop("border_color")
|
||||
self._border_color = kwargs.pop("border_color")
|
||||
require_redraw = True
|
||||
|
||||
if "border_width" in kwargs:
|
||||
self.border_width = kwargs.pop("border_width")
|
||||
self._border_width = kwargs.pop("border_width")
|
||||
require_redraw = True
|
||||
|
||||
if "command" in kwargs:
|
||||
self.command = kwargs.pop("command")
|
||||
self._command = kwargs.pop("command")
|
||||
|
||||
if "textvariable" in kwargs:
|
||||
self.textvariable = kwargs.pop("textvariable")
|
||||
self.text_label.configure(textvariable=self.textvariable)
|
||||
self._textvariable = kwargs.pop("textvariable")
|
||||
self._text_label.configure(textvariable=self._textvariable)
|
||||
|
||||
if "variable" in kwargs:
|
||||
if self.variable is not None and self.variable != "":
|
||||
self.variable.trace_remove("write", self.variable_callback_name)
|
||||
if self._variable is not None and self._variable != "":
|
||||
self._variable.trace_remove("write", self._variable_callback_name)
|
||||
|
||||
self.variable = kwargs.pop("variable")
|
||||
self._variable = kwargs.pop("variable")
|
||||
|
||||
if self.variable is not None and self.variable != "":
|
||||
self.variable_callback_name = self.variable.trace_add("write", self.variable_callback)
|
||||
self.check_state = True if self.variable.get() == self.onvalue else False
|
||||
if self._variable is not None and self._variable != "":
|
||||
self._variable_callback_name = self._variable.trace_add("write", self._variable_callback)
|
||||
self._check_state = True if self._variable.get() == self._onvalue else False
|
||||
require_redraw = True
|
||||
|
||||
super().configure(require_redraw=require_redraw, **kwargs)
|
||||
|
@ -1,4 +1,5 @@
|
||||
import tkinter
|
||||
from typing import Union, Tuple, Callable
|
||||
|
||||
from .ctk_canvas import CTkCanvas
|
||||
from ..theme_manager import ThemeManager
|
||||
@ -7,163 +8,169 @@ from .widget_base_class import CTkBaseClass
|
||||
|
||||
|
||||
class CTkTextbox(CTkBaseClass):
|
||||
"""
|
||||
Textbox with rounded corners, and all text features of tkinter Text widget.
|
||||
For detailed information check out the documentation.
|
||||
"""
|
||||
|
||||
def __init__(self, *args,
|
||||
bg_color=None,
|
||||
fg_color="default_theme",
|
||||
border_color="default_theme",
|
||||
border_width="default_theme",
|
||||
corner_radius="default_theme",
|
||||
text_font="default_theme",
|
||||
text_color="default_theme",
|
||||
width=200,
|
||||
height=200,
|
||||
bg_color: Union[str, Tuple[str, str], None] = None,
|
||||
fg_color: Union[str, Tuple[str, str], None] = "default_theme",
|
||||
border_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
border_width: Union[str, str] = "default_theme",
|
||||
corner_radius: Union[str, str] = "default_theme",
|
||||
text_font: any = "default_theme",
|
||||
text_color: Union[str, str] = "default_theme",
|
||||
width: int = 200,
|
||||
height: int = 200,
|
||||
**kwargs):
|
||||
|
||||
# transfer basic functionality (bg_color, size, _appearance_mode, scaling) to CTkBaseClass
|
||||
# transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to CTkBaseClass
|
||||
if "master" in kwargs:
|
||||
super().__init__(*args, bg_color=bg_color, width=width, height=height, master=kwargs.pop("master"))
|
||||
else:
|
||||
super().__init__(*args, bg_color=bg_color, width=width, height=height)
|
||||
|
||||
# color
|
||||
self.fg_color = ThemeManager.theme["color"]["entry"] if fg_color == "default_theme" else fg_color
|
||||
self.border_color = ThemeManager.theme["color"]["frame_border"] if border_color == "default_theme" else border_color
|
||||
self.text_color = ThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color
|
||||
self._fg_color = ThemeManager.theme["color"]["entry"] if fg_color == "default_theme" else fg_color
|
||||
self._border_color = ThemeManager.theme["color"]["frame_border"] if border_color == "default_theme" else border_color
|
||||
self._text_color = ThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color
|
||||
|
||||
# shape
|
||||
self.corner_radius = ThemeManager.theme["shape"]["frame_corner_radius"] if corner_radius == "default_theme" else corner_radius
|
||||
self.border_width = ThemeManager.theme["shape"]["frame_border_width"] if border_width == "default_theme" else border_width
|
||||
self._corner_radius = ThemeManager.theme["shape"]["frame_corner_radius"] if corner_radius == "default_theme" else corner_radius
|
||||
self._border_width = ThemeManager.theme["shape"]["frame_border_width"] if border_width == "default_theme" else border_width
|
||||
|
||||
# text
|
||||
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
|
||||
|
||||
# configure 1x1 grid
|
||||
self.grid_rowconfigure(0, weight=1)
|
||||
self.grid_columnconfigure(0, weight=1)
|
||||
|
||||
self.canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self.apply_widget_scaling(self._current_width),
|
||||
height=self.apply_widget_scaling(self._current_height))
|
||||
self.canvas.grid(row=0, column=0, padx=0, pady=0, rowspan=1, columnspan=1, sticky="nsew")
|
||||
self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
|
||||
self.draw_engine = DrawEngine(self.canvas)
|
||||
self._canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self._apply_widget_scaling(self._current_width),
|
||||
height=self._apply_widget_scaling(self._current_height))
|
||||
self._canvas.grid(row=0, column=0, padx=0, pady=0, rowspan=1, columnspan=1, sticky="nsew")
|
||||
self._canvas.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode))
|
||||
self._draw_engine = DrawEngine(self._canvas)
|
||||
|
||||
for arg in ["highlightthickness", "fg", "bg", "font", "width", "height"]:
|
||||
kwargs.pop(arg, None)
|
||||
self.textbox = tkinter.Text(self,
|
||||
fg=ThemeManager.single_color(self.text_color, self._appearance_mode),
|
||||
width=0,
|
||||
height=0,
|
||||
font=self.text_font,
|
||||
highlightthickness=0,
|
||||
relief="flat",
|
||||
insertbackground=ThemeManager.single_color(("black", "white"), self._appearance_mode),
|
||||
bg=ThemeManager.single_color(self.fg_color, self._appearance_mode),
|
||||
**kwargs)
|
||||
self.textbox.grid(row=0, column=0, padx=self.corner_radius, pady=self.corner_radius, rowspan=1, columnspan=1, sticky="nsew")
|
||||
self._textbox = tkinter.Text(self,
|
||||
fg=ThemeManager.single_color(self._text_color, self._appearance_mode),
|
||||
width=0,
|
||||
height=0,
|
||||
font=self._text_font,
|
||||
highlightthickness=0,
|
||||
relief="flat",
|
||||
insertbackground=ThemeManager.single_color(("black", "white"), self._appearance_mode),
|
||||
bg=ThemeManager.single_color(self._fg_color, self._appearance_mode),
|
||||
**kwargs)
|
||||
self._textbox.grid(row=0, column=0, padx=self._corner_radius, pady=self._corner_radius, rowspan=1, columnspan=1, sticky="nsew")
|
||||
|
||||
self.bind('<Configure>', self.update_dimensions_event)
|
||||
self.draw()
|
||||
self.bind('<Configure>', self._update_dimensions_event)
|
||||
self._draw()
|
||||
|
||||
def set_scaling(self, *args, **kwargs):
|
||||
super().set_scaling(*args, **kwargs)
|
||||
def _set_scaling(self, *args, **kwargs):
|
||||
super()._set_scaling(*args, **kwargs)
|
||||
|
||||
self.textbox.configure(font=self.apply_font_scaling(self.text_font))
|
||||
self.canvas.configure(width=self.apply_widget_scaling(self._desired_width), height=self.apply_widget_scaling(self._desired_height))
|
||||
self.draw()
|
||||
self._textbox.configure(font=self._apply_font_scaling(self.text_font))
|
||||
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
|
||||
height=self._apply_widget_scaling(self._desired_height))
|
||||
self._draw()
|
||||
|
||||
def set_dimensions(self, width=None, height=None):
|
||||
super().set_dimensions(width, height)
|
||||
def _set_dimensions(self, width=None, height=None):
|
||||
super()._set_dimensions(width, height)
|
||||
|
||||
self.canvas.configure(width=self.apply_widget_scaling(self._desired_width),
|
||||
height=self.apply_widget_scaling(self._desired_height))
|
||||
self.draw()
|
||||
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
|
||||
height=self._apply_widget_scaling(self._desired_height))
|
||||
self._draw()
|
||||
|
||||
def draw(self, no_color_updates=False):
|
||||
def _draw(self, no_color_updates=False):
|
||||
|
||||
requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.apply_widget_scaling(self._current_width),
|
||||
self.apply_widget_scaling(self._current_height),
|
||||
self.apply_widget_scaling(self.corner_radius),
|
||||
self.apply_widget_scaling(self.border_width))
|
||||
requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._current_width),
|
||||
self._apply_widget_scaling(self._current_height),
|
||||
self._apply_widget_scaling(self._corner_radius),
|
||||
self._apply_widget_scaling(self._border_width))
|
||||
|
||||
if no_color_updates is False or requires_recoloring:
|
||||
if self.fg_color is None:
|
||||
self.canvas.itemconfig("inner_parts",
|
||||
fill=ThemeManager.single_color(self.bg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.bg_color, self._appearance_mode))
|
||||
if self._fg_color is None:
|
||||
self._canvas.itemconfig("inner_parts",
|
||||
fill=ThemeManager.single_color(self._bg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._bg_color, self._appearance_mode))
|
||||
else:
|
||||
self.canvas.itemconfig("inner_parts",
|
||||
fill=ThemeManager.single_color(self.fg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.fg_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("inner_parts",
|
||||
fill=ThemeManager.single_color(self._fg_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._fg_color, self._appearance_mode))
|
||||
|
||||
self.canvas.itemconfig("border_parts",
|
||||
fill=ThemeManager.single_color(self.border_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self.border_color, self._appearance_mode))
|
||||
self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
|
||||
self._canvas.itemconfig("border_parts",
|
||||
fill=ThemeManager.single_color(self._border_color, self._appearance_mode),
|
||||
outline=ThemeManager.single_color(self._border_color, self._appearance_mode))
|
||||
self._canvas.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode))
|
||||
|
||||
self.textbox.configure(fg=ThemeManager.single_color(self.text_color, self._appearance_mode),
|
||||
bg=ThemeManager.single_color(self.fg_color, self._appearance_mode),
|
||||
insertbackground=ThemeManager.single_color(("black", "white"), self._appearance_mode))
|
||||
self._textbox.configure(fg=ThemeManager.single_color(self._text_color, self._appearance_mode),
|
||||
bg=ThemeManager.single_color(self._fg_color, self._appearance_mode),
|
||||
insertbackground=ThemeManager.single_color(("black", "white"), self._appearance_mode))
|
||||
|
||||
self.canvas.tag_lower("inner_parts")
|
||||
self.canvas.tag_lower("border_parts")
|
||||
self._canvas.tag_lower("inner_parts")
|
||||
self._canvas.tag_lower("border_parts")
|
||||
|
||||
def yview(self, *args):
|
||||
return self.textbox.yview(*args)
|
||||
return self._textbox.yview(*args)
|
||||
|
||||
def xview(self, *args):
|
||||
return self.textbox.xview(*args)
|
||||
return self._textbox.xview(*args)
|
||||
|
||||
def insert(self, *args, **kwargs):
|
||||
return self.textbox.insert(*args, **kwargs)
|
||||
return self._textbox.insert(*args, **kwargs)
|
||||
|
||||
def focus(self):
|
||||
return self.textbox.focus()
|
||||
return self._textbox.focus()
|
||||
|
||||
def tag_add(self, *args, **kwargs):
|
||||
return self.textbox.tag_add(*args, **kwargs)
|
||||
return self._textbox.tag_add(*args, **kwargs)
|
||||
|
||||
def tag_config(self, *args, **kwargs):
|
||||
return self.textbox.tag_config(*args, **kwargs)
|
||||
return self._textbox.tag_config(*args, **kwargs)
|
||||
|
||||
def tag_configure(self, *args, **kwargs):
|
||||
return self.textbox.tag_configure(*args, **kwargs)
|
||||
return self._textbox.tag_configure(*args, **kwargs)
|
||||
|
||||
def tag_remove(self, *args, **kwargs):
|
||||
return self.textbox.tag_remove(*args, **kwargs)
|
||||
return self._textbox.tag_remove(*args, **kwargs)
|
||||
|
||||
def configure(self, require_redraw=False, **kwargs):
|
||||
if "fg_color" in kwargs:
|
||||
self.fg_color = kwargs.pop("fg_color")
|
||||
self._fg_color = kwargs.pop("fg_color")
|
||||
require_redraw = True
|
||||
|
||||
# check if CTk widgets are children of the frame and change their bg_color to new frame fg_color
|
||||
# check if CTk widgets are children of the frame and change their _bg_color to new frame fg_color
|
||||
for child in self.winfo_children():
|
||||
if isinstance(child, CTkBaseClass):
|
||||
child.configure(bg_color=self.fg_color)
|
||||
if isinstance(child, CTkBaseClass) and hasattr(child, "_fg_color"):
|
||||
child.configure(bg_color=self._fg_color)
|
||||
|
||||
if "border_color" in kwargs:
|
||||
self.border_color = kwargs.pop("border_color")
|
||||
self._border_color = kwargs.pop("border_color")
|
||||
require_redraw = True
|
||||
|
||||
if "corner_radius" in kwargs:
|
||||
self.corner_radius = kwargs.pop("corner_radius")
|
||||
self._corner_radius = kwargs.pop("corner_radius")
|
||||
require_redraw = True
|
||||
|
||||
if "border_width" in kwargs:
|
||||
self.border_width = kwargs.pop("border_width")
|
||||
self._border_width = kwargs.pop("border_width")
|
||||
require_redraw = True
|
||||
|
||||
if "width" in kwargs:
|
||||
self.set_dimensions(width=kwargs.pop("width"))
|
||||
self._set_dimensions(width=kwargs.pop("width"))
|
||||
|
||||
if "height" in kwargs:
|
||||
self.set_dimensions(height=kwargs.pop("height"))
|
||||
self._set_dimensions(height=kwargs.pop("height"))
|
||||
|
||||
if "text_font" in kwargs:
|
||||
self.text_font = kwargs.pop("text_font")
|
||||
self.textbox.configure(font=self.apply_font_scaling(self.text_font))
|
||||
self._text_font = kwargs.pop("text_font")
|
||||
self._textbox.configure(font=self._apply_font_scaling(self._text_font))
|
||||
|
||||
if "font" in kwargs:
|
||||
raise ValueError("No attribute named font. Use text_font instead of font for CTk widgets")
|
||||
@ -173,4 +180,4 @@ class CTkTextbox(CTkBaseClass):
|
||||
else:
|
||||
super().configure(require_redraw=require_redraw)
|
||||
|
||||
self.textbox.configure(**kwargs)
|
||||
self._textbox.configure(**kwargs)
|
||||
|
@ -2,7 +2,7 @@ import tkinter
|
||||
import sys
|
||||
import copy
|
||||
import re
|
||||
from typing import Union
|
||||
from typing import Union, Tuple, Callable, List
|
||||
|
||||
from ..theme_manager import ThemeManager
|
||||
from ..appearance_mode_tracker import AppearanceModeTracker
|
||||
@ -11,124 +11,127 @@ 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,
|
||||
min_character_width: int = 18,
|
||||
fg_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
hover_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
text_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
text_font: Union[str, Tuple[str, str]] = "default_theme",
|
||||
command: Callable = None,
|
||||
values: List[str] = None,
|
||||
**kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
ScalingTracker.add_widget(self.set_scaling, self)
|
||||
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)
|
||||
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._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._configure_menu_for_platforms()
|
||||
|
||||
self.values = values
|
||||
self.command = command
|
||||
self._values = values
|
||||
self._command = command
|
||||
|
||||
self.add_menu_commands()
|
||||
self._add_menu_commands()
|
||||
|
||||
def configure_menu_for_platforms(self):
|
||||
""" apply platform specific appearance attributes """
|
||||
def _configure_menu_for_platforms(self):
|
||||
""" apply platform specific appearance attributes, configure all colors """
|
||||
|
||||
if sys.platform == "darwin":
|
||||
self.configure(tearoff=False,
|
||||
font=self.apply_font_scaling(self.text_font))
|
||||
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),
|
||||
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),
|
||||
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),
|
||||
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))
|
||||
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):
|
||||
""" delete existing menu labels and createe new labels with command according to values list """
|
||||
|
||||
self.delete(0, "end") # delete all old commands
|
||||
|
||||
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),
|
||||
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),
|
||||
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 _button_callback(self, value):
|
||||
if self._command is not None:
|
||||
self._command(value)
|
||||
|
||||
def open(self, x: Union[int, float], y: Union[int, float]):
|
||||
if sys.platform == "darwin":
|
||||
y += self.apply_widget_scaling(8)
|
||||
y += self._apply_widget_scaling(8)
|
||||
else:
|
||||
y += self.apply_widget_scaling(3)
|
||||
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()
|
||||
self._values = kwargs.pop("values")
|
||||
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))
|
||||
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))
|
||||
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))
|
||||
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))
|
||||
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]:
|
||||
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):
|
||||
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)):
|
||||
@ -150,16 +153,16 @@ class DropdownMenu(tkinter.Menu):
|
||||
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._spacing_scaling = new_spacing_scaling
|
||||
|
||||
self.configure(font=self.apply_font_scaling(self.text_font))
|
||||
self.configure(font=self._apply_font_scaling(self._text_font))
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
self.configure(activeborderwidth=self.apply_widget_scaling(4))
|
||||
self.configure(activeborderwidth=self._apply_widget_scaling(4))
|
||||
|
||||
def set_appearance_mode(self, mode_string):
|
||||
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":
|
||||
@ -167,4 +170,4 @@ class DropdownMenu(tkinter.Menu):
|
||||
elif mode_string.lower() == "light":
|
||||
self._appearance_mode = 0
|
||||
|
||||
self.configure_menu_for_platforms()
|
||||
self._configure_menu_for_platforms()
|
||||
|
@ -2,7 +2,7 @@ import tkinter
|
||||
import tkinter.ttk as ttk
|
||||
import copy
|
||||
import re
|
||||
from typing import Callable, Union
|
||||
from typing import Union, Callable
|
||||
|
||||
try:
|
||||
from typing import TypedDict
|
||||
@ -17,7 +17,7 @@ from ..theme_manager import ThemeManager
|
||||
|
||||
|
||||
class CTkBaseClass(tkinter.Frame):
|
||||
""" Base class of every CTk widget, handles the dimensions, bg_color,
|
||||
""" Base class of every CTk widget, handles the dimensions, _bg_color,
|
||||
appearance_mode changes, scaling, bg changes of master if master is not a CTk widget """
|
||||
|
||||
def __init__(self,
|
||||
@ -36,12 +36,12 @@ class CTkBaseClass(tkinter.Frame):
|
||||
self._desired_height = height
|
||||
|
||||
# scaling
|
||||
ScalingTracker.add_widget(self.set_scaling, self) # add callback for automatic scaling changes
|
||||
ScalingTracker.add_widget(self._set_scaling, self) # add callback for automatic scaling changes
|
||||
self._widget_scaling = ScalingTracker.get_widget_scaling(self)
|
||||
self._spacing_scaling = ScalingTracker.get_spacing_scaling(self)
|
||||
|
||||
super().configure(width=self.apply_widget_scaling(self._desired_width),
|
||||
height=self.apply_widget_scaling(self._desired_height))
|
||||
super().configure(width=self._apply_widget_scaling(self._desired_width),
|
||||
height=self._apply_widget_scaling(self._desired_height))
|
||||
|
||||
# save latest geometry function and kwargs
|
||||
class GeometryCallDict(TypedDict):
|
||||
@ -51,13 +51,13 @@ class CTkBaseClass(tkinter.Frame):
|
||||
self._last_geometry_manager_call: Union[GeometryCallDict, None] = None
|
||||
|
||||
# add set_appearance_mode method to callback list of AppearanceModeTracker for appearance mode changes
|
||||
AppearanceModeTracker.add(self.set_appearance_mode, self)
|
||||
AppearanceModeTracker.add(self._set_appearance_mode, self)
|
||||
self._appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
|
||||
|
||||
# background color
|
||||
self.bg_color = self.detect_color_of_master() if bg_color is None else bg_color
|
||||
self._bg_color = self._detect_color_of_master() if bg_color is None else bg_color
|
||||
|
||||
super().configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
|
||||
super().configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode))
|
||||
|
||||
# overwrite configure methods of master when master is tkinter widget, so that bg changes get applied on child CTk widget as well
|
||||
if isinstance(self.master, (tkinter.Tk, tkinter.Toplevel, tkinter.Frame)) and not isinstance(self.master, (CTkBaseClass, CTk, CTkToplevel)):
|
||||
@ -81,79 +81,79 @@ class CTkBaseClass(tkinter.Frame):
|
||||
self.master.configure = new_configure
|
||||
|
||||
def destroy(self):
|
||||
AppearanceModeTracker.remove(self.set_appearance_mode)
|
||||
AppearanceModeTracker.remove(self._set_appearance_mode)
|
||||
super().destroy()
|
||||
|
||||
def place(self, **kwargs):
|
||||
self._last_geometry_manager_call = {"function": super().place, "kwargs": kwargs}
|
||||
super().place(**self.apply_argument_scaling(kwargs))
|
||||
super().place(**self._apply_argument_scaling(kwargs))
|
||||
|
||||
def pack(self, **kwargs):
|
||||
self._last_geometry_manager_call = {"function": super().pack, "kwargs": kwargs}
|
||||
super().pack(**self.apply_argument_scaling(kwargs))
|
||||
super().pack(**self._apply_argument_scaling(kwargs))
|
||||
|
||||
def grid(self, **kwargs):
|
||||
self._last_geometry_manager_call = {"function": super().grid, "kwargs": kwargs}
|
||||
super().grid(**self.apply_argument_scaling(kwargs))
|
||||
super().grid(**self._apply_argument_scaling(kwargs))
|
||||
|
||||
def apply_argument_scaling(self, kwargs: dict) -> dict:
|
||||
def _apply_argument_scaling(self, kwargs: dict) -> dict:
|
||||
scaled_kwargs = copy.copy(kwargs)
|
||||
|
||||
if "pady" in scaled_kwargs:
|
||||
if isinstance(scaled_kwargs["pady"], (int, float, str)):
|
||||
scaled_kwargs["pady"] = self.apply_spacing_scaling(scaled_kwargs["pady"])
|
||||
scaled_kwargs["pady"] = self._apply_spacing_scaling(scaled_kwargs["pady"])
|
||||
elif isinstance(scaled_kwargs["pady"], tuple):
|
||||
scaled_kwargs["pady"] = tuple([self.apply_spacing_scaling(v) for v in scaled_kwargs["pady"]])
|
||||
scaled_kwargs["pady"] = tuple([self._apply_spacing_scaling(v) for v in scaled_kwargs["pady"]])
|
||||
if "padx" in kwargs:
|
||||
if isinstance(scaled_kwargs["padx"], (int, float, str)):
|
||||
scaled_kwargs["padx"] = self.apply_spacing_scaling(scaled_kwargs["padx"])
|
||||
scaled_kwargs["padx"] = self._apply_spacing_scaling(scaled_kwargs["padx"])
|
||||
elif isinstance(scaled_kwargs["padx"], tuple):
|
||||
scaled_kwargs["padx"] = tuple([self.apply_spacing_scaling(v) for v in scaled_kwargs["padx"]])
|
||||
scaled_kwargs["padx"] = tuple([self._apply_spacing_scaling(v) for v in scaled_kwargs["padx"]])
|
||||
|
||||
if "x" in scaled_kwargs:
|
||||
scaled_kwargs["x"] = self.apply_spacing_scaling(scaled_kwargs["x"])
|
||||
scaled_kwargs["x"] = self._apply_spacing_scaling(scaled_kwargs["x"])
|
||||
if "y" in scaled_kwargs:
|
||||
scaled_kwargs["y"] = self.apply_spacing_scaling(scaled_kwargs["y"])
|
||||
scaled_kwargs["y"] = self._apply_spacing_scaling(scaled_kwargs["y"])
|
||||
|
||||
return scaled_kwargs
|
||||
|
||||
def configure(self, require_redraw=False, **kwargs):
|
||||
""" basic configure with bg_color support, to be overridden """
|
||||
""" basic configure with _bg_color support, to be overridden """
|
||||
|
||||
if "bg_color" in kwargs:
|
||||
new_bg_color = kwargs.pop("bg_color")
|
||||
if new_bg_color is None:
|
||||
self.bg_color = self.detect_color_of_master()
|
||||
self._bg_color = self._detect_color_of_master()
|
||||
else:
|
||||
self.bg_color = new_bg_color
|
||||
self._bg_color = new_bg_color
|
||||
require_redraw = True
|
||||
|
||||
super().configure(**kwargs)
|
||||
|
||||
if require_redraw:
|
||||
self.draw()
|
||||
self._draw()
|
||||
|
||||
def update_dimensions_event(self, event):
|
||||
def _update_dimensions_event(self, event):
|
||||
# only redraw if dimensions changed (for performance), independent of scaling
|
||||
if round(self._current_width) != round(event.width / self._widget_scaling) or round(self._current_height) != round(event.height / self._widget_scaling):
|
||||
self._current_width = (event.width / self._widget_scaling) # adjust current size according to new size given by event
|
||||
self._current_height = (event.height / self._widget_scaling) # _current_width and _current_height are independent of the scale
|
||||
|
||||
self.draw(no_color_updates=True) # faster drawing without color changes
|
||||
self._draw(no_color_updates=True) # faster drawing without color changes
|
||||
|
||||
def detect_color_of_master(self, master_widget=None):
|
||||
""" detect color of self.master widget to set correct bg_color """
|
||||
def _detect_color_of_master(self, master_widget=None):
|
||||
""" detect color of self.master widget to set correct _bg_color """
|
||||
|
||||
if master_widget is None:
|
||||
master_widget = self.master
|
||||
|
||||
if isinstance(master_widget, (CTkBaseClass, CTk, CTkToplevel)) and hasattr(master_widget, "fg_color"):
|
||||
if master_widget.fg_color is not None:
|
||||
return master_widget.fg_color
|
||||
if isinstance(master_widget, (CTkBaseClass, CTk, CTkToplevel)) and hasattr(master_widget, "_fg_color"):
|
||||
if master_widget._fg_color is not None:
|
||||
return master_widget._fg_color
|
||||
|
||||
# if fg_color of master is None, try to retrieve fg_color from master of master
|
||||
elif hasattr(master_widget.master, "master"):
|
||||
return self.detect_color_of_master(master_widget.master)
|
||||
return self._detect_color_of_master(master_widget.master)
|
||||
|
||||
elif isinstance(master_widget, (ttk.Frame, ttk.LabelFrame, ttk.Notebook, ttk.Label)): # master is ttk widget
|
||||
try:
|
||||
@ -168,46 +168,46 @@ class CTkBaseClass(tkinter.Frame):
|
||||
except Exception:
|
||||
return "#FFFFFF", "#000000"
|
||||
|
||||
def set_appearance_mode(self, mode_string):
|
||||
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
|
||||
|
||||
self.draw()
|
||||
self._draw()
|
||||
|
||||
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._spacing_scaling = new_spacing_scaling
|
||||
|
||||
super().configure(width=self.apply_widget_scaling(self._desired_width),
|
||||
height=self.apply_widget_scaling(self._desired_height))
|
||||
super().configure(width=self._apply_widget_scaling(self._desired_width),
|
||||
height=self._apply_widget_scaling(self._desired_height))
|
||||
|
||||
if self._last_geometry_manager_call is not None:
|
||||
self._last_geometry_manager_call["function"](**self.apply_argument_scaling(self._last_geometry_manager_call["kwargs"]))
|
||||
self._last_geometry_manager_call["function"](**self._apply_argument_scaling(self._last_geometry_manager_call["kwargs"]))
|
||||
|
||||
def set_dimensions(self, width=None, height=None):
|
||||
def _set_dimensions(self, width=None, height=None):
|
||||
if width is not None:
|
||||
self._desired_width = width
|
||||
if height is not None:
|
||||
self._desired_height = height
|
||||
|
||||
super().configure(width=self.apply_widget_scaling(self._desired_width),
|
||||
height=self.apply_widget_scaling(self._desired_height))
|
||||
super().configure(width=self._apply_widget_scaling(self._desired_width),
|
||||
height=self._apply_widget_scaling(self._desired_height))
|
||||
|
||||
def apply_widget_scaling(self, value: Union[int, float, str]) -> Union[float, str]:
|
||||
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]:
|
||||
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 apply_font_scaling(self, font):
|
||||
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)):
|
||||
@ -229,6 +229,6 @@ class CTkBaseClass(tkinter.Frame):
|
||||
else:
|
||||
return font
|
||||
|
||||
def draw(self, no_color_updates: bool = False):
|
||||
def _draw(self, no_color_updates: bool = False):
|
||||
""" abstract of draw method to be overridden """
|
||||
pass
|
||||
|
@ -1,5 +1,6 @@
|
||||
import tkinter
|
||||
import time
|
||||
from typing import Union, Tuple
|
||||
|
||||
from ..widgets.ctk_label import CTkLabel
|
||||
from ..widgets.ctk_entry import CTkEntry
|
||||
@ -11,109 +12,111 @@ from ..theme_manager import ThemeManager
|
||||
|
||||
|
||||
class CTkInputDialog:
|
||||
def __init__(self,
|
||||
master=None,
|
||||
title="CTkDialog",
|
||||
text="CTkDialog",
|
||||
fg_color="default_theme",
|
||||
hover_color="default_theme",
|
||||
border_color="default_theme"):
|
||||
"""
|
||||
Dialog with extra window, message, entry widget, cancel and ok button.
|
||||
For detailed information check out the documentation.
|
||||
"""
|
||||
|
||||
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
|
||||
def __init__(self,
|
||||
master: any = None,
|
||||
title: str = "CTkDialog",
|
||||
text: str = "CTkDialog",
|
||||
fg_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
hover_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
border_color: Union[str, Tuple[str, str]] = "default_theme"):
|
||||
|
||||
self._appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
|
||||
self.master = master
|
||||
|
||||
self.user_input = None
|
||||
self.running = False
|
||||
self._window_bg_color = ThemeManager.theme["color"]["window_bg_color"]
|
||||
self._fg_color = ThemeManager.theme["color"]["button"] if fg_color == "default_theme" else fg_color
|
||||
self._hover_color = ThemeManager.theme["color"]["button_hover"] if hover_color == "default_theme" else hover_color
|
||||
self._border_color = ThemeManager.theme["color"]["button_hover"] if border_color == "default_theme" else border_color
|
||||
|
||||
self.height = len(text.split("\n"))*20 + 150
|
||||
self._user_input: Union[str, None] = None
|
||||
self._running: bool = False
|
||||
self._height: int = len(text.split("\n")) * 20 + 150
|
||||
self._text = text
|
||||
|
||||
self.text = text
|
||||
self.window_bg_color = ThemeManager.theme["color"]["window_bg_color"]
|
||||
self.fg_color = ThemeManager.theme["color"]["button"] if fg_color == "default_theme" else fg_color
|
||||
self.hover_color = ThemeManager.theme["color"]["button_hover"] if hover_color == "default_theme" else hover_color
|
||||
self.border_color = ThemeManager.theme["color"]["button_hover"] if border_color == "default_theme" else border_color
|
||||
self._toplevel_window = CTkToplevel()
|
||||
self._toplevel_window.geometry(f"{280}x{self._height}")
|
||||
self._toplevel_window.minsize(280, self._height)
|
||||
self._toplevel_window.maxsize(280, self._height)
|
||||
self._toplevel_window.title(title)
|
||||
self._toplevel_window.lift()
|
||||
self._toplevel_window.focus_force()
|
||||
self._toplevel_window.grab_set()
|
||||
self._toplevel_window.protocol("WM_DELETE_WINDOW", self._on_closing)
|
||||
self._toplevel_window.after(10, self._create_widgets) # create widgets with slight delay, to avoid white flickering of background
|
||||
|
||||
self.top = CTkToplevel()
|
||||
self.top.geometry(f"{280}x{self.height}")
|
||||
self.top.minsize(280, self.height)
|
||||
self.top.maxsize(280, self.height)
|
||||
self.top.title(title)
|
||||
self.top.lift()
|
||||
self.top.focus_force()
|
||||
self.top.grab_set()
|
||||
def _create_widgets(self):
|
||||
self._label_frame = CTkFrame(master=self._toplevel_window,
|
||||
corner_radius=0,
|
||||
fg_color=self._window_bg_color,
|
||||
width=300,
|
||||
height=self._height-100)
|
||||
self._label_frame.place(relx=0.5, rely=0, anchor=tkinter.N)
|
||||
|
||||
self.top.protocol("WM_DELETE_WINDOW", self.on_closing)
|
||||
self._button_and_entry_frame = CTkFrame(master=self._toplevel_window,
|
||||
corner_radius=0,
|
||||
fg_color=self._window_bg_color,
|
||||
width=300,
|
||||
height=100)
|
||||
self._button_and_entry_frame.place(relx=0.5, rely=1, anchor=tkinter.S)
|
||||
|
||||
self.top.after(10, self.create_widgets) # create widgets with slight delay, to avoid white flickering of background
|
||||
self._myLabel = CTkLabel(master=self._label_frame,
|
||||
text=self._text,
|
||||
width=300,
|
||||
fg_color=None,
|
||||
height=self._height-100)
|
||||
self._myLabel.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER)
|
||||
|
||||
def create_widgets(self):
|
||||
self.label_frame = CTkFrame(master=self.top,
|
||||
corner_radius=0,
|
||||
fg_color=self.window_bg_color,
|
||||
width=300,
|
||||
height=self.height-100)
|
||||
self.label_frame.place(relx=0.5, rely=0, anchor=tkinter.N)
|
||||
self._entry = CTkEntry(master=self._button_and_entry_frame,
|
||||
width=230)
|
||||
self._entry.place(relx=0.5, rely=0.15, anchor=tkinter.CENTER)
|
||||
|
||||
self.button_and_entry_frame = CTkFrame(master=self.top,
|
||||
corner_radius=0,
|
||||
fg_color=self.window_bg_color,
|
||||
width=300,
|
||||
height=100)
|
||||
self.button_and_entry_frame.place(relx=0.5, rely=1, anchor=tkinter.S)
|
||||
self._ok_button = CTkButton(master=self._button_and_entry_frame,
|
||||
text='Ok',
|
||||
width=100,
|
||||
command=self._ok_event,
|
||||
fg_color=self._fg_color,
|
||||
hover_color=self._hover_color,
|
||||
border_color=self._border_color)
|
||||
self._ok_button.place(relx=0.28, rely=0.65, anchor=tkinter.CENTER)
|
||||
|
||||
self.myLabel = CTkLabel(master=self.label_frame,
|
||||
text=self.text,
|
||||
width=300,
|
||||
fg_color=None,
|
||||
height=self.height-100)
|
||||
self.myLabel.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER)
|
||||
self._cancel_button = CTkButton(master=self._button_and_entry_frame,
|
||||
text='Cancel',
|
||||
width=100,
|
||||
command=self._cancel_event,
|
||||
fg_color=self._fg_color,
|
||||
hover_color=self._hover_color,
|
||||
border_color=self._border_color)
|
||||
self._cancel_button.place(relx=0.72, rely=0.65, anchor=tkinter.CENTER)
|
||||
|
||||
self.entry = CTkEntry(master=self.button_and_entry_frame,
|
||||
width=230)
|
||||
self.entry.place(relx=0.5, rely=0.15, anchor=tkinter.CENTER)
|
||||
self._entry.focus_force()
|
||||
self._entry.bind("<Return>", self._ok_event)
|
||||
|
||||
self.ok_button = CTkButton(master=self.button_and_entry_frame,
|
||||
text='Ok',
|
||||
width=100,
|
||||
command=self.ok_event,
|
||||
fg_color=self.fg_color,
|
||||
hover_color=self.hover_color,
|
||||
border_color=self.border_color)
|
||||
self.ok_button.place(relx=0.28, rely=0.65, anchor=tkinter.CENTER)
|
||||
def _ok_event(self, event=None):
|
||||
self._user_input = self._entry.get()
|
||||
self._running = False
|
||||
|
||||
self.cancel_button = CTkButton(master=self.button_and_entry_frame,
|
||||
text='Cancel',
|
||||
width=100,
|
||||
command=self.cancel_event,
|
||||
fg_color=self.fg_color,
|
||||
hover_color=self.hover_color,
|
||||
border_color=self.border_color)
|
||||
self.cancel_button.place(relx=0.72, rely=0.65, anchor=tkinter.CENTER)
|
||||
def _on_closing(self):
|
||||
self._running = False
|
||||
|
||||
self.entry.entry.focus_force()
|
||||
self.entry.bind("<Return>", self.ok_event)
|
||||
|
||||
def ok_event(self, event=None):
|
||||
self.user_input = self.entry.get()
|
||||
self.running = False
|
||||
|
||||
def on_closing(self):
|
||||
self.running = False
|
||||
|
||||
def cancel_event(self):
|
||||
self.running = False
|
||||
def _cancel_event(self):
|
||||
self._running = False
|
||||
|
||||
def get_input(self):
|
||||
self.running = True
|
||||
self._running = True
|
||||
|
||||
while self.running:
|
||||
while self._running:
|
||||
try:
|
||||
self.top.update()
|
||||
self._toplevel_window.update()
|
||||
except Exception:
|
||||
return self.user_input
|
||||
return self._user_input
|
||||
finally:
|
||||
time.sleep(0.01)
|
||||
|
||||
time.sleep(0.05)
|
||||
self.top.destroy()
|
||||
return self.user_input
|
||||
self._toplevel_window.destroy()
|
||||
return self._user_input
|
||||
|
@ -14,126 +14,129 @@ from ..settings import Settings
|
||||
|
||||
|
||||
class CTk(tkinter.Tk):
|
||||
"""
|
||||
Main app window with dark titlebar on Windows and macOS.
|
||||
For detailed information check out the documentation.
|
||||
"""
|
||||
|
||||
def __init__(self, *args,
|
||||
fg_color="default_theme",
|
||||
fg_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
**kwargs):
|
||||
|
||||
ScalingTracker.activate_high_dpi_awareness() # make process DPI aware
|
||||
self.enable_macos_dark_title_bar()
|
||||
self._enable_macos_dark_title_bar()
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# add set_appearance_mode method to callback list of AppearanceModeTracker for appearance mode changes
|
||||
AppearanceModeTracker.add(self.set_appearance_mode, self)
|
||||
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
|
||||
AppearanceModeTracker.add(self._set_appearance_mode, self)
|
||||
self._appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
|
||||
|
||||
# add set_scaling method to callback list of ScalingTracker for automatic scaling changes
|
||||
ScalingTracker.add_widget(self.set_scaling, self)
|
||||
self.window_scaling = ScalingTracker.get_window_scaling(self)
|
||||
ScalingTracker.add_widget(self._set_scaling, self)
|
||||
self._window_scaling = ScalingTracker.get_window_scaling(self)
|
||||
|
||||
self.current_width = 600 # initial window size, always without scaling
|
||||
self.current_height = 500
|
||||
self.min_width: int = 0
|
||||
self.min_height: int = 0
|
||||
self.max_width: int = 1_000_000
|
||||
self.max_height: int = 1_000_000
|
||||
self.last_resizable_args: Union[Tuple[list, dict], None] = None # (args, kwargs)
|
||||
self._current_width = 600 # initial window size, always without scaling
|
||||
self._current_height = 500
|
||||
self._min_width: int = 0
|
||||
self._min_height: int = 0
|
||||
self._max_width: int = 1_000_000
|
||||
self._max_height: int = 1_000_000
|
||||
self._last_resizable_args: Union[Tuple[list, dict], None] = None # (args, kwargs)
|
||||
|
||||
self.fg_color = ThemeManager.theme["color"]["window_bg_color"] if fg_color == "default_theme" else fg_color
|
||||
self._fg_color = ThemeManager.theme["color"]["window_bg_color"] if fg_color == "default_theme" else fg_color
|
||||
|
||||
if "bg" in kwargs:
|
||||
self.fg_color = kwargs["bg"]
|
||||
del kwargs["bg"]
|
||||
self._fg_color = kwargs.pop("bg")
|
||||
elif "background" in kwargs:
|
||||
self.fg_color = kwargs["background"]
|
||||
del kwargs["background"]
|
||||
self._fg_color = kwargs.pop("background")
|
||||
|
||||
super().configure(bg=ThemeManager.single_color(self.fg_color, self.appearance_mode))
|
||||
super().configure(bg=ThemeManager.single_color(self._fg_color, self._appearance_mode))
|
||||
super().title("CTk")
|
||||
self.geometry(f"{self.current_width}x{self.current_height}")
|
||||
self.geometry(f"{self._current_width}x{self._current_height}")
|
||||
|
||||
self.state_before_windows_set_titlebar_color = None
|
||||
self.window_exists = False # indicates if the window is already shown through update() or mainloop() after init
|
||||
self.withdraw_called_before_window_exists = False # indicates if withdraw() was called before window is first shown through update() or mainloop()
|
||||
self.iconify_called_before_window_exists = False # indicates if iconify() was called before window is first shown through update() or mainloop()
|
||||
self._state_before_windows_set_titlebar_color = None
|
||||
self._window_exists = False # indicates if the window is already shown through update() or mainloop() after init
|
||||
self._withdraw_called_before_window_exists = False # indicates if withdraw() was called before window is first shown through update() or mainloop()
|
||||
self._iconify_called_before_window_exists = False # indicates if iconify() was called before window is first shown through update() or mainloop()
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
if self.appearance_mode == 1:
|
||||
self.windows_set_titlebar_color("dark")
|
||||
if self._appearance_mode == 1:
|
||||
self._windows_set_titlebar_color("dark")
|
||||
else:
|
||||
self.windows_set_titlebar_color("light")
|
||||
self._windows_set_titlebar_color("light")
|
||||
|
||||
self.bind('<Configure>', self.update_dimensions_event)
|
||||
self.bind('<Configure>', self._update_dimensions_event)
|
||||
|
||||
self.block_update_dimensions_event = False
|
||||
self._block_update_dimensions_event = False
|
||||
|
||||
def update_dimensions_event(self, event=None):
|
||||
if not self.block_update_dimensions_event:
|
||||
def _update_dimensions_event(self, event=None):
|
||||
if not self._block_update_dimensions_event:
|
||||
detected_width = self.winfo_width() # detect current window size
|
||||
detected_height = self.winfo_height()
|
||||
|
||||
if self.current_width != round(detected_width / self.window_scaling) or self.current_height != round(detected_height / self.window_scaling):
|
||||
self.current_width = round(detected_width / self.window_scaling) # adjust current size according to new size given by event
|
||||
self.current_height = round(detected_height / self.window_scaling) # _current_width and _current_height are independent of the scale
|
||||
if self._current_width != round(detected_width / self._window_scaling) or self._current_height != round(detected_height / self._window_scaling):
|
||||
self._current_width = round(detected_width / self._window_scaling) # adjust current size according to new size given by event
|
||||
self._current_height = round(detected_height / self._window_scaling) # _current_width and _current_height are independent of the scale
|
||||
|
||||
def set_scaling(self, new_widget_scaling, new_spacing_scaling, new_window_scaling):
|
||||
self.window_scaling = new_window_scaling
|
||||
def _set_scaling(self, new_widget_scaling, new_spacing_scaling, new_window_scaling):
|
||||
self._window_scaling = new_window_scaling
|
||||
|
||||
# block update_dimensions_event to prevent current_width and current_height to get updated
|
||||
self.block_update_dimensions_event = True
|
||||
self._block_update_dimensions_event = True
|
||||
|
||||
# force new dimensions on window by using min, max, and geometry
|
||||
super().minsize(self.apply_window_scaling(self.current_width), self.apply_window_scaling(self.current_height))
|
||||
super().maxsize(self.apply_window_scaling(self.current_width), self.apply_window_scaling(self.current_height))
|
||||
super().geometry(f"{self.apply_window_scaling(self.current_width)}x"+f"{self.apply_window_scaling(self.current_height)}")
|
||||
super().minsize(self._apply_window_scaling(self._current_width), self._apply_window_scaling(self._current_height))
|
||||
super().maxsize(self._apply_window_scaling(self._current_width), self._apply_window_scaling(self._current_height))
|
||||
super().geometry(f"{self._apply_window_scaling(self._current_width)}x" + f"{self._apply_window_scaling(self._current_height)}")
|
||||
|
||||
# set new scaled min and max with 400ms delay (otherwise it won't work for some reason)
|
||||
self.after(400, self.set_scaled_min_max)
|
||||
self.after(400, self._set_scaled_min_max)
|
||||
|
||||
# release the blocking of update_dimensions_event after a small amount of time (slight delay is necessary)
|
||||
def set_block_update_dimensions_event_false():
|
||||
self.block_update_dimensions_event = False
|
||||
self._block_update_dimensions_event = False
|
||||
self.after(100, lambda: set_block_update_dimensions_event_false())
|
||||
|
||||
def set_scaled_min_max(self):
|
||||
if self.min_width is not None or self.min_height is not None:
|
||||
super().minsize(self.apply_window_scaling(self.min_width), self.apply_window_scaling(self.min_height))
|
||||
if self.max_width is not None or self.max_height is not None:
|
||||
super().maxsize(self.apply_window_scaling(self.max_width), self.apply_window_scaling(self.max_height))
|
||||
def _set_scaled_min_max(self):
|
||||
if self._min_width is not None or self._min_height is not None:
|
||||
super().minsize(self._apply_window_scaling(self._min_width), self._apply_window_scaling(self._min_height))
|
||||
if self._max_width is not None or self._max_height is not None:
|
||||
super().maxsize(self._apply_window_scaling(self._max_width), self._apply_window_scaling(self._max_height))
|
||||
|
||||
def destroy(self):
|
||||
AppearanceModeTracker.remove(self.set_appearance_mode)
|
||||
ScalingTracker.remove_window(self.set_scaling, self)
|
||||
self.disable_macos_dark_title_bar()
|
||||
AppearanceModeTracker.remove(self._set_appearance_mode)
|
||||
ScalingTracker.remove_window(self._set_scaling, self)
|
||||
self._disable_macos_dark_title_bar()
|
||||
super().destroy()
|
||||
|
||||
def withdraw(self):
|
||||
if self.window_exists is False:
|
||||
self.withdraw_called_before_window_exists = True
|
||||
if self._window_exists is False:
|
||||
self._withdraw_called_before_window_exists = True
|
||||
super().withdraw()
|
||||
|
||||
def iconify(self):
|
||||
if self.window_exists is False:
|
||||
self.iconify_called_before_window_exists = True
|
||||
if self._window_exists is False:
|
||||
self._iconify_called_before_window_exists = True
|
||||
super().iconify()
|
||||
|
||||
def update(self):
|
||||
if self.window_exists is False:
|
||||
self.window_exists = True
|
||||
if self._window_exists is False:
|
||||
self._window_exists = True
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
if not self.withdraw_called_before_window_exists and not self.iconify_called_before_window_exists:
|
||||
if not self._withdraw_called_before_window_exists and not self._iconify_called_before_window_exists:
|
||||
# print("window dont exists -> deiconify in update")
|
||||
self.deiconify()
|
||||
|
||||
super().update()
|
||||
|
||||
def mainloop(self, *args, **kwargs):
|
||||
if not self.window_exists:
|
||||
self.window_exists = True
|
||||
if not self._window_exists:
|
||||
self._window_exists = True
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
if not self.withdraw_called_before_window_exists and not self.iconify_called_before_window_exists:
|
||||
if not self._withdraw_called_before_window_exists and not self._iconify_called_before_window_exists:
|
||||
# print("window dont exists -> deiconify in mainloop")
|
||||
self.deiconify()
|
||||
|
||||
@ -141,42 +144,46 @@ class CTk(tkinter.Tk):
|
||||
|
||||
def resizable(self, *args, **kwargs):
|
||||
super().resizable(*args, **kwargs)
|
||||
self.last_resizable_args = (args, kwargs)
|
||||
self._last_resizable_args = (args, kwargs)
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
if self.appearance_mode == 1:
|
||||
self.windows_set_titlebar_color("dark")
|
||||
if self._appearance_mode == 1:
|
||||
self._windows_set_titlebar_color("dark")
|
||||
else:
|
||||
self.windows_set_titlebar_color("light")
|
||||
self._windows_set_titlebar_color("light")
|
||||
|
||||
def minsize(self, width=None, height=None):
|
||||
self.min_width = width
|
||||
self.min_height = height
|
||||
if self.current_width < width: self.current_width = width
|
||||
if self.current_height < height: self.current_height = height
|
||||
super().minsize(self.apply_window_scaling(self.min_width), self.apply_window_scaling(self.min_height))
|
||||
self._min_width = width
|
||||
self._min_height = height
|
||||
if self._current_width < width:
|
||||
self._current_width = width
|
||||
if self._current_height < height:
|
||||
self._current_height = height
|
||||
super().minsize(self._apply_window_scaling(self._min_width), self._apply_window_scaling(self._min_height))
|
||||
|
||||
def maxsize(self, width=None, height=None):
|
||||
self.max_width = width
|
||||
self.max_height = height
|
||||
if self.current_width > width: self.current_width = width
|
||||
if self.current_height > height: self.current_height = height
|
||||
super().maxsize(self.apply_window_scaling(self.max_width), self.apply_window_scaling(self.max_height))
|
||||
self._max_width = width
|
||||
self._max_height = height
|
||||
if self._current_width > width:
|
||||
self._current_width = width
|
||||
if self._current_height > height:
|
||||
self._current_height = height
|
||||
super().maxsize(self._apply_window_scaling(self._max_width), self._apply_window_scaling(self._max_height))
|
||||
|
||||
def geometry(self, geometry_string: str = None):
|
||||
if geometry_string is not None:
|
||||
super().geometry(self.apply_geometry_scaling(geometry_string))
|
||||
super().geometry(self._apply_geometry_scaling(geometry_string))
|
||||
|
||||
# update width and height attributes
|
||||
width, height, x, y = self.parse_geometry_string(geometry_string)
|
||||
width, height, x, y = self._parse_geometry_string(geometry_string)
|
||||
if width is not None and height is not None:
|
||||
self.current_width = max(self.min_width, min(width, self.max_width)) # bound value between min and max
|
||||
self.current_height = max(self.min_height, min(height, self.max_height))
|
||||
self._current_width = max(self._min_width, min(width, self._max_width)) # bound value between min and max
|
||||
self._current_height = max(self._min_height, min(height, self._max_height))
|
||||
else:
|
||||
return self.reverse_geometry_scaling(super().geometry())
|
||||
|
||||
@staticmethod
|
||||
def parse_geometry_string(geometry_string: str) -> tuple:
|
||||
def _parse_geometry_string(geometry_string: str) -> tuple:
|
||||
# index: 1 2 3 4 5 6
|
||||
# regex group structure: ('<width>x<height>', '<width>', '<height>', '+-<x>+-<y>', '-<x>', '-<y>')
|
||||
result = re.search(r"((\d+)x(\d+)){0,1}(\+{0,1}([+-]{0,1}\d+)\+{0,1}([+-]{0,1}\d+)){0,1}", geometry_string)
|
||||
@ -188,33 +195,33 @@ class CTk(tkinter.Tk):
|
||||
|
||||
return width, height, x, y
|
||||
|
||||
def apply_geometry_scaling(self, geometry_string: str) -> str:
|
||||
width, height, x, y = self.parse_geometry_string(geometry_string)
|
||||
def _apply_geometry_scaling(self, geometry_string: str) -> str:
|
||||
width, height, x, y = self._parse_geometry_string(geometry_string)
|
||||
|
||||
if x is None and y is None: # no <x> and <y> in geometry_string
|
||||
return f"{round(width * self.window_scaling)}x{round(height * self.window_scaling)}"
|
||||
return f"{round(width * self._window_scaling)}x{round(height * self._window_scaling)}"
|
||||
|
||||
elif width is None and height is None: # no <width> and <height> in geometry_string
|
||||
return f"+{x}+{y}"
|
||||
|
||||
else:
|
||||
return f"{round(width * self.window_scaling)}x{round(height * self.window_scaling)}+{x}+{y}"
|
||||
return f"{round(width * self._window_scaling)}x{round(height * self._window_scaling)}+{x}+{y}"
|
||||
|
||||
def reverse_geometry_scaling(self, scaled_geometry_string: str) -> str:
|
||||
width, height, x, y = self.parse_geometry_string(scaled_geometry_string)
|
||||
def _reverse_geometry_scaling(self, scaled_geometry_string: str) -> str:
|
||||
width, height, x, y = self._parse_geometry_string(scaled_geometry_string)
|
||||
|
||||
if x is None and y is None: # no <x> and <y> in geometry_string
|
||||
return f"{round(width / self.window_scaling)}x{round(height / self.window_scaling)}"
|
||||
return f"{round(width / self._window_scaling)}x{round(height / self._window_scaling)}"
|
||||
|
||||
elif width is None and height is None: # no <width> and <height> in geometry_string
|
||||
return f"+{x}+{y}"
|
||||
|
||||
else:
|
||||
return f"{round(width / self.window_scaling)}x{round(height / self.window_scaling)}+{x}+{y}"
|
||||
return f"{round(width / self._window_scaling)}x{round(height / self._window_scaling)}+{x}+{y}"
|
||||
|
||||
def apply_window_scaling(self, value):
|
||||
def _apply_window_scaling(self, value):
|
||||
if isinstance(value, (int, float)):
|
||||
return int(value * self.window_scaling)
|
||||
return int(value * self._window_scaling)
|
||||
else:
|
||||
return value
|
||||
|
||||
@ -225,26 +232,25 @@ class CTk(tkinter.Tk):
|
||||
bg_changed = False
|
||||
|
||||
if "bg" in kwargs:
|
||||
self.fg_color = kwargs["bg"]
|
||||
self._fg_color = kwargs["bg"]
|
||||
bg_changed = True
|
||||
kwargs["bg"] = ThemeManager.single_color(self.fg_color, self.appearance_mode)
|
||||
kwargs["bg"] = ThemeManager.single_color(self._fg_color, self._appearance_mode)
|
||||
elif "background" in kwargs:
|
||||
self.fg_color = kwargs["background"]
|
||||
self._fg_color = kwargs["background"]
|
||||
bg_changed = True
|
||||
kwargs["background"] = ThemeManager.single_color(self.fg_color, self.appearance_mode)
|
||||
kwargs["background"] = ThemeManager.single_color(self._fg_color, self._appearance_mode)
|
||||
elif "fg_color" in kwargs:
|
||||
self.fg_color = kwargs["fg_color"]
|
||||
kwargs["bg"] = ThemeManager.single_color(self.fg_color, self.appearance_mode)
|
||||
del kwargs["fg_color"]
|
||||
self._fg_color = kwargs.pop("fg_color")
|
||||
kwargs["bg"] = ThemeManager.single_color(self._fg_color, self._appearance_mode)
|
||||
bg_changed = True
|
||||
|
||||
elif len(args) > 0 and type(args[0]) == dict:
|
||||
if "bg" in args[0]:
|
||||
self.fg_color=args[0]["bg"]
|
||||
self._fg_color = args[0]["bg"]
|
||||
bg_changed = True
|
||||
args[0]["bg"] = ThemeManager.single_color(self.fg_color, self.appearance_mode)
|
||||
elif "background" in args[0]:
|
||||
self.fg_color=args[0]["background"]
|
||||
self._fg_color = args[0]["background"]
|
||||
bg_changed = True
|
||||
args[0]["background"] = ThemeManager.single_color(self.fg_color, self.appearance_mode)
|
||||
|
||||
@ -252,13 +258,13 @@ class CTk(tkinter.Tk):
|
||||
from ..widgets.widget_base_class import CTkBaseClass
|
||||
|
||||
for child in self.winfo_children():
|
||||
if isinstance(child, CTkBaseClass):
|
||||
child.configure(bg_color=self.fg_color)
|
||||
if isinstance(child, CTkBaseClass) and hasattr(child, "_fg_color"):
|
||||
child.configure(bg_color=self._fg_color)
|
||||
|
||||
super().configure(*args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def enable_macos_dark_title_bar():
|
||||
def _enable_macos_dark_title_bar():
|
||||
if sys.platform == "darwin" and not Settings.deactivate_macos_window_header_manipulation: # macOS
|
||||
if Version(platform.python_version()) < Version("3.10"):
|
||||
if Version(tkinter.Tcl().call("info", "patchlevel")) >= Version("8.6.9"): # Tcl/Tk >= 8.6.9
|
||||
@ -266,14 +272,14 @@ class CTk(tkinter.Tk):
|
||||
# This command allows dark-mode for all programs
|
||||
|
||||
@staticmethod
|
||||
def disable_macos_dark_title_bar():
|
||||
def _disable_macos_dark_title_bar():
|
||||
if sys.platform == "darwin" and not Settings.deactivate_macos_window_header_manipulation: # macOS
|
||||
if Version(platform.python_version()) < Version("3.10"):
|
||||
if Version(tkinter.Tcl().call("info", "patchlevel")) >= Version("8.6.9"): # Tcl/Tk >= 8.6.9
|
||||
os.system("defaults delete -g NSRequiresAquaSystemAppearance")
|
||||
# This command reverts the dark-mode setting for all programs.
|
||||
|
||||
def windows_set_titlebar_color(self, color_mode: str):
|
||||
def _windows_set_titlebar_color(self, color_mode: str):
|
||||
"""
|
||||
Set the titlebar color of the window to light or dark theme on Microsoft Windows.
|
||||
|
||||
@ -286,11 +292,11 @@ class CTk(tkinter.Tk):
|
||||
|
||||
if sys.platform.startswith("win") and not Settings.deactivate_windows_window_header_manipulation:
|
||||
|
||||
if self.window_exists:
|
||||
self.state_before_windows_set_titlebar_color = self.state()
|
||||
if self._window_exists:
|
||||
self._state_before_windows_set_titlebar_color = self.state()
|
||||
# print("window_exists -> state_before_windows_set_titlebar_color: ", self.state_before_windows_set_titlebar_color)
|
||||
|
||||
if self.state_before_windows_set_titlebar_color != "iconic" or self.state_before_windows_set_titlebar_color != "withdrawn":
|
||||
if self._state_before_windows_set_titlebar_color != "iconic" or self._state_before_windows_set_titlebar_color != "withdrawn":
|
||||
super().withdraw() # hide window so that it can be redrawn after the titlebar change so that the color change is visible
|
||||
else:
|
||||
# print("window dont exists -> withdraw and update")
|
||||
@ -322,29 +328,29 @@ class CTk(tkinter.Tk):
|
||||
except Exception as err:
|
||||
print(err)
|
||||
|
||||
if self.window_exists:
|
||||
if self._window_exists:
|
||||
# print("window_exists -> return to original state: ", self.state_before_windows_set_titlebar_color)
|
||||
if self.state_before_windows_set_titlebar_color == "normal":
|
||||
if self._state_before_windows_set_titlebar_color == "normal":
|
||||
self.deiconify()
|
||||
elif self.state_before_windows_set_titlebar_color == "iconic":
|
||||
elif self._state_before_windows_set_titlebar_color == "iconic":
|
||||
self.iconify()
|
||||
elif self.state_before_windows_set_titlebar_color == "zoomed":
|
||||
elif self._state_before_windows_set_titlebar_color == "zoomed":
|
||||
self.state("zoomed")
|
||||
else:
|
||||
self.state(self.state_before_windows_set_titlebar_color) # other states
|
||||
self.state(self._state_before_windows_set_titlebar_color) # other states
|
||||
else:
|
||||
pass # wait for update or mainloop to be called
|
||||
|
||||
def set_appearance_mode(self, mode_string):
|
||||
def _set_appearance_mode(self, mode_string):
|
||||
if mode_string.lower() == "dark":
|
||||
self.appearance_mode = 1
|
||||
self._appearance_mode = 1
|
||||
elif mode_string.lower() == "light":
|
||||
self.appearance_mode = 0
|
||||
self._appearance_mode = 0
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
if self.appearance_mode == 1:
|
||||
self.windows_set_titlebar_color("dark")
|
||||
if self._appearance_mode == 1:
|
||||
self._windows_set_titlebar_color("dark")
|
||||
else:
|
||||
self.windows_set_titlebar_color("light")
|
||||
self._windows_set_titlebar_color("light")
|
||||
|
||||
super().configure(bg=ThemeManager.single_color(self.fg_color, self.appearance_mode))
|
||||
super().configure(bg=ThemeManager.single_color(self._fg_color, self._appearance_mode))
|
||||
|
@ -14,93 +14,96 @@ from ..scaling_tracker import ScalingTracker
|
||||
|
||||
|
||||
class CTkToplevel(tkinter.Toplevel):
|
||||
"""
|
||||
Toplevel window with dark titlebar on Windows and macOS.
|
||||
For detailed information check out the documentation.
|
||||
"""
|
||||
|
||||
def __init__(self, *args,
|
||||
fg_color="default_theme",
|
||||
fg_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
**kwargs):
|
||||
|
||||
self.enable_macos_dark_title_bar()
|
||||
self._enable_macos_dark_title_bar()
|
||||
super().__init__(*args, **kwargs)
|
||||
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
|
||||
self._appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
|
||||
|
||||
# add set_scaling method to callback list of ScalingTracker for automatic scaling changes
|
||||
ScalingTracker.add_widget(self.set_scaling, self)
|
||||
self.window_scaling = ScalingTracker.get_window_scaling(self)
|
||||
ScalingTracker.add_widget(self._set_scaling, self)
|
||||
self._window_scaling = ScalingTracker.get_window_scaling(self)
|
||||
|
||||
self.current_width = 200 # initial window size, always without scaling
|
||||
self.current_height = 200
|
||||
self.min_width: int = 0
|
||||
self.min_height: int = 0
|
||||
self.max_width: int = 1_000_000
|
||||
self.max_height: int = 1_000_000
|
||||
self.last_resizable_args: Union[Tuple[list, dict], None] = None # (args, kwargs)
|
||||
self._current_width = 200 # initial window size, always without scaling
|
||||
self._current_height = 200
|
||||
self._min_width: int = 0
|
||||
self._min_height: int = 0
|
||||
self._max_width: int = 1_000_000
|
||||
self._max_height: int = 1_000_000
|
||||
self._last_resizable_args: Union[Tuple[list, dict], None] = None # (args, kwargs)
|
||||
|
||||
self.fg_color = ThemeManager.theme["color"]["window_bg_color"] if fg_color == "default_theme" else fg_color
|
||||
self._fg_color = ThemeManager.theme["color"]["window_bg_color"] if fg_color == "default_theme" else fg_color
|
||||
|
||||
if "bg" in kwargs:
|
||||
self.fg_color = kwargs["bg"]
|
||||
del kwargs["bg"]
|
||||
self._fg_color = kwargs.pop("bg")
|
||||
elif "background" in kwargs:
|
||||
self.fg_color = kwargs["background"]
|
||||
del kwargs["background"]
|
||||
self.fg_color = kwargs.pop("background")
|
||||
|
||||
# add set_appearance_mode method to callback list of AppearanceModeTracker for appearance mode changes
|
||||
AppearanceModeTracker.add(self.set_appearance_mode, self)
|
||||
super().configure(bg=ThemeManager.single_color(self.fg_color, self.appearance_mode))
|
||||
AppearanceModeTracker.add(self._set_appearance_mode, self)
|
||||
super().configure(bg=ThemeManager.single_color(self._fg_color, self._appearance_mode))
|
||||
super().title("CTkToplevel")
|
||||
|
||||
self.state_before_windows_set_titlebar_color = None
|
||||
self.windows_set_titlebar_color_called = False # indicates if windows_set_titlebar_color was called, stays True until revert_withdraw_after_windows_set_titlebar_color is called
|
||||
self.withdraw_called_after_windows_set_titlebar_color = False # indicates if withdraw() was called after windows_set_titlebar_color
|
||||
self.iconify_called_after_windows_set_titlebar_color = False # indicates if iconify() was called after windows_set_titlebar_color
|
||||
self._state_before_windows_set_titlebar_color = None
|
||||
self._windows_set_titlebar_color_called = False # indicates if windows_set_titlebar_color was called, stays True until revert_withdraw_after_windows_set_titlebar_color is called
|
||||
self._withdraw_called_after_windows_set_titlebar_color = False # indicates if withdraw() was called after windows_set_titlebar_color
|
||||
self._iconify_called_after_windows_set_titlebar_color = False # indicates if iconify() was called after windows_set_titlebar_color
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
if self.appearance_mode == 1:
|
||||
self.windows_set_titlebar_color("dark")
|
||||
if self._appearance_mode == 1:
|
||||
self._windows_set_titlebar_color("dark")
|
||||
else:
|
||||
self.windows_set_titlebar_color("light")
|
||||
self._windows_set_titlebar_color("light")
|
||||
|
||||
self.bind('<Configure>', self.update_dimensions_event)
|
||||
self.bind('<Configure>', self._update_dimensions_event)
|
||||
|
||||
def update_dimensions_event(self, event=None):
|
||||
def _update_dimensions_event(self, event=None):
|
||||
detected_width = self.winfo_width() # detect current window size
|
||||
detected_height = self.winfo_height()
|
||||
|
||||
if self.current_width != round(detected_width / self.window_scaling) or self.current_height != round(detected_height / self.window_scaling):
|
||||
self.current_width = round(detected_width / self.window_scaling) # adjust current size according to new size given by event
|
||||
self.current_height = round(detected_height / self.window_scaling) # _current_width and _current_height are independent of the scale
|
||||
if self._current_width != round(detected_width / self._window_scaling) or self._current_height != round(detected_height / self._window_scaling):
|
||||
self._current_width = round(detected_width / self._window_scaling) # adjust current size according to new size given by event
|
||||
self._current_height = round(detected_height / self._window_scaling) # _current_width and _current_height are independent of the scale
|
||||
|
||||
def set_scaling(self, new_widget_scaling, new_spacing_scaling, new_window_scaling):
|
||||
self.window_scaling = new_window_scaling
|
||||
def _set_scaling(self, new_widget_scaling, new_spacing_scaling, new_window_scaling):
|
||||
self._window_scaling = new_window_scaling
|
||||
|
||||
# force new dimensions on window by using min, max, and geometry
|
||||
super().minsize(self.apply_window_scaling(self.current_width), self.apply_window_scaling(self.current_height))
|
||||
super().maxsize(self.apply_window_scaling(self.current_width), self.apply_window_scaling(self.current_height))
|
||||
super().minsize(self._apply_window_scaling(self._current_width), self._apply_window_scaling(self._current_height))
|
||||
super().maxsize(self._apply_window_scaling(self._current_width), self._apply_window_scaling(self._current_height))
|
||||
super().geometry(
|
||||
f"{self.apply_window_scaling(self.current_width)}x" + f"{self.apply_window_scaling(self.current_height)}")
|
||||
f"{self._apply_window_scaling(self._current_width)}x" + f"{self._apply_window_scaling(self._current_height)}")
|
||||
|
||||
# set new scaled min and max with 400ms delay (otherwise it won't work for some reason)
|
||||
self.after(400, self.set_scaled_min_max)
|
||||
self.after(400, self._set_scaled_min_max)
|
||||
|
||||
def set_scaled_min_max(self):
|
||||
if self.min_width is not None or self.min_height is not None:
|
||||
super().minsize(self.apply_window_scaling(self.min_width), self.apply_window_scaling(self.min_height))
|
||||
if self.max_width is not None or self.max_height is not None:
|
||||
super().maxsize(self.apply_window_scaling(self.max_width), self.apply_window_scaling(self.max_height))
|
||||
def _set_scaled_min_max(self):
|
||||
if self._min_width is not None or self._min_height is not None:
|
||||
super().minsize(self._apply_window_scaling(self._min_width), self._apply_window_scaling(self._min_height))
|
||||
if self._max_width is not None or self._max_height is not None:
|
||||
super().maxsize(self._apply_window_scaling(self._max_width), self._apply_window_scaling(self._max_height))
|
||||
|
||||
def geometry(self, geometry_string: str = None):
|
||||
if geometry_string is not None:
|
||||
super().geometry(self.apply_geometry_scaling(geometry_string))
|
||||
super().geometry(self._apply_geometry_scaling(geometry_string))
|
||||
|
||||
# update width and height attributes
|
||||
width, height, x, y = self.parse_geometry_string(geometry_string)
|
||||
width, height, x, y = self._parse_geometry_string(geometry_string)
|
||||
if width is not None and height is not None:
|
||||
self.current_width = max(self.min_width, min(width, self.max_width)) # bound value between min and max
|
||||
self.current_height = max(self.min_height, min(height, self.max_height))
|
||||
self._current_width = max(self._min_width, min(width, self._max_width)) # bound value between min and max
|
||||
self._current_height = max(self._min_height, min(height, self._max_height))
|
||||
else:
|
||||
return self.reverse_geometry_scaling(super().geometry())
|
||||
return self._reverse_geometry_scaling(super().geometry())
|
||||
|
||||
@staticmethod
|
||||
def parse_geometry_string(geometry_string: str) -> tuple:
|
||||
def _parse_geometry_string(geometry_string: str) -> tuple:
|
||||
# index: 1 2 3 4 5 6
|
||||
# regex group structure: ('<width>x<height>', '<width>', '<height>', '+-<x>+-<y>', '-<x>', '-<y>')
|
||||
result = re.search(r"((\d+)x(\d+)){0,1}(\+{0,1}([+-]{0,1}\d+)\+{0,1}([+-]{0,1}\d+)){0,1}", geometry_string)
|
||||
@ -112,75 +115,79 @@ class CTkToplevel(tkinter.Toplevel):
|
||||
|
||||
return width, height, x, y
|
||||
|
||||
def apply_geometry_scaling(self, geometry_string: str) -> str:
|
||||
width, height, x, y = self.parse_geometry_string(geometry_string)
|
||||
def _apply_geometry_scaling(self, geometry_string: str) -> str:
|
||||
width, height, x, y = self._parse_geometry_string(geometry_string)
|
||||
|
||||
if x is None and y is None: # no <x> and <y> in geometry_string
|
||||
return f"{round(width * self.window_scaling)}x{round(height * self.window_scaling)}"
|
||||
return f"{round(width * self._window_scaling)}x{round(height * self._window_scaling)}"
|
||||
|
||||
elif width is None and height is None: # no <width> and <height> in geometry_string
|
||||
return f"+{x}+{y}"
|
||||
|
||||
else:
|
||||
return f"{round(width * self.window_scaling)}x{round(height * self.window_scaling)}+{x}+{y}"
|
||||
return f"{round(width * self._window_scaling)}x{round(height * self._window_scaling)}+{x}+{y}"
|
||||
|
||||
def reverse_geometry_scaling(self, scaled_geometry_string: str) -> str:
|
||||
width, height, x, y = self.parse_geometry_string(scaled_geometry_string)
|
||||
def _reverse_geometry_scaling(self, scaled_geometry_string: str) -> str:
|
||||
width, height, x, y = self._parse_geometry_string(scaled_geometry_string)
|
||||
|
||||
if x is None and y is None: # no <x> and <y> in geometry_string
|
||||
return f"{round(width / self.window_scaling)}x{round(height / self.window_scaling)}"
|
||||
return f"{round(width / self._window_scaling)}x{round(height / self._window_scaling)}"
|
||||
|
||||
elif width is None and height is None: # no <width> and <height> in geometry_string
|
||||
return f"+{x}+{y}"
|
||||
|
||||
else:
|
||||
return f"{round(width / self.window_scaling)}x{round(height / self.window_scaling)}+{x}+{y}"
|
||||
return f"{round(width / self._window_scaling)}x{round(height / self._window_scaling)}+{x}+{y}"
|
||||
|
||||
def apply_window_scaling(self, value):
|
||||
def _apply_window_scaling(self, value):
|
||||
if isinstance(value, (int, float)):
|
||||
return int(value * self.window_scaling)
|
||||
return int(value * self._window_scaling)
|
||||
else:
|
||||
return value
|
||||
|
||||
def destroy(self):
|
||||
AppearanceModeTracker.remove(self.set_appearance_mode)
|
||||
ScalingTracker.remove_window(self.set_scaling, self)
|
||||
self.disable_macos_dark_title_bar()
|
||||
AppearanceModeTracker.remove(self._set_appearance_mode)
|
||||
ScalingTracker.remove_window(self._set_scaling, self)
|
||||
self._disable_macos_dark_title_bar()
|
||||
super().destroy()
|
||||
|
||||
def withdraw(self):
|
||||
if self.windows_set_titlebar_color_called:
|
||||
self.withdraw_called_after_windows_set_titlebar_color = True
|
||||
if self._windows_set_titlebar_color_called:
|
||||
self._withdraw_called_after_windows_set_titlebar_color = True
|
||||
super().withdraw()
|
||||
|
||||
def iconify(self):
|
||||
if self.windows_set_titlebar_color_called:
|
||||
self.iconify_called_after_windows_set_titlebar_color = True
|
||||
if self._windows_set_titlebar_color_called:
|
||||
self._iconify_called_after_windows_set_titlebar_color = True
|
||||
super().iconify()
|
||||
|
||||
def resizable(self, *args, **kwargs):
|
||||
super().resizable(*args, **kwargs)
|
||||
self.last_resizable_args = (args, kwargs)
|
||||
self._last_resizable_args = (args, kwargs)
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
if self.appearance_mode == 1:
|
||||
self.after(10, lambda: self.windows_set_titlebar_color("dark"))
|
||||
if self._appearance_mode == 1:
|
||||
self.after(10, lambda: self._windows_set_titlebar_color("dark"))
|
||||
else:
|
||||
self.after(10, lambda: self.windows_set_titlebar_color("light"))
|
||||
self.after(10, lambda: self._windows_set_titlebar_color("light"))
|
||||
|
||||
def minsize(self, width=None, height=None):
|
||||
self.min_width = width
|
||||
self.min_height = height
|
||||
if self.current_width < width: self.current_width = width
|
||||
if self.current_height < height: self.current_height = height
|
||||
super().minsize(self.apply_window_scaling(self.min_width), self.apply_window_scaling(self.min_height))
|
||||
self._min_width = width
|
||||
self._min_height = height
|
||||
if self._current_width < width:
|
||||
self._current_width = width
|
||||
if self._current_height < height:
|
||||
self._current_height = height
|
||||
super().minsize(self._apply_window_scaling(self._min_width), self._apply_window_scaling(self._min_height))
|
||||
|
||||
def maxsize(self, width=None, height=None):
|
||||
self.max_width = width
|
||||
self.max_height = height
|
||||
if self.current_width > width: self.current_width = width
|
||||
if self.current_height > height: self.current_height = height
|
||||
super().maxsize(self.apply_window_scaling(self.max_width), self.apply_window_scaling(self.max_height))
|
||||
self._max_width = width
|
||||
self._max_height = height
|
||||
if self._current_width > width:
|
||||
self._current_width = width
|
||||
if self._current_height > height:
|
||||
self._current_height = height
|
||||
super().maxsize(self._apply_window_scaling(self._max_width), self._apply_window_scaling(self._max_height))
|
||||
|
||||
def config(self, *args, **kwargs):
|
||||
self.configure(*args, **kwargs)
|
||||
@ -189,54 +196,53 @@ class CTkToplevel(tkinter.Toplevel):
|
||||
bg_changed = False
|
||||
|
||||
if "bg" in kwargs:
|
||||
self.fg_color = kwargs["bg"]
|
||||
self._fg_color = kwargs["bg"]
|
||||
bg_changed = True
|
||||
kwargs["bg"] = ThemeManager.single_color(self.fg_color, self.appearance_mode)
|
||||
kwargs["bg"] = ThemeManager.single_color(self._fg_color, self._appearance_mode)
|
||||
elif "background" in kwargs:
|
||||
self.fg_color = kwargs["background"]
|
||||
self._fg_color = kwargs["background"]
|
||||
bg_changed = True
|
||||
kwargs["background"] = ThemeManager.single_color(self.fg_color, self.appearance_mode)
|
||||
kwargs["background"] = ThemeManager.single_color(self._fg_color, self._appearance_mode)
|
||||
elif "fg_color" in kwargs:
|
||||
self.fg_color = kwargs["fg_color"]
|
||||
kwargs["bg"] = ThemeManager.single_color(self.fg_color, self.appearance_mode)
|
||||
del kwargs["fg_color"]
|
||||
self._fg_color = kwargs.pop("fg_color")
|
||||
kwargs["bg"] = ThemeManager.single_color(self._fg_color, self._appearance_mode)
|
||||
bg_changed = True
|
||||
|
||||
elif len(args) > 0 and type(args[0]) == dict:
|
||||
if "bg" in args[0]:
|
||||
self.fg_color=args[0]["bg"]
|
||||
self._fg_color = args[0]["bg"]
|
||||
bg_changed = True
|
||||
args[0]["bg"] = ThemeManager.single_color(self.fg_color, self.appearance_mode)
|
||||
args[0]["bg"] = ThemeManager.single_color(self._fg_color, self._appearance_mode)
|
||||
elif "background" in args[0]:
|
||||
self.fg_color=args[0]["background"]
|
||||
self._fg_color = args[0]["background"]
|
||||
bg_changed = True
|
||||
args[0]["background"] = ThemeManager.single_color(self.fg_color, self.appearance_mode)
|
||||
args[0]["background"] = ThemeManager.single_color(self._fg_color, self._appearance_mode)
|
||||
|
||||
if bg_changed:
|
||||
from ..widgets.widget_base_class import CTkBaseClass
|
||||
|
||||
for child in self.winfo_children():
|
||||
if isinstance(child, CTkBaseClass):
|
||||
child.configure(bg_color=self.fg_color)
|
||||
if isinstance(child, CTkBaseClass) and hasattr(child, "_fg_color"):
|
||||
child.configure(bg_color=self._fg_color)
|
||||
|
||||
super().configure(*args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def enable_macos_dark_title_bar():
|
||||
def _enable_macos_dark_title_bar():
|
||||
if sys.platform == "darwin" and not Settings.deactivate_macos_window_header_manipulation: # macOS
|
||||
if Version(platform.python_version()) < Version("3.10"):
|
||||
if Version(tkinter.Tcl().call("info", "patchlevel")) >= Version("8.6.9"): # Tcl/Tk >= 8.6.9
|
||||
os.system("defaults write -g NSRequiresAquaSystemAppearance -bool No")
|
||||
|
||||
@staticmethod
|
||||
def disable_macos_dark_title_bar():
|
||||
def _disable_macos_dark_title_bar():
|
||||
if sys.platform == "darwin" and not Settings.deactivate_macos_window_header_manipulation: # macOS
|
||||
if Version(platform.python_version()) < Version("3.10"):
|
||||
if Version(tkinter.Tcl().call("info", "patchlevel")) >= Version("8.6.9"): # Tcl/Tk >= 8.6.9
|
||||
os.system("defaults delete -g NSRequiresAquaSystemAppearance")
|
||||
# This command reverts the dark-mode setting for all programs.
|
||||
|
||||
def windows_set_titlebar_color(self, color_mode: str):
|
||||
def _windows_set_titlebar_color(self, color_mode: str):
|
||||
"""
|
||||
Set the titlebar color of the window to light or dark theme on Microsoft Windows.
|
||||
|
||||
@ -249,7 +255,7 @@ class CTkToplevel(tkinter.Toplevel):
|
||||
|
||||
if sys.platform.startswith("win") and not Settings.deactivate_windows_window_header_manipulation:
|
||||
|
||||
self.state_before_windows_set_titlebar_color = self.state()
|
||||
self._state_before_windows_set_titlebar_color = self.state()
|
||||
super().withdraw() # hide window so that it can be redrawn after the titlebar change so that the color change is visible
|
||||
super().update()
|
||||
|
||||
@ -277,41 +283,41 @@ class CTkToplevel(tkinter.Toplevel):
|
||||
except Exception as err:
|
||||
print(err)
|
||||
|
||||
self.windows_set_titlebar_color_called = True
|
||||
self.after(5, self.revert_withdraw_after_windows_set_titlebar_color)
|
||||
self._windows_set_titlebar_color_called = True
|
||||
self.after(5, self._revert_withdraw_after_windows_set_titlebar_color)
|
||||
|
||||
def revert_withdraw_after_windows_set_titlebar_color(self):
|
||||
def _revert_withdraw_after_windows_set_titlebar_color(self):
|
||||
""" if in a short time (5ms) after """
|
||||
if self.windows_set_titlebar_color_called:
|
||||
if self._windows_set_titlebar_color_called:
|
||||
|
||||
if self.withdraw_called_after_windows_set_titlebar_color:
|
||||
if self._withdraw_called_after_windows_set_titlebar_color:
|
||||
pass # leave it withdrawed
|
||||
elif self.iconify_called_after_windows_set_titlebar_color:
|
||||
elif self._iconify_called_after_windows_set_titlebar_color:
|
||||
super().iconify()
|
||||
else:
|
||||
if self.state_before_windows_set_titlebar_color == "normal":
|
||||
if self._state_before_windows_set_titlebar_color == "normal":
|
||||
self.deiconify()
|
||||
elif self.state_before_windows_set_titlebar_color == "iconic":
|
||||
elif self._state_before_windows_set_titlebar_color == "iconic":
|
||||
self.iconify()
|
||||
elif self.state_before_windows_set_titlebar_color == "zoomed":
|
||||
elif self._state_before_windows_set_titlebar_color == "zoomed":
|
||||
self.state("zoomed")
|
||||
else:
|
||||
self.state(self.state_before_windows_set_titlebar_color) # other states
|
||||
self.state(self._state_before_windows_set_titlebar_color) # other states
|
||||
|
||||
self.windows_set_titlebar_color_called = False
|
||||
self.withdraw_called_after_windows_set_titlebar_color = False
|
||||
self.iconify_called_after_windows_set_titlebar_color = False
|
||||
self._windows_set_titlebar_color_called = False
|
||||
self._withdraw_called_after_windows_set_titlebar_color = False
|
||||
self._iconify_called_after_windows_set_titlebar_color = False
|
||||
|
||||
def set_appearance_mode(self, mode_string):
|
||||
def _set_appearance_mode(self, mode_string):
|
||||
if mode_string.lower() == "dark":
|
||||
self.appearance_mode = 1
|
||||
self._appearance_mode = 1
|
||||
elif mode_string.lower() == "light":
|
||||
self.appearance_mode = 0
|
||||
self._appearance_mode = 0
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
if self.appearance_mode == 1:
|
||||
self.windows_set_titlebar_color("dark")
|
||||
if self._appearance_mode == 1:
|
||||
self._windows_set_titlebar_color("dark")
|
||||
else:
|
||||
self.windows_set_titlebar_color("light")
|
||||
self._windows_set_titlebar_color("light")
|
||||
|
||||
super().configure(bg=ThemeManager.single_color(self.fg_color, self.appearance_mode))
|
||||
super().configure(bg=ThemeManager.single_color(self._fg_color, self._appearance_mode))
|
||||
|
@ -140,6 +140,9 @@ class App(customtkinter.CTk):
|
||||
def sidebar_button_callback(self):
|
||||
print("sidebar_button click")
|
||||
|
||||
self.entry.delete(0, tkinter.END)
|
||||
|
||||
|
||||
def on_closing(self, event=0):
|
||||
self.destroy()
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user