This commit is contained in:
demberto 2023-04-18 18:50:36 +05:30 committed by GitHub
commit baaad651a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 1010 additions and 891 deletions

View File

@ -2,49 +2,30 @@ __version__ = "5.1.2"
import os
import sys
from tkinter import Variable, StringVar, IntVar, DoubleVar, BooleanVar
from tkinter.constants import *
import tkinter.filedialog as filedialog
# import manager classes
from .windows.widgets.appearance_mode import AppearanceModeTracker
from .windows.widgets.font import FontManager
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
from .windows.widgets import CTkComboBox
from .windows.widgets import CTkEntry
from .windows.widgets import CTkFrame
from .windows.widgets import CTkLabel
from .windows.widgets import CTkOptionMenu
from .windows.widgets import CTkProgressBar
from .windows.widgets import CTkRadioButton
from .windows.widgets import CTkScrollbar
from .windows.widgets import CTkSegmentedButton
from .windows.widgets import CTkSlider
from .windows.widgets import CTkSwitch
from .windows.widgets import CTkTabview
from .windows.widgets import CTkTextbox
from .windows.widgets import CTkScrollableFrame
from tkinter import BooleanVar, DoubleVar, IntVar, StringVar, Variable
from tkinter.constants import *
# import windows
from .windows import CTk
from .windows import CTkToplevel
from .windows import CTkInputDialog
from .windows import CTk, CTkInputDialog, CTkToplevel
# import widgets
from .windows.widgets import (CTkButton, CTkCheckBox, CTkComboBox, CTkEntry,
CTkFrame, CTkLabel, CTkOptionMenu,
CTkProgressBar, CTkRadioButton,
CTkScrollableFrame, CTkScrollbar,
CTkSegmentedButton, CTkSlider, CTkSwitch,
CTkTabview, CTkTextbox)
# import manager classes
from .windows.widgets.appearance_mode import AppearanceModeTracker
# import base widgets
from .windows.widgets.core_rendering import CTkCanvas, DrawEngine
from .windows.widgets.core_widget_classes import CTkBaseClass
# import font classes
from .windows.widgets.font import CTkFont
from .windows.widgets.font import CTkFont, FontManager
# import image classes
from .windows.widgets.image import CTkImage
from .windows.widgets.scaling import ScalingTracker
from .windows.widgets.theme import ThemeManager
_ = Variable, StringVar, IntVar, DoubleVar, BooleanVar, CENTER, filedialog # prevent IDE from removing unused imports

View File

@ -1,3 +1,3 @@
from .ctk_input_dialog import CTkInputDialog
from .ctk_tk import CTk
from .ctk_toplevel import CTkToplevel
from .ctk_input_dialog import CTkInputDialog

View File

@ -1,10 +1,11 @@
from typing import Union, Tuple, Optional
from __future__ import annotations
import tkinter
from typing import Any
from .widgets import CTkLabel
from .widgets import CTkEntry
from .widgets import CTkButton
from .widgets.theme import ThemeManager
from .ctk_toplevel import CTkToplevel
from .widgets import CTkButton, CTkEntry, CTkLabel
from .widgets.theme import ThemeManager
class CTkInputDialog(CTkToplevel):
@ -14,14 +15,14 @@ class CTkInputDialog(CTkToplevel):
"""
def __init__(self,
fg_color: Optional[Union[str, Tuple[str, str]]] = None,
text_color: Optional[Union[str, Tuple[str, str]]] = None,
button_fg_color: Optional[Union[str, Tuple[str, str]]] = None,
button_hover_color: Optional[Union[str, Tuple[str, str]]] = None,
button_text_color: Optional[Union[str, Tuple[str, str]]] = None,
entry_fg_color: Optional[Union[str, Tuple[str, str]]] = None,
entry_border_color: Optional[Union[str, Tuple[str, str]]] = None,
entry_text_color: Optional[Union[str, Tuple[str, str]]] = None,
fg_color: str | tuple[str, str] | None = None,
text_color: str | tuple[str, str] | None = None,
button_fg_color: str | tuple[str, str] | None = None,
button_hover_color: str | tuple[str, str] | None = None,
button_text_color: str | tuple[str, str] | None = None,
entry_fg_color: str | tuple[str, str] | None = None,
entry_border_color: str | tuple[str, str] | None = None,
entry_text_color: str | tuple[str, str] | None = None,
title: str = "CTkDialog",
text: str = "CTkDialog"):
@ -37,7 +38,7 @@ class CTkInputDialog(CTkToplevel):
self._entry_border_color = ThemeManager.theme["CTkEntry"]["border_color"] if entry_border_color is None else self._check_color_type(entry_border_color)
self._entry_text_color = ThemeManager.theme["CTkEntry"]["text_color"] if entry_text_color is None else self._check_color_type(entry_text_color)
self._user_input: Union[str, None] = None
self._user_input: str | None = None
self._running: bool = False
self._text = text
@ -50,7 +51,6 @@ class CTkInputDialog(CTkToplevel):
self.grab_set() # make other windows not clickable
def _create_widgets(self):
self.grid_columnconfigure((0, 1), weight=1)
self.rowconfigure(0, weight=1)
@ -92,7 +92,7 @@ class CTkInputDialog(CTkToplevel):
self.after(150, lambda: self._entry.focus()) # set focus to entry with slight delay, otherwise it won't work
self._entry.bind("<Return>", self._ok_event)
def _ok_event(self, event=None):
def _ok_event(self, event: tkinter.Event[Any] | None = None):
self._user_input = self._entry.get()
self.grab_release()
self.destroy()

View File

@ -1,16 +1,19 @@
import tkinter
from distutils.version import StrictVersion as Version
import sys
from __future__ import annotations
import ctypes
import os
import platform
import ctypes
from typing import Union, Tuple, Optional
import sys
import tkinter
from distutils.version import StrictVersion as Version
from typing import Any
from customtkinter.windows.widgets.utility.utility_functions import (
check_kwargs_empty, pop_from_dict_by_set)
from .widgets.theme import ThemeManager
from .widgets.scaling import CTkScalingBaseClass
from .widgets.appearance_mode import CTkAppearanceModeBaseClass
from customtkinter.windows.widgets.utility.utility_functions import pop_from_dict_by_set, check_kwargs_empty
from .widgets.scaling import CTkScalingBaseClass
from .widgets.theme import ThemeManager
class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
@ -19,9 +22,9 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
For detailed information check out the documentation.
"""
_valid_tk_constructor_arguments: set = {"screenName", "baseName", "className", "useTk", "sync", "use"}
_valid_tk_constructor_arguments: set[str] = {"screenName", "baseName", "className", "useTk", "sync", "use"}
_valid_tk_configure_arguments: set = {'bd', 'borderwidth', 'class', 'menu', 'relief', 'screen',
_valid_tk_configure_arguments: set[str] = {'bd', 'borderwidth', 'class', 'menu', 'relief', 'screen',
'use', 'container', 'cursor', 'height',
'highlightthickness', 'padx', 'pady', 'takefocus', 'visual', 'width'}
@ -29,8 +32,8 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
_deactivate_windows_window_header_manipulation: bool = False
def __init__(self,
fg_color: Optional[Union[str, Tuple[str, str]]] = None,
**kwargs):
fg_color: str | tuple[str, str] | None = None,
**kwargs: Any):
self._enable_macos_dark_title_bar()
@ -46,7 +49,7 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
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._last_resizable_args: tuple[list, dict] | None = None # (args, kwargs)
self._fg_color = ThemeManager.theme["CTk"]["fg_color"] if fg_color is None else self._check_color_type(fg_color)
@ -86,12 +89,12 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
CTkAppearanceModeBaseClass.destroy(self)
CTkScalingBaseClass.destroy(self)
def _focus_in_event(self, event):
def _focus_in_event(self, event: tkinter.Event[Any] | None = None):
# sometimes window looses jumps back on macOS if window is selected from Mission Control, so has to be lifted again
if sys.platform == "darwin":
self.lift()
def _update_dimensions_event(self, event=None):
def _update_dimensions_event(self, event: tkinter.Event[Any] | None = None):
if not self._block_update_dimensions_event:
detected_width = super().winfo_width() # detect current window size
@ -104,7 +107,7 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
self._current_width = self._reverse_window_scaling(detected_width) # adjust current size according to new size given by event
self._current_height = self._reverse_window_scaling(detected_height) # _current_width and _current_height are independent of the scale
def _set_scaling(self, new_widget_scaling, new_window_scaling):
def _set_scaling(self, new_widget_scaling: float, new_window_scaling: float):
super()._set_scaling(new_widget_scaling, new_window_scaling)
# Force new dimensions on window by using min, max, and geometry. Without min, max it won't work.
@ -149,7 +152,7 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
super().update()
def mainloop(self, *args, **kwargs):
def mainloop(self, *args: Any, **kwargs: Any):
if not self._window_exists:
if sys.platform.startswith("win"):
self._windows_set_titlebar_color(self._get_appearance_mode())
@ -171,7 +174,7 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
return current_resizable_values
def minsize(self, width: int = None, height: int = None):
def minsize(self, width: int | None = None, height: int | None = None):
self._min_width = width
self._min_height = height
if self._current_width < width:
@ -180,7 +183,7 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
self._current_height = height
super().minsize(self._apply_window_scaling(self._min_width), self._apply_window_scaling(self._min_height))
def maxsize(self, width: int = None, height: int = None):
def maxsize(self, width: int | None = None, height: int | None = None):
self._max_width = width
self._max_height = height
if self._current_width > width:
@ -189,19 +192,19 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
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):
def geometry(self, geometry_string: str | None = None):
if geometry_string is not None:
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, *_ = 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))
else:
return self._reverse_geometry_scaling(super().geometry())
def configure(self, **kwargs):
def configure(self, **kwargs: Any):
if "fg_color" in kwargs:
self._fg_color = self._check_color_type(kwargs.pop("fg_color"))
super().configure(bg=self._apply_appearance_mode(self._fg_color))
@ -215,7 +218,7 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
super().configure(**pop_from_dict_by_set(kwargs, self._valid_tk_configure_arguments))
check_kwargs_empty(kwargs)
def cget(self, attribute_name: str) -> any:
def cget(self, attribute_name: str) -> Any:
if attribute_name == "fg_color":
return self._fg_color
else:

View File

@ -1,16 +1,24 @@
import tkinter
from distutils.version import StrictVersion as Version
import sys
from __future__ import annotations
import ctypes
import os
import platform
import ctypes
from typing import Union, Tuple, Optional
import sys
import tkinter
from distutils.version import StrictVersion as Version
from typing import Any
if sys.version_info >= (3, 8):
from typing import Literal
else:
from typing_extensions import Literal
from customtkinter.windows.widgets.utility.utility_functions import (
check_kwargs_empty, pop_from_dict_by_set)
from .widgets.theme import ThemeManager
from .widgets.scaling import CTkScalingBaseClass
from .widgets.appearance_mode import CTkAppearanceModeBaseClass
from customtkinter.windows.widgets.utility.utility_functions import pop_from_dict_by_set, check_kwargs_empty
from .widgets.scaling import CTkScalingBaseClass
from .widgets.theme import ThemeManager
class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
@ -19,16 +27,16 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl
For detailed information check out the documentation.
"""
_valid_tk_toplevel_arguments: set = {"bd", "borderwidth", "class", "container", "cursor", "height",
"highlightbackground", "highlightthickness", "menu", "relief",
"screen", "takefocus", "use", "visual", "width"}
_valid_tk_toplevel_arguments: set[str] = {"bd", "borderwidth", "class", "container", "cursor", "height",
"highlightbackground", "highlightthickness", "menu", "relief",
"screen", "takefocus", "use", "visual", "width"}
_deactivate_macos_window_header_manipulation: bool = False
_deactivate_windows_window_header_manipulation: bool = False
def __init__(self, *args,
fg_color: Optional[Union[str, Tuple[str, str]]] = None,
**kwargs):
def __init__(self, *args: Any,
fg_color: str | tuple[str, str] | None = None,
**kwargs: Any):
self._enable_macos_dark_title_bar()
@ -48,11 +56,11 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl
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._min_width: int | None = 0
self._min_height: int | None = 0
self._max_width: int | None = 1_000_000
self._max_height: int | None = 1_000_000
self._last_resizable_args: tuple[list[int], dict[str, float]] | None = None # (args, kwargs)
self._fg_color = ThemeManager.theme["CTkToplevel"]["fg_color"] if fg_color is None else self._check_color_type(fg_color)
@ -92,12 +100,12 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl
CTkAppearanceModeBaseClass.destroy(self)
CTkScalingBaseClass.destroy(self)
def _focus_in_event(self, event):
def _focus_in_event(self, event: tkinter.Event[Any] | None = None):
# sometimes window looses jumps back on macOS if window is selected from Mission Control, so has to be lifted again
if sys.platform == "darwin":
self.lift()
def _update_dimensions_event(self, event=None):
def _update_dimensions_event(self, event: tkinter.Event[Any] | None = None):
if not self._block_update_dimensions_event:
detected_width = self.winfo_width() # detect current window size
detected_height = self.winfo_height()
@ -106,7 +114,7 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl
self._current_width = self._reverse_window_scaling(detected_width) # adjust current size according to new size given by event
self._current_height = self._reverse_window_scaling(detected_height) # _current_width and _current_height are independent of the scale
def _set_scaling(self, new_widget_scaling, new_window_scaling):
def _set_scaling(self, new_widget_scaling: float, new_window_scaling: float):
super()._set_scaling(new_widget_scaling, new_window_scaling)
# Force new dimensions on window by using min, max, and geometry. Without min, max it won't work.
@ -130,7 +138,7 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl
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):
def geometry(self, geometry_string: str | None = None):
if geometry_string is not None:
super().geometry(self._apply_geometry_scaling(geometry_string))
@ -152,7 +160,7 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl
self._iconify_called_after_windows_set_titlebar_color = True
super().iconify()
def resizable(self, width: bool = None, height: bool = None):
def resizable(self, width: bool | None = None, height: bool | None = None):
current_resizable_values = super().resizable(width, height)
self._last_resizable_args = ([], {"width": width, "height": height})
@ -161,7 +169,7 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl
return current_resizable_values
def minsize(self, width=None, height=None):
def minsize(self, width: int | None = None, height: int | None = None):
self._min_width = width
self._min_height = height
if self._current_width < width:
@ -170,7 +178,7 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl
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):
def maxsize(self, width: int | None = None, height: int | None = None):
self._max_width = width
self._max_height = height
if self._current_width > width:
@ -179,7 +187,7 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl
self._current_height = height
super().maxsize(self._apply_window_scaling(self._max_width), self._apply_window_scaling(self._max_height))
def configure(self, **kwargs):
def configure(self, **kwargs: Any):
if "fg_color" in kwargs:
self._fg_color = self._check_color_type(kwargs.pop("fg_color"))
super().configure(bg=self._apply_appearance_mode(self._fg_color))
@ -193,15 +201,15 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl
super().configure(**pop_from_dict_by_set(kwargs, self._valid_tk_toplevel_arguments))
check_kwargs_empty(kwargs)
def cget(self, attribute_name: str) -> any:
def cget(self, attribute_name: str) -> Any:
if attribute_name == "fg_color":
return self._fg_color
else:
return super().cget(attribute_name)
def wm_iconbitmap(self, bitmap=None, default=None):
def wm_iconbitmap(self, bitmap: Any = None, default: Any = None):
self._iconbitmap_method_called = True
super().wm_iconbitmap(bitmap, default)
super().wm_iconbitmap(bitmap, default) # type: ignore
def _windows_set_titlebar_icon(self):
try:
@ -298,7 +306,7 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl
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: Literal["light", "dark"]):
super()._set_appearance_mode(mode_string)
if sys.platform.startswith("win"):

View File

@ -7,10 +7,10 @@ from .ctk_label import CTkLabel
from .ctk_optionmenu import CTkOptionMenu
from .ctk_progressbar import CTkProgressBar
from .ctk_radiobutton import CTkRadioButton
from .ctk_scrollable_frame import CTkScrollableFrame
from .ctk_scrollbar import CTkScrollbar
from .ctk_segmented_button import CTkSegmentedButton
from .ctk_slider import CTkSlider
from .ctk_switch import CTkSwitch
from .ctk_tabview import CTkTabview
from .ctk_textbox import CTkTextbox
from .ctk_scrollable_frame import CTkScrollableFrame

View File

@ -1,4 +1,11 @@
from typing import Union, Tuple, List
from __future__ import annotations
import sys
if sys.version_info >= (3, 8):
from typing import Literal
else:
from typing_extensions import Literal
from .appearance_mode_tracker import AppearanceModeTracker
@ -19,7 +26,7 @@ class CTkAppearanceModeBaseClass:
def destroy(self):
AppearanceModeTracker.remove(self._set_appearance_mode)
def _set_appearance_mode(self, mode_string: str):
def _set_appearance_mode(self, mode_string: Literal["light", "dark"]):
""" can be overridden but super method must be called at the beginning """
if mode_string.lower() == "dark":
self.__appearance_mode = 1
@ -33,7 +40,7 @@ class CTkAppearanceModeBaseClass:
else:
return "dark"
def _apply_appearance_mode(self, color: Union[str, Tuple[str, str], List[str]]) -> str:
def _apply_appearance_mode(self, color: str | tuple[str, str] | list[str]) -> str:
"""
color can be either a single hex color string or a color name or it can be a
tuple color with (light_color, dark_color). The functions returns
@ -46,7 +53,7 @@ class CTkAppearanceModeBaseClass:
return color
@staticmethod
def _check_color_type(color: any, transparency: bool = False):
def _check_color_type(color: Literal["transparent"] | str | tuple[str, str] | None, transparency: bool = False):
if color is None:
raise ValueError(f"color is None, for transparency set color='transparent'")
elif isinstance(color, (tuple, list)) and (color[0] == "transparent" or color[1] == "transparent"):

View File

@ -1,10 +1,20 @@
from __future__ import annotations
import sys
import tkinter
from distutils.version import StrictVersion as Version
from typing import Callable
from typing import Callable, TYPE_CHECKING
if sys.version_info >= (3, 8):
from typing import Literal
else:
from typing_extensions import Literal
if TYPE_CHECKING:
from customtkinter.windows.ctk_tk import CTk
try:
import darkdetect
import darkdetect # type: ignore
if Version(darkdetect.__version__) < Version("0.3.1"):
sys.stderr.write("WARNING: You have to upgrade the darkdetect library: pip3 install --upgrade darkdetect\n")
@ -18,13 +28,13 @@ except Exception:
class AppearanceModeTracker:
callback_list = []
app_list = []
callback_list: list[Callable[[str], None]] = []
app_list: list[CTk] = []
update_loop_running = False
update_loop_interval = 30 # milliseconds
appearance_mode_set_by = "system"
appearance_mode = 0 # Light (standard)
appearance_mode_set_by: Literal["system", "user"] = "system"
appearance_mode: Literal[0, 1] = 0 # 0 = Light, 1 = Dark
@classmethod
def init_appearance_mode(cls):
@ -36,7 +46,7 @@ class AppearanceModeTracker:
cls.update_callbacks()
@classmethod
def add(cls, callback: Callable, widget=None):
def add(cls, callback: Callable[[str], None], widget: CTk | None = None):
cls.callback_list.append(callback)
if widget is not None:
@ -49,16 +59,16 @@ class AppearanceModeTracker:
cls.update_loop_running = True
@classmethod
def remove(cls, callback: Callable):
def remove(cls, callback: Callable[[str], None]):
try:
cls.callback_list.remove(callback)
except ValueError:
return
@staticmethod
def detect_appearance_mode() -> int:
def detect_appearance_mode() -> Literal[0, 1]:
try:
if darkdetect.theme() == "Dark":
if darkdetect.theme() == "Dark": # type: ignore
return 1 # Dark
else:
return 0 # Light
@ -66,7 +76,7 @@ class AppearanceModeTracker:
return 0 # Light
@classmethod
def get_tk_root_of_widget(cls, widget):
def get_tk_root_of_widget(cls, widget: CTk) -> CTk:
current_widget = widget
while isinstance(current_widget, tkinter.Tk) is False:

View File

