Compare commits
35 Commits
Author | SHA1 | Date | |
---|---|---|---|
359226e468 | |||
dc751e46d3 | |||
79d5da439b | |||
fac2fa5e68 | |||
2de1b94575 | |||
a79502dc03 | |||
8a537076ce | |||
1396a7e484 | |||
5bbd72b5dc | |||
84bfc776b0 | |||
7f5ac69259 | |||
90157252d0 | |||
392586eaa1 | |||
f3710de173 | |||
9f8b54563d | |||
28228316eb | |||
61adb1da07 | |||
042fac7242 | |||
a49dde63b3 | |||
f11d727879 | |||
77595da9f2 | |||
d43229ef6e | |||
f068cee972 | |||
62063d6f64 | |||
3d86b5a14f | |||
5a17b1243e | |||
7572f095c2 | |||
868b2a2f42 | |||
6a3fa7fa29 | |||
a564bc35ef | |||
dd223a15b5 | |||
2c7b2c5030 | |||
f4af512290 | |||
482a6e60b7 | |||
83dedea59c |
@ -5,12 +5,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
ToDo:
|
||||
- change font attribute in wiki
|
||||
- add new button attributes to wiki
|
||||
|
||||
- create grayscale theme file
|
||||
- cursor configuring
|
||||
- overwrite winfo methods
|
||||
- set icon (self.call("wm", "iconphoto", self._w, tkinter.PhotoImage(file="test_images/CustomTkinter_logo_single.png")))
|
||||
- add option to change label position for checkbox, switch, radiobutton #628
|
||||
|
||||
|
||||
## [5.0.0] - 2022-11-13
|
||||
|
@ -1,4 +1,5 @@
|
||||
include customtkinter/assets/*
|
||||
include customtkinter/assets/fonts/*
|
||||
include customtkinter/assets/fonts/Roboto/*
|
||||
include customtkinter/assets/themes/*
|
||||
include customtkinter/assets/icons/*
|
||||
include customtkinter/assets/themes/*
|
||||
|
@ -11,7 +11,7 @@
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
</div>
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
__version__ = "5.0.0"
|
||||
__version__ = "5.0.4"
|
||||
|
||||
import os
|
||||
import sys
|
||||
@ -13,6 +13,10 @@ from .windows.widgets.scaling import ScalingTracker
|
||||
from .windows.widgets.theme import ThemeManager
|
||||
from .windows.widgets.core_rendering import DrawEngine
|
||||
|
||||
# import base widgets
|
||||
from .windows.widgets.core_rendering import CTkCanvas
|
||||
from .windows.widgets.core_widget_classes import CTkBaseClass
|
||||
|
||||
# import widgets
|
||||
from .windows.widgets import CTkButton
|
||||
from .windows.widgets import CTkCheckBox
|
||||
@ -74,4 +78,4 @@ def set_window_scaling(scaling_value: float):
|
||||
|
||||
def deactivate_automatic_dpi_awareness():
|
||||
""" deactivate DPI awareness of current process (windll.shcore.SetProcessDpiAwareness(0)) """
|
||||
ScalingTracker.deactivate_automatic_dpi_awareness = False
|
||||
ScalingTracker.deactivate_automatic_dpi_awareness = True
|
||||
|
BIN
customtkinter/assets/icons/CustomTkinter_icon_Windows.ico
Normal file
After Width: | Height: | Size: 13 KiB |
@ -40,6 +40,14 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
|
||||
CTkScalingBaseClass.__init__(self, scaling_type="window")
|
||||
check_kwargs_empty(kwargs, raise_error=True)
|
||||
|
||||
try:
|
||||
# Set Windows titlebar icon
|
||||
if sys.platform.startswith("win"):
|
||||
customtkinter_directory = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
self.after(200, lambda: self.iconbitmap(os.path.join(customtkinter_directory, "assets", "icons", "CustomTkinter_icon_Windows.ico")))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self._current_width = 600 # initial window size, independent of scaling
|
||||
self._current_height = 500
|
||||
self._min_width: int = 0
|
||||
|
@ -38,6 +38,14 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl
|
||||
CTkScalingBaseClass.__init__(self, scaling_type="window")
|
||||
check_kwargs_empty(kwargs, raise_error=True)
|
||||
|
||||
try:
|
||||
# Set Windows titlebar icon
|
||||
if sys.platform.startswith("win"):
|
||||
customtkinter_directory = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
self.after(200, lambda: self.iconbitmap(os.path.join(customtkinter_directory, "assets", "icons", "CustomTkinter_icon_Windows.ico")))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self._current_width = 200 # initial window size, always without scaling
|
||||
self._current_height = 200
|
||||
self._min_width: int = 0
|
||||
|
@ -1,4 +1,5 @@
|
||||
import sys
|
||||
import warnings
|
||||
import tkinter
|
||||
import tkinter.ttk as ttk
|
||||
from typing import Union, Callable, Tuple
|
||||
@ -158,15 +159,15 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas
|
||||
return font
|
||||
|
||||
elif type(font) == tuple and len(font) == 1:
|
||||
sys.stderr.write(f"{type(self).__name__} Warning: font {font} given without size, will be extended with default text size of current theme\n")
|
||||
warnings.warn(f"{type(self).__name__} Warning: font {font} given without size, will be extended with default text size of current theme\n")
|
||||
return font[0], ThemeManager.theme["text"]["size"]
|
||||
|
||||
elif type(font) == tuple and 2 <= len(font) <= 3:
|
||||
elif type(font) == tuple and 2 <= len(font) <= 6:
|
||||
return font
|
||||
|
||||
else:
|
||||
raise ValueError(f"Wrong font type {type(font)}\n" +
|
||||
f"For consistency, Customtkinter requires the font argument to be a tuple of len 2 or 3 or an instance of CTkFont.\n" +
|
||||
f"For consistency, Customtkinter requires the font argument to be a tuple of len 2 to 6 or an instance of CTkFont.\n" +
|
||||
f"\nUsage example:\n" +
|
||||
f"font=customtkinter.CTkFont(family='<name>', size=<size in px>)\n" +
|
||||
f"font=('<name>', <size in px>)\n")
|
||||
@ -178,8 +179,8 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas
|
||||
elif isinstance(image, CTkImage):
|
||||
return image
|
||||
else:
|
||||
sys.stderr.write(f"{type(self).__name__} Warning: Given image is not CTkImage but {type(image)}. " +
|
||||
f"Image can not be scaled on HighDPI displays, use CTkImage instead.\n")
|
||||
warnings.warn(f"{type(self).__name__} Warning: Given image is not CTkImage but {type(image)}. " +
|
||||
f"Image can not be scaled on HighDPI displays, use CTkImage instead.\n")
|
||||
return image
|
||||
|
||||
def _update_dimensions_event(self, event):
|
||||
@ -240,6 +241,18 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas
|
||||
super().configure(width=self._apply_widget_scaling(self._desired_width),
|
||||
height=self._apply_widget_scaling(self._desired_height))
|
||||
|
||||
def bind(self, sequence=None, command=None, add=None):
|
||||
raise NotImplementedError
|
||||
|
||||
def unbind(self, sequence=None, funcid=None):
|
||||
raise NotImplementedError
|
||||
|
||||
def unbind_all(self, sequence):
|
||||
raise AttributeError("'unbind_all' is not allowed, because it would delete necessary internal callbacks for all widgets")
|
||||
|
||||
def bind_all(self, sequence=None, func=None, add=None):
|
||||
raise AttributeError("'bind_all' is not allowed, could result in undefined behavior")
|
||||
|
||||
def place(self, **kwargs):
|
||||
"""
|
||||
Place a widget in the parent widget. Use as options:
|
||||
|
@ -40,7 +40,7 @@ class CTkButton(CTkBaseClass):
|
||||
text: str = "CTkButton",
|
||||
font: Optional[Union[tuple, CTkFont]] = None,
|
||||
textvariable: Union[tkinter.Variable, None] = None,
|
||||
image: Union[tkinter.PhotoImage, CTkImage, None] = None,
|
||||
image: Union[CTkImage, None] = None,
|
||||
state: str = "normal",
|
||||
hover: bool = True,
|
||||
command: Union[Callable[[], None], None] = None,
|
||||
@ -100,16 +100,38 @@ class CTkButton(CTkBaseClass):
|
||||
self._draw_engine = DrawEngine(self._canvas)
|
||||
self._draw_engine.set_round_to_even_numbers(self._round_width_to_even_numbers, self._round_height_to_even_numbers) # rendering options
|
||||
|
||||
# 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)
|
||||
|
||||
# configure cursor and initial draw
|
||||
self._create_bindings()
|
||||
self._set_cursor()
|
||||
self._draw()
|
||||
|
||||
def _create_bindings(self, sequence: Optional[str] = None):
|
||||
""" set necessary bindings for functionality of widget, will overwrite other bindings """
|
||||
|
||||
if sequence is None or sequence == "<Enter>":
|
||||
self._canvas.bind("<Enter>", self._on_enter)
|
||||
|
||||
if self._text_label is not None:
|
||||
self._text_label.bind("<Enter>", self._on_enter)
|
||||
if self._image_label is not None:
|
||||
self._image_label.bind("<Enter>", self._on_enter)
|
||||
|
||||
if sequence is None or sequence == "<Leave>":
|
||||
self._canvas.bind("<Leave>", self._on_leave)
|
||||
|
||||
if self._text_label is not None:
|
||||
self._text_label.bind("<Leave>", self._on_leave)
|
||||
if self._image_label is not None:
|
||||
self._image_label.bind("<Leave>", self._on_leave)
|
||||
|
||||
if sequence is None or sequence == "<Button-1>":
|
||||
self._canvas.bind("<Button-1>", self._clicked)
|
||||
|
||||
if self._text_label is not None:
|
||||
self._text_label.bind("<Button-1>", self._clicked)
|
||||
if self._image_label is not None:
|
||||
self._image_label.bind("<Button-1>", self._clicked)
|
||||
|
||||
def _set_scaling(self, *args, **kwargs):
|
||||
super()._set_scaling(*args, **kwargs)
|
||||
|
||||
@ -391,7 +413,7 @@ class CTkButton(CTkBaseClass):
|
||||
self._image = self._check_image_type(kwargs.pop("image"))
|
||||
if isinstance(self._image, CTkImage):
|
||||
self._image.add_configure_callback(self._update_image)
|
||||
require_redraw = True
|
||||
self._update_image()
|
||||
|
||||
if "state" in kwargs:
|
||||
self._state = kwargs.pop("state")
|
||||
@ -532,17 +554,30 @@ class CTkButton(CTkBaseClass):
|
||||
if self._command is not None:
|
||||
return self._command()
|
||||
|
||||
def bind(self, sequence: str = None, command: Callable = None, add: str = None) -> str:
|
||||
""" called on the tkinter.Label and tkinter.Canvas """
|
||||
canvas_bind_return = self._canvas.bind(sequence, command, add)
|
||||
label_bind_return = self._text_label.bind(sequence, command, add)
|
||||
return canvas_bind_return + " + " + label_bind_return
|
||||
def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True):
|
||||
""" called on the tkinter.Canvas """
|
||||
if not (add == "+" or add is True):
|
||||
raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
|
||||
self._canvas.bind(sequence, command, add=True)
|
||||
|
||||
def unbind(self, sequence: str, funcid: str = None):
|
||||
if self._text_label is not None:
|
||||
self._text_label.bind(sequence, command, add=True)
|
||||
if self._image_label is not None:
|
||||
self._image_label.bind(sequence, command, add=True)
|
||||
|
||||
def unbind(self, sequence: str = None, funcid: str = None):
|
||||
""" called on the tkinter.Label and tkinter.Canvas """
|
||||
canvas_bind_return, label_bind_return = funcid.split(" + ")
|
||||
self._canvas.unbind(sequence, canvas_bind_return)
|
||||
self._text_label.unbind(sequence, label_bind_return)
|
||||
if funcid is not None:
|
||||
raise ValueError("'funcid' argument can only be None, because there is a bug in" +
|
||||
" tkinter and its not clear whether the internal callbacks will be unbinded or not")
|
||||
self._canvas.unbind(sequence, None)
|
||||
|
||||
if self._text_label is not None:
|
||||
self._text_label.unbind(sequence, None)
|
||||
if self._image_label is not None:
|
||||
self._image_label.unbind(sequence, None)
|
||||
|
||||
self._create_bindings(sequence=sequence) # restore internal callbacks for sequence
|
||||
|
||||
def focus(self):
|
||||
return self._text_label.focus()
|
||||
|
@ -103,10 +103,6 @@ class CTkCheckBox(CTkBaseClass):
|
||||
self._canvas.grid(row=0, column=0, sticky="e")
|
||||
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._text_label = tkinter.Label(master=self,
|
||||
bd=0,
|
||||
padx=0,
|
||||
@ -118,17 +114,26 @@ class CTkCheckBox(CTkBaseClass):
|
||||
self._text_label.grid(row=0, column=2, 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)
|
||||
|
||||
# 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 self._variable.get() == self._onvalue else False
|
||||
|
||||
self._draw() # initial draw
|
||||
self._create_bindings()
|
||||
self._set_cursor()
|
||||
self._draw()
|
||||
|
||||
def _create_bindings(self, sequence: Optional[str] = None):
|
||||
""" set necessary bindings for functionality of widget, will overwrite other bindings """
|
||||
if sequence is None or sequence == "<Enter>":
|
||||
self._canvas.bind("<Enter>", self._on_enter)
|
||||
self._text_label.bind("<Enter>", self._on_enter)
|
||||
if sequence is None or sequence == "<Leave>":
|
||||
self._canvas.bind("<Leave>", self._on_leave)
|
||||
self._text_label.bind("<Leave>", self._on_leave)
|
||||
if sequence is None or sequence == "<Button-1>":
|
||||
self._canvas.bind("<Button-1>", self.toggle)
|
||||
self._text_label.bind("<Button-1>", self.toggle)
|
||||
|
||||
def _set_scaling(self, *args, **kwargs):
|
||||
super()._set_scaling(*args, **kwargs)
|
||||
@ -430,13 +435,21 @@ class CTkCheckBox(CTkBaseClass):
|
||||
def get(self) -> Union[int, str]:
|
||||
return self._onvalue if self._check_state is True else self._offvalue
|
||||
|
||||
def bind(self, sequence=None, command=None, add=None):
|
||||
def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True):
|
||||
""" called on the tkinter.Canvas """
|
||||
return self._canvas.bind(sequence, command, add)
|
||||
if not (add == "+" or add is True):
|
||||
raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
|
||||
self._canvas.bind(sequence, command, add=True)
|
||||
self._text_label.bind(sequence, command, add=True)
|
||||
|
||||
def unbind(self, sequence, funcid=None):
|
||||
""" called on the tkinter.Canvas """
|
||||
return self._canvas.unbind(sequence, funcid)
|
||||
def unbind(self, sequence: str = None, funcid: str = None):
|
||||
""" called on the tkinter.Label and tkinter.Canvas """
|
||||
if funcid is not None:
|
||||
raise ValueError("'funcid' argument can only be None, because there is a bug in" +
|
||||
" tkinter and its not clear whether the internal callbacks will be unbinded or not")
|
||||
self._canvas.unbind(sequence, None)
|
||||
self._text_label.unbind(sequence, None)
|
||||
self._create_bindings(sequence=sequence) # restore internal callbacks for sequence
|
||||
|
||||
def focus(self):
|
||||
return self._text_label.focus()
|
||||
|
@ -1,5 +1,6 @@
|
||||
import tkinter
|
||||
import sys
|
||||
import copy
|
||||
from typing import Union, Tuple, Callable, List, Optional
|
||||
|
||||
from .core_widget_classes import DropdownMenu
|
||||
@ -102,26 +103,29 @@ class CTkComboBox(CTkBaseClass):
|
||||
font=self._apply_font_scaling(self._font))
|
||||
|
||||
self._create_grid()
|
||||
|
||||
# insert default value
|
||||
if len(self._values) > 0:
|
||||
self._entry.insert(0, self._values[0])
|
||||
else:
|
||||
self._entry.insert(0, "CTkComboBox")
|
||||
|
||||
self._create_bindings()
|
||||
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)
|
||||
|
||||
if self._variable is not None:
|
||||
self._entry.configure(textvariable=self._variable)
|
||||
|
||||
# insert default value
|
||||
if self._variable is None:
|
||||
if len(self._values) > 0:
|
||||
self._entry.insert(0, self._values[0])
|
||||
else:
|
||||
self._entry.insert(0, "CTkComboBox")
|
||||
|
||||
def _create_bindings(self, sequence: Optional[str] = None):
|
||||
""" set necessary bindings for functionality of widget, will overwrite other bindings """
|
||||
if sequence is None:
|
||||
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)
|
||||
|
||||
def _create_grid(self):
|
||||
self._canvas.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="nsew")
|
||||
|
||||
@ -197,6 +201,7 @@ class CTkComboBox(CTkBaseClass):
|
||||
|
||||
self._entry.configure(bg=self._apply_appearance_mode(self._fg_color),
|
||||
fg=self._apply_appearance_mode(self._text_color),
|
||||
readonlybackground=self._apply_appearance_mode(self._fg_color),
|
||||
disabledbackground=self._apply_appearance_mode(self._fg_color),
|
||||
disabledforeground=self._apply_appearance_mode(self._text_color_disabled),
|
||||
highlightcolor=self._apply_appearance_mode(self._fg_color),
|
||||
@ -322,7 +327,7 @@ class CTkComboBox(CTkBaseClass):
|
||||
elif attribute_name == "dropdown_font":
|
||||
return self._dropdown_menu.cget("font")
|
||||
elif attribute_name == "values":
|
||||
return self._values
|
||||
return copy.copy(self._values)
|
||||
elif attribute_name == "state":
|
||||
return self._state
|
||||
elif attribute_name == "hover":
|
||||
@ -391,17 +396,23 @@ class CTkComboBox(CTkBaseClass):
|
||||
def get(self) -> str:
|
||||
return self._entry.get()
|
||||
|
||||
def _clicked(self, event=0):
|
||||
def _clicked(self, event=None):
|
||||
if self._state is not tkinter.DISABLED and len(self._values) > 0:
|
||||
self._open_dropdown_menu()
|
||||
|
||||
def bind(self, sequence=None, command=None, add=None):
|
||||
def bind(self, sequence=None, command=None, add=True):
|
||||
""" called on the tkinter.Entry """
|
||||
return self._entry.bind(sequence, command, add)
|
||||
if not (add == "+" or add is True):
|
||||
raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
|
||||
self._entry.bind(sequence, command, add=True)
|
||||
|
||||
def unbind(self, sequence, funcid=None):
|
||||
def unbind(self, sequence=None, funcid=None):
|
||||
""" called on the tkinter.Entry """
|
||||
return self._entry.unbind(sequence, funcid)
|
||||
if funcid is not None:
|
||||
raise ValueError("'funcid' argument can only be None, because there is a bug in" +
|
||||
" tkinter and its not clear whether the internal callbacks will be unbinded or not")
|
||||
self._entry.unbind(sequence, None) # unbind all callbacks for sequence
|
||||
self._create_bindings(sequence=sequence) # restore internal callbacks for sequence
|
||||
|
||||
def focus(self):
|
||||
return self._entry.focus()
|
||||
|
@ -90,16 +90,20 @@ class CTkEntry(CTkBaseClass):
|
||||
textvariable=self._textvariable,
|
||||
**pop_from_dict_by_set(kwargs, self._valid_tk_entry_attributes))
|
||||
|
||||
self._create_grid()
|
||||
|
||||
check_kwargs_empty(kwargs, raise_error=True)
|
||||
|
||||
self._entry.bind('<FocusOut>', self._entry_focus_out)
|
||||
self._entry.bind('<FocusIn>', self._entry_focus_in)
|
||||
|
||||
self._create_grid()
|
||||
self._activate_placeholder()
|
||||
self._create_bindings()
|
||||
self._draw()
|
||||
|
||||
def _create_bindings(self, sequence: Optional[str] = None):
|
||||
""" set necessary bindings for functionality of widget, will overwrite other bindings """
|
||||
if sequence is None or sequence == "<FocusIn>":
|
||||
self._entry.bind("<FocusIn>", self._entry_focus_in)
|
||||
if sequence is None or sequence == "<FocusOut>":
|
||||
self._entry.bind("<FocusOut>", self._entry_focus_out)
|
||||
|
||||
def _create_grid(self):
|
||||
self._canvas.grid(column=0, row=0, sticky="nswe")
|
||||
|
||||
@ -163,6 +167,7 @@ class CTkEntry(CTkBaseClass):
|
||||
outline=self._apply_appearance_mode(self._bg_color))
|
||||
self._entry.configure(bg=self._apply_appearance_mode(self._bg_color),
|
||||
disabledbackground=self._apply_appearance_mode(self._bg_color),
|
||||
readonlybackground=self._apply_appearance_mode(self._bg_color),
|
||||
highlightcolor=self._apply_appearance_mode(self._bg_color))
|
||||
else:
|
||||
self._canvas.itemconfig("inner_parts",
|
||||
@ -170,6 +175,7 @@ class CTkEntry(CTkBaseClass):
|
||||
outline=self._apply_appearance_mode(self._fg_color))
|
||||
self._entry.configure(bg=self._apply_appearance_mode(self._fg_color),
|
||||
disabledbackground=self._apply_appearance_mode(self._fg_color),
|
||||
readonlybackground=self._apply_appearance_mode(self._fg_color),
|
||||
highlightcolor=self._apply_appearance_mode(self._fg_color))
|
||||
|
||||
self._canvas.itemconfig("border_parts",
|
||||
@ -275,13 +281,19 @@ class CTkEntry(CTkBaseClass):
|
||||
else:
|
||||
return super().cget(attribute_name) # cget of CTkBaseClass
|
||||
|
||||
def bind(self, sequence=None, command=None, add=None):
|
||||
def bind(self, sequence=None, command=None, add=True):
|
||||
""" called on the tkinter.Entry """
|
||||
return self._entry.bind(sequence, command, add)
|
||||
if not (add == "+" or add is True):
|
||||
raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
|
||||
self._entry.bind(sequence, command, add=True)
|
||||
|
||||
def unbind(self, sequence, funcid=None):
|
||||
def unbind(self, sequence=None, funcid=None):
|
||||
""" called on the tkinter.Entry """
|
||||
return self._entry.unbind(sequence, funcid)
|
||||
if funcid is not None:
|
||||
raise ValueError("'funcid' argument can only be None, because there is a bug in" +
|
||||
" tkinter and its not clear whether the internal callbacks will be unbinded or not")
|
||||
self._entry.unbind(sequence, None) # unbind all callbacks for sequence
|
||||
self._create_bindings(sequence=sequence) # restore internal callbacks for sequence
|
||||
|
||||
def _activate_placeholder(self):
|
||||
if self._entry.get() == "" and self._placeholder_text is not None and (self._textvariable is None or self._textvariable == ""):
|
||||
@ -295,7 +307,7 @@ class CTkEntry(CTkBaseClass):
|
||||
self._entry.insert(0, self._placeholder_text)
|
||||
|
||||
def _deactivate_placeholder(self):
|
||||
if self._placeholder_text_active:
|
||||
if self._placeholder_text_active and self._entry.cget("state") != "readonly":
|
||||
self._placeholder_text_active = False
|
||||
|
||||
self._entry.config(fg=self._apply_appearance_mode(self._text_color),
|
||||
|
@ -182,10 +182,15 @@ class CTkFrame(CTkBaseClass):
|
||||
else:
|
||||
return super().cget(attribute_name)
|
||||
|
||||
def bind(self, sequence=None, command=None, add=None):
|
||||
def bind(self, sequence=None, command=None, add=True):
|
||||
""" called on the tkinter.Canvas """
|
||||
return self._canvas.bind(sequence, command, add)
|
||||
if not (add == "+" or add is True):
|
||||
raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
|
||||
self._canvas.bind(sequence, command, add=True)
|
||||
|
||||
def unbind(self, sequence, funcid=None):
|
||||
def unbind(self, sequence=None, funcid=None):
|
||||
""" called on the tkinter.Canvas """
|
||||
return self._canvas.unbind(sequence, funcid)
|
||||
if funcid is not None:
|
||||
raise ValueError("'funcid' argument can only be None, because there is a bug in" +
|
||||
" tkinter and its not clear whether the internal callbacks will be unbinded or not")
|
||||
self._canvas.unbind(sequence, None)
|
||||
|
@ -32,7 +32,7 @@ class CTkLabel(CTkBaseClass):
|
||||
|
||||
text: str = "CTkLabel",
|
||||
font: Optional[Union[tuple, CTkFont]] = None,
|
||||
image: Union[tkinter.PhotoImage, CTkImage, None] = None,
|
||||
image: Union[CTkImage, None] = None,
|
||||
compound: str = "center",
|
||||
anchor: str = "center", # label anchor: center, n, e, s, w
|
||||
wraplength: int = 0,
|
||||
@ -247,17 +247,20 @@ class CTkLabel(CTkBaseClass):
|
||||
else:
|
||||
return super().cget(attribute_name) # cget of CTkBaseClass
|
||||
|
||||
def bind(self, sequence: str = None, command: Callable = None, add: str = None) -> str:
|
||||
def bind(self, sequence: str = None, command: Callable = None, add: str = True):
|
||||
""" called on the tkinter.Label and tkinter.Canvas """
|
||||
canvas_bind_return = self._canvas.bind(sequence, command, add)
|
||||
label_bind_return = self._label.bind(sequence, command, add)
|
||||
return canvas_bind_return + " + " + label_bind_return
|
||||
if not (add == "+" or add is True):
|
||||
raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
|
||||
self._canvas.bind(sequence, command, add=True)
|
||||
self._label.bind(sequence, command, add=True)
|
||||
|
||||
def unbind(self, sequence: str, funcid: str = None):
|
||||
def unbind(self, sequence: str = None, funcid: Optional[str] = None):
|
||||
""" called on the tkinter.Label and tkinter.Canvas """
|
||||
canvas_bind_return, label_bind_return = funcid.split(" + ")
|
||||
self._canvas.unbind(sequence, canvas_bind_return)
|
||||
self._label.unbind(sequence, label_bind_return)
|
||||
if funcid is not None:
|
||||
raise ValueError("'funcid' argument can only be None, because there is a bug in" +
|
||||
" tkinter and its not clear whether the internal callbacks will be unbinded or not")
|
||||
self._canvas.unbind(sequence, None)
|
||||
self._label.unbind(sequence, None)
|
||||
|
||||
def focus(self):
|
||||
return self._label.focus()
|
||||
|
@ -1,4 +1,5 @@
|
||||
import tkinter
|
||||
import copy
|
||||
import sys
|
||||
from typing import Union, Tuple, Callable, Optional
|
||||
|
||||
@ -107,10 +108,6 @@ class CTkOptionMenu(CTkBaseClass):
|
||||
pady=0,
|
||||
borderwidth=1,
|
||||
text=self._current_value)
|
||||
self._create_grid()
|
||||
|
||||
if not self._dynamic_resizing:
|
||||
self.grid_propagate(0)
|
||||
|
||||
if self._cursor_manipulation_enabled:
|
||||
if sys.platform == "darwin":
|
||||
@ -118,17 +115,11 @@ class CTkOptionMenu(CTkBaseClass):
|
||||
elif sys.platform.startswith("win"):
|
||||
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._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._create_grid()
|
||||
if not self._dynamic_resizing:
|
||||
self.grid_propagate(0)
|
||||
|
||||
self._create_bindings()
|
||||
self._draw() # initial draw
|
||||
|
||||
if self._variable is not None:
|
||||
@ -136,6 +127,18 @@ class CTkOptionMenu(CTkBaseClass):
|
||||
self._current_value = self._variable.get()
|
||||
self._text_label.configure(text=self._current_value)
|
||||
|
||||
def _create_bindings(self, sequence: Optional[str] = None):
|
||||
""" set necessary bindings for functionality of widget, will overwrite other bindings """
|
||||
if sequence is None or sequence == "<Enter>":
|
||||
self._canvas.bind("<Enter>", self._on_enter)
|
||||
self._text_label.bind("<Enter>", self._on_enter)
|
||||
if sequence is None or sequence == "<Leave>":
|
||||
self._canvas.bind("<Leave>", self._on_leave)
|
||||
self._text_label.bind("<Leave>", self._on_leave)
|
||||
if sequence is None or sequence == "<Button-1>":
|
||||
self._canvas.bind("<Button-1>", self._clicked)
|
||||
self._text_label.bind("<Button-1>", self._clicked)
|
||||
|
||||
def _create_grid(self):
|
||||
self._canvas.grid(row=0, column=0, sticky="nsew")
|
||||
|
||||
@ -240,8 +243,8 @@ class CTkOptionMenu(CTkBaseClass):
|
||||
self._text_color = self._check_color_type(kwargs.pop("text_color"))
|
||||
require_redraw = True
|
||||
|
||||
if "dropdown_color" in kwargs:
|
||||
self._dropdown_menu.configure(fg_color=kwargs.pop("dropdown_color"))
|
||||
if "dropdown_fg_color" in kwargs:
|
||||
self._dropdown_menu.configure(fg_color=kwargs.pop("dropdown_fg_color"))
|
||||
|
||||
if "dropdown_hover_color" in kwargs:
|
||||
self._dropdown_menu.configure(hover_color=kwargs.pop("dropdown_hover_color"))
|
||||
@ -326,7 +329,7 @@ class CTkOptionMenu(CTkBaseClass):
|
||||
elif attribute_name == "dropdown_font":
|
||||
return self._dropdown_menu.cget("font")
|
||||
elif attribute_name == "values":
|
||||
return self._values
|
||||
return copy.copy(self._values)
|
||||
elif attribute_name == "variable":
|
||||
return self._variable
|
||||
elif attribute_name == "state":
|
||||
@ -393,17 +396,21 @@ class CTkOptionMenu(CTkBaseClass):
|
||||
if self._state is not tkinter.DISABLED and len(self._values) > 0:
|
||||
self._open_dropdown_menu()
|
||||
|
||||
def bind(self, sequence: str = None, command: Callable = None, add: str = None) -> str:
|
||||
""" called on the tkinter.Label and tkinter.Canvas """
|
||||
canvas_bind_return = self._canvas.bind(sequence, command, add)
|
||||
label_bind_return = self._text_label.bind(sequence, command, add)
|
||||
return canvas_bind_return + " + " + label_bind_return
|
||||
def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True):
|
||||
""" called on the tkinter.Canvas """
|
||||
if not (add == "+" or add is True):
|
||||
raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
|
||||
self._canvas.bind(sequence, command, add=True)
|
||||
self._text_label.bind(sequence, command, add=True)
|
||||
|
||||
def unbind(self, sequence: str, funcid: str = None):
|
||||
def unbind(self, sequence: str = None, funcid: str = None):
|
||||
""" called on the tkinter.Label and tkinter.Canvas """
|
||||
canvas_bind_return, label_bind_return = funcid.split(" + ")
|
||||
self._canvas.unbind(sequence, canvas_bind_return)
|
||||
self._text_label.unbind(sequence, label_bind_return)
|
||||
if funcid is not None:
|
||||
raise ValueError("'funcid' argument can only be None, because there is a bug in" +
|
||||
" tkinter and its not clear whether the internal callbacks will be unbinded or not")
|
||||
self._canvas.unbind(sequence, None)
|
||||
self._text_label.unbind(sequence, None)
|
||||
self._create_bindings(sequence=sequence) # restore internal callbacks for sequence
|
||||
|
||||
def focus(self):
|
||||
return self._text_label.focus()
|
||||
|
@ -1,6 +1,10 @@
|
||||
import tkinter
|
||||
import math
|
||||
from typing import Union, Tuple, Optional
|
||||
from typing import Union, Tuple, Optional, Callable
|
||||
try:
|
||||
from typing import Literal
|
||||
except ImportError:
|
||||
from typing_extensions import Literal
|
||||
|
||||
from .core_rendering import CTkCanvas
|
||||
from .theme import ThemeManager
|
||||
@ -29,7 +33,7 @@ class CTkProgressBar(CTkBaseClass):
|
||||
|
||||
variable: Union[tkinter.Variable, None] = None,
|
||||
orientation: str = "horizontal",
|
||||
mode: str = "determinate",
|
||||
mode: Literal["determinate", "indeterminate"] = "determinate",
|
||||
determinate_speed: float = 1,
|
||||
indeterminate_speed: float = 1,
|
||||
**kwargs):
|
||||
@ -58,6 +62,7 @@ class CTkProgressBar(CTkBaseClass):
|
||||
self._variable = variable
|
||||
self._variable_callback_blocked = False
|
||||
self._variable_callback_name = None
|
||||
self._loop_after_id = None
|
||||
|
||||
# shape
|
||||
self._corner_radius = ThemeManager.theme["CTkProgressBar"]["corner_radius"] if corner_radius is None else corner_radius
|
||||
@ -249,13 +254,15 @@ class CTkProgressBar(CTkBaseClass):
|
||||
return self._determinate_value
|
||||
|
||||
def start(self):
|
||||
""" start indeterminate mode """
|
||||
""" start automatic mode """
|
||||
if not self._loop_running:
|
||||
self._loop_running = True
|
||||
self._internal_loop()
|
||||
|
||||
def stop(self):
|
||||
""" stop indeterminate mode """
|
||||
""" stop automatic mode """
|
||||
if self._loop_after_id is not None:
|
||||
self.after_cancel(self._loop_after_id)
|
||||
self._loop_running = False
|
||||
|
||||
def _internal_loop(self):
|
||||
@ -265,13 +272,14 @@ class CTkProgressBar(CTkBaseClass):
|
||||
if self._determinate_value > 1:
|
||||
self._determinate_value -= 1
|
||||
self._draw()
|
||||
self.after(20, self._internal_loop)
|
||||
self._loop_after_id = self.after(20, self._internal_loop)
|
||||
else:
|
||||
self._indeterminate_value += self._indeterminate_speed
|
||||
self._draw()
|
||||
self.after(20, self._internal_loop)
|
||||
self._loop_after_id = self.after(20, self._internal_loop)
|
||||
|
||||
def step(self):
|
||||
""" increase progress """
|
||||
if self._mode == "determinate":
|
||||
self._determinate_value += self._determinate_speed / 50
|
||||
if self._determinate_value > 1:
|
||||
@ -281,13 +289,18 @@ class CTkProgressBar(CTkBaseClass):
|
||||
self._indeterminate_value += self._indeterminate_speed
|
||||
self._draw()
|
||||
|
||||
def bind(self, sequence=None, command=None, add=None):
|
||||
def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True):
|
||||
""" called on the tkinter.Canvas """
|
||||
return self._canvas.bind(sequence, command, add)
|
||||
if not (add == "+" or add is True):
|
||||
raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
|
||||
self._canvas.bind(sequence, command, add=True)
|
||||
|
||||
def unbind(self, sequence, funcid=None):
|
||||
""" called on the tkinter.Canvas """
|
||||
return self._canvas.unbind(sequence, funcid)
|
||||
def unbind(self, sequence: str = None, funcid: str = None):
|
||||
""" called on the tkinter.Label and tkinter.Canvas """
|
||||
if funcid is not None:
|
||||
raise ValueError("'funcid' argument can only be None, because there is a bug in" +
|
||||
" tkinter and its not clear whether the internal callbacks will be unbinded or not")
|
||||
self._canvas.unbind(sequence, None)
|
||||
|
||||
def focus(self):
|
||||
return self._canvas.focus()
|
||||
|
@ -99,10 +99,6 @@ class CTkRadioButton(CTkBaseClass):
|
||||
self._canvas.grid(row=0, column=0)
|
||||
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._text_label = tkinter.Label(master=self,
|
||||
bd=0,
|
||||
padx=0,
|
||||
@ -114,16 +110,25 @@ class CTkRadioButton(CTkBaseClass):
|
||||
self._text_label.grid(row=0, column=2, 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)
|
||||
|
||||
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._create_bindings()
|
||||
self._set_cursor()
|
||||
self._draw()
|
||||
|
||||
def _create_bindings(self, sequence: Optional[str] = None):
|
||||
""" set necessary bindings for functionality of widget, will overwrite other bindings """
|
||||
if sequence is None or sequence == "<Enter>":
|
||||
self._canvas.bind("<Enter>", self._on_enter)
|
||||
self._text_label.bind("<Enter>", self._on_enter)
|
||||
if sequence is None or sequence == "<Leave>":
|
||||
self._canvas.bind("<Leave>", self._on_leave)
|
||||
self._text_label.bind("<Leave>", self._on_leave)
|
||||
if sequence is None or sequence == "<Button-1>":
|
||||
self._canvas.bind("<Button-1>", self.invoke)
|
||||
self._text_label.bind("<Button-1>", self.invoke)
|
||||
|
||||
def _set_scaling(self, *args, **kwargs):
|
||||
super()._set_scaling(*args, **kwargs)
|
||||
@ -377,8 +382,8 @@ class CTkRadioButton(CTkBaseClass):
|
||||
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
|
||||
@ -398,13 +403,21 @@ class CTkRadioButton(CTkBaseClass):
|
||||
self._variable.set("")
|
||||
self._variable_callback_blocked = False
|
||||
|
||||
def bind(self, sequence=None, command=None, add=None):
|
||||
def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True):
|
||||
""" called on the tkinter.Canvas """
|
||||
return self._canvas.bind(sequence, command, add)
|
||||
if not (add == "+" or add is True):
|
||||
raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
|
||||
self._canvas.bind(sequence, command, add=True)
|
||||
self._text_label.bind(sequence, command, add=True)
|
||||
|
||||
def unbind(self, sequence, funcid=None):
|
||||
""" called on the tkinter.Canvas """
|
||||
return self._canvas.unbind(sequence, funcid)
|
||||
def unbind(self, sequence: str = None, funcid: str = None):
|
||||
""" called on the tkinter.Label and tkinter.Canvas """
|
||||
if funcid is not None:
|
||||
raise ValueError("'funcid' argument can only be None, because there is a bug in" +
|
||||
" tkinter and its not clear whether the internal callbacks will be unbinded or not")
|
||||
self._canvas.unbind(sequence, None)
|
||||
self._text_label.unbind(sequence, None)
|
||||
self._create_bindings(sequence=sequence) # restore internal callbacks for sequence
|
||||
|
||||
def focus(self):
|
||||
return self._text_label.focus()
|
||||
|
@ -49,8 +49,8 @@ class CTkScrollbar(CTkBaseClass):
|
||||
|
||||
# color
|
||||
self._fg_color = ThemeManager.theme["CTkScrollbar"]["fg_color"] if fg_color is None else self._check_color_type(fg_color, transparency=True)
|
||||
self._button_color = ThemeManager.theme["CTkScrollbar"]["scrollbar_color"] if button_color is None else self._check_color_type(button_color)
|
||||
self._button_hover_color = ThemeManager.theme["CTkScrollbar"]["scrollbar_hover_color"] if button_hover_color is None else self._check_color_type(button_hover_color)
|
||||
self._button_color = ThemeManager.theme["CTkScrollbar"]["button_color"] if button_color is None else self._check_color_type(button_color)
|
||||
self._button_hover_color = ThemeManager.theme["CTkScrollbar"]["button_hover_color"] if button_hover_color is None else self._check_color_type(button_hover_color)
|
||||
|
||||
# shape
|
||||
self._corner_radius = ThemeManager.theme["CTkScrollbar"]["corner_radius"] if corner_radius is None else corner_radius
|
||||
@ -71,14 +71,22 @@ class CTkScrollbar(CTkBaseClass):
|
||||
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._create_bindings()
|
||||
self._draw()
|
||||
|
||||
def _create_bindings(self, sequence: Optional[str] = None):
|
||||
""" set necessary bindings for functionality of widget, will overwrite other bindings """
|
||||
if sequence is None:
|
||||
self._canvas.tag_bind("border_parts", "<Button-1>", self._clicked)
|
||||
if sequence is None or sequence == "<Enter>":
|
||||
self._canvas.bind("<Enter>", self._on_enter)
|
||||
if sequence is None or sequence == "<Leave>":
|
||||
self._canvas.bind("<Leave>", self._on_leave)
|
||||
if sequence is None or sequence == "<B1-Motion>":
|
||||
self._canvas.bind("<B1-Motion>", self._clicked)
|
||||
if sequence is None or sequence == "<MouseWheel>":
|
||||
self._canvas.bind("<MouseWheel>", self._mouse_scroll_event)
|
||||
|
||||
def _set_scaling(self, *args, **kwargs):
|
||||
super()._set_scaling(*args, **kwargs)
|
||||
|
||||
@ -249,13 +257,19 @@ class CTkScrollbar(CTkBaseClass):
|
||||
def get(self):
|
||||
return self._start_value, self._end_value
|
||||
|
||||
def bind(self, sequence=None, command=None, add=None):
|
||||
def bind(self, sequence=None, command=None, add=True):
|
||||
""" called on the tkinter.Canvas """
|
||||
return self._canvas.bind(sequence, command, add)
|
||||
if not (add == "+" or add is True):
|
||||
raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
|
||||
self._canvas.bind(sequence, command, add=True)
|
||||
|
||||
def unbind(self, sequence, funcid=None):
|
||||
""" called on the tkinter.Canvas """
|
||||
return self._canvas.unbind(sequence, funcid)
|
||||
def unbind(self, sequence=None, funcid=None):
|
||||
""" called on the tkinter.Canvas, restores internal callbacks """
|
||||
if funcid is not None:
|
||||
raise ValueError("'funcid' argument can only be None, because there is a bug in" +
|
||||
" tkinter and its not clear whether the internal callbacks will be unbinded or not")
|
||||
self._canvas.unbind(sequence, None) # unbind all callbacks for sequence
|
||||
self._create_bindings(sequence=sequence) # restore internal callbacks for sequence
|
||||
|
||||
def focus(self):
|
||||
return self._canvas.focus()
|
||||
|
@ -1,5 +1,10 @@
|
||||
import tkinter
|
||||
from typing import Union, Tuple, List, Dict, Callable, Optional, Literal
|
||||
import copy
|
||||
from typing import Union, Tuple, List, Dict, Callable, Optional
|
||||
try:
|
||||
from typing import Literal
|
||||
except ImportError:
|
||||
from typing_extensions import Literal
|
||||
|
||||
from .theme import ThemeManager
|
||||
from .font import CTkFont
|
||||
@ -317,7 +322,7 @@ class CTkSegmentedButton(CTkFrame):
|
||||
elif attribute_name == "font":
|
||||
return self._font
|
||||
elif attribute_name == "values":
|
||||
return self._value_list
|
||||
return copy.copy(self._value_list)
|
||||
elif attribute_name == "variable":
|
||||
return self._variable
|
||||
elif attribute_name == "dynamic_resizing":
|
||||
@ -408,3 +413,9 @@ class CTkSegmentedButton(CTkFrame):
|
||||
else:
|
||||
raise ValueError(f"CTkSegmentedButton does not contain value '{value}'")
|
||||
|
||||
def bind(self, sequence=None, command=None, add=None):
|
||||
raise NotImplementedError
|
||||
|
||||
def unbind(self, sequence=None, funcid=None):
|
||||
raise NotImplementedError
|
||||
|
||||
|
@ -96,11 +96,7 @@ class CTkSlider(CTkBaseClass):
|
||||
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._create_bindings()
|
||||
self._set_cursor()
|
||||
self._draw() # initial draw
|
||||
|
||||
@ -110,6 +106,17 @@ class CTkSlider(CTkBaseClass):
|
||||
self.set(self._variable.get(), from_variable_callback=True)
|
||||
self._variable_callback_blocked = False
|
||||
|
||||
def _create_bindings(self, sequence: Optional[str] = None):
|
||||
""" set necessary bindings for functionality of widget, will overwrite other bindings """
|
||||
if sequence is None or sequence == "<Enter>":
|
||||
self._canvas.bind("<Enter>", self._on_enter)
|
||||
if sequence is None or sequence == "<Leave>":
|
||||
self._canvas.bind("<Leave>", self._on_leave)
|
||||
if sequence is None or sequence == "<Button-1>":
|
||||
self._canvas.bind("<Button-1>", self._clicked)
|
||||
if sequence is None or sequence == "<B1-Motion>":
|
||||
self._canvas.bind("<B1-Motion>", self._clicked)
|
||||
|
||||
def _set_scaling(self, *args, **kwargs):
|
||||
super()._set_scaling(*args, **kwargs)
|
||||
|
||||
@ -366,13 +373,19 @@ class CTkSlider(CTkBaseClass):
|
||||
if not self._variable_callback_blocked:
|
||||
self.set(self._variable.get(), from_variable_callback=True)
|
||||
|
||||
def bind(self, sequence=None, command=None, add=None):
|
||||
def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True):
|
||||
""" called on the tkinter.Canvas """
|
||||
return self._canvas.bind(sequence, command, add)
|
||||
if not (add == "+" or add is True):
|
||||
raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
|
||||
self._canvas.bind(sequence, command, add=True)
|
||||
|
||||
def unbind(self, sequence, funcid=None):
|
||||
""" called on the tkinter.Canvas """
|
||||
return self._canvas.unbind(sequence, funcid)
|
||||
def unbind(self, sequence: str = None, funcid: str = None):
|
||||
""" called on the tkinter.Label and tkinter.Canvas """
|
||||
if funcid is not None:
|
||||
raise ValueError("'funcid' argument can only be None, because there is a bug in" +
|
||||
" tkinter and its not clear whether the internal callbacks will be unbinded or not")
|
||||
self._canvas.unbind(sequence, None)
|
||||
self._create_bindings(sequence=sequence) # restore internal callbacks for sequence
|
||||
|
||||
def focus(self):
|
||||
return self._canvas.focus()
|
||||
|
@ -106,10 +106,6 @@ class CTkSwitch(CTkBaseClass):
|
||||
self._canvas.grid(row=0, column=0, sticky="")
|
||||
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._text_label = tkinter.Label(master=self,
|
||||
bd=0,
|
||||
padx=0,
|
||||
@ -121,16 +117,25 @@ class CTkSwitch(CTkBaseClass):
|
||||
self._text_label.grid(row=0, column=2, 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)
|
||||
|
||||
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._check_state = True if self._variable.get() == self._onvalue else False
|
||||
|
||||
self._draw() # initial draw
|
||||
self._create_bindings()
|
||||
self._set_cursor()
|
||||
self._draw() # initial draw
|
||||
|
||||
def _create_bindings(self, sequence: Optional[str] = None):
|
||||
""" set necessary bindings for functionality of widget, will overwrite other bindings """
|
||||
if sequence is None or sequence == "<Enter>":
|
||||
self._canvas.bind("<Enter>", self._on_enter)
|
||||
self._text_label.bind("<Enter>", self._on_enter)
|
||||
if sequence is None or sequence == "<Leave>":
|
||||
self._canvas.bind("<Leave>", self._on_leave)
|
||||
self._text_label.bind("<Leave>", self._on_leave)
|
||||
if sequence is None or sequence == "<Button-1>":
|
||||
self._canvas.bind("<Button-1>", self.toggle)
|
||||
self._text_label.bind("<Button-1>", self.toggle)
|
||||
|
||||
def _set_scaling(self, *args, **kwargs):
|
||||
super()._set_scaling(*args, **kwargs)
|
||||
@ -437,13 +442,21 @@ class CTkSwitch(CTkBaseClass):
|
||||
elif self._variable.get() == self._offvalue:
|
||||
self.deselect(from_variable_callback=True)
|
||||
|
||||
def bind(self, sequence=None, command=None, add=None):
|
||||
def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True):
|
||||
""" called on the tkinter.Canvas """
|
||||
return self._canvas.bind(sequence, command, add)
|
||||
if not (add == "+" or add is True):
|
||||
raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
|
||||
self._canvas.bind(sequence, command, add=True)
|
||||
self._text_label.bind(sequence, command, add=True)
|
||||
|
||||
def unbind(self, sequence, funcid=None):
|
||||
""" called on the tkinter.Canvas """
|
||||
return self._canvas.unbind(sequence, funcid)
|
||||
def unbind(self, sequence: str = None, funcid: str = None):
|
||||
""" called on the tkinter.Label and tkinter.Canvas """
|
||||
if funcid is not None:
|
||||
raise ValueError("'funcid' argument can only be None, because there is a bug in" +
|
||||
" tkinter and its not clear whether the internal callbacks will be unbinded or not")
|
||||
self._canvas.unbind(sequence, None)
|
||||
self._text_label.unbind(sequence, None)
|
||||
self._create_bindings(sequence=sequence) # restore internal callbacks for sequence
|
||||
|
||||
def focus(self):
|
||||
return self._text_label.focus()
|
||||
|
@ -1,5 +1,5 @@
|
||||
import tkinter
|
||||
from typing import Union, Tuple, Optional
|
||||
from typing import Union, Tuple, Optional, Callable
|
||||
|
||||
from .core_rendering import CTkCanvas
|
||||
from .ctk_scrollbar import CTkScrollbar
|
||||
@ -86,7 +86,6 @@ class CTkTextbox(CTkBaseClass):
|
||||
highlightthickness=0,
|
||||
relief="flat",
|
||||
insertbackground=self._apply_appearance_mode(self._text_color),
|
||||
bg=self._apply_appearance_mode(self._fg_color),
|
||||
**pop_from_dict_by_set(kwargs, self._valid_tk_text_attributes))
|
||||
|
||||
check_kwargs_empty(kwargs, raise_error=True)
|
||||
@ -227,10 +226,10 @@ class CTkTextbox(CTkBaseClass):
|
||||
self._textbox.configure(fg=self._apply_appearance_mode(self._text_color),
|
||||
bg=self._apply_appearance_mode(self._bg_color),
|
||||
insertbackground=self._apply_appearance_mode(self._text_color))
|
||||
self._x_scrollbar.configure(fg_color=self._bg_color, scrollbar_color=self._scrollbar_button_color,
|
||||
scrollbar_hover_color=self._scrollbar_button_hover_color)
|
||||
self._y_scrollbar.configure(fg_color=self._bg_color, scrollbar_color=self._scrollbar_button_color,
|
||||
scrollbar_hover_color=self._scrollbar_button_hover_color)
|
||||
self._x_scrollbar.configure(fg_color=self._bg_color, button_color=self._scrollbar_button_color,
|
||||
button_hover_color=self._scrollbar_button_hover_color)
|
||||
self._y_scrollbar.configure(fg_color=self._bg_color, button_color=self._scrollbar_button_color,
|
||||
button_hover_color=self._scrollbar_button_hover_color)
|
||||
else:
|
||||
self._canvas.itemconfig("inner_parts",
|
||||
fill=self._apply_appearance_mode(self._fg_color),
|
||||
@ -327,18 +326,18 @@ class CTkTextbox(CTkBaseClass):
|
||||
else:
|
||||
return super().cget(attribute_name)
|
||||
|
||||
def bind(self, sequence=None, command=None, add=None):
|
||||
""" called on the tkinter.Text """
|
||||
def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True):
|
||||
""" called on the tkinter.Canvas """
|
||||
if not (add == "+" or add is True):
|
||||
raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
|
||||
self._textbox.bind(sequence, command, add=True)
|
||||
|
||||
# if sequence is <KeyRelease>, allow only to add the binding to keep the _textbox_modified_event() being called
|
||||
if sequence == "<KeyRelease>":
|
||||
return self._textbox.bind(sequence, command, add="+")
|
||||
else:
|
||||
return self._textbox.bind(sequence, command, add)
|
||||
|
||||
def unbind(self, sequence, funcid=None):
|
||||
""" called on the tkinter.Text """
|
||||
return self._textbox.unbind(sequence, funcid)
|
||||
def unbind(self, sequence: str = None, funcid: str = None):
|
||||
""" called on the tkinter.Label and tkinter.Canvas """
|
||||
if funcid is not None:
|
||||
raise ValueError("'funcid' argument can only be None, because there is a bug in" +
|
||||
" tkinter and its not clear whether the internal callbacks will be unbinded or not")
|
||||
self._textbox.unbind(sequence, None)
|
||||
|
||||
def focus(self):
|
||||
return self._textbox.focus()
|
||||
|
@ -1,6 +1,10 @@
|
||||
from tkinter.font import Font
|
||||
import copy
|
||||
from typing import List, Callable, Tuple, Optional, Literal
|
||||
from typing import List, Callable, Tuple, Optional
|
||||
try:
|
||||
from typing import Literal
|
||||
except ImportError:
|
||||
from typing_extensions import Literal
|
||||
|
||||
from ..theme import ThemeManager
|
||||
|
||||
@ -51,7 +55,6 @@ class CTkFont(Font):
|
||||
self._size_configure_callback_list.remove(callback)
|
||||
|
||||
def create_scaled_tuple(self, font_scaling: float) -> Tuple[str, int, str]:
|
||||
|
||||
""" return scaled tuple representation of font in the form (family: str, size: int, style: str)"""
|
||||
return self._family, round(-abs(self._size) * font_scaling), self._tuple_style_string
|
||||
|
||||
|
@ -19,8 +19,8 @@ class CTkImage:
|
||||
_checked_PIL_import = False
|
||||
|
||||
def __init__(self,
|
||||
light_image: Image.Image = None,
|
||||
dark_image: Image.Image = None,
|
||||
light_image: "Image.Image" = None,
|
||||
dark_image: "Image.Image" = None,
|
||||
size: Tuple[int, int] = (20, 20)):
|
||||
|
||||
if not self._checked_PIL_import:
|
||||
@ -92,21 +92,21 @@ class CTkImage:
|
||||
def _get_scaled_size(self, widget_scaling: float) -> Tuple[int, int]:
|
||||
return round(self._size[0] * widget_scaling), round(self._size[1] * widget_scaling)
|
||||
|
||||
def _get_scaled_light_photo_image(self, scaled_size: Tuple[int, int]) -> ImageTk.PhotoImage:
|
||||
def _get_scaled_light_photo_image(self, scaled_size: Tuple[int, int]) -> "ImageTk.PhotoImage":
|
||||
if scaled_size in self._scaled_light_photo_images:
|
||||
return self._scaled_light_photo_images[scaled_size]
|
||||
else:
|
||||
self._scaled_light_photo_images[scaled_size] = ImageTk.PhotoImage(self._light_image.resize(scaled_size))
|
||||
return self._scaled_light_photo_images[scaled_size]
|
||||
|
||||
def _get_scaled_dark_photo_image(self, scaled_size: Tuple[int, int]) -> ImageTk.PhotoImage:
|
||||
def _get_scaled_dark_photo_image(self, scaled_size: Tuple[int, int]) -> "ImageTk.PhotoImage":
|
||||
if scaled_size in self._scaled_dark_photo_images:
|
||||
return self._scaled_dark_photo_images[scaled_size]
|
||||
else:
|
||||
self._scaled_dark_photo_images[scaled_size] = ImageTk.PhotoImage(self._dark_image.resize(scaled_size))
|
||||
return self._scaled_dark_photo_images[scaled_size]
|
||||
|
||||
def create_scaled_photo_image(self, widget_scaling: float, appearance_mode: str) -> ImageTk.PhotoImage:
|
||||
def create_scaled_photo_image(self, widget_scaling: float, appearance_mode: str) -> "ImageTk.PhotoImage":
|
||||
scaled_size = self._get_scaled_size(widget_scaling)
|
||||
|
||||
if appearance_mode == "light" and self._light_image is not None:
|
||||
|
@ -82,8 +82,8 @@ class CTkScalingBaseClass:
|
||||
return font
|
||||
elif len(font) == 2:
|
||||
return font[0], -abs(round(font[1] * self.__widget_scaling))
|
||||
elif len(font) == 3:
|
||||
return font[0], -abs(round(font[1] * self.__widget_scaling)), font[2]
|
||||
elif 3 <= len(font) <= 6:
|
||||
return font[0], -abs(round(font[1] * self.__widget_scaling)), font[2:]
|
||||
else:
|
||||
raise ValueError(f"Can not scale font {font}. font needs to be tuple of len 1, 2 or 3")
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
import tkinter
|
||||
import customtkinter
|
||||
|
||||
customtkinter.set_appearance_mode("dark") # Modes: "System" (standard), "Dark", "Light"
|
||||
@ -8,7 +7,6 @@ app = customtkinter.CTk()
|
||||
app.geometry("400x780")
|
||||
app.title("CustomTkinter simple_example.py")
|
||||
|
||||
|
||||
def button_callback():
|
||||
print("Button click", combobox_1.get())
|
||||
|
||||
@ -20,7 +18,7 @@ def slider_callback(value):
|
||||
frame_1 = customtkinter.CTkFrame(master=app)
|
||||
frame_1.pack(pady=20, padx=60, fill="both", expand=True)
|
||||
|
||||
label_1 = customtkinter.CTkLabel(master=frame_1, justify=tkinter.LEFT)
|
||||
label_1 = customtkinter.CTkLabel(master=frame_1, justify=customtkinter.LEFT)
|
||||
label_1.pack(pady=10, padx=10)
|
||||
|
||||
progressbar_1 = customtkinter.CTkProgressBar(master=frame_1)
|
||||
@ -42,12 +40,12 @@ optionmenu_1.set("CTkOptionMenu")
|
||||
|
||||
combobox_1 = customtkinter.CTkComboBox(frame_1, values=["Option 1", "Option 2", "Option 42 long long long..."])
|
||||
combobox_1.pack(pady=10, padx=10)
|
||||
optionmenu_1.set("CTkComboBox")
|
||||
combobox_1.set("CTkComboBox")
|
||||
|
||||
checkbox_1 = customtkinter.CTkCheckBox(master=frame_1)
|
||||
checkbox_1.pack(pady=10, padx=10)
|
||||
|
||||
radiobutton_var = tkinter.IntVar(value=1)
|
||||
radiobutton_var = customtkinter.IntVar(value=1)
|
||||
|
||||
radiobutton_1 = customtkinter.CTkRadioButton(master=frame_1, variable=radiobutton_var, value=1)
|
||||
radiobutton_1.pack(pady=10, padx=10)
|
||||
|
@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
|
||||
github_url = "https://github.com/TomSchimansky/CustomTkinter"
|
||||
|
||||
[tool.tbump.version]
|
||||
current = "5.0.0"
|
||||
current = "5.0.4"
|
||||
|
||||
# Example of a semver regexp.
|
||||
# Make sure this matches current_version before
|
||||
|
@ -1,8 +1,8 @@
|
||||
[metadata]
|
||||
name = customtkinter
|
||||
version = 5.0.0
|
||||
version = 5.0.4
|
||||
description = Create modern looking GUIs with Python
|
||||
long_description = file: Readme.md
|
||||
long_description = A modern and customizable python UI-library based on Tkinter: https://github.com/TomSchimansky/CustomTkinter
|
||||
long_description_content_type = text/markdown
|
||||
url = https://github.com/TomSchimansky/CustomTkinter
|
||||
author = Tom Schimansky
|
||||
@ -17,7 +17,6 @@ classifiers =
|
||||
python_requires = >=3.7
|
||||
packages =
|
||||
customtkinter
|
||||
customtkinter.utility
|
||||
customtkinter.windows
|
||||
customtkinter.windows.widgets
|
||||
customtkinter.windows.widgets.appearance_mode
|
||||
@ -27,6 +26,7 @@ packages =
|
||||
customtkinter.windows.widgets.image
|
||||
customtkinter.windows.widgets.scaling
|
||||
customtkinter.windows.widgets.theme
|
||||
customtkinter.windows.widgets.utility
|
||||
install_requires =
|
||||
darkdetect
|
||||
typing_extensions; python_version<="3.7"
|
||||
|
After Width: | Height: | Size: 198 KiB |
Before Width: | Height: | Size: 7.9 KiB |
Before Width: | Height: | Size: 4.8 KiB |
BIN
test/manual_integration_tests/test_images/add_user_dark.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
test/manual_integration_tests/test_images/add_user_light.png
Normal file
After Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 10 KiB |
BIN
test/manual_integration_tests/test_images/chat_dark.png
Normal file
After Width: | Height: | Size: 9.2 KiB |
BIN
test/manual_integration_tests/test_images/chat_light.png
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
test/manual_integration_tests/test_images/home_dark.png
Normal file
After Width: | Height: | Size: 7.0 KiB |
BIN
test/manual_integration_tests/test_images/home_light.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
test/manual_integration_tests/test_images/image_icon_light.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
test/manual_integration_tests/test_images/large_test_image.png
Normal file
After Width: | Height: | Size: 3.3 MiB |
Before Width: | Height: | Size: 18 KiB |