@ -1,6 +1,9 @@
import tkinter
from __future__ import annotations
import sys
from typing import Union, Tuple
import tkinter
from typing import Any
class CTkCanvas(tkinter.Canvas):
@ -25,11 +28,11 @@ class CTkCanvas(tkinter.Canvas):
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
radius_to_char_fine: dict[int, str] = {} # dict to map radius to font circle character
def __init__(self, *args, **kwargs):
def __init__(self, *args: Any, **kwargs: Any):
super().__init__(*args, **kwargs)
self._aa_circle_canvas_ids = set()
self._aa_circle_canvas_ids: set[str | int] = set()
@classmethod
def init_font_character_mapping(cls):
@ -71,7 +74,7 @@ class CTkCanvas(tkinter.Canvas):
return self.radius_to_char_fine[radius]
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:
tags: 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,
font=("CustomTkinter_shapes_font", -radius * 2), tags=tags, angle=angle)
@ -80,8 +83,7 @@ class CTkCanvas(tkinter.Canvas):
return circle_1
def coords(self, tag_or_id, *args):
def coords(self, tag_or_id: int | str, *args: Any):
if type(tag_or_id) == str and "ctk_aa_circle_font_element" in self.gettags(tag_or_id):
coords_id = self.find_withtag(tag_or_id)[0] # take the lowest id for the given tag
super().coords(coords_id, *args[:2])
@ -98,7 +100,7 @@ class CTkCanvas(tkinter.Canvas):
else:
super().coords(tag_or_id, *args)
def itemconfig(self, tag_or_id, *args, **kwargs):
def itemconfig(self, tag_or_id: int | str, *args: Any, **kwargs: Any):
kwargs_except_outline = kwargs.copy()
if "outline" in kwargs_except_outline:
del kwargs_except_outline["outline"]

View File

@ -1,8 +1,14 @@
from __future__ import annotations
import sys
import math
import sys
import tkinter
from typing import Union, TYPE_CHECKING
from typing import TYPE_CHECKING
if sys.version_info >= (3, 8):
from typing import Literal
else:
from typing_extensions import Literal
if TYPE_CHECKING:
from ..core_rendering import CTkCanvas
@ -26,7 +32,7 @@ class DrawEngine:
"""
preferred_drawing_method: str = None # 'polygon_shapes', 'font_shapes', 'circle_shapes'
preferred_drawing_method: Literal["polygon_shapes", "font_shapes", "circle_shapes"] = None
def __init__(self, canvas: CTkCanvas):
self._canvas = canvas
@ -37,7 +43,7 @@ class DrawEngine:
self._round_width_to_even_numbers: bool = round_width_to_even_numbers
self._round_height_to_even_numbers: bool = round_height_to_even_numbers
def __calc_optimal_corner_radius(self, user_corner_radius: Union[float, int]) -> Union[float, int]:
def __calc_optimal_corner_radius(self, user_corner_radius: float | int) -> float | int:
# optimize for drawing with polygon shapes
if self.preferred_drawing_method == "polygon_shapes":
if sys.platform == "darwin":
@ -61,7 +67,7 @@ class DrawEngine:
else:
return user_corner_radius
def draw_background_corners(self, width: Union[float, int], height: Union[float, int], ):
def draw_background_corners(self, width: float | int, height: float | int):
if self._round_width_to_even_numbers:
width = math.floor(width / 2) * 2 # round (floor) _current_width and _current_height and restrict them to even values only
if self._round_height_to_even_numbers:
@ -93,8 +99,8 @@ class DrawEngine:
return requires_recoloring
def draw_rounded_rect_with_border(self, width: Union[float, int], height: Union[float, int], corner_radius: Union[float, int],
border_width: Union[float, int], overwrite_preferred_drawing_method: str = None) -> bool:
def draw_rounded_rect_with_border(self, width: float | int, height: float | int, corner_radius: float | int,
border_width: float | int, overwrite_preferred_drawing_method: str | None = None) -> bool:
""" Draws a rounded rectangle with a corner_radius and border_width on the canvas. The border elements have a 'border_parts' tag,
the main foreground elements have an 'inner_parts' tag to color the elements accordingly.
@ -184,7 +190,7 @@ class DrawEngine:
return requires_recoloring
def __draw_rounded_rect_with_border_font_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int,
exclude_parts: tuple) -> bool:
exclude_parts: tuple[str, ...]) -> bool:
requires_recoloring = False
# create border button parts
@ -396,8 +402,8 @@ class DrawEngine:
return requires_recoloring
def draw_rounded_rect_with_border_vertical_split(self, width: Union[float, int], height: Union[float, int], corner_radius: Union[float, int],
border_width: Union[float, int], left_section_width: Union[float, int]) -> bool:
def draw_rounded_rect_with_border_vertical_split(self, width: float | int, height: float | int, corner_radius: float | int,
border_width: float | int, left_section_width: float | int) -> bool:
""" Draws a rounded rectangle with a corner_radius and border_width on the canvas which is split at left_section_width.
The border elements have the tags 'border_parts_left', 'border_parts_lright',
the main foreground elements have an 'inner_parts_left' and inner_parts_right' tag,
@ -690,8 +696,8 @@ class DrawEngine:
return requires_recoloring
def draw_rounded_progress_bar_with_border(self, width: Union[float, int], height: Union[float, int], corner_radius: Union[float, int],
border_width: Union[float, int], progress_value_1: float, progress_value_2: float, orientation: str) -> bool:
def draw_rounded_progress_bar_with_border(self, width: float | int, height: float | int, corner_radius: float | int,
border_width: float | int, progress_value_1: float, progress_value_2: float, orientation: str) -> bool:
""" Draws a rounded bar on the canvas, and onntop sits a progress bar from value 1 to value 2 (range 0-1, left to right, bottom to top).
The border elements get the 'border_parts' tag", the main elements get the 'inner_parts' tag and
the progress elements get the 'progress_parts' tag. The 'orientation' argument defines from which direction the progress starts (n, w, s, e).
@ -868,8 +874,8 @@ class DrawEngine:
return requires_recoloring or requires_recoloring_2
def draw_rounded_slider_with_border_and_button(self, width: Union[float, int], height: Union[float, int], corner_radius: Union[float, int],
border_width: Union[float, int], button_length: Union[float, int], button_corner_radius: Union[float, int],
def draw_rounded_slider_with_border_and_button(self, width: float | int, height: float | int, corner_radius: float | int,
border_width: float | int, button_length: float | int, button_corner_radius: float | int,
slider_value: float, orientation: str) -> bool:
if self._round_width_to_even_numbers:
@ -1028,8 +1034,8 @@ class DrawEngine:
return requires_recoloring
def draw_rounded_scrollbar(self, width: Union[float, int], height: Union[float, int], corner_radius: Union[float, int],
border_spacing: Union[float, int], start_value: float, end_value: float, orientation: str) -> bool:
def draw_rounded_scrollbar(self, width: float | int, height: float | int, corner_radius: float | int,
border_spacing: float | int, start_value: float, end_value: float, orientation: str) -> bool:
if self._round_width_to_even_numbers:
width = math.floor(width / 2) * 2 # round _current_width and _current_height and restrict them to even values only
@ -1171,7 +1177,7 @@ class DrawEngine:
return requires_recoloring
def draw_checkmark(self, width: Union[float, int], height: Union[float, int], size: Union[int, float]) -> bool:
def draw_checkmark(self, width: float | int, height: float | int, size: int | float) -> bool:
""" Draws a rounded rectangle with a corner_radius and border_width on the canvas. The border elements have a 'border_parts' tag,
the main foreground elements have an 'inner_parts' tag to color the elements accordingly.
@ -1201,7 +1207,7 @@ class DrawEngine:
return requires_recoloring
def draw_dropdown_arrow(self, x_position: Union[int, float], y_position: Union[int, float], size: Union[int, float]) -> bool:
def draw_dropdown_arrow(self, x_position: int | float, y_position: int | float, size: int | float) -> bool:
""" Draws a dropdown bottom facing arrow at (x_position, y_position) in a given size
returns bool if recoloring is necessary """

View File

@ -1,2 +1,2 @@
from .dropdown_menu import DropdownMenu
from .ctk_base_class import CTkBaseClass
from .dropdown_menu import DropdownMenu

View File

@ -1,8 +1,9 @@
import sys
import warnings
from __future__ import annotations
import tkinter
import tkinter.ttk as ttk
from typing import Union, Callable, Tuple
import warnings
from typing import Any, Callable
try:
from typing import TypedDict
@ -10,14 +11,12 @@ except ImportError:
from typing_extensions import TypedDict
from .... import windows # import windows for isinstance checks
from ..theme import ThemeManager
from ..appearance_mode import CTkAppearanceModeBaseClass
from ..font import CTkFont
from ..image import CTkImage
from ..appearance_mode import CTkAppearanceModeBaseClass
from ..scaling import CTkScalingBaseClass
from ..utility import pop_from_dict_by_set, check_kwargs_empty
from ..theme import ThemeManager
from ..utility import check_kwargs_empty, pop_from_dict_by_set
class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
@ -25,17 +24,17 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas
appearance_mode changes, scaling, bg changes of master if master is not a CTk widget """
# attributes that are passed to and managed by the tkinter frame only:
_valid_tk_frame_attributes: set = {"cursor"}
_valid_tk_frame_attributes: set[str] = {"cursor"}
_cursor_manipulation_enabled: bool = True
def __init__(self,
master: any,
width: int = 0,
height: int = 0,
master: Any,
width: int | str = 0,
height: int | str = 0,
bg_color: Union[str, Tuple[str, str]] = "transparent",
**kwargs):
bg_color: str | tuple[str, str] = "transparent",
**kwargs: Any):
# call init methods of super classes
tkinter.Frame.__init__(self, master=master, width=width, height=height, **pop_from_dict_by_set(kwargs, self._valid_tk_frame_attributes))
@ -57,12 +56,12 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas
# save latest geometry function and kwargs
class GeometryCallDict(TypedDict):
function: Callable
kwargs: dict
self._last_geometry_manager_call: Union[GeometryCallDict, None] = None
function: Callable[..., None]
kwargs: dict[Any, Any]
self._last_geometry_manager_call: GeometryCallDict | None = None
# background color
self._bg_color: Union[str, Tuple[str, str]] = self._detect_color_of_master() if bg_color == "transparent" else self._check_color_type(bg_color, transparency=True)
self._bg_color: str | tuple[str, str] = self._detect_color_of_master() if bg_color == "transparent" else self._check_color_type(bg_color, transparency=True)
# set bg color of tkinter.Frame
super().configure(bg=self._apply_appearance_mode(self._bg_color))
@ -74,7 +73,7 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas
if isinstance(self.master, (tkinter.Tk, tkinter.Toplevel, tkinter.Frame, tkinter.LabelFrame, ttk.Frame, ttk.LabelFrame, ttk.Notebook)) and not isinstance(self.master, (CTkBaseClass, CTkAppearanceModeBaseClass)):
master_old_configure = self.master.config
def new_configure(*args, **kwargs):
def new_configure(*args, **kwargs: Any):
if "bg" in kwargs:
self.configure(bg_color=kwargs["bg"])
elif "background" in kwargs:
@ -107,10 +106,10 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas
# super().configure(bg=self._apply_appearance_mode(self._bg_color))
pass
def config(self, *args, **kwargs):
def config(self, *args: Any, **kwargs: Any):
raise AttributeError("'config' is not implemented for CTk widgets. For consistency, always use 'configure' instead.")
def configure(self, require_redraw=False, **kwargs):
def configure(self, require_redraw: bool =False, **kwargs: Any):
""" basic configure with bg_color, width, height support, calls configure of tkinter.Frame, updates in the end """
if "width" in kwargs:
@ -150,7 +149,7 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas
else:
raise ValueError(f"'{attribute_name}' is not a supported argument. Look at the documentation for supported arguments.")
def _check_font_type(self, font: any):
def _check_font_type(self, font: CTkFont | tuple[Any, ...]):
""" check font type when passed to widget """
if isinstance(font, CTkFont):
return font
@ -169,7 +168,7 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas
f"font=customtkinter.CTkFont(family='<name>', size=<size in px>)\n" +
f"font=('<name>', <size in px>)\n")
def _check_image_type(self, image: any):
def _check_image_type(self, image: Any):
""" check image type when passed to widget """
if image is None:
return image
@ -179,7 +178,7 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas
warnings.warn(f"{type(self).__name__} Warning: Given image is not CTkImage but {type(image)}. Image can not be scaled on HighDPI displays, use CTkImage instead.\n")
return image
def _update_dimensions_event(self, event):
def _update_dimensions_event(self, event: tkinter.Event[Any] | None = None):
# only redraw if dimensions changed (for performance), independent of scaling
if round(self._current_width) != round(self._reverse_widget_scaling(event.width)) or round(self._current_height) != round(self._reverse_widget_scaling(event.height)):
self._current_width = self._reverse_widget_scaling(event.width) # adjust current size according to new size given by event
@ -187,7 +186,7 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas
self._draw(no_color_updates=True) # faster drawing without color changes
def _detect_color_of_master(self, master_widget=None) -> Union[str, Tuple[str, str]]:
def _detect_color_of_master(self, master_widget=None) -> str | tuple[str, str]:
""" detect foreground color of master widget for bg_color and transparent color """
if master_widget is None:
@ -222,7 +221,7 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas
self._draw()
super().update_idletasks()
def _set_scaling(self, new_widget_scaling, new_window_scaling):
def _set_scaling(self, new_widget_scaling: float, new_window_scaling: float):
super()._set_scaling(new_widget_scaling, new_window_scaling)
super().configure(width=self._apply_widget_scaling(self._desired_width),
@ -231,7 +230,7 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas
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"]))
def _set_dimensions(self, width=None, height=None):
def _set_dimensions(self, width: int | None = None, height: int | None = None):
if width is not None:
self._desired_width = width
if height is not None:
@ -240,19 +239,19 @@ 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):
def bind(self, sequence: str, command: Callable[..., None], add: bool = False):
raise NotImplementedError
def unbind(self, sequence=None, funcid=None):
def unbind(self, sequence: str, funcid: str | None = None):
raise NotImplementedError
def unbind_all(self, sequence):
def unbind_all(self, sequence: str):
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):
def bind_all(self, sequence: str, func: Callable[..., None], add: bool = False):
raise AttributeError("'bind_all' is not allowed, could result in undefined behavior")
def place(self, **kwargs):
def place(self, **kwargs: Any):
"""
Place a widget in the parent widget. Use as options:
in=master - master relative to which the widget is placed
@ -278,7 +277,7 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas
self._last_geometry_manager_call = None
return super().place_forget()
def pack(self, **kwargs):
def pack(self, **kwargs: Any):
"""
Pack a widget in the parent widget. Use as options:
after=widget - pack it after you have packed widget
@ -302,7 +301,7 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas
self._last_geometry_manager_call = None
return super().pack_forget()
def grid(self, **kwargs):
def grid(self, **kwargs: Any):
"""
Position a widget in the parent widget in a grid. Use as options:
column=number - use cell identified with given column (starting with 0)

View File

@ -1,25 +1,27 @@
import tkinter
import sys
from typing import Union, Tuple, Callable, List, Optional
from __future__ import annotations
import sys
import tkinter
from typing import Any, Callable
from ..theme import ThemeManager
from ..font import CTkFont
from ..appearance_mode import CTkAppearanceModeBaseClass
from ..font import CTkFont
from ..scaling import CTkScalingBaseClass
from ..theme import ThemeManager
class DropdownMenu(tkinter.Menu, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
def __init__(self, *args,
def __init__(self, *args: Any,
min_character_width: int = 18,
fg_color: Optional[Union[str, Tuple[str, str]]] = None,
hover_color: Optional[Union[str, Tuple[str, str]]] = None,
text_color: Optional[Union[str, Tuple[str, str]]] = None,
fg_color: str | tuple[str, str] | None = None,
hover_color: str | tuple[str, str] | None = None,
text_color: str | tuple[str, str] | None = None,
font: Optional[Union[tuple, CTkFont]] = None,
command: Union[Callable, None] = None,
values: Optional[List[str]] = None,
**kwargs):
font: tuple[Any, ...] | CTkFont | None = None,
command: Callable[..., None] | None = None,
values: list[str] | None = None,
**kwargs: Any):
# call init methods of super classes
tkinter.Menu.__init__(self, *args, **kwargs)
@ -105,7 +107,7 @@ class DropdownMenu(tkinter.Menu, CTkAppearanceModeBaseClass, CTkScalingBaseClass
if self._command is not None:
self._command(value)
def open(self, x: Union[int, float], y: Union[int, float]):
def open(self, x: int | float, y: int | float):
if sys.platform == "darwin":
y += self._apply_widget_scaling(8)
@ -117,7 +119,7 @@ class DropdownMenu(tkinter.Menu, CTkAppearanceModeBaseClass, CTkScalingBaseClass
else: # Linux
self.tk_popup(int(x), int(y))
def configure(self, **kwargs):
def configure(self, **kwargs: Any):
if "fg_color" in kwargs:
self._fg_color = self._check_color_type(kwargs.pop("fg_color"))
super().configure(bg=self._apply_appearance_mode(self._fg_color))
@ -148,7 +150,7 @@ class DropdownMenu(tkinter.Menu, CTkAppearanceModeBaseClass, CTkScalingBaseClass
super().configure(**kwargs)
def cget(self, attribute_name: str) -> any:
def cget(self, attribute_name: str) -> Any:
if attribute_name == "min_character_width":
return self._min_character_width
@ -170,7 +172,7 @@ class DropdownMenu(tkinter.Menu, CTkAppearanceModeBaseClass, CTkScalingBaseClass
return super().cget(attribute_name)
@staticmethod
def _check_font_type(font: any):
def _check_font_type(font: Any):
if isinstance(font, CTkFont):
return font
@ -188,7 +190,7 @@ class DropdownMenu(tkinter.Menu, CTkAppearanceModeBaseClass, CTkScalingBaseClass
f"font=customtkinter.CTkFont(family='<name>', size=<size in px>)\n" +
f"font=('<name>', <size in px>)\n")
def _set_scaling(self, new_widget_scaling, new_window_scaling):
def _set_scaling(self, new_widget_scaling: float, new_window_scaling: float):
super()._set_scaling(new_widget_scaling, new_window_scaling)
self._configure_menu_for_platforms()

View File

@ -1,14 +1,22 @@
import tkinter
import sys
from typing import Union, Tuple, Callable, Optional
from __future__ import annotations
from .core_rendering import CTkCanvas
from .theme import ThemeManager
from .core_rendering import DrawEngine
import sys
import tkinter
from typing import Any, Callable, TYPE_CHECKING
if sys.version_info >= (3, 8):
from typing import Literal
else:
from typing_extensions import Literal
from .core_rendering import CTkCanvas, DrawEngine
from .core_widget_classes import CTkBaseClass
from .font import CTkFont
from .image import CTkImage
from .theme import ThemeManager
if TYPE_CHECKING:
from PIL import ImageTk
class CTkButton(CTkBaseClass):
"""
@ -19,34 +27,34 @@ class CTkButton(CTkBaseClass):
_image_label_spacing: int = 6
def __init__(self,
master: any,
master: CTkBaseClass,
width: int = 140,
height: int = 28,
corner_radius: Optional[int] = None,
border_width: Optional[int] = None,
corner_radius: int | None = None,
border_width: int | None = None,
border_spacing: int = 2,
bg_color: Union[str, Tuple[str, str]] = "transparent",
fg_color: Optional[Union[str, Tuple[str, str]]] = None,
hover_color: Optional[Union[str, Tuple[str, str]]] = None,
border_color: Optional[Union[str, Tuple[str, str]]] = None,
text_color: Optional[Union[str, Tuple[str, str]]] = None,
text_color_disabled: Optional[Union[str, Tuple[str, str]]] = None,
bg_color: str | tuple[str, str] = "transparent",
fg_color: str | tuple[str, str] | None = None,
hover_color: str | tuple[str, str] | None = None,
border_color: str | tuple[str, str] | None = None,
text_color: str | tuple[str, str] | None = None,
text_color_disabled: str | tuple[str, str] | None = None,
background_corner_colors: Union[Tuple[Union[str, Tuple[str, str]]], None] = None,
background_corner_colors: tuple[str | tuple[str, str]] | None = None,
round_width_to_even_numbers: bool = True,
round_height_to_even_numbers: bool = True,
text: str = "CTkButton",
font: Optional[Union[tuple, CTkFont]] = None,
textvariable: Union[tkinter.Variable, None] = None,
image: Union[CTkImage, "ImageTk.PhotoImage", None] = None,
font: tuple[Any, ...] | CTkFont | None = None,
textvariable: tkinter.Variable | None = None,
image: CTkImage | ImageTk.PhotoImage | None = None,
state: str = "normal",
hover: bool = True,
command: Union[Callable[[], None], None] = None,
command: Callable[[], None] | None = None,
compound: str = "left",
anchor: str = "center",
**kwargs):
**kwargs: Any):
# transfer basic functionality (bg_color, size, appearance_mode, scaling) to CTkBaseClass
super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
@ -58,38 +66,38 @@ class CTkButton(CTkBaseClass):
self._border_spacing: int = border_spacing
# color
self._fg_color: Union[str, Tuple[str, str]] = ThemeManager.theme["CTkButton"]["fg_color"] if fg_color is None else self._check_color_type(fg_color, transparency=True)
self._hover_color: Union[str, Tuple[str, str]] = ThemeManager.theme["CTkButton"]["hover_color"] if hover_color is None else self._check_color_type(hover_color)
self._border_color: Union[str, Tuple[str, str]] = ThemeManager.theme["CTkButton"]["border_color"] if border_color is None else self._check_color_type(border_color)
self._text_color: Union[str, Tuple[str, str]] = ThemeManager.theme["CTkButton"]["text_color"] if text_color is None else self._check_color_type(text_color)
self._text_color_disabled: Union[str, Tuple[str, str]] = ThemeManager.theme["CTkButton"]["text_color_disabled"] if text_color_disabled is None else self._check_color_type(text_color_disabled)
self._fg_color: str | tuple[str, str] = ThemeManager.theme["CTkButton"]["fg_color"] if fg_color is None else self._check_color_type(fg_color, transparency=True)
self._hover_color: str | tuple[str, str] = ThemeManager.theme["CTkButton"]["hover_color"] if hover_color is None else self._check_color_type(hover_color)
self._border_color: str | tuple[str, str] = ThemeManager.theme["CTkButton"]["border_color"] if border_color is None else self._check_color_type(border_color)
self._text_color: str | tuple[str, str] = ThemeManager.theme["CTkButton"]["text_color"] if text_color is None else self._check_color_type(text_color)
self._text_color_disabled: str | tuple[str, str] = ThemeManager.theme["CTkButton"]["text_color_disabled"] if text_color_disabled is None else self._check_color_type(text_color_disabled)
# rendering options
self._background_corner_colors: Union[Tuple[Union[str, Tuple[str, str]]], None] = background_corner_colors # rendering options for DrawEngine
self._background_corner_colors: tuple[str | tuple[str, str]] | None = background_corner_colors # rendering options for DrawEngine
self._round_width_to_even_numbers: bool = round_width_to_even_numbers # rendering options for DrawEngine
self._round_height_to_even_numbers: bool = round_height_to_even_numbers # rendering options for DrawEngine
# text, font
self._text = text
self._text_label: Union[tkinter.Label, None] = None
self._textvariable: tkinter.Variable = textvariable
self._font: Union[tuple, CTkFont] = CTkFont() if font is None else self._check_font_type(font)
self._text_label: tkinter.Label | None = None
self._textvariable = textvariable
self._font = CTkFont() if font is None else self._check_font_type(font)
if isinstance(self._font, CTkFont):
self._font.add_size_configure_callback(self._update_font)
# image
self._image = self._check_image_type(image)
self._image_label: Union[tkinter.Label, None] = None
self._image_label: tkinter.Label | None = None
if isinstance(self._image, CTkImage):
self._image.add_configure_callback(self._update_image)
# other
self._state: str = state
self._hover: bool = hover
self._command: Callable = command
self._compound: str = compound
self._anchor: str = anchor
self._click_animation_running: bool = False
self._state = state
self._hover = hover
self._command = command
self._compound = compound
self._anchor = anchor
self._click_animation_running = False
# canvas and draw engine
self._canvas = CTkCanvas(master=self,
@ -105,7 +113,7 @@ class CTkButton(CTkBaseClass):
self._set_cursor()
self._draw()
def _create_bindings(self, sequence: Optional[str] = None):
def _create_bindings(self, sequence: str | None = None):
""" set necessary bindings for functionality of widget, will overwrite other bindings """
if sequence is None or sequence == "<Enter>":
@ -132,7 +140,7 @@ class CTkButton(CTkBaseClass):
if self._image_label is not None:
self._image_label.bind("<Button-1>", self._clicked)
def _set_scaling(self, *args, **kwargs):
def _set_scaling(self, *args: Any, **kwargs: Any):
super()._set_scaling(*args, **kwargs)
self._create_grid()
@ -146,11 +154,11 @@ class CTkButton(CTkBaseClass):
height=self._apply_widget_scaling(self._desired_height))
self._draw(no_color_updates=True)
def _set_appearance_mode(self, mode_string):
def _set_appearance_mode(self, mode_string: Literal["light", "dark"]):
super()._set_appearance_mode(mode_string)
self._update_image()
def _set_dimensions(self, width: int = None, height: int = None):
def _set_dimensions(self, width: int | None = None, height: int | None = None):
super()._set_dimensions(width, height)
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
@ -180,7 +188,7 @@ class CTkButton(CTkBaseClass):
self._font.remove_size_configure_callback(self._update_font)
super().destroy()
def _draw(self, no_color_updates=False):
def _draw(self, no_color_updates: bool = False):
super()._draw(no_color_updates)
if self._background_corner_colors is not None:
@ -349,7 +357,7 @@ class CTkButton(CTkBaseClass):
if self._text_label is not None:
self._text_label.grid(row=1, column=2, sticky="s")
def configure(self, require_redraw=False, **kwargs):
def configure(self, require_redraw: bool = False, **kwargs: Any):
if "corner_radius" in kwargs:
self._corner_radius = kwargs.pop("corner_radius")
self._create_grid()
@ -440,7 +448,7 @@ class CTkButton(CTkBaseClass):
super().configure(require_redraw=require_redraw, **kwargs)
def cget(self, attribute_name: str) -> any:
def cget(self, attribute_name: str) -> Any:
if attribute_name == "corner_radius":
return self._corner_radius
elif attribute_name == "border_width":
@ -496,7 +504,7 @@ class CTkButton(CTkBaseClass):
elif sys.platform.startswith("win") and self._command is not None:
self.configure(cursor="hand2")
def _on_enter(self, event=None):
def _on_enter(self, event: tkinter.Event[Any] | None = None):
if self._hover is True and self._state == "normal":
if self._hover_color is None:
inner_parts_color = self._fg_color
@ -516,7 +524,7 @@ class CTkButton(CTkBaseClass):
if self._image_label is not None:
self._image_label.configure(bg=self._apply_appearance_mode(inner_parts_color))
def _on_leave(self, event=None):
def _on_leave(self, event: tkinter.Event[Any] | None = None):
self._click_animation_running = False
if self._fg_color == "transparent":
@ -541,7 +549,7 @@ class CTkButton(CTkBaseClass):
if self._click_animation_running:
self._on_enter()
def _clicked(self, event=None):
def _clicked(self, event: tkinter.Event[Any] | None = None):
if self._state != tkinter.DISABLED:
# click animation: change color with .on_leave() and back to normal after 100ms with click_animation()
@ -558,7 +566,7 @@ class CTkButton(CTkBaseClass):
if self._command is not None:
return self._command()
def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True):
def bind(self, sequence: str, command: Callable[..., None] | None = None, add: 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")
@ -569,7 +577,7 @@ class CTkButton(CTkBaseClass):
if self._image_label is not None:
self._image_label.bind(sequence, command, add=True)
def unbind(self, sequence: str = None, funcid: str = None):
def unbind(self, sequence: str, funcid: str | None = 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" +

View File

@ -1,12 +1,18 @@
import tkinter
import sys
from typing import Union, Tuple, Callable, Optional
from __future__ import annotations
from .core_rendering import CTkCanvas
from .theme import ThemeManager
from .core_rendering import DrawEngine
import sys
import tkinter
from typing import Any, Callable
if sys.version_info >= (3, 8):
from typing import Literal
else:
from typing_extensions import Literal
from .core_rendering import CTkCanvas, DrawEngine
from .core_widget_classes import CTkBaseClass
from .font import CTkFont
from .theme import ThemeManager
class CTkCheckBox(CTkBaseClass):
@ -16,32 +22,32 @@ class CTkCheckBox(CTkBaseClass):
"""
def __init__(self,
master: any,
master: CTkBaseClass,
width: int = 100,
height: int = 24,
checkbox_width: int = 24,
checkbox_height: int = 24,
corner_radius: Optional[int] = None,
border_width: Optional[int] = None,
corner_radius: int | None = None,
border_width: int | None = None,
bg_color: Union[str, Tuple[str, str]] = "transparent",
fg_color: Optional[Union[str, Tuple[str, str]]] = None,
hover_color: Optional[Union[str, Tuple[str, str]]] = None,
border_color: Optional[Union[str, Tuple[str, str]]] = None,
checkmark_color: Optional[Union[str, Tuple[str, str]]] = None,
text_color: Optional[Union[str, Tuple[str, str]]] = None,
text_color_disabled: Optional[Union[str, Tuple[str, str]]] = None,
bg_color: str | tuple[str, str] = "transparent",
fg_color: str | tuple[str, str] | None = None,
hover_color: str | tuple[str, str] | None = None,
border_color: str | tuple[str, str] | None = None,
checkmark_color: str | tuple[str, str] | None = None,
text_color: str | tuple[str, str] | None = None,
text_color_disabled: str | tuple[str, str] | None = None,
text: str = "CTkCheckBox",
font: Optional[Union[tuple, CTkFont]] = None,
textvariable: Union[tkinter.Variable, None] = None,
state: str = tkinter.NORMAL,
font: tuple[Any, ...] | CTkFont | None = None,
textvariable: tkinter.Variable | None = None,
state: Literal["normal", "disabled", "readonly"] = "normal",
hover: bool = True,
command: Union[Callable[[], None], None] = None,
onvalue: Union[int, str] = 1,
offvalue: Union[int, str] = 0,
variable: Union[tkinter.Variable, None] = None,
**kwargs):
command: Callable[[], None] | None = None,
onvalue: int | str = 1,
offvalue: int | str = 0,
variable: tkinter.Variable | None = None,
**kwargs: Any):
# transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass
super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
@ -62,7 +68,6 @@ class CTkCheckBox(CTkBaseClass):
# text
self._text = text
self._text_label: Union[tkinter.Label, None] = None
self._text_color = ThemeManager.theme["CTkCheckbox"]["text_color"] if text_color is None else self._check_color_type(text_color)
self._text_color_disabled = ThemeManager.theme["CTkCheckbox"]["text_color_disabled"] if text_color_disabled is None else self._check_color_type(text_color_disabled)
@ -79,9 +84,9 @@ class CTkCheckBox(CTkBaseClass):
self._onvalue = onvalue
self._offvalue = offvalue
self._variable: tkinter.Variable = variable
self._variable = variable
self._variable_callback_blocked = False
self._textvariable: tkinter.Variable = textvariable
self._textvariable = textvariable
self._variable_callback_name = None
# configure grid system (1x3)
@ -123,7 +128,7 @@ class CTkCheckBox(CTkBaseClass):
self._set_cursor()
self._draw()
def _create_bindings(self, sequence: Optional[str] = None):
def _create_bindings(self, sequence: str | None = 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)
@ -135,7 +140,7 @@ class CTkCheckBox(CTkBaseClass):
self._canvas.bind("<Button-1>", self.toggle)
self._text_label.bind("<Button-1>", self.toggle)
def _set_scaling(self, *args, **kwargs):
def _set_scaling(self, *args: Any, **kwargs: Any):
super()._set_scaling(*args, **kwargs)
self.grid_columnconfigure(1, weight=0, minsize=self._apply_widget_scaling(6))
@ -148,7 +153,7 @@ class CTkCheckBox(CTkBaseClass):
height=self._apply_widget_scaling(self._checkbox_height))
self._draw(no_color_updates=True)
def _set_dimensions(self, width: int = None, height: int = None):
def _set_dimensions(self, width: int | None = None, height: int | None = None):
super()._set_dimensions(width, height)
self._bg_canvas.configure(width=self._apply_widget_scaling(self._desired_width),
@ -173,7 +178,7 @@ class CTkCheckBox(CTkBaseClass):
super().destroy()
def _draw(self, no_color_updates=False):
def _draw(self, no_color_updates: bool = False):
super()._draw(no_color_updates)
requires_recoloring_1 = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._checkbox_width),
@ -220,22 +225,22 @@ class CTkCheckBox(CTkBaseClass):
self._text_label.configure(bg=self._apply_appearance_mode(self._bg_color))
def configure(self, require_redraw=False, **kwargs):
def configure(self, require_redraw: bool = False, **kwargs: Any):
if "corner_radius" in kwargs:
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: int = kwargs.pop("border_width")
require_redraw = True
if "checkbox_width" in kwargs:
self._checkbox_width = kwargs.pop("checkbox_width")
self._checkbox_width: int = kwargs.pop("checkbox_width")
self._canvas.configure(width=self._apply_widget_scaling(self._checkbox_width))
require_redraw = True
if "checkbox_height" in kwargs:
self._checkbox_height = kwargs.pop("checkbox_height")
self._checkbox_height: int = kwargs.pop("checkbox_height")
self._canvas.configure(height=self._apply_widget_scaling(self._checkbox_height))
require_redraw = True
@ -296,7 +301,7 @@ class CTkCheckBox(CTkBaseClass):
super().configure(require_redraw=require_redraw, **kwargs)
def cget(self, attribute_name: str) -> any:
def cget(self, attribute_name: str) -> Any:
if attribute_name == "corner_radius":
return self._corner_radius
elif attribute_name == "border_width":
@ -360,7 +365,7 @@ class CTkCheckBox(CTkBaseClass):
if self._text_label is not None:
self._text_label.configure(cursor="hand2")
def _on_enter(self, event=0):
def _on_enter(self, event: tkinter.Event[Any] | None = None):
if self._hover is True and self._state == tkinter.NORMAL:
if self._check_state is True:
self._canvas.itemconfig("inner_parts",
@ -374,7 +379,7 @@ class CTkCheckBox(CTkBaseClass):
fill=self._apply_appearance_mode(self._hover_color),
outline=self._apply_appearance_mode(self._hover_color))
def _on_leave(self, event=0):
def _on_leave(self, event: tkinter.Event[Any] | None = None):
if self._check_state is True:
self._canvas.itemconfig("inner_parts",
fill=self._apply_appearance_mode(self._fg_color),
@ -390,14 +395,14 @@ class CTkCheckBox(CTkBaseClass):
fill=self._apply_appearance_mode(self._border_color),
outline=self._apply_appearance_mode(self._border_color))
def _variable_callback(self, var_name, index, mode):
def _variable_callback(self, var_name: str, index: int, mode: Any):
if not self._variable_callback_blocked:
if self._variable.get() == self._onvalue:
self.select(from_variable_callback=True)
elif self._variable.get() == self._offvalue:
self.deselect(from_variable_callback=True)
def toggle(self, event=0):
def toggle(self, event: tkinter.Event[Any] | None = None):
if self._state == tkinter.NORMAL:
if self._check_state is True:
self._check_state = False
@ -414,7 +419,7 @@ class CTkCheckBox(CTkBaseClass):
if self._command is not None:
self._command()
def select(self, from_variable_callback=False):
def select(self, from_variable_callback: bool =False):
self._check_state = True
self._draw()
@ -423,7 +428,7 @@ class CTkCheckBox(CTkBaseClass):
self._variable.set(self._onvalue)
self._variable_callback_blocked = False
def deselect(self, from_variable_callback=False):
def deselect(self, from_variable_callback: bool = False):
self._check_state = False
self._draw()
@ -432,17 +437,17 @@ class CTkCheckBox(CTkBaseClass):
self._variable.set(self._offvalue)
self._variable_callback_blocked = False
def get(self) -> Union[int, str]:
def get(self) -> int | str:
return self._onvalue if self._check_state is True else self._offvalue
def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True):
def bind(self, sequence: str, command: Callable[..., None] | None = None, add: 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 = None, funcid: str = None):
def unbind(self, sequence: str, funcid: str | None = 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" +

View File

@ -1,14 +1,19 @@
import tkinter
import sys
import copy
from typing import Union, Tuple, Callable, List, Optional
from __future__ import annotations
from .core_widget_classes import DropdownMenu
from .core_rendering import CTkCanvas
from .theme import ThemeManager
from .core_rendering import DrawEngine
from .core_widget_classes import CTkBaseClass
import copy
import sys
import tkinter
from typing import Any, Callable
if sys.version_info >= (3, 8):
from typing import Literal
else:
from typing_extensions import Literal
from .core_rendering import CTkCanvas, DrawEngine
from .core_widget_classes import CTkBaseClass, DropdownMenu
from .font import CTkFont
from .theme import ThemeManager
class CTkComboBox(CTkBaseClass):
@ -18,32 +23,32 @@ class CTkComboBox(CTkBaseClass):
"""
def __init__(self,
master: any,
master: CTkBaseClass,
width: int = 140,
height: int = 28,
corner_radius: Optional[int] = None,
border_width: Optional[int] = None,
corner_radius: int | None = None,
border_width: int | None = None,
bg_color: Union[str, Tuple[str, str]] = "transparent",
fg_color: Optional[Union[str, Tuple[str, str]]] = None,
border_color: Optional[Union[str, Tuple[str, str]]] = None,
button_color: Optional[Union[str, Tuple[str, str]]] = None,
button_hover_color: Optional[Union[str, Tuple[str, str]]] = None,
dropdown_fg_color: Optional[Union[str, Tuple[str, str]]] = None,
dropdown_hover_color: Optional[Union[str, Tuple[str, str]]] = None,
dropdown_text_color: Optional[Union[str, Tuple[str, str]]] = None,
text_color: Optional[Union[str, Tuple[str, str]]] = None,
text_color_disabled: Optional[Union[str, Tuple[str, str]]] = None,
bg_color: str | tuple[str, str] = "transparent",
fg_color: str | tuple[str, str] | None = None,
border_color: str | tuple[str, str] | None = None,
button_color: str | tuple[str, str] | None = None,
button_hover_color: str | tuple[str, str] | None = None,
dropdown_fg_color: str | tuple[str, str] | None = None,
dropdown_hover_color: str | tuple[str, str] | None = None,
dropdown_text_color: str | tuple[str, str] | None = None,
text_color: str | tuple[str, str] | None = None,
text_color_disabled: str | tuple[str, str] | None = None,
font: Optional[Union[tuple, CTkFont]] = None,
dropdown_font: Optional[Union[tuple, CTkFont]] = None,
values: Optional[List[str]] = None,
state: str = tkinter.NORMAL,
font: tuple[Any, ...] | CTkFont | None = None,
dropdown_font: tuple[Any, ...] | CTkFont | None = None,
values: list[str] | None = None,
state: Literal["normal", "disabled", "readonly"] = "normal",
hover: bool = True,
variable: Union[tkinter.Variable, None] = None,
command: Union[Callable[[str], None], None] = None,
justify: str = "left",
**kwargs):
variable: tkinter.Variable | None = None,
command: Callable[[str], None] | None = None,
justify: Literal["left", "center", "right"] = "left",
**kwargs: Any):
# transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass
super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
@ -70,11 +75,7 @@ class CTkComboBox(CTkBaseClass):
self._variable = variable
self._state = state
self._hover = hover
if values is None:
self._values = ["CTkComboBox"]
else:
self._values = values
self._values = values or ["CTkComboBox"]
self._dropdown_menu = DropdownMenu(master=self,
values=self._values,
@ -116,7 +117,7 @@ class CTkComboBox(CTkBaseClass):
else:
self._entry.insert(0, "CTkComboBox")
def _create_bindings(self, sequence: Optional[str] = None):
def _create_bindings(self, sequence: str | None = 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)
@ -135,7 +136,7 @@ class CTkComboBox(CTkBaseClass):
max(self._apply_widget_scaling(self._current_width - left_section_width + 3), self._apply_widget_scaling(3))),
pady=self._apply_widget_scaling(self._border_width))
def _set_scaling(self, *args, **kwargs):
def _set_scaling(self, *args: Any, **kwargs: Any):
super()._set_scaling(*args, **kwargs)
# change entry font size and grid padding
@ -146,7 +147,7 @@ class CTkComboBox(CTkBaseClass):
height=self._apply_widget_scaling(self._desired_height))
self._draw(no_color_updates=True)
def _set_dimensions(self, width: int = None, height: int = None):
def _set_dimensions(self, width: int | None = None, height: int | None = None):
super()._set_dimensions(width, height)
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
@ -168,7 +169,7 @@ class CTkComboBox(CTkBaseClass):
super().destroy()
def _draw(self, no_color_updates=False):
def _draw(self, no_color_updates: bool = False):
super()._draw(no_color_updates)
left_section_width = self._current_width - self._current_height
@ -218,7 +219,7 @@ class CTkComboBox(CTkBaseClass):
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):
def configure(self, require_redraw: bool = False, **kwargs: Any):
if "corner_radius" in kwargs:
self._corner_radius = kwargs.pop("corner_radius")
require_redraw = True
@ -297,7 +298,7 @@ class CTkComboBox(CTkBaseClass):
super().configure(require_redraw=require_redraw, **kwargs)
def cget(self, attribute_name: str) -> any:
def cget(self, attribute_name: str) -> Any:
if attribute_name == "corner_radius":
return self._corner_radius
elif attribute_name == "border_width":
@ -341,7 +342,7 @@ class CTkComboBox(CTkBaseClass):
else:
return super().cget(attribute_name)
def _on_enter(self, event=0):
def _on_enter(self, event: tkinter.Event[Any] | None = None):
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 self._cursor_manipulation_enabled:
self._canvas.configure(cursor="pointinghand")
@ -356,7 +357,7 @@ class CTkComboBox(CTkBaseClass):
outline=self._apply_appearance_mode(self._button_hover_color),
fill=self._apply_appearance_mode(self._button_hover_color))
def _on_leave(self, event=0):
def _on_leave(self, event: tkinter.Event[Any] | None = None):
if sys.platform == "darwin" and len(self._values) > 0 and self._cursor_manipulation_enabled:
self._canvas.configure(cursor="arrow")
elif sys.platform.startswith("win") and len(self._values) > 0 and self._cursor_manipulation_enabled:
@ -396,17 +397,17 @@ class CTkComboBox(CTkBaseClass):
def get(self) -> str:
return self._entry.get()
def _clicked(self, event=None):
def _clicked(self, event: tkinter.Event[Any] | None = 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=True):
def bind(self, sequence: str | None = None, command=None, add: Literal["+"] | bool = True):
""" called on the tkinter.Entry """
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=None, funcid=None):
def unbind(self, sequence=None, funcid: str | None = None):
""" called on the tkinter.Entry """
if funcid is not None:
raise ValueError("'funcid' argument can only be None, because there is a bug in" +

View File

@ -1,12 +1,19 @@
import tkinter
from typing import Union, Tuple, Optional
from __future__ import annotations
from .core_rendering import CTkCanvas
from .theme import ThemeManager
from .core_rendering import DrawEngine
import sys
import tkinter
from typing import Any
if sys.version_info >= (3, 8):
from typing import Literal
else:
from typing_extensions import Literal
from .core_rendering import CTkCanvas, DrawEngine
from .core_widget_classes import CTkBaseClass
from .font import CTkFont
from .utility import pop_from_dict_by_set, check_kwargs_empty
from .theme import ThemeManager
from .utility import check_kwargs_empty, pop_from_dict_by_set
class CTkEntry(CTkBaseClass):
@ -23,23 +30,23 @@ class CTkEntry(CTkBaseClass):
"show", "takefocus", "validate", "validatecommand", "xscrollcommand"}
def __init__(self,
master: any,
master: CTkBaseClass,
width: int = 140,
height: int = 28,
corner_radius: Optional[int] = None,
border_width: Optional[int] = None,
corner_radius: int | None = None,
border_width: int | None = None,
bg_color: Union[str, Tuple[str, str]] = "transparent",
fg_color: Optional[Union[str, Tuple[str, str]]] = None,
border_color: Optional[Union[str, Tuple[str, str]]] = None,
text_color: Optional[Union[str, Tuple[str, str]]] = None,
placeholder_text_color: Optional[Union[str, Tuple[str, str]]] = None,
bg_color: str | tuple[str, str] = "transparent",
fg_color: str | tuple[str, str] | None = None,
border_color: str | tuple[str, str] | None = None,
text_color: str | tuple[str, str] | None = None,
placeholder_text_color: str | tuple[str, str] | None = None,
textvariable: Union[tkinter.Variable, None] = None,
placeholder_text: Union[str, None] = None,
font: Optional[Union[tuple, CTkFont]] = None,
state: str = tkinter.NORMAL,
**kwargs):
textvariable: tkinter.Variable | None = None,
placeholder_text: str | None = None,
font: tuple[Any, ...] | CTkFont | None = None,
state: Literal["normal", "disabled", "readonly"] = "normal",
**kwargs: Any):
# transfer basic functionality (bg_color, size, appearance_mode, scaling) to CTkBaseClass
super().__init__(master=master, bg_color=bg_color, width=width, height=height)
@ -97,7 +104,7 @@ class CTkEntry(CTkBaseClass):
self._create_bindings()
self._draw()
def _create_bindings(self, sequence: Optional[str] = None):
def _create_bindings(self, sequence: str | None = 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)
@ -116,11 +123,11 @@ class CTkEntry(CTkBaseClass):
padx=self._apply_widget_scaling(self._minimum_x_padding),
pady=(self._apply_widget_scaling(self._border_width), self._apply_widget_scaling(self._border_width + 1)))
def _textvariable_callback(self, var_name, index, mode):
def _textvariable_callback(self, var_name: str, index: int, mode: Any):
if self._textvariable.get() == "":
self._activate_placeholder()
def _set_scaling(self, *args, **kwargs):
def _set_scaling(self, *args: Any, **kwargs: Any):
super()._set_scaling(*args, **kwargs)
self._entry.configure(font=self._apply_font_scaling(self._font))
@ -128,7 +135,7 @@ class CTkEntry(CTkBaseClass):
self._create_grid()
self._draw(no_color_updates=True)
def _set_dimensions(self, width=None, height=None):
def _set_dimensions(self, width: int | None = None, height: int | None = None):
super()._set_dimensions(width, height)
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
@ -150,7 +157,7 @@ class CTkEntry(CTkBaseClass):
super().destroy()
def _draw(self, no_color_updates=False):
def _draw(self, no_color_updates: bool = False):
super()._draw(no_color_updates)
requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._current_width),
@ -191,7 +198,7 @@ class CTkEntry(CTkBaseClass):
disabledforeground=self._apply_appearance_mode(self._text_color),
insertbackground=self._apply_appearance_mode(self._text_color))
def configure(self, require_redraw=False, **kwargs):
def configure(self, require_redraw: bool = False, **kwargs: Any):
if "state" in kwargs:
self._state = kwargs.pop("state")
self._entry.configure(state=self._state)
@ -252,7 +259,7 @@ class CTkEntry(CTkBaseClass):
self._entry.configure(**pop_from_dict_by_set(kwargs, self._valid_tk_entry_attributes)) # configure Tkinter.Entry
super().configure(require_redraw=require_redraw, **kwargs) # configure CTkBaseClass
def cget(self, attribute_name: str) -> any:
def cget(self, attribute_name: str) -> Any:
if attribute_name == "corner_radius":
return self._corner_radius
elif attribute_name == "border_width":
@ -281,7 +288,7 @@ class CTkEntry(CTkBaseClass):
else:
return super().cget(attribute_name) # cget of CTkBaseClass
def bind(self, sequence=None, command=None, add=True):
def bind(self, sequence=None, command=None, add: Literal["+"] | bool = True):
""" called on the tkinter.Entry """
if not (add == "+" or add is True):
raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
@ -316,11 +323,11 @@ class CTkEntry(CTkBaseClass):
for argument, value in self._pre_placeholder_arguments.items():
self._entry[argument] = value
def _entry_focus_out(self, event=None):
def _entry_focus_out(self, event: tkinter.Event[Any] | None = None):
self._activate_placeholder()
self._is_focused = False
def _entry_focus_in(self, event=None):
def _entry_focus_in(self, event: tkinter.Event[Any] | None = None):
self._deactivate_placeholder()
self._is_focused = True
@ -330,7 +337,7 @@ class CTkEntry(CTkBaseClass):
if not self._is_focused and self._entry.get() == "":
self._activate_placeholder()
def insert(self, index, string):
def insert(self, index: int, string):
self._deactivate_placeholder()
return self._entry.insert(index, string)

View File

@ -1,9 +1,17 @@
from typing import Union, Tuple, List, Optional
from __future__ import annotations
from .core_rendering import CTkCanvas
from .theme import ThemeManager
from .core_rendering import DrawEngine
import sys
import tkinter
from typing import Any, Callable
if sys.version_info >= (3, 8):
from typing import Literal
else:
from typing_extensions import Literal
from .core_rendering import CTkCanvas, DrawEngine
from .core_widget_classes import CTkBaseClass
from .theme import ThemeManager
class CTkFrame(CTkBaseClass):
@ -15,19 +23,19 @@ class CTkFrame(CTkBaseClass):
"""
def __init__(self,
master: any,
master: CTkBaseClass,
width: int = 200,
height: int = 200,
corner_radius: Optional[Union[int, str]] = None,
border_width: Optional[Union[int, str]] = None,
corner_radius: int | str | None = None,
border_width: int | str | None = None,
bg_color: Union[str, Tuple[str, str]] = "transparent",
fg_color: Optional[Union[str, Tuple[str, str]]] = None,
border_color: Optional[Union[str, Tuple[str, str]]] = None,
bg_color: str | tuple[str, str] = "transparent",
fg_color: str | tuple[str, str] | None = None,
border_color: str | tuple[str, str] | None = None,
background_corner_colors: Union[Tuple[Union[str, Tuple[str, str]]], None] = None,
overwrite_preferred_drawing_method: Union[str, None] = None,
**kwargs):
background_corner_colors: tuple[str | tuple[str, str]] | None = None,
overwrite_preferred_drawing_method: str | None = None,
**kwargs: Any):
# transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass
super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
@ -64,7 +72,7 @@ class CTkFrame(CTkBaseClass):
self._draw(no_color_updates=True)
def winfo_children(self) -> List[any]:
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
@ -77,21 +85,21 @@ class CTkFrame(CTkBaseClass):
except ValueError:
return child_widgets
def _set_scaling(self, *args, **kwargs):
def _set_scaling(self, *args: Any, **kwargs: Any):
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()
def _set_dimensions(self, width=None, height=None):
def _set_dimensions(self, width: int | None = None, height: int | None = 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()
def _draw(self, no_color_updates=False):
def _draw(self, no_color_updates: bool = False):
super()._draw(no_color_updates)
if not self._canvas.winfo_exists():
@ -131,7 +139,7 @@ class CTkFrame(CTkBaseClass):
# self._canvas.tag_lower("inner_parts") # maybe unnecessary, I don't know ???
# self._canvas.tag_lower("border_parts")
def configure(self, require_redraw=False, **kwargs):
def configure(self, require_redraw: bool = False, **kwargs: Any):
if "fg_color" in kwargs:
self._fg_color = self._check_color_type(kwargs.pop("fg_color"), transparency=True)
require_redraw = True
@ -166,7 +174,7 @@ class CTkFrame(CTkBaseClass):
super().configure(require_redraw=require_redraw, **kwargs)
def cget(self, attribute_name: str) -> any:
def cget(self, attribute_name: str) -> Any:
if attribute_name == "corner_radius":
return self._corner_radius
elif attribute_name == "border_width":
@ -182,13 +190,13 @@ class CTkFrame(CTkBaseClass):
else:
return super().cget(attribute_name)
def bind(self, sequence=None, command=None, add=True):
def bind(self, sequence: str | None = None, command: Callable[[tkinter.Event[Any]], None] | None = None, add: Literal["+"] | 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=None, funcid=None):
def unbind(self, sequence=None, funcid: str | None = None):
""" called on the tkinter.Canvas """
if funcid is not None:
raise ValueError("'funcid' argument can only be None, because there is a bug in" +

View File

@ -1,13 +1,14 @@
import tkinter
from typing import Union, Tuple, Callable, Optional
from __future__ import annotations
from .core_rendering import CTkCanvas
from .theme import ThemeManager
from .core_rendering import DrawEngine
import tkinter
from typing import Any, Callable
from .core_rendering import CTkCanvas, DrawEngine
from .core_widget_classes import CTkBaseClass
from .font import CTkFont
from .image import CTkImage
from .utility import pop_from_dict_by_set, check_kwargs_empty
from .theme import ThemeManager
from .utility import check_kwargs_empty, pop_from_dict_by_set
class CTkLabel(CTkBaseClass):
@ -21,22 +22,22 @@ class CTkLabel(CTkBaseClass):
"textvariable", "state", "takefocus", "underline"}
def __init__(self,
master: any,
master: CTkBaseClass,
width: int = 0,
height: int = 28,
corner_radius: Optional[int] = None,
corner_radius: int | None = None,
bg_color: Union[str, Tuple[str, str]] = "transparent",
fg_color: Optional[Union[str, Tuple[str, str]]] = None,
text_color: Optional[Union[str, Tuple[str, str]]] = None,
bg_color: str | tuple[str, str] = "transparent",
fg_color: str | tuple[str, str] | None = None,
text_color: str | tuple[str, str] | None = None,
text: str = "CTkLabel",
font: Optional[Union[tuple, CTkFont]] = None,
image: Union[CTkImage, None] = None,
font: tuple[Any, ...] | CTkFont | None = None,
image: CTkImage | None = None,
compound: str = "center",
anchor: str = "center", # label anchor: center, n, e, s, w
wraplength: int = 0,
**kwargs):
**kwargs: Any):
# transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass
super().__init__(master=master, bg_color=bg_color, width=width, height=height)
@ -93,7 +94,7 @@ class CTkLabel(CTkBaseClass):
self._update_image()
self._draw()
def _set_scaling(self, *args, **kwargs):
def _set_scaling(self, *args: Any, **kwargs: Any):
super()._set_scaling(*args, **kwargs)
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), height=self._apply_widget_scaling(self._desired_height))
@ -108,7 +109,7 @@ class CTkLabel(CTkBaseClass):
super()._set_appearance_mode(mode_string)
self._update_image()
def _set_dimensions(self, width=None, height=None):
def _set_dimensions(self, width: int | None = None, height: int | None = None):
super()._set_dimensions(width, height)
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
@ -144,7 +145,7 @@ class CTkLabel(CTkBaseClass):
self._label.grid(row=0, column=0, sticky=text_label_grid_sticky,
padx=self._apply_widget_scaling(min(self._corner_radius, round(self._current_height / 2))))
def _draw(self, no_color_updates=False):
def _draw(self, no_color_updates: bool = False):
super()._draw(no_color_updates)
requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._current_width),
@ -170,7 +171,7 @@ class CTkLabel(CTkBaseClass):
self._canvas.configure(bg=self._apply_appearance_mode(self._bg_color))
def configure(self, require_redraw=False, **kwargs):
def configure(self, require_redraw: bool = False, **kwargs: Any):
if "corner_radius" in kwargs:
self._corner_radius = kwargs.pop("corner_radius")
self._create_grid()
@ -220,7 +221,7 @@ class CTkLabel(CTkBaseClass):
self._label.configure(**pop_from_dict_by_set(kwargs, self._valid_tk_label_attributes)) # configure tkinter.Label
super().configure(require_redraw=require_redraw, **kwargs) # configure CTkBaseClass
def cget(self, attribute_name: str) -> any:
def cget(self, attribute_name: str) -> Any:
if attribute_name == "corner_radius":
return self._corner_radius
@ -247,14 +248,14 @@ class CTkLabel(CTkBaseClass):
else:
return super().cget(attribute_name) # cget of CTkBaseClass
def bind(self, sequence: str = None, command: Callable = None, add: str = True):
def bind(self, sequence: str, command: Callable[..., None] | None = None, add: str = True):
""" called on the tkinter.Label and 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._label.bind(sequence, command, add=True)
def unbind(self, sequence: str = None, funcid: Optional[str] = None):
def unbind(self, sequence: str, funcid: str | None = 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" +

View File

@ -1,14 +1,19 @@
import tkinter
from __future__ import annotations
import copy
import sys
from typing import Union, Tuple, Callable, Optional
import tkinter
from typing import Any, Callable
from .core_rendering import CTkCanvas
from .theme import ThemeManager
from .core_rendering import DrawEngine
from .core_widget_classes import CTkBaseClass
from .core_widget_classes import DropdownMenu
if sys.version_info >= (3, 8):
from typing import Literal
else:
from typing_extensions import Literal
from .core_rendering import CTkCanvas, DrawEngine
from .core_widget_classes import CTkBaseClass, DropdownMenu
from .font import CTkFont
from .theme import ThemeManager
class CTkOptionMenu(CTkBaseClass):
@ -18,31 +23,31 @@ class CTkOptionMenu(CTkBaseClass):
"""
def __init__(self,
master: any,
master: CTkBaseClass,
width: int = 140,
height: int = 28,
corner_radius: Optional[Union[int]] = None,
corner_radius: int | None = None,
bg_color: Union[str, Tuple[str, str]] = "transparent",
fg_color: Optional[Union[str, Tuple[str, str]]] = None,
button_color: Optional[Union[str, Tuple[str, str]]] = None,
button_hover_color: Optional[Union[str, Tuple[str, str]]] = None,
text_color: Optional[Union[str, Tuple[str, str]]] = None,
text_color_disabled: Optional[Union[str, Tuple[str, str]]] = None,
dropdown_fg_color: Optional[Union[str, Tuple[str, str]]] = None,
dropdown_hover_color: Optional[Union[str, Tuple[str, str]]] = None,
dropdown_text_color: Optional[Union[str, Tuple[str, str]]] = None,
bg_color: str | tuple[str, str] = "transparent",
fg_color: str | tuple[str, str] | None = None,
button_color: str | tuple[str, str] | None = None,
button_hover_color: str | tuple[str, str] | None = None,
text_color: str | tuple[str, str] | None = None,
text_color_disabled: str | tuple[str, str] | None = None,
dropdown_fg_color: str | tuple[str, str] | None = None,
dropdown_hover_color: str | tuple[str, str] | None = None,
dropdown_text_color: str | tuple[str, str] | None = None,
font: Optional[Union[tuple, CTkFont]] = None,
dropdown_font: Optional[Union[tuple, CTkFont]] = None,
values: Optional[list] = None,
variable: Union[tkinter.Variable, None] = None,
state: str = tkinter.NORMAL,
font: tuple[Any, ...] | CTkFont | None = None,
dropdown_font: tuple[Any, ...] | CTkFont | None = None,
values: list | None = None,
variable: tkinter.Variable | None = None,
state: Literal["normal", "disabled", "readonly"] = "normal",
hover: bool = True,
command: Union[Callable[[str], None], None] = None,
command: Callable[[str], None] | None = None,
dynamic_resizing: bool = True,
anchor: str = "w",
**kwargs):
**kwargs: Any):
# transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass
super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
@ -68,7 +73,7 @@ class CTkOptionMenu(CTkBaseClass):
self._command = command
self._variable = variable
self._variable_callback_blocked: bool = False
self._variable_callback_name: Union[str, None] = None
self._variable_callback_name: str | None = None
self._state = state
self._hover = hover
self._dynamic_resizing = dynamic_resizing
@ -127,7 +132,7 @@ 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):
def _create_bindings(self, sequence: str | None = 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)
@ -147,7 +152,7 @@ class CTkOptionMenu(CTkBaseClass):
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))))
def _set_scaling(self, *args, **kwargs):
def _set_scaling(self, *args: Any, **kwargs: Any):
super()._set_scaling(*args, **kwargs)
# change label font size and grid padding
@ -157,7 +162,7 @@ class CTkOptionMenu(CTkBaseClass):
self._create_grid()
self._draw(no_color_updates=True)
def _set_dimensions(self, width: int = None, height: int = None):
def _set_dimensions(self, width: int | None = None, height: int | None = None):
super()._set_dimensions(width, height)
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
@ -182,7 +187,7 @@ class CTkOptionMenu(CTkBaseClass):
super().destroy()
def _draw(self, no_color_updates=False):
def _draw(self, no_color_updates: bool = False):
super()._draw(no_color_updates)
left_section_width = self._current_width - self._current_height
@ -221,7 +226,7 @@ class CTkOptionMenu(CTkBaseClass):
self._canvas.update_idletasks()
def configure(self, require_redraw=False, **kwargs):
def configure(self, require_redraw: bool = False, **kwargs: Any):
if "corner_radius" in kwargs:
self._corner_radius = kwargs.pop("corner_radius")
self._create_grid()
@ -303,7 +308,7 @@ class CTkOptionMenu(CTkBaseClass):
super().configure(require_redraw=require_redraw, **kwargs)
def cget(self, attribute_name: str) -> any:
def cget(self, attribute_name: str) -> Any:
if attribute_name == "corner_radius":
return self._corner_radius
@ -350,20 +355,20 @@ class CTkOptionMenu(CTkBaseClass):
self._dropdown_menu.open(self.winfo_rootx(),
self.winfo_rooty() + self._apply_widget_scaling(self._current_height + 0))
def _on_enter(self, event=0):
def _on_enter(self, event: tkinter.Event[Any] | None = None):
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=self._apply_appearance_mode(self._button_hover_color),
fill=self._apply_appearance_mode(self._button_hover_color))
def _on_leave(self, event=0):
def _on_leave(self, event: tkinter.Event[Any] | None = None):
# set color of inner button parts
self._canvas.itemconfig("inner_parts_right",
outline=self._apply_appearance_mode(self._button_color),
fill=self._apply_appearance_mode(self._button_color))
def _variable_callback(self, var_name, index, mode):
def _variable_callback(self, var_name: str, index: int, mode: Any):
if not self._variable_callback_blocked:
self._current_value = self._variable.get()
self._text_label.configure(text=self._current_value)
@ -392,18 +397,18 @@ class CTkOptionMenu(CTkBaseClass):
def get(self) -> str:
return self._current_value
def _clicked(self, event=0):
def _clicked(self, event: tkinter.Event[Any] | None = None):
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: Union[str, bool] = True):
def bind(self, sequence: str, command: Callable[..., None] | None = None, add: 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 = None, funcid: str = None):
def unbind(self, sequence: str, funcid: str | None = 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" +

View File

@ -1,15 +1,17 @@
import tkinter
from __future__ import annotations
import math
from typing import Union, Tuple, Optional, Callable
import tkinter
from typing import Any, Callable
try:
from typing import Literal
except ImportError:
from typing_extensions import Literal
from .core_rendering import CTkCanvas
from .theme import ThemeManager
from .core_rendering import DrawEngine
from .core_rendering import CTkCanvas, DrawEngine
from .core_widget_classes import CTkBaseClass
from .theme import ThemeManager
class CTkProgressBar(CTkBaseClass):
@ -20,23 +22,23 @@ class CTkProgressBar(CTkBaseClass):
"""
def __init__(self,
master: any,
width: Optional[int] = None,
height: Optional[int] = None,
corner_radius: Optional[int] = None,
border_width: Optional[int] = None,
master: CTkBaseClass,
width: int | None = None,
height: int | None = None,
corner_radius: int | None = None,
border_width: int | None = None,
bg_color: Union[str, Tuple[str, str]] = "transparent",
fg_color: Optional[Union[str, Tuple[str, str]]] = None,
border_color: Optional[Union[str, Tuple[str, str]]] = None,
progress_color: Optional[Union[str, Tuple[str, str]]] = None,
bg_color: str | tuple[str, str] = "transparent",
fg_color: str | tuple[str, str] | None = None,
border_color: str | tuple[str, str] | None = None,
progress_color: str | tuple[str, str] | None = None,
variable: Union[tkinter.Variable, None] = None,
variable: tkinter.Variable | None = None,
orientation: str = "horizontal",
mode: Literal["determinate", "indeterminate"] = "determinate",
determinate_speed: float = 1,
indeterminate_speed: float = 1,
**kwargs):
**kwargs: Any):
# set default dimensions according to orientation
if width is None:
@ -94,14 +96,14 @@ class CTkProgressBar(CTkBaseClass):
self.set(self._variable.get(), from_variable_callback=True)
self._variable_callback_blocked = False
def _set_scaling(self, *args, **kwargs):
def _set_scaling(self, *args: Any, **kwargs: Any):
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)
def _set_dimensions(self, width=None, height=None):
def _set_dimensions(self, width: int | None = None, height: int | None = None):
super()._set_dimensions(width, height)
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
@ -114,7 +116,7 @@ class CTkProgressBar(CTkBaseClass):
super().destroy()
def _draw(self, no_color_updates=False):
def _draw(self, no_color_updates: bool = False):
super()._draw(no_color_updates)
if self._orientation.lower() == "horizontal":
@ -157,7 +159,7 @@ class CTkProgressBar(CTkBaseClass):
fill=self._apply_appearance_mode(self._progress_color),
outline=self._apply_appearance_mode(self._progress_color))
def configure(self, require_redraw=False, **kwargs):
def configure(self, require_redraw: bool = False, **kwargs: Any):
if "corner_radius" in kwargs:
self._corner_radius = kwargs.pop("corner_radius")
require_redraw = True
@ -202,7 +204,7 @@ class CTkProgressBar(CTkBaseClass):
super().configure(require_redraw=require_redraw, **kwargs)
def cget(self, attribute_name: str) -> any:
def cget(self, attribute_name: str) -> Any:
if attribute_name == "corner_radius":
return self._corner_radius
elif attribute_name == "border_width":
@ -229,11 +231,11 @@ class CTkProgressBar(CTkBaseClass):
else:
return super().cget(attribute_name)
def _variable_callback(self, var_name, index, mode):
def _variable_callback(self, var_name: str, index: int, mode: Any):
if not self._variable_callback_blocked:
self.set(self._variable.get(), from_variable_callback=True)
def set(self, value, from_variable_callback=False):
def set(self, value, from_variable_callback: bool = False):
""" set determinate value """
self._determinate_value = value
@ -289,13 +291,13 @@ class CTkProgressBar(CTkBaseClass):
self._indeterminate_value += self._indeterminate_speed
self._draw()
def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True):
def bind(self, sequence: str, command: Callable[..., None] | None = None, add: 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 = None, funcid: str = None):
def unbind(self, sequence: str, funcid: str | None = 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" +

View File

@ -1,12 +1,18 @@
import tkinter
import sys
from typing import Union, Tuple, Callable, Optional
from __future__ import annotations
from .core_rendering import CTkCanvas
from .theme import ThemeManager
from .core_rendering import DrawEngine
import sys
import tkinter
from typing import Any, Callable
if sys.version_info >= (3, 8):
from typing import Literal
else:
from typing_extensions import Literal
from .core_rendering import CTkCanvas, DrawEngine
from .core_widget_classes import CTkBaseClass
from .font import CTkFont
from .theme import ThemeManager
class CTkRadioButton(CTkBaseClass):
@ -16,31 +22,31 @@ class CTkRadioButton(CTkBaseClass):
"""
def __init__(self,
master: any,
master: CTkBaseClass,
width: int = 100,
height: int = 22,
radiobutton_width: int = 22,
radiobutton_height: int = 22,
corner_radius: Optional[int] = None,
border_width_unchecked: Optional[int] = None,
border_width_checked: Optional[int] = None,
corner_radius: int | None = None,
border_width_unchecked: int | None = None,
border_width_checked: int | None = None,
bg_color: Union[str, Tuple[str, str]] = "transparent",
fg_color: Optional[Union[str, Tuple[str, str]]] = None,
hover_color: Optional[Union[str, Tuple[str, str]]] = None,
border_color: Optional[Union[str, Tuple[str, str]]] = None,
text_color: Optional[Union[str, Tuple[str, str]]] = None,
text_color_disabled: Optional[Union[str, Tuple[str, str]]] = None,
bg_color: str | tuple[str, str] = "transparent",
fg_color: str | tuple[str, str] | None = None,
hover_color: str | tuple[str, str] | None = None,
border_color: str | tuple[str, str] | None = None,
text_color: str | tuple[str, str] | None = None,
text_color_disabled: str | tuple[str, str] | None = None,
text: str = "CTkRadioButton",
font: Optional[Union[tuple, CTkFont]] = None,
textvariable: Union[tkinter.Variable, None] = None,
variable: Union[tkinter.Variable, None] = None,
value: Union[int, str] = 0,
state: str = tkinter.NORMAL,
font: tuple[Any, ...] | CTkFont | None = None,
textvariable: tkinter.Variable | None = None,
variable: tkinter.Variable | None = None,
value: int | str = 0,
state: Literal["normal", "disabled", "readonly"] = "normal",
hover: bool = True,
command: Union[Callable, None] = None,
**kwargs):
command: Callable[..., None] | None = None,
**kwargs: Any):
# transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass
super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
@ -61,7 +67,7 @@ class CTkRadioButton(CTkBaseClass):
# text
self._text = text
self._text_label: Union[tkinter.Label, None] = None
self._text_label: tkinter.Label | None = None
self._text_color = ThemeManager.theme["CTkRadiobutton"]["text_color"] if text_color is None else self._check_color_type(text_color)
self._text_color_disabled = ThemeManager.theme["CTkRadiobutton"]["text_color_disabled"] if text_color_disabled is None else self._check_color_type(text_color_disabled)
@ -79,7 +85,7 @@ class CTkRadioButton(CTkBaseClass):
self._variable: tkinter.Variable = variable
self._variable_callback_blocked: bool = False
self._textvariable = textvariable
self._variable_callback_name: Union[str, None] = None
self._variable_callback_name: str | None = None
# configure grid system (3x1)
self.grid_columnconfigure(0, weight=0)
@ -119,7 +125,7 @@ class CTkRadioButton(CTkBaseClass):
self._set_cursor()
self._draw()
def _create_bindings(self, sequence: Optional[str] = None):
def _create_bindings(self, sequence: str | None = 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)
@ -131,7 +137,7 @@ class CTkRadioButton(CTkBaseClass):
self._canvas.bind("<Button-1>", self.invoke)
self._text_label.bind("<Button-1>", self.invoke)
def _set_scaling(self, *args, **kwargs):
def _set_scaling(self, *args: Any, **kwargs: Any):
super()._set_scaling(*args, **kwargs)
self.grid_columnconfigure(1, weight=0, minsize=self._apply_widget_scaling(6))
@ -143,7 +149,7 @@ class CTkRadioButton(CTkBaseClass):
height=self._apply_widget_scaling(self._radiobutton_height))
self._draw(no_color_updates=True)
def _set_dimensions(self, width: int = None, height: int = None):
def _set_dimensions(self, width: int | None = None, height: int | None = None):
super()._set_dimensions(width, height)
self._bg_canvas.configure(width=self._apply_widget_scaling(self._desired_width),
@ -167,7 +173,7 @@ class CTkRadioButton(CTkBaseClass):
super().destroy()
def _draw(self, no_color_updates=False):
def _draw(self, no_color_updates: bool = False):
super()._draw(no_color_updates)
if self._check_state is True:
@ -205,7 +211,7 @@ class CTkRadioButton(CTkBaseClass):
self._text_label.configure(bg=self._apply_appearance_mode(self._bg_color))
def configure(self, require_redraw=False, **kwargs):
def configure(self, require_redraw: bool = False, **kwargs: Any):
if "corner_radius" in kwargs:
self._corner_radius = kwargs.pop("corner_radius")
require_redraw = True
@ -289,7 +295,7 @@ class CTkRadioButton(CTkBaseClass):
super().configure(require_redraw=require_redraw, **kwargs)
def cget(self, attribute_name: str) -> any:
def cget(self, attribute_name: str) -> Any:
if attribute_name == "corner_radius":
return self._corner_radius
elif attribute_name == "border_width_unchecked":
@ -354,13 +360,13 @@ class CTkRadioButton(CTkBaseClass):
if self._text_label is not None:
self._text_label.configure(cursor="hand2")
def _on_enter(self, event=0):
def _on_enter(self, event: tkinter.Event[Any] | None = None):
if self._hover is True and self._state == tkinter.NORMAL:
self._canvas.itemconfig("border_parts",
fill=self._apply_appearance_mode(self._hover_color),
outline=self._apply_appearance_mode(self._hover_color))
def _on_leave(self, event=0):
def _on_leave(self, event: tkinter.Event[Any] | None = None):
if self._check_state is True:
self._canvas.itemconfig("border_parts",
fill=self._apply_appearance_mode(self._fg_color),
@ -370,14 +376,14 @@ class CTkRadioButton(CTkBaseClass):
fill=self._apply_appearance_mode(self._border_color),
outline=self._apply_appearance_mode(self._border_color))
def _variable_callback(self, var_name, index, mode):
def _variable_callback(self, var_name: str, index: int, mode: Any):
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):
def invoke(self, event: tkinter.Event[Any] | None = None):
if self._state == tkinter.NORMAL:
if self._check_state is False:
self._check_state = True
@ -386,7 +392,7 @@ class CTkRadioButton(CTkBaseClass):
if self._command is not None:
self._command()
def select(self, from_variable_callback=False):
def select(self, from_variable_callback: bool = False):
self._check_state = True
self._draw()
@ -395,7 +401,7 @@ class CTkRadioButton(CTkBaseClass):
self._variable.set(self._value)
self._variable_callback_blocked = False
def deselect(self, from_variable_callback=False):
def deselect(self, from_variable_callback: bool = False):
self._check_state = False
self._draw()
@ -404,14 +410,14 @@ class CTkRadioButton(CTkBaseClass):
self._variable.set("")
self._variable_callback_blocked = False
def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True):
def bind(self, sequence: str, command: Callable[..., None] | None = None, add: 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 = None, funcid: str = None):
def unbind(self, sequence: str, funcid: str | None = 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" +

View File

@ -1,40 +1,43 @@
from typing import Union, Tuple, Optional
from __future__ import annotations
import sys
import tkinter
from typing import Any
try:
from typing import Literal
except ImportError:
from typing_extensions import Literal
import tkinter
import sys
from .ctk_frame import CTkFrame
from .ctk_scrollbar import CTkScrollbar
from .appearance_mode import CTkAppearanceModeBaseClass
from .scaling import CTkScalingBaseClass
from .core_widget_classes import CTkBaseClass
from .ctk_frame import CTkFrame
from .ctk_label import CTkLabel
from .ctk_scrollbar import CTkScrollbar
from .font import CTkFont
from .scaling import CTkScalingBaseClass
from .theme import ThemeManager
class CTkScrollableFrame(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
def __init__(self,
master: any,
master: CTkBaseClass,
width: int = 200,
height: int = 200,
corner_radius: Optional[Union[int, str]] = None,
border_width: Optional[Union[int, str]] = None,
corner_radius: int | str | None = None,
border_width: int | str | None = None,
bg_color: Union[str, Tuple[str, str]] = "transparent",
fg_color: Optional[Union[str, Tuple[str, str]]] = None,
border_color: Optional[Union[str, Tuple[str, str]]] = None,
scrollbar_fg_color: Optional[Union[str, Tuple[str, str]]] = None,
scrollbar_button_color: Optional[Union[str, Tuple[str, str]]] = None,
scrollbar_button_hover_color: Optional[Union[str, Tuple[str, str]]] = None,
label_fg_color: Optional[Union[str, Tuple[str, str]]] = None,
label_text_color: Optional[Union[str, Tuple[str, str]]] = None,
bg_color: str | tuple[str, str] = "transparent",
fg_color: str | tuple[str, str] | None = None,
border_color: str | tuple[str, str] | None = None,
scrollbar_fg_color: str | tuple[str, str] | None = None,
scrollbar_button_color: str | tuple[str, str] | None = None,
scrollbar_button_hover_color: str | tuple[str, str] | None = None,
label_fg_color: str | tuple[str, str] | None = None,
label_text_color: str | tuple[str, str] | None = None,
label_text: str = "",
label_font: Optional[Union[tuple, CTkFont]] = None,
label_font: tuple[Any, ...] | CTkFont | None = None,
label_anchor: str = "center",
orientation: Literal["vertical", "horizontal"] = "vertical"):
@ -120,7 +123,7 @@ class CTkScrollableFrame(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBa
else:
self._label.grid_forget()
def _set_appearance_mode(self, mode_string):
def _set_appearance_mode(self, mode_string: Literal["light", "dark"]):
super()._set_appearance_mode(mode_string)
if self._parent_frame.cget("fg_color") == "transparent":
@ -130,13 +133,13 @@ class CTkScrollableFrame(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBa
tkinter.Frame.configure(self, bg=self._apply_appearance_mode(self._parent_frame.cget("fg_color")))
self._parent_canvas.configure(bg=self._apply_appearance_mode(self._parent_frame.cget("fg_color")))
def _set_scaling(self, new_widget_scaling, new_window_scaling):
def _set_scaling(self, new_widget_scaling: float, new_window_scaling: float):
super()._set_scaling(new_widget_scaling, new_window_scaling)
self._parent_canvas.configure(width=self._apply_widget_scaling(self._desired_width),
height=self._apply_widget_scaling(self._desired_height))
def _set_dimensions(self, width=None, height=None):
def _set_dimensions(self, width: int | None = None, height: int | None = None):
if width is not None:
self._desired_width = width
if height is not None:
@ -145,7 +148,7 @@ class CTkScrollableFrame(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBa
self._parent_canvas.configure(width=self._apply_widget_scaling(self._desired_width),
height=self._apply_widget_scaling(self._desired_height))
def configure(self, **kwargs):
def configure(self, **kwargs: Any):
if "width" in kwargs:
self._set_dimensions(width=kwargs.pop("width"))
@ -232,7 +235,7 @@ class CTkScrollableFrame(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBa
else:
return self._parent_frame.cget(attribute_name)
def _fit_frame_dimensions_to_canvas(self, event):
def _fit_frame_dimensions_to_canvas(self, event: tkinter.Event[Any] | None = None):
if self._orientation == "horizontal":
self._parent_canvas.itemconfigure(self._create_window_id, height=self._parent_canvas.winfo_height())
elif self._orientation == "vertical":
@ -244,7 +247,7 @@ class CTkScrollableFrame(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBa
elif sys.platform == "darwin":
self._parent_canvas.configure(xscrollincrement=4, yscrollincrement=8)
def _mouse_wheel_all(self, event):
def _mouse_wheel_all(self, event: tkinter.Event[Any] | None = None):
if self.check_if_master_is_canvas(event.widget):
if sys.platform.startswith("win"):
if self._shift_pressed:
@ -268,10 +271,10 @@ class CTkScrollableFrame(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBa
if self._parent_canvas.yview() != (0.0, 1.0):
self._parent_canvas.yview("scroll", -event.delta, "units")
def _keyboard_shift_press_all(self, event):
def _keyboard_shift_press_all(self, event: tkinter.Event[Any] | None = None):
self._shift_pressed = True
def _keyboard_shift_release_all(self, event):
def _keyboard_shift_release_all(self, event: tkinter.Event[Any] | None = None):
self._shift_pressed = False
def check_if_master_is_canvas(self, widget):
@ -282,35 +285,35 @@ class CTkScrollableFrame(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBa
else:
return False
def pack(self, **kwargs):
def pack(self, **kwargs: Any):
self._parent_frame.pack(**kwargs)
def place(self, **kwargs):
def place(self, **kwargs: Any):
self._parent_frame.place(**kwargs)
def grid(self, **kwargs):
def grid(self, **kwargs: Any):
self._parent_frame.grid(**kwargs)
def pack_forget(self):
self._parent_frame.pack_forget()
def place_forget(self, **kwargs):
def place_forget(self, **kwargs: Any):
self._parent_frame.place_forget()
def grid_forget(self, **kwargs):
def grid_forget(self, **kwargs: Any):
self._parent_frame.grid_forget()
def grid_remove(self, **kwargs):
def grid_remove(self, **kwargs: Any):
self._parent_frame.grid_remove()
def grid_propagate(self, **kwargs):
def grid_propagate(self, **kwargs: Any):
self._parent_frame.grid_propagate()
def grid_info(self, **kwargs):
def grid_info(self, **kwargs: Any):
return self._parent_frame.grid_info()
def lift(self, aboveThis=None):
def lift(self, aboveThis: Any =None):
self._parent_frame.lift(aboveThis)
def lower(self, belowThis=None):
def lower(self, belowThis: Any = None):
self._parent_frame.lower(belowThis)

View File

@ -1,10 +1,17 @@
import sys
from typing import Union, Tuple, Callable, Optional
from __future__ import annotations
from .core_rendering import CTkCanvas
from .theme import ThemeManager
from .core_rendering import DrawEngine
import sys
import tkinter
from typing import Any, Callable
if sys.version_info >= (3, 8):
from typing import Literal
else:
from typing_extensions import Literal
from .core_rendering import CTkCanvas, DrawEngine
from .core_widget_classes import CTkBaseClass
from .theme import ThemeManager
class CTkScrollbar(CTkBaseClass):
@ -15,22 +22,22 @@ class CTkScrollbar(CTkBaseClass):
"""
def __init__(self,
master: any,
width: Optional[Union[int, str]] = None,
height: Optional[Union[int, str]] = None,
corner_radius: Optional[int] = None,
border_spacing: Optional[int] = None,
master: CTkBaseClass,
width: int | str | None = None,
height: int | str | None = None,
corner_radius: int | None = None,
border_spacing: int | None = None,
minimum_pixel_length: int = 20,
bg_color: Union[str, Tuple[str, str]] = "transparent",
fg_color: Optional[Union[str, Tuple[str, str]]] = None,
button_color: Optional[Union[str, Tuple[str, str]]] = None,
button_hover_color: Optional[Union[str, Tuple[str, str]]] = None,
bg_color: str | tuple[str, str] = "transparent",
fg_color: str | tuple[str, str] | None = None,
button_color: str | tuple[str, str] | None = None,
button_hover_color: str | tuple[str, str] | None = None,
hover: bool = True,
command: Union[Callable, None] = None,
command: Callable[..., None] | None = None,
orientation: str = "vertical",
**kwargs):
**kwargs: Any):
# set default dimensions according to orientation
if width is None:
@ -74,7 +81,7 @@ class CTkScrollbar(CTkBaseClass):
self._create_bindings()
self._draw()
def _create_bindings(self, sequence: Optional[str] = None):
def _create_bindings(self, sequence: str | None = 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)
@ -87,14 +94,14 @@ class CTkScrollbar(CTkBaseClass):
if sequence is None or sequence == "<MouseWheel>":
self._canvas.bind("<MouseWheel>", self._mouse_scroll_event)
def _set_scaling(self, *args, **kwargs):
def _set_scaling(self, *args: Any, **kwargs: Any):
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)
def _set_dimensions(self, width=None, height=None):
def _set_dimensions(self, width: int | None = None, height: int | None = None):
super()._set_dimensions(width, height)
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
@ -125,7 +132,7 @@ class CTkScrollbar(CTkBaseClass):
else:
return self._start_value, self._end_value
def _draw(self, no_color_updates=False):
def _draw(self, no_color_updates: bool = False):
super()._draw(no_color_updates)
corrected_start_value, corrected_end_value = self._get_scrollbar_values_for_minimum_pixel_size()
@ -160,7 +167,7 @@ class CTkScrollbar(CTkBaseClass):
self._canvas.update_idletasks()
def configure(self, require_redraw=False, **kwargs):
def configure(self, require_redraw: bool = False, **kwargs: Any):
if "fg_color" in kwargs:
self._fg_color = self._check_color_type(kwargs.pop("fg_color"), transparency=True)
require_redraw = True
@ -189,7 +196,7 @@ class CTkScrollbar(CTkBaseClass):
super().configure(require_redraw=require_redraw, **kwargs)
def cget(self, attribute_name: str) -> any:
def cget(self, attribute_name: str) -> Any:
if attribute_name == "corner_radius":
return self._corner_radius
elif attribute_name == "border_spacing":
@ -214,24 +221,24 @@ class CTkScrollbar(CTkBaseClass):
else:
return super().cget(attribute_name)
def _on_enter(self, event=0):
def _on_enter(self, event: tkinter.Event[Any] | None = None):
if self._hover is True:
self._hover_state = True
self._canvas.itemconfig("scrollbar_parts",
outline=self._apply_appearance_mode(self._button_hover_color),
fill=self._apply_appearance_mode(self._button_hover_color))
def _on_leave(self, event=0):
def _on_leave(self, event: tkinter.Event[Any] | None = None):
self._hover_state = False
self._canvas.itemconfig("scrollbar_parts",
outline=self._apply_appearance_mode(self._button_color),
fill=self._apply_appearance_mode(self._button_color))
def _clicked(self, event):
def _clicked(self, event: tkinter.Event[Any] | None = None):
if self._orientation == "vertical":
value = self._reverse_widget_scaling(((event.y - self._border_spacing) / (self._current_height - 2 * self._border_spacing)))
value = self._reverse_widget_scaling((event.y - self._border_spacing) / (self._current_height - 2 * self._border_spacing))
else:
value = self._reverse_widget_scaling(((event.x - self._border_spacing) / (self._current_width - 2 * self._border_spacing)))
value = self._reverse_widget_scaling((event.x - self._border_spacing) / (self._current_width - 2 * self._border_spacing))
current_scrollbar_length = self._end_value - self._start_value
value = max(current_scrollbar_length / 2, min(value, 1 - (current_scrollbar_length / 2)))
@ -242,7 +249,7 @@ class CTkScrollbar(CTkBaseClass):
if self._command is not None:
self._command('moveto', self._start_value)
def _mouse_scroll_event(self, event=None):
def _mouse_scroll_event(self, event: tkinter.Event[Any] | None = None):
if self._command is not None:
if sys.platform.startswith("win"):
self._command('scroll', -int(event.delta/40), 'units')
@ -257,7 +264,7 @@ class CTkScrollbar(CTkBaseClass):
def get(self):
return self._start_value, self._end_value
def bind(self, sequence=None, command=None, add=True):
def bind(self, sequence=None, command: Callable[..., None] | None = None, add: Literal["+"] | 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")

View File

@ -1,15 +1,13 @@
import tkinter
import copy
from typing import Union, Tuple, List, Dict, Callable, Optional
try:
from typing import Literal
except ImportError:
from typing_extensions import Literal
from __future__ import annotations
import copy
import tkinter
from typing import Any, Callable
from .theme import ThemeManager
from .font import CTkFont
from .ctk_button import CTkButton
from .ctk_frame import CTkFrame
from .font import CTkFont
from .theme import ThemeManager
class CTkSegmentedButton(CTkFrame):
@ -19,29 +17,29 @@ class CTkSegmentedButton(CTkFrame):
"""
def __init__(self,
master: any,
master: CTkBaseClass,
width: int = 140,
height: int = 28,
corner_radius: Optional[int] = None,
corner_radius: int | None = None,
border_width: int = 3,
bg_color: Union[str, Tuple[str, str]] = "transparent",
fg_color: Optional[Union[str, Tuple[str, str]]] = None,
selected_color: Optional[Union[str, Tuple[str, str]]] = None,
selected_hover_color: Optional[Union[str, Tuple[str, str]]] = None,
unselected_color: Optional[Union[str, Tuple[str, str]]] = None,
unselected_hover_color: Optional[Union[str, Tuple[str, str]]] = None,
text_color: Optional[Union[str, Tuple[str, str]]] = None,
text_color_disabled: Optional[Union[str, Tuple[str, str]]] = None,
background_corner_colors: Union[Tuple[Union[str, Tuple[str, str]]], None] = None,
bg_color: str | tuple[str, str] = "transparent",
fg_color: str | tuple[str, str] | None = None,
selected_color: str | tuple[str, str] | None = None,
selected_hover_color: str | tuple[str, str] | None = None,
unselected_color: str | tuple[str, str] | None = None,
unselected_hover_color: str | tuple[str, str] | None = None,
text_color: str | tuple[str, str] | None = None,
text_color_disabled: str | tuple[str, str] | None = None,
background_corner_colors: tuple[str | tuple[str, str]] | None = None,
font: Optional[Union[tuple, CTkFont]] = None,
values: Optional[list] = None,
variable: Union[tkinter.Variable, None] = None,
font: tuple[Any, ...] | CTkFont | None = None,
values: list[str] | None = None,
variable: tkinter.Variable | None = None,
dynamic_resizing: bool = True,
command: Union[Callable[[str], None], None] = None,
command: Callable[[str], None] | None = None,
state: str = "normal",
**kwargs):
**kwargs: Any):
super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
@ -61,15 +59,15 @@ class CTkSegmentedButton(CTkFrame):
self._background_corner_colors = background_corner_colors # rendering options for DrawEngine
self._command: Callable[[str], None] = command
self._command = command
self._font = CTkFont() if font is None else font
self._state = state
self._buttons_dict: Dict[str, CTkButton] = {} # mapped from value to button object
self._buttons_dict: dict[str, CTkButton] = {} # mapped from value to button object
if values is None:
self._value_list: List[str] = ["CTkSegmentedButton"]
self._value_list: list[str] = ["CTkSegmentedButton"]
else:
self._value_list: List[str] = values # Values ordered like buttons rendered on widget
self._value_list: list[str] = values # Values ordered like buttons rendered on widget
self._dynamic_resizing = dynamic_resizing
if not self._dynamic_resizing:
@ -83,7 +81,7 @@ class CTkSegmentedButton(CTkFrame):
self._variable = variable
self._variable_callback_blocked: bool = False
self._variable_callback_name: Union[str, None] = None
self._variable_callback_name: str | None = None
if self._variable is not None:
self._variable_callback_name = self._variable.trace_add("write", self._variable_callback)
@ -97,13 +95,13 @@ class CTkSegmentedButton(CTkFrame):
super().destroy()
def _set_dimensions(self, width: int = None, height: int = None):
def _set_dimensions(self, width: int | None = None, height: int | None = None):
super()._set_dimensions(width, height)
for button in self._buttons_dict.values():
button.configure(height=height)
def _variable_callback(self, var_name, index, mode):
def _variable_callback(self, var_name: str, index: int, mode: Any):
if not self._variable_callback_blocked:
self.set(self._variable.get(), from_variable_callback=True)
@ -172,7 +170,7 @@ class CTkSegmentedButton(CTkFrame):
return new_button
@staticmethod
def _check_unique_values(values: List[str]):
def _check_unique_values(values: list[str]):
""" raises exception if values are not unique """
if len(values) != len(set(values)):
raise ValueError("CTkSegmentedButton values are not unique")
@ -196,7 +194,7 @@ class CTkSegmentedButton(CTkFrame):
self._buttons_dict[value] = self._create_button(index, value)
self._configure_button_corners_for_index(index)
def configure(self, **kwargs):
def configure(self, **kwargs: Any):
if "bg_color" in kwargs:
super().configure(bg_color=kwargs.pop("bg_color"))
@ -298,7 +296,7 @@ class CTkSegmentedButton(CTkFrame):
super().configure(**kwargs)
def cget(self, attribute_name: str) -> any:
def cget(self, attribute_name: str) -> Any:
if attribute_name == "corner_radius":
return self._sb_corner_radius
elif attribute_name == "border_width":
@ -413,9 +411,8 @@ class CTkSegmentedButton(CTkFrame):
else:
raise ValueError(f"CTkSegmentedButton does not contain value '{value}'")
def bind(self, sequence=None, command=None, add=None):
def bind(self, sequence: Any =None, command: Any =None, add: Any = None):
raise NotImplementedError
def unbind(self, sequence=None, funcid=None):
def unbind(self, sequence: Any = None, funcid: Any = None):
raise NotImplementedError

View File

@ -1,11 +1,12 @@
import tkinter
import sys
from typing import Union, Tuple, Callable, Optional
from __future__ import annotations
from .core_rendering import CTkCanvas
from .theme import ThemeManager
from .core_rendering import DrawEngine
import sys
import tkinter
from typing import Any, Callable
from .core_rendering import CTkCanvas, DrawEngine
from .core_widget_classes import CTkBaseClass
from .theme import ThemeManager
class CTkSlider(CTkBaseClass):
@ -15,30 +16,30 @@ class CTkSlider(CTkBaseClass):
"""
def __init__(self,
master: any,
width: Optional[int] = None,
height: Optional[int] = None,
corner_radius: Optional[int] = None,
button_corner_radius: Optional[int] = None,
border_width: Optional[int] = None,
button_length: Optional[int] = None,
master: CTkBaseClass,
width: int | None = None,
height: int | None = None,
corner_radius: int | None = None,
button_corner_radius: int | None = None,
border_width: int | None = None,
button_length: int | None = None,
bg_color: Union[str, Tuple[str, str]] = "transparent",
fg_color: Optional[Union[str, Tuple[str, str]]] = None,
border_color: Union[str, Tuple[str, str]] = "transparent",
progress_color: Optional[Union[str, Tuple[str, str]]] = None,
button_color: Optional[Union[str, Tuple[str, str]]] = None,
button_hover_color: Optional[Union[str, Tuple[str, str]]] = None,
bg_color: str | tuple[str, str] = "transparent",
fg_color: str | tuple[str, str] | None = None,
border_color: str | tuple[str, str] = "transparent",
progress_color: str | tuple[str, str] | None = None,
button_color: str | tuple[str, str] | None = None,
button_hover_color: str | tuple[str, str] | None = None,
from_: int = 0,
to: int = 1,
state: str = "normal",
number_of_steps: Union[int, None] = None,
number_of_steps: int | None = None,
hover: bool = True,
command: Union[Callable[[float], None], None] = None,
variable: Union[tkinter.Variable, None] = None,
command: Callable[[float], None] | None = None,
variable: tkinter.Variable | None = None,
orientation: str = "horizontal",
**kwargs):
**kwargs: Any):
# set default dimensions according to orientation
if width is None:
@ -81,9 +82,9 @@ class CTkSlider(CTkBaseClass):
# callback and control variables
self._command = command
self._variable: tkinter.Variable = variable
self._variable_callback_blocked: bool = False
self._variable_callback_name: Union[bool, None] = None
self._variable = variable
self._variable_callback_blocked = False
self._variable_callback_name: bool | None = None
self._state = state
self.grid_rowconfigure(0, weight=1)
@ -106,7 +107,7 @@ 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):
def _create_bindings(self, sequence: str | None = 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)
@ -117,14 +118,14 @@ class CTkSlider(CTkBaseClass):
if sequence is None or sequence == "<B1-Motion>":
self._canvas.bind("<B1-Motion>", self._clicked)
def _set_scaling(self, *args, **kwargs):
def _set_scaling(self, *args: Any, **kwargs: Any):
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)
def _set_dimensions(self, width=None, height=None):
def _set_dimensions(self, width: int | None = None, height: int | None = None):
super()._set_dimensions(width, height)
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
@ -151,7 +152,7 @@ class CTkSlider(CTkBaseClass):
elif sys.platform.startswith("win"):
self.configure(cursor="arrow")
def _draw(self, no_color_updates=False):
def _draw(self, no_color_updates: bool = False):
super()._draw(no_color_updates)
if self._orientation.lower() == "horizontal":
@ -198,7 +199,7 @@ class CTkSlider(CTkBaseClass):
fill=self._apply_appearance_mode(self._button_color),
outline=self._apply_appearance_mode(self._button_color))
def configure(self, require_redraw=False, **kwargs):
def configure(self, require_redraw: bool = False, **kwargs: Any):
if "state" in kwargs:
self._state = kwargs.pop("state")
self._set_cursor()
@ -257,7 +258,7 @@ class CTkSlider(CTkBaseClass):
super().configure(require_redraw=require_redraw, **kwargs)
def cget(self, attribute_name: str) -> any:
def cget(self, attribute_name: str) -> Any:
if attribute_name == "corner_radius":
return self._corner_radius
elif attribute_name == "button_corner_radius":
@ -298,7 +299,7 @@ class CTkSlider(CTkBaseClass):
else:
return super().cget(attribute_name)
def _clicked(self, event=None):
def _clicked(self, event: tkinter.Event[Any] | None = None):
if self._state == "normal":
if self._orientation.lower() == "horizontal":
self._value = self._reverse_widget_scaling(event.x / self._current_width)
@ -323,20 +324,20 @@ class CTkSlider(CTkBaseClass):
if self._command is not None:
self._command(self._output_value)
def _on_enter(self, event=0):
def _on_enter(self, event: tkinter.Event[Any] | None = None):
if self._hover is True and self._state == "normal":
self._hover_state = True
self._canvas.itemconfig("slider_parts",
fill=self._apply_appearance_mode(self._button_hover_color),
outline=self._apply_appearance_mode(self._button_hover_color))
def _on_leave(self, event=0):
def _on_leave(self, event: tkinter.Event[Any] | None = None):
self._hover_state = False
self._canvas.itemconfig("slider_parts",
fill=self._apply_appearance_mode(self._button_color),
outline=self._apply_appearance_mode(self._button_color))
def _round_to_step_size(self, value) -> float:
def _round_to_step_size(self, value: float) -> 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)
@ -347,7 +348,7 @@ class CTkSlider(CTkBaseClass):
def get(self) -> float:
return self._output_value
def set(self, output_value, from_variable_callback=False):
def set(self, output_value: float, from_variable_callback: bool = False):
if self._from_ < self._to:
if output_value > self._to:
output_value = self._to
@ -369,17 +370,17 @@ class CTkSlider(CTkBaseClass):
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):
def _variable_callback(self, var_name: str, index: int, mode: Any):
if not self._variable_callback_blocked:
self.set(self._variable.get(), from_variable_callback=True)
def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True):
def bind(self, sequence: str, command: Callable[..., None] | None = None, add: 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 = None, funcid: str = None):
def unbind(self, sequence: str, funcid: str | None = 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" +

View File

@ -1,12 +1,18 @@
import tkinter
import sys
from typing import Union, Tuple, Callable, Optional
from __future__ import annotations
from .core_rendering import CTkCanvas
from .theme import ThemeManager
from .core_rendering import DrawEngine
import sys
import tkinter
from typing import Any, Callable
if sys.version_info >= (3, 8):
from typing import Literal
else:
from typing_extensions import Literal
from .core_rendering import CTkCanvas, DrawEngine
from .core_widget_classes import CTkBaseClass
from .font import CTkFont
from .theme import ThemeManager
class CTkSwitch(CTkBaseClass):
@ -16,34 +22,34 @@ class CTkSwitch(CTkBaseClass):
"""
def __init__(self,
master: any,
master: CTkBaseClass,
width: int = 100,
height: int = 24,
switch_width: int = 36,
switch_height: int = 18,
corner_radius: Optional[int] = None,
border_width: Optional[int] = None,
button_length: Optional[int] = None,
corner_radius: int | None = None,
border_width: int | None = None,
button_length: int | None = None,
bg_color: Union[str, Tuple[str, str]] = "transparent",
fg_color: Optional[Union[str, Tuple[str, str]]] = None,
border_color: Union[str, Tuple[str, str]] = "transparent",
progress_color: Optional[Union[str, Tuple[str, str]]] = None,
button_color: Optional[Union[str, Tuple[str, str]]] = None,
button_hover_color: Optional[Union[str, Tuple[str, str]]] = None,
text_color: Optional[Union[str, Tuple[str, str]]] = None,
text_color_disabled: Optional[Union[str, Tuple[str, str]]] = None,
bg_color: str | tuple[str, str] = "transparent",
fg_color: str | tuple[str, str] | None = None,
border_color: str | tuple[str, str] = "transparent",
progress_color: str | tuple[str, str] | None = None,
button_color: str | tuple[str, str] | None = None,
button_hover_color: str | tuple[str, str] | None = None,
text_color: str | tuple[str, str] | None = None,
text_color_disabled: str | tuple[str, str] | None = None,
text: str = "CTkSwitch",
font: Optional[Union[tuple, CTkFont]] = None,
textvariable: Union[tkinter.Variable, None] = None,
onvalue: Union[int, str] = 1,
offvalue: Union[int, str] = 0,
variable: Union[tkinter.Variable, None] = None,
font: tuple[Any, ...] | CTkFont | None = None,
textvariable: tkinter.Variable | None = None,
onvalue: int | str = 1,
offvalue: int | str = 0,
variable: tkinter.Variable | None = None,
hover: bool = True,
command: Union[Callable, None] = None,
state: str = tkinter.NORMAL,
**kwargs):
command: Callable[..., None] | None = None,
state: Literal["normal", "disabled", "readonly"] = "normal",
**kwargs: Any):
# transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass
super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
@ -126,7 +132,7 @@ class CTkSwitch(CTkBaseClass):
self._set_cursor()
self._draw() # initial draw
def _create_bindings(self, sequence: Optional[str] = None):
def _create_bindings(self, sequence: str | None = 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)
@ -138,7 +144,7 @@ class CTkSwitch(CTkBaseClass):
self._canvas.bind("<Button-1>", self.toggle)
self._text_label.bind("<Button-1>", self.toggle)
def _set_scaling(self, *args, **kwargs):
def _set_scaling(self, *args: Any, **kwargs: Any):
super()._set_scaling(*args, **kwargs)
self.grid_columnconfigure(1, weight=0, minsize=self._apply_widget_scaling(6))
@ -150,7 +156,7 @@ class CTkSwitch(CTkBaseClass):
height=self._apply_widget_scaling(self._switch_height))
self._draw(no_color_updates=True)
def _set_dimensions(self, width: int = None, height: int = None):
def _set_dimensions(self, width: int | None = None, height: int | None = None):
super()._set_dimensions(width, height)
self._bg_canvas.configure(width=self._apply_widget_scaling(self._desired_width),
@ -197,7 +203,7 @@ class CTkSwitch(CTkBaseClass):
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: bool = False):
super()._draw(no_color_updates)
if self._check_state is True:
@ -254,7 +260,7 @@ class CTkSwitch(CTkBaseClass):
self._text_label.configure(bg=self._apply_appearance_mode(self._bg_color))
def configure(self, require_redraw=False, **kwargs):
def configure(self, require_redraw: bool = False, **kwargs: Any):
if "corner_radius" in kwargs:
self._corner_radius = kwargs.pop("corner_radius")
require_redraw = True
@ -338,7 +344,7 @@ class CTkSwitch(CTkBaseClass):
super().configure(require_redraw=require_redraw, **kwargs)
def cget(self, attribute_name: str) -> any:
def cget(self, attribute_name: str) -> Any:
if attribute_name == "corner_radius":
return self._corner_radius
elif attribute_name == "border_width":
@ -387,7 +393,7 @@ class CTkSwitch(CTkBaseClass):
else:
return super().cget(attribute_name)
def toggle(self, event=None):
def toggle(self, event: tkinter.Event[Any] | None = None):
if self._state is not tkinter.DISABLED:
if self._check_state is True:
self._check_state = False
@ -404,7 +410,7 @@ class CTkSwitch(CTkBaseClass):
if self._command is not None:
self._command()
def select(self, from_variable_callback=False):
def select(self, from_variable_callback: bool = False):
if self._state is not tkinter.DISABLED or from_variable_callback:
self._check_state = True
@ -415,7 +421,7 @@ class CTkSwitch(CTkBaseClass):
self._variable.set(self._onvalue)
self._variable_callback_blocked = False
def deselect(self, from_variable_callback=False):
def deselect(self, from_variable_callback: bool = False):
if self._state is not tkinter.DISABLED or from_variable_callback:
self._check_state = False
@ -426,37 +432,37 @@ class CTkSwitch(CTkBaseClass):
self._variable.set(self._offvalue)
self._variable_callback_blocked = False
def get(self) -> Union[int, str]:
def get(self) -> int | str:
return self._onvalue if self._check_state is True else self._offvalue
def _on_enter(self, event=0):
def _on_enter(self, event: tkinter.Event[Any] | None = None):
if self._hover is True and self._state == "normal":
self._hover_state = True
self._canvas.itemconfig("slider_parts",
fill=self._apply_appearance_mode(self._button_hover_color),
outline=self._apply_appearance_mode(self._button_hover_color))
def _on_leave(self, event=0):
def _on_leave(self, event: tkinter.Event[Any] | None = None):
self._hover_state = False
self._canvas.itemconfig("slider_parts",
fill=self._apply_appearance_mode(self._button_color),
outline=self._apply_appearance_mode(self._button_color))
def _variable_callback(self, var_name, index, mode):
def _variable_callback(self, var_name: str, index: int, mode: Any):
if not self._variable_callback_blocked:
if self._variable.get() == self._onvalue:
self.select(from_variable_callback=True)
elif self._variable.get() == self._offvalue:
self.deselect(from_variable_callback=True)
def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True):
def bind(self, sequence: str, command: Callable[..., None] | None = None, add: 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 = None, funcid: str = None):
def unbind(self, sequence: str, funcid: str | None = 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" +

View File

@ -1,12 +1,13 @@
import tkinter
from typing import Union, Tuple, Dict, List, Callable, Optional
from __future__ import annotations
from .theme import ThemeManager
from .ctk_frame import CTkFrame
from .core_rendering import CTkCanvas
from .core_rendering import DrawEngine
import tkinter
from typing import Any, Callable
from .core_rendering import CTkCanvas, DrawEngine
from .core_widget_classes import CTkBaseClass
from .ctk_frame import CTkFrame
from .ctk_segmented_button import CTkSegmentedButton
from .theme import ThemeManager
class CTkTabview(CTkBaseClass):
@ -21,28 +22,28 @@ class CTkTabview(CTkBaseClass):
_segmented_button_border_width: int = 3
def __init__(self,
master: any,
master: CTkBaseClass,
width: int = 300,
height: int = 250,
corner_radius: Optional[int] = None,
border_width: Optional[int] = None,
corner_radius: int | None = None,
border_width: int | None = None,
bg_color: Union[str, Tuple[str, str]] = "transparent",
fg_color: Optional[Union[str, Tuple[str, str]]] = None,
border_color: Optional[Union[str, Tuple[str, str]]] = None,
bg_color: str | tuple[str, str] = "transparent",
fg_color: str | tuple[str, str] | None = None,
border_color: str | tuple[str, str] | None = None,
segmented_button_fg_color: Optional[Union[str, Tuple[str, str]]] = None,
segmented_button_selected_color: Optional[Union[str, Tuple[str, str]]] = None,
segmented_button_selected_hover_color: Optional[Union[str, Tuple[str, str]]] = None,
segmented_button_unselected_color: Optional[Union[str, Tuple[str, str]]] = None,
segmented_button_unselected_hover_color: Optional[Union[str, Tuple[str, str]]] = None,
segmented_button_fg_color: str | tuple[str, str] | None = None,
segmented_button_selected_color: str | tuple[str, str] | None = None,
segmented_button_selected_hover_color: str | tuple[str, str] | None = None,
segmented_button_unselected_color: str | tuple[str, str] | None = None,
segmented_button_unselected_hover_color: str | tuple[str, str] | None = None,
text_color: Optional[Union[str, Tuple[str, str]]] = None,
text_color_disabled: Optional[Union[str, Tuple[str, str]]] = None,
text_color: str | tuple[str, str] | None = None,
text_color_disabled: str | tuple[str, str] | None = None,
command: Union[Callable, None] = None,
command: Callable[..., None] | None = None,
state: str = "normal",
**kwargs):
**kwargs: Any):
# transfer some functionality to CTkFrame
super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
@ -91,14 +92,14 @@ class CTkTabview(CTkBaseClass):
self._configure_grid()
self._set_grid_canvas()
self._tab_dict: Dict[str, CTkFrame] = {}
self._name_list: List[str] = [] # list of unique tab names in order of tabs
self._tab_dict: dict[str, CTkFrame] = {}
self._name_list: list[str] = [] # list of unique tab names in order of tabs
self._current_name: str = ""
self._command = command
self._draw()
def _segmented_button_callback(self, selected_name):
def _segmented_button_callback(self, selected_name: str):
self._current_name = selected_name
self._grid_forget_all_tabs()
self._set_grid_tab_by_name(self._current_name)
@ -106,7 +107,7 @@ class CTkTabview(CTkBaseClass):
if self._command is not None:
self._command()
def winfo_children(self) -> List[any]:
def winfo_children(self) -> list[Any]:
"""
winfo_children of CTkTabview without canvas and segmented button widgets,
because it's not a child but part of the CTkTabview itself
@ -120,7 +121,7 @@ class CTkTabview(CTkBaseClass):
except ValueError:
return child_widgets
def _set_scaling(self, *args, **kwargs):
def _set_scaling(self, *args: Any, **kwargs: Any):
super()._set_scaling(*args, **kwargs)
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
@ -128,7 +129,7 @@ class CTkTabview(CTkBaseClass):
self._configure_grid()
self._draw(no_color_updates=True)
def _set_dimensions(self, width=None, height=None):
def _set_dimensions(self, width: int | None = None, height: int | None = None):
super()._set_dimensions(width, height)
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
@ -211,7 +212,7 @@ class CTkTabview(CTkBaseClass):
self._canvas.configure(bg=self._apply_appearance_mode(self._bg_color))
tkinter.Frame.configure(self, bg=self._apply_appearance_mode(self._bg_color)) # configure bg color of tkinter.Frame, cuase canvas does not fill frame
def configure(self, require_redraw=False, **kwargs):
def configure(self, require_redraw: bool = False, **kwargs: Any):
if "corner_radius" in kwargs:
self._corner_radius = kwargs.pop("corner_radius")
require_redraw = True

View File

@ -1,13 +1,20 @@
import tkinter
from typing import Union, Tuple, Optional, Callable
from __future__ import annotations
from .core_rendering import CTkCanvas
from .ctk_scrollbar import CTkScrollbar
from .theme import ThemeManager
from .core_rendering import DrawEngine
import sys
import tkinter
from typing import Any, Callable
if sys.version_info >= (3, 8):
from typing import Literal
else:
from typing_extensions import Literal
from .core_rendering import CTkCanvas, DrawEngine
from .core_widget_classes import CTkBaseClass
from .ctk_scrollbar import CTkScrollbar
from .font import CTkFont
from .utility import pop_from_dict_by_set, check_kwargs_empty
from .theme import ThemeManager
from .utility import check_kwargs_empty, pop_from_dict_by_set
class CTkTextbox(CTkBaseClass):
@ -32,23 +39,23 @@ class CTkTextbox(CTkBaseClass):
"xscrollcommand", "yscrollcommand"}
def __init__(self,
master: any,
master: CTkBaseClass,
width: int = 200,
height: int = 200,
corner_radius: Optional[int] = None,
border_width: Optional[int] = None,
corner_radius: int | None = None,
border_width: int | None = None,
border_spacing: int = 3,
bg_color: Union[str, Tuple[str, str]] = "transparent",
fg_color: Optional[Union[str, Tuple[str, str]]] = None,
border_color: Optional[Union[str, Tuple[str, str]]] = None,
text_color: Optional[Union[str, str]] = None,
scrollbar_button_color: Optional[Union[str, Tuple[str, str]]] = None,
scrollbar_button_hover_color: Optional[Union[str, Tuple[str, str]]] = None,
bg_color: str | tuple[str, str] = "transparent",
fg_color: str | tuple[str, str] | None = None,
border_color: str | tuple[str, str] | None = None,
text_color: str | str | None = None,
scrollbar_button_color: str | tuple[str, str] | None = None,
scrollbar_button_hover_color: str | tuple[str, str] | None = None,
font: Optional[Union[tuple, CTkFont]] = None,
font: tuple[Any, ...] | CTkFont | None = None,
activate_scrollbars: bool = True,
**kwargs):
**kwargs: Any):
# transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass
super().__init__(master=master, bg_color=bg_color, width=width, height=height)
@ -122,7 +129,7 @@ class CTkTextbox(CTkBaseClass):
self.after(50, self._check_if_scrollbars_needed, None, True)
self._draw()
def _create_grid_for_text_and_scrollbars(self, re_grid_textbox=False, re_grid_x_scrollbar=False, re_grid_y_scrollbar=False):
def _create_grid_for_text_and_scrollbars(self, re_grid_textbox: bool = False, re_grid_x_scrollbar: bool = False, re_grid_y_scrollbar: bool = False):
# configure 2x2 grid
self.grid_rowconfigure(0, weight=1)
@ -151,7 +158,7 @@ class CTkTextbox(CTkBaseClass):
else:
self._y_scrollbar.grid_forget()
def _check_if_scrollbars_needed(self, event=None, continue_loop: bool = False):
def _check_if_scrollbars_needed(self, event: tkinter.Event[Any] | None = None, continue_loop: bool = False):
""" Method hides or places the scrollbars if they are needed on key release event of tkinter.text widget """
if self._scrollbars_activated:
@ -176,7 +183,7 @@ class CTkTextbox(CTkBaseClass):
if self._textbox.winfo_exists() and continue_loop is True:
self.after(self._scrollbar_update_time, lambda: self._check_if_scrollbars_needed(continue_loop=True))
def _set_scaling(self, *args, **kwargs):
def _set_scaling(self, *args: Any, **kwargs: Any):
super()._set_scaling(*args, **kwargs)
self._textbox.configure(font=self._apply_font_scaling(self._font))
@ -185,7 +192,7 @@ class CTkTextbox(CTkBaseClass):
self._create_grid_for_text_and_scrollbars(re_grid_textbox=True, re_grid_x_scrollbar=True, re_grid_y_scrollbar=True)
self._draw(no_color_updates=True)
def _set_dimensions(self, width=None, height=None):
def _set_dimensions(self, width: int | None = None, height: int | None = None):
super()._set_dimensions(width, height)
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
@ -207,7 +214,7 @@ class CTkTextbox(CTkBaseClass):
super().destroy()
def _draw(self, no_color_updates=False):
def _draw(self, no_color_updates: bool = False):
super()._draw(no_color_updates)
if not self._canvas.winfo_exists():
@ -250,7 +257,7 @@ class CTkTextbox(CTkBaseClass):
self._canvas.tag_lower("inner_parts")
self._canvas.tag_lower("border_parts")
def configure(self, require_redraw=False, **kwargs):
def configure(self, require_redraw: bool = False, **kwargs: Any):
if "fg_color" in kwargs:
self._fg_color = self._check_color_type(kwargs.pop("fg_color"), transparency=True)
require_redraw = True
@ -305,7 +312,7 @@ class CTkTextbox(CTkBaseClass):
self._textbox.configure(**pop_from_dict_by_set(kwargs, self._valid_tk_text_attributes))
super().configure(require_redraw=require_redraw, **kwargs)
def cget(self, attribute_name: str) -> any:
def cget(self, attribute_name: str) -> Any:
if attribute_name == "corner_radius":
return self._corner_radius
elif attribute_name == "border_width":
@ -326,13 +333,13 @@ class CTkTextbox(CTkBaseClass):
else:
return super().cget(attribute_name)
def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True):
def bind(self, sequence: str, command: Callable[..., None] | None = None, add: 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)
def unbind(self, sequence: str = None, funcid: str = None):
def unbind(self, sequence: str, funcid: str | None = 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" +
@ -348,7 +355,7 @@ class CTkTextbox(CTkBaseClass):
def focus_force(self):
return self._textbox.focus_force()
def insert(self, index, text, tags=None):
def insert(self, index: int, text: str, tags=None):
return self._textbox.insert(index, text, tags)
def get(self, index1, index2=None):
@ -357,7 +364,7 @@ class CTkTextbox(CTkBaseClass):
def bbox(self, index):
return self._textbox.bbox(index)
def compare(self, index, op, index2):
def compare(self, index: int, op, index2):
return self._textbox.compare(index, op, index2)
def delete(self, index1, index2=None):
@ -383,10 +390,10 @@ class CTkTextbox(CTkBaseClass):
self._check_if_scrollbars_needed()
return self._textbox.edit_undo()
def image_create(self, index, **kwargs):
def image_create(self, index: int, **kwargs: Any):
raise AttributeError("embedding images is forbidden, because would be incompatible with scaling")
def image_cget(self, index, option):
def image_cget(self, index: int, option):
raise AttributeError("embedding images is forbidden, because would be incompatible with scaling")
def image_configure(self, index):
@ -422,78 +429,78 @@ class CTkTextbox(CTkBaseClass):
def scan_mark(self, x, y):
return self._textbox.scan_mark(x, y)
def search(self, pattern, index, *args, **kwargs):
def search(self, pattern, index: int, *args: Any, **kwargs: Any):
return self._textbox.search(pattern, index, *args, **kwargs)
def see(self, index):
return self._textbox.see(index)
def tag_add(self, tagName, index1, index2=None):
def tag_add(self, tagName: str, index1, index2=None):
return self._textbox.tag_add(tagName, index1, index2)
def tag_bind(self, tagName, sequence, func, add=None):
def tag_bind(self, tagName: str, sequence: str, func, add: bool | Literal["", "+"] | None = None):
return self._textbox.tag_bind(tagName, sequence, func, add)
def tag_cget(self, tagName, option):
def tag_cget(self, tagName: str, option: str):
return self._textbox.tag_cget(tagName, option)
def tag_config(self, tagName, **kwargs):
def tag_config(self, tagName: str, **kwargs: Any):
if "font" in kwargs:
raise AttributeError("'font' option forbidden, because would be incompatible with scaling")
return self._textbox.tag_config(tagName, **kwargs)
def tag_delete(self, *tagName):
def tag_delete(self, *tagName: str):
return self._textbox.tag_delete(*tagName)
def tag_lower(self, tagName, belowThis=None):
def tag_lower(self, tagName: str, belowThis=None):
return self._textbox.tag_lower(tagName, belowThis)
def tag_names(self, index=None):
return self._textbox.tag_names(index)
def tag_nextrange(self, tagName, index1, index2=None):
def tag_nextrange(self, tagName: str, index1, index2=None):
return self._textbox.tag_nextrange(tagName, index1, index2)
def tag_prevrange(self, tagName, index1, index2=None):
def tag_prevrange(self, tagName: str, index1, index2=None):
return self._textbox.tag_prevrange(tagName, index1, index2)
def tag_raise(self, tagName, aboveThis=None):
def tag_raise(self, tagName: str, aboveThis: str | None = None):
return self._textbox.tag_raise(tagName, aboveThis)
def tag_ranges(self, tagName):
def tag_ranges(self, tagName: str):
return self._textbox.tag_ranges(tagName)
def tag_remove(self, tagName, index1, index2=None):
def tag_remove(self, tagName: str, index1, index2=None):
return self._textbox.tag_remove(tagName, index1, index2)
def tag_unbind(self, tagName, sequence, funcid=None):
def tag_unbind(self, tagName: str, sequence, funcid=None):
return self._textbox.tag_unbind(tagName, sequence, funcid)
def window_cget(self, index, option):
def window_cget(self, index: int, option: str):
raise AttributeError("embedding widgets is forbidden, would probably cause all kinds of problems ;)")
def window_configure(self, index, option):
def window_configure(self, index: int, option: str):
raise AttributeError("embedding widgets is forbidden, would probably cause all kinds of problems ;)")
def window_create(self, index, **kwargs):
def window_create(self, index: int, **kwargs: Any):
raise AttributeError("embedding widgets is forbidden, would probably cause all kinds of problems ;)")
def window_names(self):
raise AttributeError("embedding widgets is forbidden, would probably cause all kinds of problems ;)")
def xview(self, *args):
def xview(self, *args: Any):
return self._textbox.xview(*args)
def xview_moveto(self, fraction):
def xview_moveto(self, fraction: float):
return self._textbox.xview_moveto(fraction)
def xview_scroll(self, n, what):
def xview_scroll(self, n: int, what: Literal["units", "pages"]):
return self._textbox.xview_scroll(n, what)
def yview(self, *args):
return self._textbox.yview(*args)
def yview_moveto(self, fraction):
def yview_moveto(self, fraction: float):
return self._textbox.yview_moveto(fraction)
def yview_scroll(self, n, what):

View File

@ -1,11 +1,10 @@
import os
import sys
from .ctk_font import CTkFont
from .font_manager import FontManager
# import DrawEngine to set preferred_drawing_method if loading shapes font fails
from ..core_rendering import DrawEngine
from .ctk_font import CTkFont
from .font_manager import FontManager
FontManager.init_font_manager()

View File

@ -1,6 +1,9 @@
from tkinter.font import Font
from __future__ import annotations
import copy
from typing import List, Callable, Tuple, Optional
from tkinter.font import Font
from typing import Any, Callable
try:
from typing import Literal
except ImportError:
@ -25,14 +28,14 @@ class CTkFont(Font):
"""
def __init__(self,
family: Optional[str] = None,
size: Optional[int] = None,
weight: Literal["normal", "bold"] = None,
family: str | None = None,
size: int | None = None,
weight: Literal["normal", "bold"] | None = None,
slant: Literal["italic", "roman"] = "roman",
underline: bool = False,
overstrike: bool = False):
self._size_configure_callback_list: List[Callable] = []
self._size_configure_callback_list: list[Callable[..., None]] = []
self._size = ThemeManager.theme["CTkFont"]["size"] if size is None else size
@ -46,22 +49,22 @@ class CTkFont(Font):
self._family = super().cget("family")
self._tuple_style_string = f"{super().cget('weight')} {slant} {'underline' if underline else ''} {'overstrike' if overstrike else ''}"
def add_size_configure_callback(self, callback: Callable):
def add_size_configure_callback(self, callback: Callable[..., None]):
""" add function, that gets called when font got configured """
self._size_configure_callback_list.append(callback)
def remove_size_configure_callback(self, callback: Callable):
def remove_size_configure_callback(self, callback: Callable[..., None]):
""" remove function, that gets called when font got configured """
self._size_configure_callback_list.remove(callback)
def create_scaled_tuple(self, font_scaling: float) -> Tuple[str, int, str]:
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
def config(self, *args, **kwargs):
def config(self, *args: Any, **kwargs: Any):
raise AttributeError("'config' is not implemented for CTk widgets. For consistency, always use 'configure' instead.")
def configure(self, **kwargs):
def configure(self, **kwargs: Any):
if "size" in kwargs:
self._size = kwargs.pop("size")
super().configure(size=-abs(self._size))
@ -79,7 +82,7 @@ class CTkFont(Font):
for callback in self._size_configure_callback_list:
callback()
def cget(self, attribute_name: str) -> any:
def cget(self, attribute_name: str) -> Any:
if attribute_name == "size":
return self._size
if attribute_name == "family":
@ -87,5 +90,5 @@ class CTkFont(Font):
else:
return super().cget(attribute_name)
def copy(self) -> "CTkFont":
def copy(self) -> CTkFont:
return copy.deepcopy(self)

View File

@ -1,11 +1,11 @@
import sys
from __future__ import annotations
import os
import shutil
from typing import Union
import sys
class FontManager:
linux_font_path = "~/.fonts/"
@classmethod
@ -25,10 +25,11 @@ class FontManager:
return True
@classmethod
def windows_load_font(cls, font_path: Union[str, bytes], private: bool = True, enumerable: bool = False) -> bool:
""" Function taken from: https://stackoverflow.com/questions/11993290/truly-custom-font-in-tkinter/30631309#30631309 """
def windows_load_font(cls, font_path: str | bytes, private: bool = True, enumerable: bool = False) -> bool:
""" Function taken from: https://stackoverflow.com/a/30631309/ """
from ctypes import windll, byref, create_unicode_buffer, create_string_buffer
from ctypes import (byref, create_string_buffer, create_unicode_buffer,
windll)
FR_PRIVATE = 0x10
FR_NOT_ENUM = 0x20

View File

@ -1,4 +1,7 @@
from typing import Tuple, Dict, Callable, List
from __future__ import annotations
from typing import Any, Callable
try:
from PIL import Image, ImageTk
except ImportError:
@ -19,9 +22,9 @@ class CTkImage:
_checked_PIL_import = False
def __init__(self,
light_image: "Image.Image" = None,
dark_image: "Image.Image" = None,
size: Tuple[int, int] = (20, 20)):
light_image: Image.Image | None = None,
dark_image: Image.Image | None = None,
size: tuple[int, int] = (20, 20)):
if not self._checked_PIL_import:
self._check_pil_import()
@ -31,9 +34,9 @@ class CTkImage:
self._check_images()
self._size = size
self._configure_callback_list: List[Callable] = []
self._scaled_light_photo_images: Dict[Tuple[int, int], ImageTk.PhotoImage] = {}
self._scaled_dark_photo_images: Dict[Tuple[int, int], ImageTk.PhotoImage] = {}
self._configure_callback_list: list[Callable[..., None]] = []
self._scaled_light_photo_images: dict[tuple[int, int], ImageTk.PhotoImage] = {}
self._scaled_dark_photo_images: dict[tuple[int, int], ImageTk.PhotoImage] = {}
@classmethod
def _check_pil_import(cls):
@ -42,15 +45,15 @@ class CTkImage:
except NameError:
raise ImportError("PIL.Image and PIL.ImageTk couldn't be imported")
def add_configure_callback(self, callback: Callable):
def add_configure_callback(self, callback: Callable[..., None]):
""" add function, that gets called when image got configured """
self._configure_callback_list.append(callback)
def remove_configure_callback(self, callback: Callable):
def remove_configure_callback(self, callback: Callable[..., None]):
""" remove function, that gets called when image got configured """
self._configure_callback_list.remove(callback)
def configure(self, **kwargs):
def configure(self, **kwargs: Any):
if "light_image" in kwargs:
self._light_image = kwargs.pop("light_image")
self._scaled_light_photo_images = {}
@ -66,7 +69,7 @@ class CTkImage:
for callback in self._configure_callback_list:
callback()
def cget(self, attribute_name: str) -> any:
def cget(self, attribute_name: str) -> Any:
if attribute_name == "light_image":
return self._light_image
if attribute_name == "dark_image":
@ -89,24 +92,24 @@ class CTkImage:
if self._light_image is not None and self._dark_image is not None and self._light_image.size != self._dark_image.size:
raise ValueError(f"CTkImage: light_image size {self._light_image.size} must be the same as dark_image size {self._dark_image.size}.")
def _get_scaled_size(self, widget_scaling: float) -> Tuple[int, int]:
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:
@ -118,5 +121,3 @@ class CTkImage:
return self._get_scaled_dark_photo_image(scaled_size)
elif appearance_mode == "dark" and self._dark_image is None:
return self._get_scaled_light_photo_image(scaled_size)

View File

@ -1,14 +1,20 @@
from typing import Union, Tuple
from __future__ import annotations
import copy
import re
try:
import sys
from typing import Any, TypeVar
if sys.version_info >= (3, 8):
from typing import Literal
except ImportError:
else:
from typing_extensions import Literal
from .scaling_tracker import ScalingTracker
from ..font import CTkFont
from .scaling_tracker import ScalingTracker
KT = TypeVar("KT")
VT = TypeVar("VT")
class CTkScalingBaseClass:
"""
@ -46,7 +52,7 @@ class CTkScalingBaseClass:
elif self.__scaling_type == "window":
ScalingTracker.remove_window(self._set_scaling, self)
def _set_scaling(self, new_widget_scaling, new_window_scaling):
def _set_scaling(self, new_widget_scaling: float, new_window_scaling: float):
""" can be overridden, but super method must be called at the beginning """
self.__widget_scaling = new_widget_scaling
self.__window_scaling = new_window_scaling
@ -57,23 +63,23 @@ class CTkScalingBaseClass:
def _get_window_scaling(self) -> float:
return self.__window_scaling
def _apply_widget_scaling(self, value: Union[int, float]) -> Union[float]:
def _apply_widget_scaling(self, value: int | float) -> float:
assert self.__scaling_type == "widget"
return value * self.__widget_scaling
def _reverse_widget_scaling(self, value: Union[int, float]) -> Union[float]:
def _reverse_widget_scaling(self, value: int | float) -> float:
assert self.__scaling_type == "widget"
return value / self.__widget_scaling
def _apply_window_scaling(self, value: Union[int, float]) -> int:
def _apply_window_scaling(self, value: int | float) -> int:
assert self.__scaling_type == "window"
return int(value * self.__window_scaling)
def _reverse_window_scaling(self, scaled_value: Union[int, float]) -> int:
def _reverse_window_scaling(self, scaled_value: int | float) -> int:
assert self.__scaling_type == "window"
return int(scaled_value / self.__window_scaling)
def _apply_font_scaling(self, font: Union[Tuple, CTkFont]) -> tuple:
def _apply_font_scaling(self, font: tuple[Any, ...] | CTkFont) -> tuple[str, int, str]:
""" Takes CTkFont object and returns tuple font with scaled size, has to be called again for every change of font object """
assert self.__scaling_type == "widget"
@ -92,7 +98,7 @@ class CTkScalingBaseClass:
else:
raise ValueError(f"Can not scale font '{font}' of type {type(font)}. font needs to be tuple or instance of CTkFont")
def _apply_argument_scaling(self, kwargs: dict) -> dict:
def _apply_argument_scaling(self, kwargs: dict[KT, VT]) -> dict[KT, VT]:
assert self.__scaling_type == "widget"
scaled_kwargs = copy.copy(kwargs)
@ -118,7 +124,7 @@ class CTkScalingBaseClass:
return scaled_kwargs
@staticmethod
def _parse_geometry_string(geometry_string: str) -> tuple:
def _parse_geometry_string(geometry_string: str) -> tuple[int | None, int | None, int | None, int | None]:
# 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)

View File

@ -1,13 +1,17 @@
import tkinter
import sys
from typing import Callable
from __future__ import annotations
import sys
import tkinter
from typing import Callable, TYPE_CHECKING
if TYPE_CHECKING:
from customtkinter.windows import CTk
class ScalingTracker:
deactivate_automatic_dpi_awareness = False
window_widgets_dict = {} # contains window objects as keys with list of widget callbacks as elements
window_dpi_scaling_dict = {} # contains window objects as keys and corresponding scaling factors
window_widgets_dict: dict[CTk, list[Callable[..., None]]] = {} # contains window objects as keys with list of widget callbacks as elements
window_dpi_scaling_dict: dict[CTk, float] = {} # contains window objects as keys and corresponding scaling factors
widget_scaling = 1 # user values which multiply to detected window scaling factor
window_scaling = 1
@ -17,12 +21,12 @@ class ScalingTracker:
loop_pause_after_new_scaling = 1500 # ms
@classmethod
def get_widget_scaling(cls, widget) -> float:
def get_widget_scaling(cls, widget: CTk) -> float:
window_root = cls.get_window_root_of_widget(widget)
return cls.window_dpi_scaling_dict[window_root] * cls.widget_scaling
@classmethod
def get_window_scaling(cls, window) -> float:
def get_window_scaling(cls, window: CTk) -> float:
window_root = cls.get_window_root_of_widget(window)
return cls.window_dpi_scaling_dict[window_root] * cls.window_scaling
@ -37,7 +41,7 @@ class ScalingTracker:
cls.update_scaling_callbacks_all()
@classmethod
def get_window_root_of_widget(cls, widget):
def get_window_root_of_widget(cls, widget: CTk):
current_widget = widget
while isinstance(current_widget, tkinter.Tk) is False and\
@ -58,7 +62,7 @@ class ScalingTracker:
cls.window_scaling)
@classmethod
def update_scaling_callbacks_for_window(cls, window):
def update_scaling_callbacks_for_window(cls, window: CTk):
for set_scaling_callback in cls.window_widgets_dict[window]:
if not cls.deactivate_automatic_dpi_awareness:
set_scaling_callback(cls.window_dpi_scaling_dict[window] * cls.widget_scaling,
@ -68,7 +72,7 @@ class ScalingTracker:
cls.window_scaling)
@classmethod
def add_widget(cls, widget_callback: Callable, widget):
def add_widget(cls, widget_callback: Callable[..., None], widget: CTk):
window_root = cls.get_window_root_of_widget(widget)
if window_root not in cls.window_widgets_dict:
@ -84,7 +88,7 @@ class ScalingTracker:
cls.update_loop_running = True
@classmethod
def remove_widget(cls, widget_callback, widget):
def remove_widget(cls, widget_callback: Callable[..., None], widget: CTk):
window_root = cls.get_window_root_of_widget(widget)
try:
cls.window_widgets_dict[window_root].remove(widget_callback)
@ -92,14 +96,14 @@ class ScalingTracker:
pass
@classmethod
def remove_window(cls, window_callback, window):
def remove_window(cls, window_callback: Callable[..., None], window: CTk):
try:
del cls.window_widgets_dict[window]
except:
pass
@classmethod
def add_window(cls, window_callback, window):
def add_window(cls, window_callback: Callable[..., None], window: CTk):
if window not in cls.window_widgets_dict:
cls.window_widgets_dict[window] = [window_callback]
else:
@ -135,11 +139,9 @@ class ScalingTracker:
# DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE = 18,
# DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = 34
# }
# ctypes.windll.user32.SetProcessDpiAwarenessContext(34) # Non client area scaling at runtime (titlebar)
# does not work with resizable(False, False), window starts growing on monitor with different scaling (weird tkinter bug...)
# ctypes.windll.user32.EnableNonClientDpiScaling(hwnd) does not work for some reason (tested on Windows 11)
# It's too bad, that these Windows API methods don't work properly with tkinter. But I tested days with multiple monitor setups,
# and I don't think there is anything left to do. So this is the best option at the moment:
@ -148,13 +150,13 @@ class ScalingTracker:
pass # DPI awareness on Linux not implemented
@classmethod
def get_window_dpi_scaling(cls, window) -> float:
def get_window_dpi_scaling(cls, window: CTk) -> float:
if not cls.deactivate_automatic_dpi_awareness:
if sys.platform == "darwin":
return 1 # scaling works automatically on macOS
elif sys.platform.startswith("win"):
from ctypes import windll, pointer, wintypes
from ctypes import pointer, windll, wintypes
DPI100pc = 96 # DPI 96 is 100% scaling
DPI_type = 0 # MDT_EFFECTIVE_DPI = 0, MDT_ANGULAR_DPI = 1, MDT_RAW_DPI = 2
@ -181,14 +183,14 @@ class ScalingTracker:
cls.window_dpi_scaling_dict[window] = current_dpi_scaling_value
if sys.platform.startswith("win"):
window.attributes("-alpha", 0.15)
window.attributes("-alpha", 0.15) # type: ignore
window.block_update_dimensions_event()
cls.update_scaling_callbacks_for_window(window)
window.unblock_update_dimensions_event()
if sys.platform.startswith("win"):
window.attributes("-alpha", 1)
window.attributes("-alpha", 1) # type: ignore
new_scaling_detected = True

View File

@ -1,24 +1,25 @@
import sys
import os
from __future__ import annotations
import json
from typing import List, Union
import os
import sys
from typing import Any
class ThemeManager:
theme: dict = {} # contains all the theme data
_built_in_themes: List[str] = ["blue", "green", "dark-blue", "sweetkind"]
_currently_loaded_theme: Union[str, None] = None
theme: dict[str, Any] = {} # contains all the theme data
_built_in_themes: list[str] = ["blue", "green", "dark-blue", "sweetkind"]
_currently_loaded_theme: str | None = None
@classmethod
def load_theme(cls, theme_name_or_path: str):
script_directory = os.path.dirname(os.path.abspath(__file__))
if theme_name_or_path in cls._built_in_themes:
with open(os.path.join(script_directory, "../../../assets", "themes", f"{theme_name_or_path}.json"), "r") as f:
with open(os.path.join(script_directory, "../../../assets", "themes", f"{theme_name_or_path}.json")) as f:
cls.theme = json.load(f)
else:
with open(theme_name_or_path, "r") as f:
with open(theme_name_or_path) as f:
cls.theme = json.load(f)
# store theme path for saving
@ -39,9 +40,9 @@ class ThemeManager:
def save_theme(cls):
if cls._currently_loaded_theme is not None:
if cls._currently_loaded_theme not in cls._built_in_themes:
with open(cls._currently_loaded_theme, "r") as f:
with open(cls._currently_loaded_theme) as f:
json.dump(cls.theme, f, indent=2)
else:
raise ValueError(f"cannot modify builtin theme '{cls._currently_loaded_theme}'")
else:
raise ValueError(f"cannot save theme, no theme is loaded")
raise ValueError("cannot save theme, no theme is loaded")

View File

@ -1 +1 @@
from .utility_functions import pop_from_dict_by_set, check_kwargs_empty
from .utility_functions import check_kwargs_empty, pop_from_dict_by_set

View File

@ -1,7 +1,14 @@
from __future__ import annotations
def pop_from_dict_by_set(dictionary: dict, valid_keys: set) -> dict:
from typing import Any, TypeVar
KT = TypeVar("KT")
VT = TypeVar("VT")
def pop_from_dict_by_set(dictionary: dict[KT, VT], valid_keys: set[KT]) -> dict[KT, VT]:
""" remove and create new dict with key value pairs of dictionary, where key is in valid_keys """
new_dictionary = {}
new_dictionary: dict[KT, VT] = {}
for key in list(dictionary.keys()):
if key in valid_keys:
@ -10,7 +17,7 @@ def pop_from_dict_by_set(dictionary: dict, valid_keys: set) -> dict:
return new_dictionary
def check_kwargs_empty(kwargs_dict, raise_error=False) -> bool:
def check_kwargs_empty(kwargs_dict: dict[Any, Any], raise_error: bool =False) -> bool:
""" returns True if kwargs are empty, False otherwise, raises error if not empty """
if len(kwargs_dict) > 0:

View File

@ -1,3 +1,7 @@
from __future__ import annotations
from typing import Any
import customtkinter
from PIL import Image
import os
@ -9,7 +13,7 @@ class App(customtkinter.CTk):
width = 900
height = 600
def __init__(self, *args, **kwargs):
def __init__(self, *args: Any, **kwargs: Any):
super().__init__(*args, **kwargs)
self.title("CustomTkinter example_background_image.py")

View File

@ -1,3 +1,7 @@
from __future__ import annotations
from typing import Any
import customtkinter
import os
from PIL import Image
@ -79,7 +83,7 @@ class App(customtkinter.CTk):
# select default frame
self.select_frame_by_name("home")
def select_frame_by_name(self, name):
def select_frame_by_name(self, name: str):
# set button color for selected button
self.home_button.configure(fg_color=("gray75", "gray25") if name == "home" else "transparent")
self.frame_2_button.configure(fg_color=("gray75", "gray25") if name == "frame_2" else "transparent")
@ -108,11 +112,10 @@ class App(customtkinter.CTk):
def frame_3_button_event(self):
self.select_frame_by_name("frame_3")
def change_appearance_mode_event(self, new_appearance_mode):
def change_appearance_mode_event(self, new_appearance_mode: Any):
customtkinter.set_appearance_mode(new_appearance_mode)
if __name__ == "__main__":
app = App()
app.mainloop()

View File

@ -1,25 +1,29 @@
from __future__ import annotations
from typing import Any, Callable
import customtkinter
import os
from PIL import Image
class ScrollableCheckBoxFrame(customtkinter.CTkScrollableFrame):
def __init__(self, master, item_list, command=None, **kwargs):
def __init__(self, master, item_list: list[str], command: Callable[..., None] | None = None, **kwargs: Any):
super().__init__(master, **kwargs)
self.command = command
self.checkbox_list = []
for i, item in enumerate(item_list):
self.checkbox_list: list[customtkinter.CTkCheckBox] = []
for item in item_list:
self.add_item(item)
def add_item(self, item):
def add_item(self, item: str):
checkbox = customtkinter.CTkCheckBox(self, text=item)
if self.command is not None:
checkbox.configure(command=self.command)
checkbox.grid(row=len(self.checkbox_list), column=0, pady=(0, 10))
self.checkbox_list.append(checkbox)
def remove_item(self, item):
def remove_item(self, item: str):
for checkbox in self.checkbox_list:
if item == checkbox.cget("text"):
checkbox.destroy()
@ -31,7 +35,7 @@ class ScrollableCheckBoxFrame(customtkinter.CTkScrollableFrame):
class ScrollableRadiobuttonFrame(customtkinter.CTkScrollableFrame):
def __init__(self, master, item_list, command=None, **kwargs):
def __init__(self, master, item_list: list[str], command: Callable[..., None] | None = None, **kwargs: Any):
super().__init__(master, **kwargs)
self.command = command
@ -59,7 +63,7 @@ class ScrollableRadiobuttonFrame(customtkinter.CTkScrollableFrame):
class ScrollableLabelButtonFrame(customtkinter.CTkScrollableFrame):
def __init__(self, master, command=None, **kwargs):
def __init__(self, master, command=None, **kwargs: Any):
super().__init__(master, **kwargs)
self.grid_columnconfigure(0, weight=1)

View File

@ -2,19 +2,24 @@
name = customtkinter
version = 5.1.2
description = Create modern looking GUIs with Python
long_description = A modern and customizable python UI-library based on Tkinter: https://github.com/TomSchimansky/CustomTkinter
long_description = file: Readme.md
long_description_content_type = text/markdown
url = https://github.com/TomSchimansky/CustomTkinter
author = Tom Schimansky
license = Creative Commons Zero v1.0 Universal
license = MIT
license_file = LICENSE
classifiers =
License :: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
License :: OSI Approved :: MIT License
Operating System :: OS Independent
Programming Language :: Python :: 3
Programming Language :: Python :: 3 :: Only
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
[options]
python_requires = >=3.7
packages =
customtkinter
customtkinter.windows
@ -29,5 +34,6 @@ packages =
customtkinter.windows.widgets.utility
install_requires =
darkdetect
typing_extensions; python_version<="3.7"
typing-extensions;python_version=="3.7"
python_requires = >=3.7
include_package_data = True

View File

@ -1,10 +1,14 @@
from __future__ import annotations
from typing import Any, Callable
import customtkinter
customtkinter.set_appearance_mode("dark")
class ToplevelWindow(customtkinter.CTkToplevel):
def __init__(self, *args, closing_event=None, **kwargs):
def __init__(self, *args: Any, closing_event: Callable[[], None] | None = None, **kwargs: Any):
super().__init__(*args, **kwargs)
self.protocol("WM_DELETE_WINDOW", self.closing)
self.geometry("500x300")

View File

@ -1,12 +1,11 @@
import tkinter.messagebox
import customtkinter
customtkinter.set_appearance_mode("dark")
class App(customtkinter.CTk):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def __init__(self, *args: Any, **kwargs: Any):
super().__init__(*args, **kwargs: Any)
self.title("test filedialog")

View File

@ -40,4 +40,3 @@ label_3 = customtkinter.CTkLabel(app, image=ImageTk.PhotoImage(Image.open(file_p
label_3.pack(padx=20, pady=20)
app.mainloop()

View File

@ -1,4 +1,3 @@
import time
import customtkinter

View File

@ -1,4 +1,3 @@
import time
import customtkinter

View File

@ -1,5 +1,4 @@
import customtkinter
import time
app = customtkinter.CTk()