Merge 79cd10ddfde1152171a885b88814ea52b4d17565 into 09e584634c867f2b6074fcbfe41cba80d2b78c66

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 os
import sys import sys
from tkinter import Variable, StringVar, IntVar, DoubleVar, BooleanVar
from tkinter.constants import *
import tkinter.filedialog as filedialog import tkinter.filedialog as filedialog
from tkinter import BooleanVar, DoubleVar, IntVar, StringVar, Variable
# import manager classes from tkinter.constants import *
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
# import windows # import windows
from .windows import CTk from .windows import CTk, CTkInputDialog, CTkToplevel
from .windows import CTkToplevel # import widgets
from .windows import CTkInputDialog 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 # import font classes
from .windows.widgets.font import CTkFont from .windows.widgets.font import CTkFont, FontManager
# import image classes # import image classes
from .windows.widgets.image import CTkImage 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 _ = 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_tk import CTk
from .ctk_toplevel import CTkToplevel 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 .ctk_toplevel import CTkToplevel
from .widgets import CTkButton, CTkEntry, CTkLabel
from .widgets.theme import ThemeManager
class CTkInputDialog(CTkToplevel): class CTkInputDialog(CTkToplevel):
@ -14,14 +15,14 @@ class CTkInputDialog(CTkToplevel):
""" """
def __init__(self, def __init__(self,
fg_color: Optional[Union[str, Tuple[str, str]]] = None, fg_color: str | tuple[str, str] | None = None,
text_color: Optional[Union[str, Tuple[str, str]]] = None, text_color: str | tuple[str, str] | None = None,
button_fg_color: Optional[Union[str, Tuple[str, str]]] = None, button_fg_color: str | tuple[str, str] | None = None,
button_hover_color: Optional[Union[str, Tuple[str, str]]] = None, button_hover_color: str | tuple[str, str] | None = None,
button_text_color: Optional[Union[str, Tuple[str, str]]] = None, button_text_color: str | tuple[str, str] | None = None,
entry_fg_color: Optional[Union[str, Tuple[str, str]]] = None, entry_fg_color: str | tuple[str, str] | None = None,
entry_border_color: Optional[Union[str, Tuple[str, str]]] = None, entry_border_color: str | tuple[str, str] | None = None,
entry_text_color: Optional[Union[str, Tuple[str, str]]] = None, entry_text_color: str | tuple[str, str] | None = None,
title: str = "CTkDialog", title: str = "CTkDialog",
text: 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_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._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._running: bool = False
self._text = text self._text = text
@ -50,7 +51,6 @@ class CTkInputDialog(CTkToplevel):
self.grab_set() # make other windows not clickable self.grab_set() # make other windows not clickable
def _create_widgets(self): def _create_widgets(self):
self.grid_columnconfigure((0, 1), weight=1) self.grid_columnconfigure((0, 1), weight=1)
self.rowconfigure(0, 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.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) 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._user_input = self._entry.get()
self.grab_release() self.grab_release()
self.destroy() self.destroy()

View File

@ -1,16 +1,19 @@
import tkinter from __future__ import annotations
from distutils.version import StrictVersion as Version
import sys import ctypes
import os import os
import platform import platform
import ctypes import sys
from typing import Union, Tuple, Optional 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 .widgets.appearance_mode import CTkAppearanceModeBaseClass
from .widgets.scaling import CTkScalingBaseClass
from customtkinter.windows.widgets.utility.utility_functions import pop_from_dict_by_set, check_kwargs_empty from .widgets.theme import ThemeManager
class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass): class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
@ -19,9 +22,9 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
For detailed information check out the documentation. 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', 'use', 'container', 'cursor', 'height',
'highlightthickness', 'padx', 'pady', 'takefocus', 'visual', 'width'} 'highlightthickness', 'padx', 'pady', 'takefocus', 'visual', 'width'}
@ -29,8 +32,8 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
_deactivate_windows_window_header_manipulation: bool = False _deactivate_windows_window_header_manipulation: bool = False
def __init__(self, def __init__(self,
fg_color: Optional[Union[str, Tuple[str, str]]] = None, fg_color: str | tuple[str, str] | None = None,
**kwargs): **kwargs: Any):
self._enable_macos_dark_title_bar() self._enable_macos_dark_title_bar()
@ -46,7 +49,7 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
self._min_height: int = 0 self._min_height: int = 0
self._max_width: int = 1_000_000 self._max_width: int = 1_000_000
self._max_height: 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) 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) CTkAppearanceModeBaseClass.destroy(self)
CTkScalingBaseClass.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 # sometimes window looses jumps back on macOS if window is selected from Mission Control, so has to be lifted again
if sys.platform == "darwin": if sys.platform == "darwin":
self.lift() 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: if not self._block_update_dimensions_event:
detected_width = super().winfo_width() # detect current window size 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_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 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) 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. # 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() super().update()
def mainloop(self, *args, **kwargs): def mainloop(self, *args: Any, **kwargs: Any):
if not self._window_exists: if not self._window_exists:
if sys.platform.startswith("win"): if sys.platform.startswith("win"):
self._windows_set_titlebar_color(self._get_appearance_mode()) self._windows_set_titlebar_color(self._get_appearance_mode())
@ -171,7 +174,7 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
return current_resizable_values 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_width = width
self._min_height = height self._min_height = height
if self._current_width < width: if self._current_width < width:
@ -180,7 +183,7 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
self._current_height = height self._current_height = height
super().minsize(self._apply_window_scaling(self._min_width), self._apply_window_scaling(self._min_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_width = width
self._max_height = height self._max_height = height
if self._current_width > width: if self._current_width > width:
@ -189,19 +192,19 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
self._current_height = height self._current_height = height
super().maxsize(self._apply_window_scaling(self._max_width), self._apply_window_scaling(self._max_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: if geometry_string is not None:
super().geometry(self._apply_geometry_scaling(geometry_string)) super().geometry(self._apply_geometry_scaling(geometry_string))
# update width and height attributes # 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: 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_width = max(self._min_width, min(width, self._max_width)) # bound value between min and max
self._current_height = max(self._min_height, min(height, self._max_height)) self._current_height = max(self._min_height, min(height, self._max_height))
else: else:
return self._reverse_geometry_scaling(super().geometry()) return self._reverse_geometry_scaling(super().geometry())
def configure(self, **kwargs): def configure(self, **kwargs: Any):
if "fg_color" in kwargs: if "fg_color" in kwargs:
self._fg_color = self._check_color_type(kwargs.pop("fg_color")) self._fg_color = self._check_color_type(kwargs.pop("fg_color"))
super().configure(bg=self._apply_appearance_mode(self._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)) super().configure(**pop_from_dict_by_set(kwargs, self._valid_tk_configure_arguments))
check_kwargs_empty(kwargs) check_kwargs_empty(kwargs)
def cget(self, attribute_name: str) -> any: def cget(self, attribute_name: str) -> Any:
if attribute_name == "fg_color": if attribute_name == "fg_color":
return self._fg_color return self._fg_color
else: else:

View File

@ -1,16 +1,24 @@
import tkinter from __future__ import annotations
from distutils.version import StrictVersion as Version
import sys import ctypes
import os import os
import platform import platform
import ctypes import sys
from typing import Union, Tuple, Optional 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 .widgets.appearance_mode import CTkAppearanceModeBaseClass
from .widgets.scaling import CTkScalingBaseClass
from customtkinter.windows.widgets.utility.utility_functions import pop_from_dict_by_set, check_kwargs_empty from .widgets.theme import ThemeManager
class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseClass): class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
@ -19,16 +27,16 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl
For detailed information check out the documentation. For detailed information check out the documentation.
""" """
_valid_tk_toplevel_arguments: set = {"bd", "borderwidth", "class", "container", "cursor", "height", _valid_tk_toplevel_arguments: set[str] = {"bd", "borderwidth", "class", "container", "cursor", "height",
"highlightbackground", "highlightthickness", "menu", "relief", "highlightbackground", "highlightthickness", "menu", "relief",
"screen", "takefocus", "use", "visual", "width"} "screen", "takefocus", "use", "visual", "width"}
_deactivate_macos_window_header_manipulation: bool = False _deactivate_macos_window_header_manipulation: bool = False
_deactivate_windows_window_header_manipulation: bool = False _deactivate_windows_window_header_manipulation: bool = False
def __init__(self, *args, def __init__(self, *args: Any,
fg_color: Optional[Union[str, Tuple[str, str]]] = None, fg_color: str | tuple[str, str] | None = None,
**kwargs): **kwargs: Any):
self._enable_macos_dark_title_bar() 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_width = 200 # initial window size, always without scaling
self._current_height = 200 self._current_height = 200
self._min_width: int = 0 self._min_width: int | None = 0
self._min_height: int = 0 self._min_height: int | None = 0
self._max_width: int = 1_000_000 self._max_width: int | None = 1_000_000
self._max_height: int = 1_000_000 self._max_height: int | None = 1_000_000
self._last_resizable_args: Union[Tuple[list, dict], None] = None # (args, kwargs) 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) 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) CTkAppearanceModeBaseClass.destroy(self)
CTkScalingBaseClass.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 # sometimes window looses jumps back on macOS if window is selected from Mission Control, so has to be lifted again
if sys.platform == "darwin": if sys.platform == "darwin":
self.lift() 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: if not self._block_update_dimensions_event:
detected_width = self.winfo_width() # detect current window size detected_width = self.winfo_width() # detect current window size
detected_height = self.winfo_height() 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_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 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) 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. # 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: 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)) 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: if geometry_string is not None:
super().geometry(self._apply_geometry_scaling(geometry_string)) 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 self._iconify_called_after_windows_set_titlebar_color = True
super().iconify() 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) current_resizable_values = super().resizable(width, height)
self._last_resizable_args = ([], {"width": width, "height": height}) self._last_resizable_args = ([], {"width": width, "height": height})
@ -161,7 +169,7 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl
return current_resizable_values 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_width = width
self._min_height = height self._min_height = height
if self._current_width < width: if self._current_width < width:
@ -170,7 +178,7 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl
self._current_height = height self._current_height = height
super().minsize(self._apply_window_scaling(self._min_width), self._apply_window_scaling(self._min_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_width = width
self._max_height = height self._max_height = height
if self._current_width > width: if self._current_width > width:
@ -179,7 +187,7 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl
self._current_height = height self._current_height = height
super().maxsize(self._apply_window_scaling(self._max_width), self._apply_window_scaling(self._max_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: if "fg_color" in kwargs:
self._fg_color = self._check_color_type(kwargs.pop("fg_color")) self._fg_color = self._check_color_type(kwargs.pop("fg_color"))
super().configure(bg=self._apply_appearance_mode(self._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)) super().configure(**pop_from_dict_by_set(kwargs, self._valid_tk_toplevel_arguments))
check_kwargs_empty(kwargs) check_kwargs_empty(kwargs)
def cget(self, attribute_name: str) -> any: def cget(self, attribute_name: str) -> Any:
if attribute_name == "fg_color": if attribute_name == "fg_color":
return self._fg_color return self._fg_color
else: else:
return super().cget(attribute_name) 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 self._iconbitmap_method_called = True
super().wm_iconbitmap(bitmap, default) super().wm_iconbitmap(bitmap, default) # type: ignore
def _windows_set_titlebar_icon(self): def _windows_set_titlebar_icon(self):
try: try:
@ -298,7 +306,7 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl
self._withdraw_called_after_windows_set_titlebar_color = False self._withdraw_called_after_windows_set_titlebar_color = False
self._iconify_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) super()._set_appearance_mode(mode_string)
if sys.platform.startswith("win"): if sys.platform.startswith("win"):

View File

@ -7,10 +7,10 @@ from .ctk_label import CTkLabel
from .ctk_optionmenu import CTkOptionMenu from .ctk_optionmenu import CTkOptionMenu
from .ctk_progressbar import CTkProgressBar from .ctk_progressbar import CTkProgressBar
from .ctk_radiobutton import CTkRadioButton from .ctk_radiobutton import CTkRadioButton
from .ctk_scrollable_frame import CTkScrollableFrame
from .ctk_scrollbar import CTkScrollbar from .ctk_scrollbar import CTkScrollbar
from .ctk_segmented_button import CTkSegmentedButton from .ctk_segmented_button import CTkSegmentedButton
from .ctk_slider import CTkSlider from .ctk_slider import CTkSlider
from .ctk_switch import CTkSwitch from .ctk_switch import CTkSwitch
from .ctk_tabview import CTkTabview from .ctk_tabview import CTkTabview
from .ctk_textbox import CTkTextbox 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 from .appearance_mode_tracker import AppearanceModeTracker
@ -19,7 +26,7 @@ class CTkAppearanceModeBaseClass:
def destroy(self): def destroy(self):
AppearanceModeTracker.remove(self._set_appearance_mode) 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 """ """ can be overridden but super method must be called at the beginning """
if mode_string.lower() == "dark": if mode_string.lower() == "dark":
self.__appearance_mode = 1 self.__appearance_mode = 1
@ -33,7 +40,7 @@ class CTkAppearanceModeBaseClass:
else: else:
return "dark" 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 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 tuple color with (light_color, dark_color). The functions returns
@ -46,7 +53,7 @@ class CTkAppearanceModeBaseClass:
return color return color
@staticmethod @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: if color is None:
raise ValueError(f"color is None, for transparency set color='transparent'") raise ValueError(f"color is None, for transparency set color='transparent'")
elif isinstance(color, (tuple, list)) and (color[0] == "transparent" or color[1] == "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 sys
import tkinter import tkinter
from distutils.version import StrictVersion as Version 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: try:
import darkdetect import darkdetect # type: ignore
if Version(darkdetect.__version__) < Version("0.3.1"): if Version(darkdetect.__version__) < Version("0.3.1"):
sys.stderr.write("WARNING: You have to upgrade the darkdetect library: pip3 install --upgrade darkdetect\n") sys.stderr.write("WARNING: You have to upgrade the darkdetect library: pip3 install --upgrade darkdetect\n")
@ -18,13 +28,13 @@ except Exception:
class AppearanceModeTracker: class AppearanceModeTracker:
callback_list = [] callback_list: list[Callable[[str], None]] = []
app_list = [] app_list: list[CTk] = []
update_loop_running = False update_loop_running = False
update_loop_interval = 30 # milliseconds update_loop_interval = 30 # milliseconds
appearance_mode_set_by = "system" appearance_mode_set_by: Literal["system", "user"] = "system"
appearance_mode = 0 # Light (standard) appearance_mode: Literal[0, 1] = 0 # 0 = Light, 1 = Dark
@classmethod @classmethod
def init_appearance_mode(cls): def init_appearance_mode(cls):
@ -36,7 +46,7 @@ class AppearanceModeTracker:
cls.update_callbacks() cls.update_callbacks()
@classmethod @classmethod
def add(cls, callback: Callable, widget=None): def add(cls, callback: Callable[[str], None], widget: CTk | None = None):
cls.callback_list.append(callback) cls.callback_list.append(callback)
if widget is not None: if widget is not None:
@ -49,16 +59,16 @@ class AppearanceModeTracker:
cls.update_loop_running = True cls.update_loop_running = True
@classmethod @classmethod
def remove(cls, callback: Callable): def remove(cls, callback: Callable[[str], None]):
try: try:
cls.callback_list.remove(callback) cls.callback_list.remove(callback)
except ValueError: except ValueError:
return return
@staticmethod @staticmethod
def detect_appearance_mode() -> int: def detect_appearance_mode() -> Literal[0, 1]:
try: try:
if darkdetect.theme() == "Dark": if darkdetect.theme() == "Dark": # type: ignore
return 1 # Dark return 1 # Dark
else: else:
return 0 # Light return 0 # Light
@ -66,7 +76,7 @@ class AppearanceModeTracker:
return 0 # Light return 0 # Light
@classmethod @classmethod
def get_tk_root_of_widget(cls, widget): def get_tk_root_of_widget(cls, widget: CTk) -> CTk:
current_widget = widget current_widget = widget
while isinstance(current_widget, tkinter.Tk) is False: while isinstance(current_widget, tkinter.Tk) is False:

View File

@ -1,6 +1,9 @@
import tkinter from __future__ import annotations
import sys import sys
from typing import Union, Tuple import tkinter
from typing import Any
class CTkCanvas(tkinter.Canvas): class CTkCanvas(tkinter.Canvas):
@ -25,11 +28,11 @@ class CTkCanvas(tkinter.Canvas):
not can be a problem when using only a single circle character. 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) super().__init__(*args, **kwargs)
self._aa_circle_canvas_ids = set() self._aa_circle_canvas_ids: set[str | int] = set()
@classmethod @classmethod
def init_font_character_mapping(cls): def init_font_character_mapping(cls):
@ -71,7 +74,7 @@ class CTkCanvas(tkinter.Canvas):
return self.radius_to_char_fine[radius] 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", 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 # create a circle with a font element
circle_1 = self.create_text(x_pos, y_pos, text=self._get_char_from_radius(radius), anchor=anchor, fill=fill, circle_1 = self.create_text(x_pos, y_pos, text=self._get_char_from_radius(radius), anchor=anchor, fill=fill,
font=("CustomTkinter_shapes_font", -radius * 2), tags=tags, angle=angle) font=("CustomTkinter_shapes_font", -radius * 2), tags=tags, angle=angle)
@ -80,8 +83,7 @@ class CTkCanvas(tkinter.Canvas):
return circle_1 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): 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 coords_id = self.find_withtag(tag_or_id)[0] # take the lowest id for the given tag
super().coords(coords_id, *args[:2]) super().coords(coords_id, *args[:2])
@ -98,7 +100,7 @@ class CTkCanvas(tkinter.Canvas):
else: else:
super().coords(tag_or_id, *args) 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() kwargs_except_outline = kwargs.copy()
if "outline" in kwargs_except_outline: if "outline" in kwargs_except_outline:
del kwargs_except_outline["outline"] del kwargs_except_outline["outline"]

View File

@ -1,8 +1,14 @@
from __future__ import annotations from __future__ import annotations
import sys
import math import math
import sys
import tkinter 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: if TYPE_CHECKING:
from ..core_rendering import CTkCanvas 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): def __init__(self, canvas: CTkCanvas):
self._canvas = canvas self._canvas = canvas
@ -37,7 +43,7 @@ class DrawEngine:
self._round_width_to_even_numbers: bool = round_width_to_even_numbers self._round_width_to_even_numbers: bool = round_width_to_even_numbers
self._round_height_to_even_numbers: bool = round_height_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 # optimize for drawing with polygon shapes
if self.preferred_drawing_method == "polygon_shapes": if self.preferred_drawing_method == "polygon_shapes":
if sys.platform == "darwin": if sys.platform == "darwin":
@ -61,7 +67,7 @@ class DrawEngine:
else: else:
return user_corner_radius 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: 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 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: if self._round_height_to_even_numbers:
@ -93,8 +99,8 @@ class DrawEngine:
return requires_recoloring return requires_recoloring
def draw_rounded_rect_with_border(self, width: Union[float, int], height: Union[float, int], corner_radius: Union[float, int], def draw_rounded_rect_with_border(self, width: float | int, height: float | int, corner_radius: float | int,
border_width: Union[float, int], overwrite_preferred_drawing_method: str = None) -> bool: 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, """ 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. the main foreground elements have an 'inner_parts' tag to color the elements accordingly.
@ -184,7 +190,7 @@ class DrawEngine:
return requires_recoloring 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, 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 requires_recoloring = False
# create border button parts # create border button parts
@ -396,8 +402,8 @@ class DrawEngine:
return requires_recoloring 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], def draw_rounded_rect_with_border_vertical_split(self, width: float | int, height: float | int, corner_radius: float | int,
border_width: Union[float, int], left_section_width: Union[float, int]) -> bool: 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. """ 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 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, the main foreground elements have an 'inner_parts_left' and inner_parts_right' tag,
@ -690,8 +696,8 @@ class DrawEngine:
return requires_recoloring return requires_recoloring
def draw_rounded_progress_bar_with_border(self, width: Union[float, int], height: Union[float, int], corner_radius: Union[float, int], def draw_rounded_progress_bar_with_border(self, width: float | int, height: float | int, corner_radius: float | int,
border_width: Union[float, int], progress_value_1: float, progress_value_2: float, orientation: str) -> bool: 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). """ 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 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). 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 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], def draw_rounded_slider_with_border_and_button(self, width: float | int, height: float | int, corner_radius: float | int,
border_width: Union[float, int], button_length: Union[float, int], button_corner_radius: Union[float, int], border_width: float | int, button_length: float | int, button_corner_radius: float | int,
slider_value: float, orientation: str) -> bool: slider_value: float, orientation: str) -> bool:
if self._round_width_to_even_numbers: if self._round_width_to_even_numbers:
@ -1028,8 +1034,8 @@ class DrawEngine:
return requires_recoloring return requires_recoloring
def draw_rounded_scrollbar(self, width: Union[float, int], height: Union[float, int], corner_radius: Union[float, int], def draw_rounded_scrollbar(self, width: float | int, height: float | int, corner_radius: float | int,
border_spacing: Union[float, int], start_value: float, end_value: float, orientation: str) -> bool: border_spacing: float | int, start_value: float, end_value: float, orientation: str) -> bool:
if self._round_width_to_even_numbers: 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 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 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, """ 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. the main foreground elements have an 'inner_parts' tag to color the elements accordingly.
@ -1201,7 +1207,7 @@ class DrawEngine:
return requires_recoloring 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 """ Draws a dropdown bottom facing arrow at (x_position, y_position) in a given size
returns bool if recoloring is necessary """ returns bool if recoloring is necessary """

View File

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

View File

@ -1,8 +1,9 @@
import sys from __future__ import annotations
import warnings
import tkinter import tkinter
import tkinter.ttk as ttk import tkinter.ttk as ttk
from typing import Union, Callable, Tuple import warnings
from typing import Any, Callable
try: try:
from typing import TypedDict from typing import TypedDict
@ -10,14 +11,12 @@ except ImportError:
from typing_extensions import TypedDict from typing_extensions import TypedDict
from .... import windows # import windows for isinstance checks from .... import windows # import windows for isinstance checks
from ..appearance_mode import CTkAppearanceModeBaseClass
from ..theme import ThemeManager
from ..font import CTkFont from ..font import CTkFont
from ..image import CTkImage from ..image import CTkImage
from ..appearance_mode import CTkAppearanceModeBaseClass
from ..scaling import CTkScalingBaseClass from ..scaling import CTkScalingBaseClass
from ..theme import ThemeManager
from ..utility import pop_from_dict_by_set, check_kwargs_empty from ..utility import check_kwargs_empty, pop_from_dict_by_set
class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClass): 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 """ 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: # 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 _cursor_manipulation_enabled: bool = True
def __init__(self, def __init__(self,
master: any, master: Any,
width: int = 0, width: int | str = 0,
height: int = 0, height: int | str = 0,
bg_color: Union[str, Tuple[str, str]] = "transparent", bg_color: str | tuple[str, str] = "transparent",
**kwargs): **kwargs: Any):
# call init methods of super classes # 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)) 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 # save latest geometry function and kwargs
class GeometryCallDict(TypedDict): class GeometryCallDict(TypedDict):
function: Callable function: Callable[..., None]
kwargs: dict kwargs: dict[Any, Any]
self._last_geometry_manager_call: Union[GeometryCallDict, None] = None self._last_geometry_manager_call: GeometryCallDict | None = None
# background color # 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 # set bg color of tkinter.Frame
super().configure(bg=self._apply_appearance_mode(self._bg_color)) 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)): 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 master_old_configure = self.master.config
def new_configure(*args, **kwargs): def new_configure(*args, **kwargs: Any):
if "bg" in kwargs: if "bg" in kwargs:
self.configure(bg_color=kwargs["bg"]) self.configure(bg_color=kwargs["bg"])
elif "background" in kwargs: elif "background" in kwargs:
@ -107,10 +106,10 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas
# super().configure(bg=self._apply_appearance_mode(self._bg_color)) # super().configure(bg=self._apply_appearance_mode(self._bg_color))
pass 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.") 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 """ """ basic configure with bg_color, width, height support, calls configure of tkinter.Frame, updates in the end """
if "width" in kwargs: if "width" in kwargs:
@ -150,7 +149,7 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas
else: else:
raise ValueError(f"'{attribute_name}' is not a supported argument. Look at the documentation for supported arguments.") 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 """ """ check font type when passed to widget """
if isinstance(font, CTkFont): if isinstance(font, CTkFont):
return font 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=customtkinter.CTkFont(family='<name>', size=<size in px>)\n" +
f"font=('<name>', <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 """ """ check image type when passed to widget """
if image is None: if image is None:
return image 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") 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 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 # 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)): 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 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 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 """ """ detect foreground color of master widget for bg_color and transparent color """
if master_widget is None: if master_widget is None:
@ -222,7 +221,7 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas
self._draw() self._draw()
super().update_idletasks() 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()._set_scaling(new_widget_scaling, new_window_scaling)
super().configure(width=self._apply_widget_scaling(self._desired_width), 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: if self._last_geometry_manager_call is not None:
self._last_geometry_manager_call["function"](**self._apply_argument_scaling(self._last_geometry_manager_call["kwargs"])) self._last_geometry_manager_call["function"](**self._apply_argument_scaling(self._last_geometry_manager_call["kwargs"]))
def _set_dimensions(self, width=None, height=None): def _set_dimensions(self, width: int | None = None, height: int | None = None):
if width is not None: if width is not None:
self._desired_width = width self._desired_width = width
if height is not None: 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), super().configure(width=self._apply_widget_scaling(self._desired_width),
height=self._apply_widget_scaling(self._desired_height)) 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 raise NotImplementedError
def unbind(self, sequence=None, funcid=None): def unbind(self, sequence: str, funcid: str | None = None):
raise NotImplementedError 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") 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") 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: Place a widget in the parent widget. Use as options:
in=master - master relative to which the widget is placed 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 self._last_geometry_manager_call = None
return super().place_forget() return super().place_forget()
def pack(self, **kwargs): def pack(self, **kwargs: Any):
""" """
Pack a widget in the parent widget. Use as options: Pack a widget in the parent widget. Use as options:
after=widget - pack it after you have packed widget 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 self._last_geometry_manager_call = None
return super().pack_forget() 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: Position a widget in the parent widget in a grid. Use as options:
column=number - use cell identified with given column (starting with 0) column=number - use cell identified with given column (starting with 0)

View File

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

View File

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

View File

@ -1,12 +1,18 @@
import tkinter from __future__ import annotations
import sys
from typing import Union, Tuple, Callable, Optional
from .core_rendering import CTkCanvas import sys
from .theme import ThemeManager import tkinter
from .core_rendering import DrawEngine 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 .core_widget_classes import CTkBaseClass
from .font import CTkFont from .font import CTkFont
from .theme import ThemeManager
class CTkCheckBox(CTkBaseClass): class CTkCheckBox(CTkBaseClass):
@ -16,32 +22,32 @@ class CTkCheckBox(CTkBaseClass):
""" """
def __init__(self, def __init__(self,
master: any, master: CTkBaseClass,
width: int = 100, width: int = 100,
height: int = 24, height: int = 24,
checkbox_width: int = 24, checkbox_width: int = 24,
checkbox_height: int = 24, checkbox_height: int = 24,
corner_radius: Optional[int] = None, corner_radius: int | None = None,
border_width: Optional[int] = None, border_width: int | None = None,
bg_color: Union[str, Tuple[str, str]] = "transparent", bg_color: str | tuple[str, str] = "transparent",
fg_color: Optional[Union[str, Tuple[str, str]]] = None, fg_color: str | tuple[str, str] | None = None,
hover_color: Optional[Union[str, Tuple[str, str]]] = None, hover_color: str | tuple[str, str] | None = None,
border_color: Optional[Union[str, Tuple[str, str]]] = None, border_color: str | tuple[str, str] | None = None,
checkmark_color: Optional[Union[str, Tuple[str, str]]] = None, checkmark_color: str | tuple[str, str] | None = None,
text_color: Optional[Union[str, Tuple[str, str]]] = None, text_color: str | tuple[str, str] | None = None,
text_color_disabled: Optional[Union[str, Tuple[str, str]]] = None, text_color_disabled: str | tuple[str, str] | None = None,
text: str = "CTkCheckBox", text: str = "CTkCheckBox",
font: Optional[Union[tuple, CTkFont]] = None, font: tuple[Any, ...] | CTkFont | None = None,
textvariable: Union[tkinter.Variable, None] = None, textvariable: tkinter.Variable | None = None,
state: str = tkinter.NORMAL, state: Literal["normal", "disabled", "readonly"] = "normal",
hover: bool = True, hover: bool = True,
command: Union[Callable[[], None], None] = None, command: Callable[[], None] | None = None,
onvalue: Union[int, str] = 1, onvalue: int | str = 1,
offvalue: Union[int, str] = 0, offvalue: int | str = 0,
variable: Union[tkinter.Variable, None] = None, variable: tkinter.Variable | None = None,
**kwargs): **kwargs: Any):
# transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass
super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs) super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
@ -62,7 +68,6 @@ class CTkCheckBox(CTkBaseClass):
# text # text
self._text = 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 = 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) 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._onvalue = onvalue
self._offvalue = offvalue self._offvalue = offvalue
self._variable: tkinter.Variable = variable self._variable = variable
self._variable_callback_blocked = False self._variable_callback_blocked = False
self._textvariable: tkinter.Variable = textvariable self._textvariable = textvariable
self._variable_callback_name = None self._variable_callback_name = None
# configure grid system (1x3) # configure grid system (1x3)
@ -123,7 +128,7 @@ class CTkCheckBox(CTkBaseClass):
self._set_cursor() self._set_cursor()
self._draw() 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 """ """ set necessary bindings for functionality of widget, will overwrite other bindings """
if sequence is None or sequence == "<Enter>": if sequence is None or sequence == "<Enter>":
self._canvas.bind("<Enter>", self._on_enter) self._canvas.bind("<Enter>", self._on_enter)
@ -135,7 +140,7 @@ class CTkCheckBox(CTkBaseClass):
self._canvas.bind("<Button-1>", self.toggle) self._canvas.bind("<Button-1>", self.toggle)
self._text_label.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) super()._set_scaling(*args, **kwargs)
self.grid_columnconfigure(1, weight=0, minsize=self._apply_widget_scaling(6)) 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)) height=self._apply_widget_scaling(self._checkbox_height))
self._draw(no_color_updates=True) 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) super()._set_dimensions(width, height)
self._bg_canvas.configure(width=self._apply_widget_scaling(self._desired_width), self._bg_canvas.configure(width=self._apply_widget_scaling(self._desired_width),
@ -173,7 +178,7 @@ class CTkCheckBox(CTkBaseClass):
super().destroy() super().destroy()
def _draw(self, no_color_updates=False): def _draw(self, no_color_updates: bool = False):
super()._draw(no_color_updates) super()._draw(no_color_updates)
requires_recoloring_1 = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._checkbox_width), 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)) 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: if "corner_radius" in kwargs:
self._corner_radius = kwargs.pop("corner_radius") self._corner_radius = kwargs.pop("corner_radius")
require_redraw = True require_redraw = True
if "border_width" in kwargs: if "border_width" in kwargs:
self._border_width = kwargs.pop("border_width") self._border_width: int = kwargs.pop("border_width")
require_redraw = True require_redraw = True
if "checkbox_width" in kwargs: 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)) self._canvas.configure(width=self._apply_widget_scaling(self._checkbox_width))
require_redraw = True require_redraw = True
if "checkbox_height" in kwargs: 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)) self._canvas.configure(height=self._apply_widget_scaling(self._checkbox_height))
require_redraw = True require_redraw = True
@ -296,7 +301,7 @@ class CTkCheckBox(CTkBaseClass):
super().configure(require_redraw=require_redraw, **kwargs) 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": if attribute_name == "corner_radius":
return self._corner_radius return self._corner_radius
elif attribute_name == "border_width": elif attribute_name == "border_width":
@ -360,7 +365,7 @@ class CTkCheckBox(CTkBaseClass):
if self._text_label is not None: if self._text_label is not None:
self._text_label.configure(cursor="hand2") 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._hover is True and self._state == tkinter.NORMAL:
if self._check_state is True: if self._check_state is True:
self._canvas.itemconfig("inner_parts", self._canvas.itemconfig("inner_parts",
@ -374,7 +379,7 @@ class CTkCheckBox(CTkBaseClass):
fill=self._apply_appearance_mode(self._hover_color), fill=self._apply_appearance_mode(self._hover_color),
outline=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: if self._check_state is True:
self._canvas.itemconfig("inner_parts", self._canvas.itemconfig("inner_parts",
fill=self._apply_appearance_mode(self._fg_color), fill=self._apply_appearance_mode(self._fg_color),
@ -390,14 +395,14 @@ class CTkCheckBox(CTkBaseClass):
fill=self._apply_appearance_mode(self._border_color), fill=self._apply_appearance_mode(self._border_color),
outline=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 not self._variable_callback_blocked:
if self._variable.get() == self._onvalue: if self._variable.get() == self._onvalue:
self.select(from_variable_callback=True) self.select(from_variable_callback=True)
elif self._variable.get() == self._offvalue: elif self._variable.get() == self._offvalue:
self.deselect(from_variable_callback=True) 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._state == tkinter.NORMAL:
if self._check_state is True: if self._check_state is True:
self._check_state = False self._check_state = False
@ -414,7 +419,7 @@ class CTkCheckBox(CTkBaseClass):
if self._command is not None: if self._command is not None:
self._command() self._command()
def select(self, from_variable_callback=False): def select(self, from_variable_callback: bool =False):
self._check_state = True self._check_state = True
self._draw() self._draw()
@ -423,7 +428,7 @@ class CTkCheckBox(CTkBaseClass):
self._variable.set(self._onvalue) self._variable.set(self._onvalue)
self._variable_callback_blocked = False 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._check_state = False
self._draw() self._draw()
@ -432,17 +437,17 @@ class CTkCheckBox(CTkBaseClass):
self._variable.set(self._offvalue) self._variable.set(self._offvalue)
self._variable_callback_blocked = False 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 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 """ """ called on the tkinter.Canvas """
if not (add == "+" or add is True): if not (add == "+" or add is True):
raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks") raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
self._canvas.bind(sequence, command, add=True) self._canvas.bind(sequence, command, add=True)
self._text_label.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 """ """ called on the tkinter.Label and tkinter.Canvas """
if funcid is not None: if funcid is not None:
raise ValueError("'funcid' argument can only be None, because there is a bug in" + 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 sys
import copy
from typing import Union, Tuple, Callable, List, Optional
from .core_widget_classes import DropdownMenu import copy
from .core_rendering import CTkCanvas import sys
from .theme import ThemeManager import tkinter
from .core_rendering import DrawEngine from typing import Any, Callable
from .core_widget_classes import CTkBaseClass
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 .font import CTkFont
from .theme import ThemeManager
class CTkComboBox(CTkBaseClass): class CTkComboBox(CTkBaseClass):
@ -18,32 +23,32 @@ class CTkComboBox(CTkBaseClass):
""" """
def __init__(self, def __init__(self,
master: any, master: CTkBaseClass,
width: int = 140, width: int = 140,
height: int = 28, height: int = 28,
corner_radius: Optional[int] = None, corner_radius: int | None = None,
border_width: Optional[int] = None, border_width: int | None = None,
bg_color: Union[str, Tuple[str, str]] = "transparent", bg_color: str | tuple[str, str] = "transparent",
fg_color: Optional[Union[str, Tuple[str, str]]] = None, fg_color: str | tuple[str, str] | None = None,
border_color: Optional[Union[str, Tuple[str, str]]] = None, border_color: str | tuple[str, str] | None = None,
button_color: Optional[Union[str, Tuple[str, str]]] = None, button_color: str | tuple[str, str] | None = None,
button_hover_color: Optional[Union[str, Tuple[str, str]]] = None, button_hover_color: str | tuple[str, str] | None = None,
dropdown_fg_color: Optional[Union[str, Tuple[str, str]]] = None, dropdown_fg_color: str | tuple[str, str] | None = None,
dropdown_hover_color: Optional[Union[str, Tuple[str, str]]] = None, dropdown_hover_color: str | tuple[str, str] | None = None,
dropdown_text_color: Optional[Union[str, Tuple[str, str]]] = None, dropdown_text_color: str | tuple[str, str] | None = None,
text_color: Optional[Union[str, Tuple[str, str]]] = None, text_color: str | tuple[str, str] | None = None,
text_color_disabled: Optional[Union[str, Tuple[str, str]]] = None, text_color_disabled: str | tuple[str, str] | None = None,
font: Optional[Union[tuple, CTkFont]] = None, font: tuple[Any, ...] | CTkFont | None = None,
dropdown_font: Optional[Union[tuple, CTkFont]] = None, dropdown_font: tuple[Any, ...] | CTkFont | None = None,
values: Optional[List[str]] = None, values: list[str] | None = None,
state: str = tkinter.NORMAL, state: Literal["normal", "disabled", "readonly"] = "normal",
hover: bool = True, hover: bool = True,
variable: Union[tkinter.Variable, None] = None, variable: tkinter.Variable | None = None,
command: Union[Callable[[str], None], None] = None, command: Callable[[str], None] | None = None,
justify: str = "left", justify: Literal["left", "center", "right"] = "left",
**kwargs): **kwargs: Any):
# transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass
super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs) super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
@ -70,11 +75,7 @@ class CTkComboBox(CTkBaseClass):
self._variable = variable self._variable = variable
self._state = state self._state = state
self._hover = hover self._hover = hover
self._values = values or ["CTkComboBox"]
if values is None:
self._values = ["CTkComboBox"]
else:
self._values = values
self._dropdown_menu = DropdownMenu(master=self, self._dropdown_menu = DropdownMenu(master=self,
values=self._values, values=self._values,
@ -116,7 +117,7 @@ class CTkComboBox(CTkBaseClass):
else: else:
self._entry.insert(0, "CTkComboBox") 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 """ """ set necessary bindings for functionality of widget, will overwrite other bindings """
if sequence is None: if sequence is None:
self._canvas.tag_bind("right_parts", "<Enter>", self._on_enter) 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))), 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)) 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) super()._set_scaling(*args, **kwargs)
# change entry font size and grid padding # change entry font size and grid padding
@ -146,7 +147,7 @@ class CTkComboBox(CTkBaseClass):
height=self._apply_widget_scaling(self._desired_height)) height=self._apply_widget_scaling(self._desired_height))
self._draw(no_color_updates=True) 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) super()._set_dimensions(width, height)
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
@ -168,7 +169,7 @@ class CTkComboBox(CTkBaseClass):
super().destroy() super().destroy()
def _draw(self, no_color_updates=False): def _draw(self, no_color_updates: bool = False):
super()._draw(no_color_updates) super()._draw(no_color_updates)
left_section_width = self._current_width - self._current_height left_section_width = self._current_width - self._current_height
@ -218,7 +219,7 @@ class CTkComboBox(CTkBaseClass):
self._dropdown_menu.open(self.winfo_rootx(), self._dropdown_menu.open(self.winfo_rootx(),
self.winfo_rooty() + self._apply_widget_scaling(self._current_height + 0)) 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: if "corner_radius" in kwargs:
self._corner_radius = kwargs.pop("corner_radius") self._corner_radius = kwargs.pop("corner_radius")
require_redraw = True require_redraw = True
@ -297,7 +298,7 @@ class CTkComboBox(CTkBaseClass):
super().configure(require_redraw=require_redraw, **kwargs) 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": if attribute_name == "corner_radius":
return self._corner_radius return self._corner_radius
elif attribute_name == "border_width": elif attribute_name == "border_width":
@ -341,7 +342,7 @@ class CTkComboBox(CTkBaseClass):
else: else:
return super().cget(attribute_name) 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 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: if sys.platform == "darwin" and len(self._values) > 0 and self._cursor_manipulation_enabled:
self._canvas.configure(cursor="pointinghand") self._canvas.configure(cursor="pointinghand")
@ -356,7 +357,7 @@ class CTkComboBox(CTkBaseClass):
outline=self._apply_appearance_mode(self._button_hover_color), outline=self._apply_appearance_mode(self._button_hover_color),
fill=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: if sys.platform == "darwin" and len(self._values) > 0 and self._cursor_manipulation_enabled:
self._canvas.configure(cursor="arrow") self._canvas.configure(cursor="arrow")
elif sys.platform.startswith("win") and len(self._values) > 0 and self._cursor_manipulation_enabled: 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: def get(self) -> str:
return self._entry.get() 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: if self._state is not tkinter.DISABLED and len(self._values) > 0:
self._open_dropdown_menu() 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 """ """ called on the tkinter.Entry """
if not (add == "+" or add is True): if not (add == "+" or add is True):
raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks") raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
self._entry.bind(sequence, command, add=True) 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 """ """ called on the tkinter.Entry """
if funcid is not None: if funcid is not None:
raise ValueError("'funcid' argument can only be None, because there is a bug in" + raise ValueError("'funcid' argument can only be None, because there is a bug in" +

View File

@ -1,12 +1,19 @@
import tkinter from __future__ import annotations
from typing import Union, Tuple, Optional
from .core_rendering import CTkCanvas import sys
from .theme import ThemeManager import tkinter
from .core_rendering import DrawEngine 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 .core_widget_classes import CTkBaseClass
from .font import CTkFont 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): class CTkEntry(CTkBaseClass):
@ -23,23 +30,23 @@ class CTkEntry(CTkBaseClass):
"show", "takefocus", "validate", "validatecommand", "xscrollcommand"} "show", "takefocus", "validate", "validatecommand", "xscrollcommand"}
def __init__(self, def __init__(self,
master: any, master: CTkBaseClass,
width: int = 140, width: int = 140,
height: int = 28, height: int = 28,
corner_radius: Optional[int] = None, corner_radius: int | None = None,
border_width: Optional[int] = None, border_width: int | None = None,
bg_color: Union[str, Tuple[str, str]] = "transparent", bg_color: str | tuple[str, str] = "transparent",
fg_color: Optional[Union[str, Tuple[str, str]]] = None, fg_color: str | tuple[str, str] | None = None,
border_color: Optional[Union[str, Tuple[str, str]]] = None, border_color: str | tuple[str, str] | None = None,
text_color: Optional[Union[str, Tuple[str, str]]] = None, text_color: str | tuple[str, str] | None = None,
placeholder_text_color: Optional[Union[str, Tuple[str, str]]] = None, placeholder_text_color: str | tuple[str, str] | None = None,
textvariable: Union[tkinter.Variable, None] = None, textvariable: tkinter.Variable | None = None,
placeholder_text: Union[str, None] = None, placeholder_text: str | None = None,
font: Optional[Union[tuple, CTkFont]] = None, font: tuple[Any, ...] | CTkFont | None = None,
state: str = tkinter.NORMAL, state: Literal["normal", "disabled", "readonly"] = "normal",
**kwargs): **kwargs: Any):
# transfer basic functionality (bg_color, size, appearance_mode, scaling) to CTkBaseClass # transfer basic functionality (bg_color, size, appearance_mode, scaling) to CTkBaseClass
super().__init__(master=master, bg_color=bg_color, width=width, height=height) super().__init__(master=master, bg_color=bg_color, width=width, height=height)
@ -97,7 +104,7 @@ class CTkEntry(CTkBaseClass):
self._create_bindings() self._create_bindings()
self._draw() 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 """ """ set necessary bindings for functionality of widget, will overwrite other bindings """
if sequence is None or sequence == "<FocusIn>": if sequence is None or sequence == "<FocusIn>":
self._entry.bind("<FocusIn>", self._entry_focus_in) self._entry.bind("<FocusIn>", self._entry_focus_in)
@ -116,11 +123,11 @@ class CTkEntry(CTkBaseClass):
padx=self._apply_widget_scaling(self._minimum_x_padding), 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))) 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() == "": if self._textvariable.get() == "":
self._activate_placeholder() self._activate_placeholder()
def _set_scaling(self, *args, **kwargs): def _set_scaling(self, *args: Any, **kwargs: Any):
super()._set_scaling(*args, **kwargs) super()._set_scaling(*args, **kwargs)
self._entry.configure(font=self._apply_font_scaling(self._font)) self._entry.configure(font=self._apply_font_scaling(self._font))
@ -128,7 +135,7 @@ class CTkEntry(CTkBaseClass):
self._create_grid() self._create_grid()
self._draw(no_color_updates=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) super()._set_dimensions(width, height)
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
@ -150,7 +157,7 @@ class CTkEntry(CTkBaseClass):
super().destroy() super().destroy()
def _draw(self, no_color_updates=False): def _draw(self, no_color_updates: bool = False):
super()._draw(no_color_updates) super()._draw(no_color_updates)
requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._current_width), 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), disabledforeground=self._apply_appearance_mode(self._text_color),
insertbackground=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: if "state" in kwargs:
self._state = kwargs.pop("state") self._state = kwargs.pop("state")
self._entry.configure(state=self._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 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 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": if attribute_name == "corner_radius":
return self._corner_radius return self._corner_radius
elif attribute_name == "border_width": elif attribute_name == "border_width":
@ -281,7 +288,7 @@ class CTkEntry(CTkBaseClass):
else: else:
return super().cget(attribute_name) # cget of CTkBaseClass 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 """ """ called on the tkinter.Entry """
if not (add == "+" or add is True): if not (add == "+" or add is True):
raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks") 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(): for argument, value in self._pre_placeholder_arguments.items():
self._entry[argument] = value 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._activate_placeholder()
self._is_focused = False 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._deactivate_placeholder()
self._is_focused = True self._is_focused = True
@ -330,7 +337,7 @@ class CTkEntry(CTkBaseClass):
if not self._is_focused and self._entry.get() == "": if not self._is_focused and self._entry.get() == "":
self._activate_placeholder() self._activate_placeholder()
def insert(self, index, string): def insert(self, index: int, string):
self._deactivate_placeholder() self._deactivate_placeholder()
return self._entry.insert(index, string) 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 import sys
from .theme import ThemeManager import tkinter
from .core_rendering import DrawEngine 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 .core_widget_classes import CTkBaseClass
from .theme import ThemeManager
class CTkFrame(CTkBaseClass): class CTkFrame(CTkBaseClass):
@ -15,19 +23,19 @@ class CTkFrame(CTkBaseClass):
""" """
def __init__(self, def __init__(self,
master: any, master: CTkBaseClass,
width: int = 200, width: int = 200,
height: int = 200, height: int = 200,
corner_radius: Optional[Union[int, str]] = None, corner_radius: int | str | None = None,
border_width: Optional[Union[int, str]] = None, border_width: int | str | None = None,
bg_color: Union[str, Tuple[str, str]] = "transparent", bg_color: str | tuple[str, str] = "transparent",
fg_color: Optional[Union[str, Tuple[str, str]]] = None, fg_color: str | tuple[str, str] | None = None,
border_color: Optional[Union[str, Tuple[str, str]]] = None, border_color: 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,
overwrite_preferred_drawing_method: Union[str, None] = None, overwrite_preferred_drawing_method: str | None = None,
**kwargs): **kwargs: Any):
# transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass
super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs) 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) 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, winfo_children of CTkFrame without self.canvas widget,
because it's not a child but part of the CTkFrame itself because it's not a child but part of the CTkFrame itself
@ -77,21 +85,21 @@ class CTkFrame(CTkBaseClass):
except ValueError: except ValueError:
return child_widgets return child_widgets
def _set_scaling(self, *args, **kwargs): def _set_scaling(self, *args: Any, **kwargs: Any):
super()._set_scaling(*args, **kwargs) super()._set_scaling(*args, **kwargs)
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
height=self._apply_widget_scaling(self._desired_height)) height=self._apply_widget_scaling(self._desired_height))
self._draw() 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) super()._set_dimensions(width, height)
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
height=self._apply_widget_scaling(self._desired_height)) height=self._apply_widget_scaling(self._desired_height))
self._draw() self._draw()
def _draw(self, no_color_updates=False): def _draw(self, no_color_updates: bool = False):
super()._draw(no_color_updates) super()._draw(no_color_updates)
if not self._canvas.winfo_exists(): 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("inner_parts") # maybe unnecessary, I don't know ???
# self._canvas.tag_lower("border_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: if "fg_color" in kwargs:
self._fg_color = self._check_color_type(kwargs.pop("fg_color"), transparency=True) self._fg_color = self._check_color_type(kwargs.pop("fg_color"), transparency=True)
require_redraw = True require_redraw = True
@ -166,7 +174,7 @@ class CTkFrame(CTkBaseClass):
super().configure(require_redraw=require_redraw, **kwargs) 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": if attribute_name == "corner_radius":
return self._corner_radius return self._corner_radius
elif attribute_name == "border_width": elif attribute_name == "border_width":
@ -182,13 +190,13 @@ class CTkFrame(CTkBaseClass):
else: else:
return super().cget(attribute_name) 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 """ """ called on the tkinter.Canvas """
if not (add == "+" or add is True): if not (add == "+" or add is True):
raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks") raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
self._canvas.bind(sequence, command, add=True) 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 """ """ called on the tkinter.Canvas """
if funcid is not None: if funcid is not None:
raise ValueError("'funcid' argument can only be None, because there is a bug in" + raise ValueError("'funcid' argument can only be None, because there is a bug in" +

View File

@ -1,13 +1,14 @@
import tkinter from __future__ import annotations
from typing import Union, Tuple, Callable, Optional
from .core_rendering import CTkCanvas import tkinter
from .theme import ThemeManager from typing import Any, Callable
from .core_rendering import DrawEngine
from .core_rendering import CTkCanvas, DrawEngine
from .core_widget_classes import CTkBaseClass from .core_widget_classes import CTkBaseClass
from .font import CTkFont from .font import CTkFont
from .image import CTkImage 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): class CTkLabel(CTkBaseClass):
@ -21,22 +22,22 @@ class CTkLabel(CTkBaseClass):
"textvariable", "state", "takefocus", "underline"} "textvariable", "state", "takefocus", "underline"}
def __init__(self, def __init__(self,
master: any, master: CTkBaseClass,
width: int = 0, width: int = 0,
height: int = 28, height: int = 28,
corner_radius: Optional[int] = None, corner_radius: int | None = None,
bg_color: Union[str, Tuple[str, str]] = "transparent", bg_color: str | tuple[str, str] = "transparent",
fg_color: Optional[Union[str, Tuple[str, str]]] = None, fg_color: str | tuple[str, str] | None = None,
text_color: Optional[Union[str, Tuple[str, str]]] = None, text_color: str | tuple[str, str] | None = None,
text: str = "CTkLabel", text: str = "CTkLabel",
font: Optional[Union[tuple, CTkFont]] = None, font: tuple[Any, ...] | CTkFont | None = None,
image: Union[CTkImage, None] = None, image: CTkImage | None = None,
compound: str = "center", compound: str = "center",
anchor: str = "center", # label anchor: center, n, e, s, w anchor: str = "center", # label anchor: center, n, e, s, w
wraplength: int = 0, wraplength: int = 0,
**kwargs): **kwargs: Any):
# transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass
super().__init__(master=master, bg_color=bg_color, width=width, height=height) super().__init__(master=master, bg_color=bg_color, width=width, height=height)
@ -93,7 +94,7 @@ class CTkLabel(CTkBaseClass):
self._update_image() self._update_image()
self._draw() self._draw()
def _set_scaling(self, *args, **kwargs): def _set_scaling(self, *args: Any, **kwargs: Any):
super()._set_scaling(*args, **kwargs) super()._set_scaling(*args, **kwargs)
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), height=self._apply_widget_scaling(self._desired_height)) self._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) super()._set_appearance_mode(mode_string)
self._update_image() 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) super()._set_dimensions(width, height)
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), 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, 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)))) 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) super()._draw(no_color_updates)
requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._current_width), 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)) 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: if "corner_radius" in kwargs:
self._corner_radius = kwargs.pop("corner_radius") self._corner_radius = kwargs.pop("corner_radius")
self._create_grid() 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 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 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": if attribute_name == "corner_radius":
return self._corner_radius return self._corner_radius
@ -247,14 +248,14 @@ class CTkLabel(CTkBaseClass):
else: else:
return super().cget(attribute_name) # cget of CTkBaseClass 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 """ """ called on the tkinter.Label and tkinter.Canvas """
if not (add == "+" or add is True): if not (add == "+" or add is True):
raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks") raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
self._canvas.bind(sequence, command, add=True) self._canvas.bind(sequence, command, add=True)
self._label.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 """ """ called on the tkinter.Label and tkinter.Canvas """
if funcid is not None: if funcid is not None:
raise ValueError("'funcid' argument can only be None, because there is a bug in" + 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 copy
import sys import sys
from typing import Union, Tuple, Callable, Optional import tkinter
from typing import Any, Callable
from .core_rendering import CTkCanvas if sys.version_info >= (3, 8):
from .theme import ThemeManager from typing import Literal
from .core_rendering import DrawEngine else:
from .core_widget_classes import CTkBaseClass from typing_extensions import Literal
from .core_widget_classes import DropdownMenu
from .core_rendering import CTkCanvas, DrawEngine
from .core_widget_classes import CTkBaseClass, DropdownMenu
from .font import CTkFont from .font import CTkFont
from .theme import ThemeManager
class CTkOptionMenu(CTkBaseClass): class CTkOptionMenu(CTkBaseClass):
@ -18,31 +23,31 @@ class CTkOptionMenu(CTkBaseClass):
""" """
def __init__(self, def __init__(self,
master: any, master: CTkBaseClass,
width: int = 140, width: int = 140,
height: int = 28, height: int = 28,
corner_radius: Optional[Union[int]] = None, corner_radius: int | None = None,
bg_color: Union[str, Tuple[str, str]] = "transparent", bg_color: str | tuple[str, str] = "transparent",
fg_color: Optional[Union[str, Tuple[str, str]]] = None, fg_color: str | tuple[str, str] | None = None,
button_color: Optional[Union[str, Tuple[str, str]]] = None, button_color: str | tuple[str, str] | None = None,
button_hover_color: Optional[Union[str, Tuple[str, str]]] = None, button_hover_color: str | tuple[str, str] | None = None,
text_color: Optional[Union[str, Tuple[str, str]]] = None, text_color: str | tuple[str, str] | None = None,
text_color_disabled: Optional[Union[str, Tuple[str, str]]] = None, text_color_disabled: str | tuple[str, str] | None = None,
dropdown_fg_color: Optional[Union[str, Tuple[str, str]]] = None, dropdown_fg_color: str | tuple[str, str] | None = None,
dropdown_hover_color: Optional[Union[str, Tuple[str, str]]] = None, dropdown_hover_color: str | tuple[str, str] | None = None,
dropdown_text_color: Optional[Union[str, Tuple[str, str]]] = None, dropdown_text_color: str | tuple[str, str] | None = None,
font: Optional[Union[tuple, CTkFont]] = None, font: tuple[Any, ...] | CTkFont | None = None,
dropdown_font: Optional[Union[tuple, CTkFont]] = None, dropdown_font: tuple[Any, ...] | CTkFont | None = None,
values: Optional[list] = None, values: list | None = None,
variable: Union[tkinter.Variable, None] = None, variable: tkinter.Variable | None = None,
state: str = tkinter.NORMAL, state: Literal["normal", "disabled", "readonly"] = "normal",
hover: bool = True, hover: bool = True,
command: Union[Callable[[str], None], None] = None, command: Callable[[str], None] | None = None,
dynamic_resizing: bool = True, dynamic_resizing: bool = True,
anchor: str = "w", anchor: str = "w",
**kwargs): **kwargs: Any):
# transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass
super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs) super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
@ -68,7 +73,7 @@ class CTkOptionMenu(CTkBaseClass):
self._command = command self._command = command
self._variable = variable self._variable = variable
self._variable_callback_blocked: bool = False 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._state = state
self._hover = hover self._hover = hover
self._dynamic_resizing = dynamic_resizing self._dynamic_resizing = dynamic_resizing
@ -127,7 +132,7 @@ class CTkOptionMenu(CTkBaseClass):
self._current_value = self._variable.get() self._current_value = self._variable.get()
self._text_label.configure(text=self._current_value) 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 """ """ set necessary bindings for functionality of widget, will overwrite other bindings """
if sequence is None or sequence == "<Enter>": if sequence is None or sequence == "<Enter>":
self._canvas.bind("<Enter>", self._on_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)), 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)))) 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) super()._set_scaling(*args, **kwargs)
# change label font size and grid padding # change label font size and grid padding
@ -157,7 +162,7 @@ class CTkOptionMenu(CTkBaseClass):
self._create_grid() self._create_grid()
self._draw(no_color_updates=True) 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) super()._set_dimensions(width, height)
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
@ -182,7 +187,7 @@ class CTkOptionMenu(CTkBaseClass):
super().destroy() super().destroy()
def _draw(self, no_color_updates=False): def _draw(self, no_color_updates: bool = False):
super()._draw(no_color_updates) super()._draw(no_color_updates)
left_section_width = self._current_width - self._current_height left_section_width = self._current_width - self._current_height
@ -221,7 +226,7 @@ class CTkOptionMenu(CTkBaseClass):
self._canvas.update_idletasks() 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: if "corner_radius" in kwargs:
self._corner_radius = kwargs.pop("corner_radius") self._corner_radius = kwargs.pop("corner_radius")
self._create_grid() self._create_grid()
@ -303,7 +308,7 @@ class CTkOptionMenu(CTkBaseClass):
super().configure(require_redraw=require_redraw, **kwargs) 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": if attribute_name == "corner_radius":
return self._corner_radius return self._corner_radius
@ -350,20 +355,20 @@ class CTkOptionMenu(CTkBaseClass):
self._dropdown_menu.open(self.winfo_rootx(), self._dropdown_menu.open(self.winfo_rootx(),
self.winfo_rooty() + self._apply_widget_scaling(self._current_height + 0)) 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: if self._hover is True and self._state == tkinter.NORMAL and len(self._values) > 0:
# set color of inner button parts to hover color # set color of inner button parts to hover color
self._canvas.itemconfig("inner_parts_right", self._canvas.itemconfig("inner_parts_right",
outline=self._apply_appearance_mode(self._button_hover_color), outline=self._apply_appearance_mode(self._button_hover_color),
fill=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 # set color of inner button parts
self._canvas.itemconfig("inner_parts_right", self._canvas.itemconfig("inner_parts_right",
outline=self._apply_appearance_mode(self._button_color), outline=self._apply_appearance_mode(self._button_color),
fill=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: if not self._variable_callback_blocked:
self._current_value = self._variable.get() self._current_value = self._variable.get()
self._text_label.configure(text=self._current_value) self._text_label.configure(text=self._current_value)
@ -392,18 +397,18 @@ class CTkOptionMenu(CTkBaseClass):
def get(self) -> str: def get(self) -> str:
return self._current_value 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: if self._state is not tkinter.DISABLED and len(self._values) > 0:
self._open_dropdown_menu() 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 """ """ called on the tkinter.Canvas """
if not (add == "+" or add is True): if not (add == "+" or add is True):
raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks") raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
self._canvas.bind(sequence, command, add=True) self._canvas.bind(sequence, command, add=True)
self._text_label.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 """ """ called on the tkinter.Label and tkinter.Canvas """
if funcid is not None: if funcid is not None:
raise ValueError("'funcid' argument can only be None, because there is a bug in" + 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 import math
from typing import Union, Tuple, Optional, Callable import tkinter
from typing import Any, Callable
try: try:
from typing import Literal from typing import Literal
except ImportError: except ImportError:
from typing_extensions import Literal from typing_extensions import Literal
from .core_rendering import CTkCanvas from .core_rendering import CTkCanvas, DrawEngine
from .theme import ThemeManager
from .core_rendering import DrawEngine
from .core_widget_classes import CTkBaseClass from .core_widget_classes import CTkBaseClass
from .theme import ThemeManager
class CTkProgressBar(CTkBaseClass): class CTkProgressBar(CTkBaseClass):
@ -20,23 +22,23 @@ class CTkProgressBar(CTkBaseClass):
""" """
def __init__(self, def __init__(self,
master: any, master: CTkBaseClass,
width: Optional[int] = None, width: int | None = None,
height: Optional[int] = None, height: int | None = None,
corner_radius: Optional[int] = None, corner_radius: int | None = None,
border_width: Optional[int] = None, border_width: int | None = None,
bg_color: Union[str, Tuple[str, str]] = "transparent", bg_color: str | tuple[str, str] = "transparent",
fg_color: Optional[Union[str, Tuple[str, str]]] = None, fg_color: str | tuple[str, str] | None = None,
border_color: Optional[Union[str, Tuple[str, str]]] = None, border_color: str | tuple[str, str] | None = None,
progress_color: Optional[Union[str, Tuple[str, str]]] = None, progress_color: str | tuple[str, str] | None = None,
variable: Union[tkinter.Variable, None] = None, variable: tkinter.Variable | None = None,
orientation: str = "horizontal", orientation: str = "horizontal",
mode: Literal["determinate", "indeterminate"] = "determinate", mode: Literal["determinate", "indeterminate"] = "determinate",
determinate_speed: float = 1, determinate_speed: float = 1,
indeterminate_speed: float = 1, indeterminate_speed: float = 1,
**kwargs): **kwargs: Any):
# set default dimensions according to orientation # set default dimensions according to orientation
if width is None: if width is None:
@ -94,14 +96,14 @@ class CTkProgressBar(CTkBaseClass):
self.set(self._variable.get(), from_variable_callback=True) self.set(self._variable.get(), from_variable_callback=True)
self._variable_callback_blocked = False self._variable_callback_blocked = False
def _set_scaling(self, *args, **kwargs): def _set_scaling(self, *args: Any, **kwargs: Any):
super()._set_scaling(*args, **kwargs) super()._set_scaling(*args, **kwargs)
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
height=self._apply_widget_scaling(self._desired_height)) height=self._apply_widget_scaling(self._desired_height))
self._draw(no_color_updates=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) super()._set_dimensions(width, height)
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
@ -114,7 +116,7 @@ class CTkProgressBar(CTkBaseClass):
super().destroy() super().destroy()
def _draw(self, no_color_updates=False): def _draw(self, no_color_updates: bool = False):
super()._draw(no_color_updates) super()._draw(no_color_updates)
if self._orientation.lower() == "horizontal": if self._orientation.lower() == "horizontal":
@ -157,7 +159,7 @@ class CTkProgressBar(CTkBaseClass):
fill=self._apply_appearance_mode(self._progress_color), fill=self._apply_appearance_mode(self._progress_color),
outline=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: if "corner_radius" in kwargs:
self._corner_radius = kwargs.pop("corner_radius") self._corner_radius = kwargs.pop("corner_radius")
require_redraw = True require_redraw = True
@ -202,7 +204,7 @@ class CTkProgressBar(CTkBaseClass):
super().configure(require_redraw=require_redraw, **kwargs) 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": if attribute_name == "corner_radius":
return self._corner_radius return self._corner_radius
elif attribute_name == "border_width": elif attribute_name == "border_width":
@ -229,11 +231,11 @@ class CTkProgressBar(CTkBaseClass):
else: else:
return super().cget(attribute_name) 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: if not self._variable_callback_blocked:
self.set(self._variable.get(), from_variable_callback=True) 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 """ """ set determinate value """
self._determinate_value = value self._determinate_value = value
@ -289,13 +291,13 @@ class CTkProgressBar(CTkBaseClass):
self._indeterminate_value += self._indeterminate_speed self._indeterminate_value += self._indeterminate_speed
self._draw() 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 """ """ called on the tkinter.Canvas """
if not (add == "+" or add is True): if not (add == "+" or add is True):
raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks") raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
self._canvas.bind(sequence, command, add=True) 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 """ """ called on the tkinter.Label and tkinter.Canvas """
if funcid is not None: if funcid is not None:
raise ValueError("'funcid' argument can only be None, because there is a bug in" + raise ValueError("'funcid' argument can only be None, because there is a bug in" +

View File

@ -1,12 +1,18 @@
import tkinter from __future__ import annotations
import sys
from typing import Union, Tuple, Callable, Optional
from .core_rendering import CTkCanvas import sys
from .theme import ThemeManager import tkinter
from .core_rendering import DrawEngine 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 .core_widget_classes import CTkBaseClass
from .font import CTkFont from .font import CTkFont
from .theme import ThemeManager
class CTkRadioButton(CTkBaseClass): class CTkRadioButton(CTkBaseClass):
@ -16,31 +22,31 @@ class CTkRadioButton(CTkBaseClass):
""" """
def __init__(self, def __init__(self,
master: any, master: CTkBaseClass,
width: int = 100, width: int = 100,
height: int = 22, height: int = 22,
radiobutton_width: int = 22, radiobutton_width: int = 22,
radiobutton_height: int = 22, radiobutton_height: int = 22,
corner_radius: Optional[int] = None, corner_radius: int | None = None,
border_width_unchecked: Optional[int] = None, border_width_unchecked: int | None = None,
border_width_checked: Optional[int] = None, border_width_checked: int | None = None,
bg_color: Union[str, Tuple[str, str]] = "transparent", bg_color: str | tuple[str, str] = "transparent",
fg_color: Optional[Union[str, Tuple[str, str]]] = None, fg_color: str | tuple[str, str] | None = None,
hover_color: Optional[Union[str, Tuple[str, str]]] = None, hover_color: str | tuple[str, str] | None = None,
border_color: Optional[Union[str, Tuple[str, str]]] = None, border_color: str | tuple[str, str] | None = None,
text_color: Optional[Union[str, Tuple[str, str]]] = None, text_color: str | tuple[str, str] | None = None,
text_color_disabled: Optional[Union[str, Tuple[str, str]]] = None, text_color_disabled: str | tuple[str, str] | None = None,
text: str = "CTkRadioButton", text: str = "CTkRadioButton",
font: Optional[Union[tuple, CTkFont]] = None, font: tuple[Any, ...] | CTkFont | None = None,
textvariable: Union[tkinter.Variable, None] = None, textvariable: tkinter.Variable | None = None,
variable: Union[tkinter.Variable, None] = None, variable: tkinter.Variable | None = None,
value: Union[int, str] = 0, value: int | str = 0,
state: str = tkinter.NORMAL, state: Literal["normal", "disabled", "readonly"] = "normal",
hover: bool = True, hover: bool = True,
command: Union[Callable, None] = None, command: Callable[..., None] | None = None,
**kwargs): **kwargs: Any):
# transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass
super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs) super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
@ -61,7 +67,7 @@ class CTkRadioButton(CTkBaseClass):
# text # text
self._text = 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 = 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) 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: tkinter.Variable = variable
self._variable_callback_blocked: bool = False self._variable_callback_blocked: bool = False
self._textvariable = textvariable self._textvariable = textvariable
self._variable_callback_name: Union[str, None] = None self._variable_callback_name: str | None = None
# configure grid system (3x1) # configure grid system (3x1)
self.grid_columnconfigure(0, weight=0) self.grid_columnconfigure(0, weight=0)
@ -119,7 +125,7 @@ class CTkRadioButton(CTkBaseClass):
self._set_cursor() self._set_cursor()
self._draw() 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 """ """ set necessary bindings for functionality of widget, will overwrite other bindings """
if sequence is None or sequence == "<Enter>": if sequence is None or sequence == "<Enter>":
self._canvas.bind("<Enter>", self._on_enter) self._canvas.bind("<Enter>", self._on_enter)
@ -131,7 +137,7 @@ class CTkRadioButton(CTkBaseClass):
self._canvas.bind("<Button-1>", self.invoke) self._canvas.bind("<Button-1>", self.invoke)
self._text_label.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) super()._set_scaling(*args, **kwargs)
self.grid_columnconfigure(1, weight=0, minsize=self._apply_widget_scaling(6)) 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)) height=self._apply_widget_scaling(self._radiobutton_height))
self._draw(no_color_updates=True) 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) super()._set_dimensions(width, height)
self._bg_canvas.configure(width=self._apply_widget_scaling(self._desired_width), self._bg_canvas.configure(width=self._apply_widget_scaling(self._desired_width),
@ -167,7 +173,7 @@ class CTkRadioButton(CTkBaseClass):
super().destroy() super().destroy()
def _draw(self, no_color_updates=False): def _draw(self, no_color_updates: bool = False):
super()._draw(no_color_updates) super()._draw(no_color_updates)
if self._check_state is True: 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)) 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: if "corner_radius" in kwargs:
self._corner_radius = kwargs.pop("corner_radius") self._corner_radius = kwargs.pop("corner_radius")
require_redraw = True require_redraw = True
@ -289,7 +295,7 @@ class CTkRadioButton(CTkBaseClass):
super().configure(require_redraw=require_redraw, **kwargs) 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": if attribute_name == "corner_radius":
return self._corner_radius return self._corner_radius
elif attribute_name == "border_width_unchecked": elif attribute_name == "border_width_unchecked":
@ -354,13 +360,13 @@ class CTkRadioButton(CTkBaseClass):
if self._text_label is not None: if self._text_label is not None:
self._text_label.configure(cursor="hand2") 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._hover is True and self._state == tkinter.NORMAL:
self._canvas.itemconfig("border_parts", self._canvas.itemconfig("border_parts",
fill=self._apply_appearance_mode(self._hover_color), fill=self._apply_appearance_mode(self._hover_color),
outline=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: if self._check_state is True:
self._canvas.itemconfig("border_parts", self._canvas.itemconfig("border_parts",
fill=self._apply_appearance_mode(self._fg_color), fill=self._apply_appearance_mode(self._fg_color),
@ -370,14 +376,14 @@ class CTkRadioButton(CTkBaseClass):
fill=self._apply_appearance_mode(self._border_color), fill=self._apply_appearance_mode(self._border_color),
outline=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 not self._variable_callback_blocked:
if self._variable.get() == self._value: if self._variable.get() == self._value:
self.select(from_variable_callback=True) self.select(from_variable_callback=True)
else: else:
self.deselect(from_variable_callback=True) 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._state == tkinter.NORMAL:
if self._check_state is False: if self._check_state is False:
self._check_state = True self._check_state = True
@ -386,7 +392,7 @@ class CTkRadioButton(CTkBaseClass):
if self._command is not None: if self._command is not None:
self._command() self._command()
def select(self, from_variable_callback=False): def select(self, from_variable_callback: bool = False):
self._check_state = True self._check_state = True
self._draw() self._draw()
@ -395,7 +401,7 @@ class CTkRadioButton(CTkBaseClass):
self._variable.set(self._value) self._variable.set(self._value)
self._variable_callback_blocked = False 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._check_state = False
self._draw() self._draw()
@ -404,14 +410,14 @@ class CTkRadioButton(CTkBaseClass):
self._variable.set("") self._variable.set("")
self._variable_callback_blocked = False 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 """ """ called on the tkinter.Canvas """
if not (add == "+" or add is True): if not (add == "+" or add is True):
raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks") raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
self._canvas.bind(sequence, command, add=True) self._canvas.bind(sequence, command, add=True)
self._text_label.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 """ """ called on the tkinter.Label and tkinter.Canvas """
if funcid is not None: if funcid is not None:
raise ValueError("'funcid' argument can only be None, because there is a bug in" + 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: try:
from typing import Literal from typing import Literal
except ImportError: except ImportError:
from typing_extensions import Literal 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 .appearance_mode import CTkAppearanceModeBaseClass
from .scaling import CTkScalingBaseClass
from .core_widget_classes import CTkBaseClass from .core_widget_classes import CTkBaseClass
from .ctk_frame import CTkFrame
from .ctk_label import CTkLabel from .ctk_label import CTkLabel
from .ctk_scrollbar import CTkScrollbar
from .font import CTkFont from .font import CTkFont
from .scaling import CTkScalingBaseClass
from .theme import ThemeManager from .theme import ThemeManager
class CTkScrollableFrame(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClass): class CTkScrollableFrame(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
def __init__(self, def __init__(self,
master: any, master: CTkBaseClass,
width: int = 200, width: int = 200,
height: int = 200, height: int = 200,
corner_radius: Optional[Union[int, str]] = None, corner_radius: int | str | None = None,
border_width: Optional[Union[int, str]] = None, border_width: int | str | None = None,
bg_color: Union[str, Tuple[str, str]] = "transparent", bg_color: str | tuple[str, str] = "transparent",
fg_color: Optional[Union[str, Tuple[str, str]]] = None, fg_color: str | tuple[str, str] | None = None,
border_color: Optional[Union[str, Tuple[str, str]]] = None, border_color: str | tuple[str, str] | None = None,
scrollbar_fg_color: Optional[Union[str, Tuple[str, str]]] = None, scrollbar_fg_color: str | tuple[str, str] | None = None,
scrollbar_button_color: Optional[Union[str, Tuple[str, str]]] = None, scrollbar_button_color: str | tuple[str, str] | None = None,
scrollbar_button_hover_color: Optional[Union[str, Tuple[str, str]]] = None, scrollbar_button_hover_color: str | tuple[str, str] | None = None,
label_fg_color: Optional[Union[str, Tuple[str, str]]] = None, label_fg_color: str | tuple[str, str] | None = None,
label_text_color: Optional[Union[str, Tuple[str, str]]] = None, label_text_color: str | tuple[str, str] | None = None,
label_text: str = "", label_text: str = "",
label_font: Optional[Union[tuple, CTkFont]] = None, label_font: tuple[Any, ...] | CTkFont | None = None,
label_anchor: str = "center", label_anchor: str = "center",
orientation: Literal["vertical", "horizontal"] = "vertical"): orientation: Literal["vertical", "horizontal"] = "vertical"):
@ -120,7 +123,7 @@ class CTkScrollableFrame(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBa
else: else:
self._label.grid_forget() 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) super()._set_appearance_mode(mode_string)
if self._parent_frame.cget("fg_color") == "transparent": 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"))) 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"))) 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) super()._set_scaling(new_widget_scaling, new_window_scaling)
self._parent_canvas.configure(width=self._apply_widget_scaling(self._desired_width), self._parent_canvas.configure(width=self._apply_widget_scaling(self._desired_width),
height=self._apply_widget_scaling(self._desired_height)) 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: if width is not None:
self._desired_width = width self._desired_width = width
if height is not None: 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), self._parent_canvas.configure(width=self._apply_widget_scaling(self._desired_width),
height=self._apply_widget_scaling(self._desired_height)) height=self._apply_widget_scaling(self._desired_height))
def configure(self, **kwargs): def configure(self, **kwargs: Any):
if "width" in kwargs: if "width" in kwargs:
self._set_dimensions(width=kwargs.pop("width")) self._set_dimensions(width=kwargs.pop("width"))
@ -232,7 +235,7 @@ class CTkScrollableFrame(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBa
else: else:
return self._parent_frame.cget(attribute_name) 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": if self._orientation == "horizontal":
self._parent_canvas.itemconfigure(self._create_window_id, height=self._parent_canvas.winfo_height()) self._parent_canvas.itemconfigure(self._create_window_id, height=self._parent_canvas.winfo_height())
elif self._orientation == "vertical": elif self._orientation == "vertical":
@ -244,7 +247,7 @@ class CTkScrollableFrame(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBa
elif sys.platform == "darwin": elif sys.platform == "darwin":
self._parent_canvas.configure(xscrollincrement=4, yscrollincrement=8) 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 self.check_if_master_is_canvas(event.widget):
if sys.platform.startswith("win"): if sys.platform.startswith("win"):
if self._shift_pressed: if self._shift_pressed:
@ -268,10 +271,10 @@ class CTkScrollableFrame(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBa
if self._parent_canvas.yview() != (0.0, 1.0): if self._parent_canvas.yview() != (0.0, 1.0):
self._parent_canvas.yview("scroll", -event.delta, "units") 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 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 self._shift_pressed = False
def check_if_master_is_canvas(self, widget): def check_if_master_is_canvas(self, widget):
@ -282,35 +285,35 @@ class CTkScrollableFrame(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBa
else: else:
return False return False
def pack(self, **kwargs): def pack(self, **kwargs: Any):
self._parent_frame.pack(**kwargs) self._parent_frame.pack(**kwargs)
def place(self, **kwargs): def place(self, **kwargs: Any):
self._parent_frame.place(**kwargs) self._parent_frame.place(**kwargs)
def grid(self, **kwargs): def grid(self, **kwargs: Any):
self._parent_frame.grid(**kwargs) self._parent_frame.grid(**kwargs)
def pack_forget(self): def pack_forget(self):
self._parent_frame.pack_forget() self._parent_frame.pack_forget()
def place_forget(self, **kwargs): def place_forget(self, **kwargs: Any):
self._parent_frame.place_forget() self._parent_frame.place_forget()
def grid_forget(self, **kwargs): def grid_forget(self, **kwargs: Any):
self._parent_frame.grid_forget() self._parent_frame.grid_forget()
def grid_remove(self, **kwargs): def grid_remove(self, **kwargs: Any):
self._parent_frame.grid_remove() self._parent_frame.grid_remove()
def grid_propagate(self, **kwargs): def grid_propagate(self, **kwargs: Any):
self._parent_frame.grid_propagate() self._parent_frame.grid_propagate()
def grid_info(self, **kwargs): def grid_info(self, **kwargs: Any):
return self._parent_frame.grid_info() return self._parent_frame.grid_info()
def lift(self, aboveThis=None): def lift(self, aboveThis: Any =None):
self._parent_frame.lift(aboveThis) self._parent_frame.lift(aboveThis)
def lower(self, belowThis=None): def lower(self, belowThis: Any = None):
self._parent_frame.lower(belowThis) self._parent_frame.lower(belowThis)

View File

@ -1,10 +1,17 @@
import sys from __future__ import annotations
from typing import Union, Tuple, Callable, Optional
from .core_rendering import CTkCanvas import sys
from .theme import ThemeManager import tkinter
from .core_rendering import DrawEngine 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 .core_widget_classes import CTkBaseClass
from .theme import ThemeManager
class CTkScrollbar(CTkBaseClass): class CTkScrollbar(CTkBaseClass):
@ -15,22 +22,22 @@ class CTkScrollbar(CTkBaseClass):
""" """
def __init__(self, def __init__(self,
master: any, master: CTkBaseClass,
width: Optional[Union[int, str]] = None, width: int | str | None = None,
height: Optional[Union[int, str]] = None, height: int | str | None = None,
corner_radius: Optional[int] = None, corner_radius: int | None = None,
border_spacing: Optional[int] = None, border_spacing: int | None = None,
minimum_pixel_length: int = 20, minimum_pixel_length: int = 20,
bg_color: Union[str, Tuple[str, str]] = "transparent", bg_color: str | tuple[str, str] = "transparent",
fg_color: Optional[Union[str, Tuple[str, str]]] = None, fg_color: str | tuple[str, str] | None = None,
button_color: Optional[Union[str, Tuple[str, str]]] = None, button_color: str | tuple[str, str] | None = None,
button_hover_color: Optional[Union[str, Tuple[str, str]]] = None, button_hover_color: str | tuple[str, str] | None = None,
hover: bool = True, hover: bool = True,
command: Union[Callable, None] = None, command: Callable[..., None] | None = None,
orientation: str = "vertical", orientation: str = "vertical",
**kwargs): **kwargs: Any):
# set default dimensions according to orientation # set default dimensions according to orientation
if width is None: if width is None:
@ -74,7 +81,7 @@ class CTkScrollbar(CTkBaseClass):
self._create_bindings() self._create_bindings()
self._draw() 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 """ """ set necessary bindings for functionality of widget, will overwrite other bindings """
if sequence is None: if sequence is None:
self._canvas.tag_bind("border_parts", "<Button-1>", self._clicked) self._canvas.tag_bind("border_parts", "<Button-1>", self._clicked)
@ -87,14 +94,14 @@ class CTkScrollbar(CTkBaseClass):
if sequence is None or sequence == "<MouseWheel>": if sequence is None or sequence == "<MouseWheel>":
self._canvas.bind("<MouseWheel>", self._mouse_scroll_event) 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) super()._set_scaling(*args, **kwargs)
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
height=self._apply_widget_scaling(self._desired_height)) height=self._apply_widget_scaling(self._desired_height))
self._draw(no_color_updates=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) super()._set_dimensions(width, height)
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
@ -125,7 +132,7 @@ class CTkScrollbar(CTkBaseClass):
else: else:
return self._start_value, self._end_value 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) super()._draw(no_color_updates)
corrected_start_value, corrected_end_value = self._get_scrollbar_values_for_minimum_pixel_size() 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() 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: if "fg_color" in kwargs:
self._fg_color = self._check_color_type(kwargs.pop("fg_color"), transparency=True) self._fg_color = self._check_color_type(kwargs.pop("fg_color"), transparency=True)
require_redraw = True require_redraw = True
@ -189,7 +196,7 @@ class CTkScrollbar(CTkBaseClass):
super().configure(require_redraw=require_redraw, **kwargs) 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": if attribute_name == "corner_radius":
return self._corner_radius return self._corner_radius
elif attribute_name == "border_spacing": elif attribute_name == "border_spacing":
@ -214,24 +221,24 @@ class CTkScrollbar(CTkBaseClass):
else: else:
return super().cget(attribute_name) 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: if self._hover is True:
self._hover_state = True self._hover_state = True
self._canvas.itemconfig("scrollbar_parts", self._canvas.itemconfig("scrollbar_parts",
outline=self._apply_appearance_mode(self._button_hover_color), outline=self._apply_appearance_mode(self._button_hover_color),
fill=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._hover_state = False
self._canvas.itemconfig("scrollbar_parts", self._canvas.itemconfig("scrollbar_parts",
outline=self._apply_appearance_mode(self._button_color), outline=self._apply_appearance_mode(self._button_color),
fill=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": 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: 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 current_scrollbar_length = self._end_value - self._start_value
value = max(current_scrollbar_length / 2, min(value, 1 - (current_scrollbar_length / 2))) 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: if self._command is not None:
self._command('moveto', self._start_value) 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 self._command is not None:
if sys.platform.startswith("win"): if sys.platform.startswith("win"):
self._command('scroll', -int(event.delta/40), 'units') self._command('scroll', -int(event.delta/40), 'units')
@ -257,7 +264,7 @@ class CTkScrollbar(CTkBaseClass):
def get(self): def get(self):
return self._start_value, self._end_value 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 """ """ called on the tkinter.Canvas """
if not (add == "+" or add is True): if not (add == "+" or add is True):
raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks") raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")

View File

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

View File

@ -1,11 +1,12 @@
import tkinter from __future__ import annotations
import sys
from typing import Union, Tuple, Callable, Optional
from .core_rendering import CTkCanvas import sys
from .theme import ThemeManager import tkinter
from .core_rendering import DrawEngine from typing import Any, Callable
from .core_rendering import CTkCanvas, DrawEngine
from .core_widget_classes import CTkBaseClass from .core_widget_classes import CTkBaseClass
from .theme import ThemeManager
class CTkSlider(CTkBaseClass): class CTkSlider(CTkBaseClass):
@ -15,30 +16,30 @@ class CTkSlider(CTkBaseClass):
""" """
def __init__(self, def __init__(self,
master: any, master: CTkBaseClass,
width: Optional[int] = None, width: int | None = None,
height: Optional[int] = None, height: int | None = None,
corner_radius: Optional[int] = None, corner_radius: int | None = None,
button_corner_radius: Optional[int] = None, button_corner_radius: int | None = None,
border_width: Optional[int] = None, border_width: int | None = None,
button_length: Optional[int] = None, button_length: int | None = None,
bg_color: Union[str, Tuple[str, str]] = "transparent", bg_color: str | tuple[str, str] = "transparent",
fg_color: Optional[Union[str, Tuple[str, str]]] = None, fg_color: str | tuple[str, str] | None = None,
border_color: Union[str, Tuple[str, str]] = "transparent", border_color: str | tuple[str, str] = "transparent",
progress_color: Optional[Union[str, Tuple[str, str]]] = None, progress_color: str | tuple[str, str] | None = None,
button_color: Optional[Union[str, Tuple[str, str]]] = None, button_color: str | tuple[str, str] | None = None,
button_hover_color: Optional[Union[str, Tuple[str, str]]] = None, button_hover_color: str | tuple[str, str] | None = None,
from_: int = 0, from_: int = 0,
to: int = 1, to: int = 1,
state: str = "normal", state: str = "normal",
number_of_steps: Union[int, None] = None, number_of_steps: int | None = None,
hover: bool = True, hover: bool = True,
command: Union[Callable[[float], None], None] = None, command: Callable[[float], None] | None = None,
variable: Union[tkinter.Variable, None] = None, variable: tkinter.Variable | None = None,
orientation: str = "horizontal", orientation: str = "horizontal",
**kwargs): **kwargs: Any):
# set default dimensions according to orientation # set default dimensions according to orientation
if width is None: if width is None:
@ -81,9 +82,9 @@ class CTkSlider(CTkBaseClass):
# callback and control variables # callback and control variables
self._command = command self._command = command
self._variable: tkinter.Variable = variable self._variable = variable
self._variable_callback_blocked: bool = False self._variable_callback_blocked = False
self._variable_callback_name: Union[bool, None] = None self._variable_callback_name: bool | None = None
self._state = state self._state = state
self.grid_rowconfigure(0, weight=1) self.grid_rowconfigure(0, weight=1)
@ -106,7 +107,7 @@ class CTkSlider(CTkBaseClass):
self.set(self._variable.get(), from_variable_callback=True) self.set(self._variable.get(), from_variable_callback=True)
self._variable_callback_blocked = False 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 """ """ set necessary bindings for functionality of widget, will overwrite other bindings """
if sequence is None or sequence == "<Enter>": if sequence is None or sequence == "<Enter>":
self._canvas.bind("<Enter>", self._on_enter) self._canvas.bind("<Enter>", self._on_enter)
@ -117,14 +118,14 @@ class CTkSlider(CTkBaseClass):
if sequence is None or sequence == "<B1-Motion>": if sequence is None or sequence == "<B1-Motion>":
self._canvas.bind("<B1-Motion>", self._clicked) 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) super()._set_scaling(*args, **kwargs)
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
height=self._apply_widget_scaling(self._desired_height)) height=self._apply_widget_scaling(self._desired_height))
self._draw(no_color_updates=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) super()._set_dimensions(width, height)
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
@ -151,7 +152,7 @@ class CTkSlider(CTkBaseClass):
elif sys.platform.startswith("win"): elif sys.platform.startswith("win"):
self.configure(cursor="arrow") self.configure(cursor="arrow")
def _draw(self, no_color_updates=False): def _draw(self, no_color_updates: bool = False):
super()._draw(no_color_updates) super()._draw(no_color_updates)
if self._orientation.lower() == "horizontal": if self._orientation.lower() == "horizontal":
@ -198,7 +199,7 @@ class CTkSlider(CTkBaseClass):
fill=self._apply_appearance_mode(self._button_color), fill=self._apply_appearance_mode(self._button_color),
outline=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: if "state" in kwargs:
self._state = kwargs.pop("state") self._state = kwargs.pop("state")
self._set_cursor() self._set_cursor()
@ -257,7 +258,7 @@ class CTkSlider(CTkBaseClass):
super().configure(require_redraw=require_redraw, **kwargs) 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": if attribute_name == "corner_radius":
return self._corner_radius return self._corner_radius
elif attribute_name == "button_corner_radius": elif attribute_name == "button_corner_radius":
@ -298,7 +299,7 @@ class CTkSlider(CTkBaseClass):
else: else:
return super().cget(attribute_name) 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._state == "normal":
if self._orientation.lower() == "horizontal": if self._orientation.lower() == "horizontal":
self._value = self._reverse_widget_scaling(event.x / self._current_width) self._value = self._reverse_widget_scaling(event.x / self._current_width)
@ -323,20 +324,20 @@ class CTkSlider(CTkBaseClass):
if self._command is not None: if self._command is not None:
self._command(self._output_value) 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": if self._hover is True and self._state == "normal":
self._hover_state = True self._hover_state = True
self._canvas.itemconfig("slider_parts", self._canvas.itemconfig("slider_parts",
fill=self._apply_appearance_mode(self._button_hover_color), fill=self._apply_appearance_mode(self._button_hover_color),
outline=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._hover_state = False
self._canvas.itemconfig("slider_parts", self._canvas.itemconfig("slider_parts",
fill=self._apply_appearance_mode(self._button_color), fill=self._apply_appearance_mode(self._button_color),
outline=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: if self._number_of_steps is not None:
step_size = (self._to - self._from_) / self._number_of_steps step_size = (self._to - self._from_) / self._number_of_steps
value = self._to - (round((self._to - value) / step_size) * step_size) value = self._to - (round((self._to - value) / step_size) * step_size)
@ -347,7 +348,7 @@ class CTkSlider(CTkBaseClass):
def get(self) -> float: def get(self) -> float:
return self._output_value 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 self._from_ < self._to:
if output_value > self._to: if output_value > self._to:
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.set(round(self._output_value) if isinstance(self._variable, tkinter.IntVar) else self._output_value)
self._variable_callback_blocked = False 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: if not self._variable_callback_blocked:
self.set(self._variable.get(), from_variable_callback=True) 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 """ """ called on the tkinter.Canvas """
if not (add == "+" or add is True): if not (add == "+" or add is True):
raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks") raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
self._canvas.bind(sequence, command, add=True) 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 """ """ called on the tkinter.Label and tkinter.Canvas """
if funcid is not None: if funcid is not None:
raise ValueError("'funcid' argument can only be None, because there is a bug in" + raise ValueError("'funcid' argument can only be None, because there is a bug in" +

View File

@ -1,12 +1,18 @@
import tkinter from __future__ import annotations
import sys
from typing import Union, Tuple, Callable, Optional
from .core_rendering import CTkCanvas import sys
from .theme import ThemeManager import tkinter
from .core_rendering import DrawEngine 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 .core_widget_classes import CTkBaseClass
from .font import CTkFont from .font import CTkFont
from .theme import ThemeManager
class CTkSwitch(CTkBaseClass): class CTkSwitch(CTkBaseClass):
@ -16,34 +22,34 @@ class CTkSwitch(CTkBaseClass):
""" """
def __init__(self, def __init__(self,
master: any, master: CTkBaseClass,
width: int = 100, width: int = 100,
height: int = 24, height: int = 24,
switch_width: int = 36, switch_width: int = 36,
switch_height: int = 18, switch_height: int = 18,
corner_radius: Optional[int] = None, corner_radius: int | None = None,
border_width: Optional[int] = None, border_width: int | None = None,
button_length: Optional[int] = None, button_length: int | None = None,
bg_color: Union[str, Tuple[str, str]] = "transparent", bg_color: str | tuple[str, str] = "transparent",
fg_color: Optional[Union[str, Tuple[str, str]]] = None, fg_color: str | tuple[str, str] | None = None,
border_color: Union[str, Tuple[str, str]] = "transparent", border_color: str | tuple[str, str] = "transparent",
progress_color: Optional[Union[str, Tuple[str, str]]] = None, progress_color: str | tuple[str, str] | None = None,
button_color: Optional[Union[str, Tuple[str, str]]] = None, button_color: str | tuple[str, str] | None = None,
button_hover_color: Optional[Union[str, Tuple[str, str]]] = None, button_hover_color: str | tuple[str, str] | None = None,
text_color: Optional[Union[str, Tuple[str, str]]] = None, text_color: str | tuple[str, str] | None = None,
text_color_disabled: Optional[Union[str, Tuple[str, str]]] = None, text_color_disabled: str | tuple[str, str] | None = None,
text: str = "CTkSwitch", text: str = "CTkSwitch",
font: Optional[Union[tuple, CTkFont]] = None, font: tuple[Any, ...] | CTkFont | None = None,
textvariable: Union[tkinter.Variable, None] = None, textvariable: tkinter.Variable | None = None,
onvalue: Union[int, str] = 1, onvalue: int | str = 1,
offvalue: Union[int, str] = 0, offvalue: int | str = 0,
variable: Union[tkinter.Variable, None] = None, variable: tkinter.Variable | None = None,
hover: bool = True, hover: bool = True,
command: Union[Callable, None] = None, command: Callable[..., None] | None = None,
state: str = tkinter.NORMAL, state: Literal["normal", "disabled", "readonly"] = "normal",
**kwargs): **kwargs: Any):
# transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass
super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs) super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
@ -126,7 +132,7 @@ class CTkSwitch(CTkBaseClass):
self._set_cursor() self._set_cursor()
self._draw() # initial draw 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 """ """ set necessary bindings for functionality of widget, will overwrite other bindings """
if sequence is None or sequence == "<Enter>": if sequence is None or sequence == "<Enter>":
self._canvas.bind("<Enter>", self._on_enter) self._canvas.bind("<Enter>", self._on_enter)
@ -138,7 +144,7 @@ class CTkSwitch(CTkBaseClass):
self._canvas.bind("<Button-1>", self.toggle) self._canvas.bind("<Button-1>", self.toggle)
self._text_label.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) super()._set_scaling(*args, **kwargs)
self.grid_columnconfigure(1, weight=0, minsize=self._apply_widget_scaling(6)) 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)) height=self._apply_widget_scaling(self._switch_height))
self._draw(no_color_updates=True) 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) super()._set_dimensions(width, height)
self._bg_canvas.configure(width=self._apply_widget_scaling(self._desired_width), 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: if self._text_label is not None:
self._text_label.configure(cursor="hand2") 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) super()._draw(no_color_updates)
if self._check_state is True: 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)) 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: if "corner_radius" in kwargs:
self._corner_radius = kwargs.pop("corner_radius") self._corner_radius = kwargs.pop("corner_radius")
require_redraw = True require_redraw = True
@ -338,7 +344,7 @@ class CTkSwitch(CTkBaseClass):
super().configure(require_redraw=require_redraw, **kwargs) 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": if attribute_name == "corner_radius":
return self._corner_radius return self._corner_radius
elif attribute_name == "border_width": elif attribute_name == "border_width":
@ -387,7 +393,7 @@ class CTkSwitch(CTkBaseClass):
else: else:
return super().cget(attribute_name) 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._state is not tkinter.DISABLED:
if self._check_state is True: if self._check_state is True:
self._check_state = False self._check_state = False
@ -404,7 +410,7 @@ class CTkSwitch(CTkBaseClass):
if self._command is not None: if self._command is not None:
self._command() 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: if self._state is not tkinter.DISABLED or from_variable_callback:
self._check_state = True self._check_state = True
@ -415,7 +421,7 @@ class CTkSwitch(CTkBaseClass):
self._variable.set(self._onvalue) self._variable.set(self._onvalue)
self._variable_callback_blocked = False 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: if self._state is not tkinter.DISABLED or from_variable_callback:
self._check_state = False self._check_state = False
@ -426,37 +432,37 @@ class CTkSwitch(CTkBaseClass):
self._variable.set(self._offvalue) self._variable.set(self._offvalue)
self._variable_callback_blocked = False 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 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": if self._hover is True and self._state == "normal":
self._hover_state = True self._hover_state = True
self._canvas.itemconfig("slider_parts", self._canvas.itemconfig("slider_parts",
fill=self._apply_appearance_mode(self._button_hover_color), fill=self._apply_appearance_mode(self._button_hover_color),
outline=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._hover_state = False
self._canvas.itemconfig("slider_parts", self._canvas.itemconfig("slider_parts",
fill=self._apply_appearance_mode(self._button_color), fill=self._apply_appearance_mode(self._button_color),
outline=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 not self._variable_callback_blocked:
if self._variable.get() == self._onvalue: if self._variable.get() == self._onvalue:
self.select(from_variable_callback=True) self.select(from_variable_callback=True)
elif self._variable.get() == self._offvalue: elif self._variable.get() == self._offvalue:
self.deselect(from_variable_callback=True) 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 """ """ called on the tkinter.Canvas """
if not (add == "+" or add is True): if not (add == "+" or add is True):
raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks") raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
self._canvas.bind(sequence, command, add=True) self._canvas.bind(sequence, command, add=True)
self._text_label.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 """ """ called on the tkinter.Label and tkinter.Canvas """
if funcid is not None: if funcid is not None:
raise ValueError("'funcid' argument can only be None, because there is a bug in" + raise ValueError("'funcid' argument can only be None, because there is a bug in" +

View File

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

View File

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

View File

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

View File

@ -1,6 +1,9 @@
from tkinter.font import Font from __future__ import annotations
import copy import copy
from typing import List, Callable, Tuple, Optional from tkinter.font import Font
from typing import Any, Callable
try: try:
from typing import Literal from typing import Literal
except ImportError: except ImportError:
@ -25,14 +28,14 @@ class CTkFont(Font):
""" """
def __init__(self, def __init__(self,
family: Optional[str] = None, family: str | None = None,
size: Optional[int] = None, size: int | None = None,
weight: Literal["normal", "bold"] = None, weight: Literal["normal", "bold"] | None = None,
slant: Literal["italic", "roman"] = "roman", slant: Literal["italic", "roman"] = "roman",
underline: bool = False, underline: bool = False,
overstrike: 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 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._family = super().cget("family")
self._tuple_style_string = f"{super().cget('weight')} {slant} {'underline' if underline else ''} {'overstrike' if overstrike else ''}" 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 """ """ add function, that gets called when font got configured """
self._size_configure_callback_list.append(callback) 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 """ """ remove function, that gets called when font got configured """
self._size_configure_callback_list.remove(callback) 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 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 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.") 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: if "size" in kwargs:
self._size = kwargs.pop("size") self._size = kwargs.pop("size")
super().configure(size=-abs(self._size)) super().configure(size=-abs(self._size))
@ -79,7 +82,7 @@ class CTkFont(Font):
for callback in self._size_configure_callback_list: for callback in self._size_configure_callback_list:
callback() callback()
def cget(self, attribute_name: str) -> any: def cget(self, attribute_name: str) -> Any:
if attribute_name == "size": if attribute_name == "size":
return self._size return self._size
if attribute_name == "family": if attribute_name == "family":
@ -87,5 +90,5 @@ class CTkFont(Font):
else: else:
return super().cget(attribute_name) return super().cget(attribute_name)
def copy(self) -> "CTkFont": def copy(self) -> CTkFont:
return copy.deepcopy(self) return copy.deepcopy(self)

View File

@ -1,11 +1,11 @@
import sys from __future__ import annotations
import os import os
import shutil import shutil
from typing import Union import sys
class FontManager: class FontManager:
linux_font_path = "~/.fonts/" linux_font_path = "~/.fonts/"
@classmethod @classmethod
@ -25,10 +25,11 @@ class FontManager:
return True return True
@classmethod @classmethod
def windows_load_font(cls, font_path: Union[str, bytes], private: bool = True, enumerable: bool = False) -> bool: def windows_load_font(cls, font_path: str | bytes, private: bool = True, enumerable: bool = False) -> bool:
""" Function taken from: https://stackoverflow.com/questions/11993290/truly-custom-font-in-tkinter/30631309#30631309 """ """ 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_PRIVATE = 0x10
FR_NOT_ENUM = 0x20 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: try:
from PIL import Image, ImageTk from PIL import Image, ImageTk
except ImportError: except ImportError:
@ -19,9 +22,9 @@ class CTkImage:
_checked_PIL_import = False _checked_PIL_import = False
def __init__(self, def __init__(self,
light_image: "Image.Image" = None, light_image: Image.Image | None = None,
dark_image: "Image.Image" = None, dark_image: Image.Image | None = None,
size: Tuple[int, int] = (20, 20)): size: tuple[int, int] = (20, 20)):
if not self._checked_PIL_import: if not self._checked_PIL_import:
self._check_pil_import() self._check_pil_import()
@ -31,9 +34,9 @@ class CTkImage:
self._check_images() self._check_images()
self._size = size self._size = size
self._configure_callback_list: List[Callable] = [] self._configure_callback_list: list[Callable[..., None]] = []
self._scaled_light_photo_images: Dict[Tuple[int, int], ImageTk.PhotoImage] = {} self._scaled_light_photo_images: dict[tuple[int, int], ImageTk.PhotoImage] = {}
self._scaled_dark_photo_images: Dict[Tuple[int, int], ImageTk.PhotoImage] = {} self._scaled_dark_photo_images: dict[tuple[int, int], ImageTk.PhotoImage] = {}
@classmethod @classmethod
def _check_pil_import(cls): def _check_pil_import(cls):
@ -42,15 +45,15 @@ class CTkImage:
except NameError: except NameError:
raise ImportError("PIL.Image and PIL.ImageTk couldn't be imported") 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 """ """ add function, that gets called when image got configured """
self._configure_callback_list.append(callback) 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 """ """ remove function, that gets called when image got configured """
self._configure_callback_list.remove(callback) self._configure_callback_list.remove(callback)
def configure(self, **kwargs): def configure(self, **kwargs: Any):
if "light_image" in kwargs: if "light_image" in kwargs:
self._light_image = kwargs.pop("light_image") self._light_image = kwargs.pop("light_image")
self._scaled_light_photo_images = {} self._scaled_light_photo_images = {}
@ -66,7 +69,7 @@ class CTkImage:
for callback in self._configure_callback_list: for callback in self._configure_callback_list:
callback() callback()
def cget(self, attribute_name: str) -> any: def cget(self, attribute_name: str) -> Any:
if attribute_name == "light_image": if attribute_name == "light_image":
return self._light_image return self._light_image
if attribute_name == "dark_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: 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}.") 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) 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: if scaled_size in self._scaled_light_photo_images:
return self._scaled_light_photo_images[scaled_size] return self._scaled_light_photo_images[scaled_size]
else: else:
self._scaled_light_photo_images[scaled_size] = ImageTk.PhotoImage(self._light_image.resize(scaled_size)) self._scaled_light_photo_images[scaled_size] = ImageTk.PhotoImage(self._light_image.resize(scaled_size))
return self._scaled_light_photo_images[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: if scaled_size in self._scaled_dark_photo_images:
return self._scaled_dark_photo_images[scaled_size] return self._scaled_dark_photo_images[scaled_size]
else: else:
self._scaled_dark_photo_images[scaled_size] = ImageTk.PhotoImage(self._dark_image.resize(scaled_size)) self._scaled_dark_photo_images[scaled_size] = ImageTk.PhotoImage(self._dark_image.resize(scaled_size))
return self._scaled_dark_photo_images[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) scaled_size = self._get_scaled_size(widget_scaling)
if appearance_mode == "light" and self._light_image is not None: 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) return self._get_scaled_dark_photo_image(scaled_size)
elif appearance_mode == "dark" and self._dark_image is None: elif appearance_mode == "dark" and self._dark_image is None:
return self._get_scaled_light_photo_image(scaled_size) 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 copy
import re import re
try: import sys
from typing import Any, TypeVar
if sys.version_info >= (3, 8):
from typing import Literal from typing import Literal
except ImportError: else:
from typing_extensions import Literal from typing_extensions import Literal
from .scaling_tracker import ScalingTracker
from ..font import CTkFont from ..font import CTkFont
from .scaling_tracker import ScalingTracker
KT = TypeVar("KT")
VT = TypeVar("VT")
class CTkScalingBaseClass: class CTkScalingBaseClass:
""" """
@ -46,7 +52,7 @@ class CTkScalingBaseClass:
elif self.__scaling_type == "window": elif self.__scaling_type == "window":
ScalingTracker.remove_window(self._set_scaling, self) 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 """ """ can be overridden, but super method must be called at the beginning """
self.__widget_scaling = new_widget_scaling self.__widget_scaling = new_widget_scaling
self.__window_scaling = new_window_scaling self.__window_scaling = new_window_scaling
@ -57,23 +63,23 @@ class CTkScalingBaseClass:
def _get_window_scaling(self) -> float: def _get_window_scaling(self) -> float:
return self.__window_scaling 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" assert self.__scaling_type == "widget"
return value * self.__widget_scaling 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" assert self.__scaling_type == "widget"
return value / self.__widget_scaling 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" assert self.__scaling_type == "window"
return int(value * self.__window_scaling) 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" assert self.__scaling_type == "window"
return int(scaled_value / self.__window_scaling) 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 """ """ 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" assert self.__scaling_type == "widget"
@ -92,7 +98,7 @@ class CTkScalingBaseClass:
else: else:
raise ValueError(f"Can not scale font '{font}' of type {type(font)}. font needs to be tuple or instance of CTkFont") 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" assert self.__scaling_type == "widget"
scaled_kwargs = copy.copy(kwargs) scaled_kwargs = copy.copy(kwargs)
@ -118,7 +124,7 @@ class CTkScalingBaseClass:
return scaled_kwargs return scaled_kwargs
@staticmethod @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 # index: 1 2 3 4 5 6
# regex group structure: ('<width>x<height>', '<width>', '<height>', '+-<x>+-<y>', '-<x>', '-<y>') # 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) 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 from __future__ import annotations
import sys
from typing import Callable
import sys
import tkinter
from typing import Callable, TYPE_CHECKING
if TYPE_CHECKING:
from customtkinter.windows import CTk
class ScalingTracker: class ScalingTracker:
deactivate_automatic_dpi_awareness = False deactivate_automatic_dpi_awareness = False
window_widgets_dict = {} # contains window objects as keys with list of widget callbacks as elements window_widgets_dict: dict[CTk, list[Callable[..., None]]] = {} # 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_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 widget_scaling = 1 # user values which multiply to detected window scaling factor
window_scaling = 1 window_scaling = 1
@ -17,12 +21,12 @@ class ScalingTracker:
loop_pause_after_new_scaling = 1500 # ms loop_pause_after_new_scaling = 1500 # ms
@classmethod @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) window_root = cls.get_window_root_of_widget(widget)
return cls.window_dpi_scaling_dict[window_root] * cls.widget_scaling return cls.window_dpi_scaling_dict[window_root] * cls.widget_scaling
@classmethod @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) window_root = cls.get_window_root_of_widget(window)
return cls.window_dpi_scaling_dict[window_root] * cls.window_scaling return cls.window_dpi_scaling_dict[window_root] * cls.window_scaling
@ -37,7 +41,7 @@ class ScalingTracker:
cls.update_scaling_callbacks_all() cls.update_scaling_callbacks_all()
@classmethod @classmethod
def get_window_root_of_widget(cls, widget): def get_window_root_of_widget(cls, widget: CTk):
current_widget = widget current_widget = widget
while isinstance(current_widget, tkinter.Tk) is False and\ while isinstance(current_widget, tkinter.Tk) is False and\
@ -58,7 +62,7 @@ class ScalingTracker:
cls.window_scaling) cls.window_scaling)
@classmethod @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]: for set_scaling_callback in cls.window_widgets_dict[window]:
if not cls.deactivate_automatic_dpi_awareness: if not cls.deactivate_automatic_dpi_awareness:
set_scaling_callback(cls.window_dpi_scaling_dict[window] * cls.widget_scaling, set_scaling_callback(cls.window_dpi_scaling_dict[window] * cls.widget_scaling,
@ -68,7 +72,7 @@ class ScalingTracker:
cls.window_scaling) cls.window_scaling)
@classmethod @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) window_root = cls.get_window_root_of_widget(widget)
if window_root not in cls.window_widgets_dict: if window_root not in cls.window_widgets_dict:
@ -84,7 +88,7 @@ class ScalingTracker:
cls.update_loop_running = True cls.update_loop_running = True
@classmethod @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) window_root = cls.get_window_root_of_widget(widget)
try: try:
cls.window_widgets_dict[window_root].remove(widget_callback) cls.window_widgets_dict[window_root].remove(widget_callback)
@ -92,14 +96,14 @@ class ScalingTracker:
pass pass
@classmethod @classmethod
def remove_window(cls, window_callback, window): def remove_window(cls, window_callback: Callable[..., None], window: CTk):
try: try:
del cls.window_widgets_dict[window] del cls.window_widgets_dict[window]
except: except:
pass pass
@classmethod @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: if window not in cls.window_widgets_dict:
cls.window_widgets_dict[window] = [window_callback] cls.window_widgets_dict[window] = [window_callback]
else: else:
@ -135,11 +139,9 @@ class ScalingTracker:
# DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE = 18, # DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE = 18,
# DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = 34 # DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = 34
# } # }
# ctypes.windll.user32.SetProcessDpiAwarenessContext(34) # Non client area scaling at runtime (titlebar) # 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...) # 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) # 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, # 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: # 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 pass # DPI awareness on Linux not implemented
@classmethod @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 not cls.deactivate_automatic_dpi_awareness:
if sys.platform == "darwin": if sys.platform == "darwin":
return 1 # scaling works automatically on macOS return 1 # scaling works automatically on macOS
elif sys.platform.startswith("win"): elif sys.platform.startswith("win"):
from ctypes import windll, pointer, wintypes from ctypes import pointer, windll, wintypes
DPI100pc = 96 # DPI 96 is 100% scaling DPI100pc = 96 # DPI 96 is 100% scaling
DPI_type = 0 # MDT_EFFECTIVE_DPI = 0, MDT_ANGULAR_DPI = 1, MDT_RAW_DPI = 2 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 cls.window_dpi_scaling_dict[window] = current_dpi_scaling_value
if sys.platform.startswith("win"): if sys.platform.startswith("win"):
window.attributes("-alpha", 0.15) window.attributes("-alpha", 0.15) # type: ignore
window.block_update_dimensions_event() window.block_update_dimensions_event()
cls.update_scaling_callbacks_for_window(window) cls.update_scaling_callbacks_for_window(window)
window.unblock_update_dimensions_event() window.unblock_update_dimensions_event()
if sys.platform.startswith("win"): if sys.platform.startswith("win"):
window.attributes("-alpha", 1) window.attributes("-alpha", 1) # type: ignore
new_scaling_detected = True new_scaling_detected = True

View File

@ -1,24 +1,25 @@
import sys from __future__ import annotations
import os
import json import json
from typing import List, Union import os
import sys
from typing import Any
class ThemeManager: class ThemeManager:
theme: dict[str, Any] = {} # contains all the theme data
theme: dict = {} # contains all the theme data _built_in_themes: list[str] = ["blue", "green", "dark-blue", "sweetkind"]
_built_in_themes: List[str] = ["blue", "green", "dark-blue", "sweetkind"] _currently_loaded_theme: str | None = None
_currently_loaded_theme: Union[str, None] = None
@classmethod @classmethod
def load_theme(cls, theme_name_or_path: str): def load_theme(cls, theme_name_or_path: str):
script_directory = os.path.dirname(os.path.abspath(__file__)) script_directory = os.path.dirname(os.path.abspath(__file__))
if theme_name_or_path in cls._built_in_themes: 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) cls.theme = json.load(f)
else: else:
with open(theme_name_or_path, "r") as f: with open(theme_name_or_path) as f:
cls.theme = json.load(f) cls.theme = json.load(f)
# store theme path for saving # store theme path for saving
@ -39,9 +40,9 @@ class ThemeManager:
def save_theme(cls): def save_theme(cls):
if cls._currently_loaded_theme is not None: if cls._currently_loaded_theme is not None:
if cls._currently_loaded_theme not in cls._built_in_themes: 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) json.dump(cls.theme, f, indent=2)
else: else:
raise ValueError(f"cannot modify builtin theme '{cls._currently_loaded_theme}'") raise ValueError(f"cannot modify builtin theme '{cls._currently_loaded_theme}'")
else: 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 """ """ 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()): for key in list(dictionary.keys()):
if key in valid_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 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 """ """ returns True if kwargs are empty, False otherwise, raises error if not empty """
if len(kwargs_dict) > 0: if len(kwargs_dict) > 0:

View File

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

View File

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

View File

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

View File

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

View File

@ -1,10 +1,14 @@
from __future__ import annotations
from typing import Any, Callable
import customtkinter import customtkinter
customtkinter.set_appearance_mode("dark") customtkinter.set_appearance_mode("dark")
class ToplevelWindow(customtkinter.CTkToplevel): 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) super().__init__(*args, **kwargs)
self.protocol("WM_DELETE_WINDOW", self.closing) self.protocol("WM_DELETE_WINDOW", self.closing)
self.geometry("500x300") self.geometry("500x300")

View File

@ -1,12 +1,11 @@
import tkinter.messagebox
import customtkinter import customtkinter
customtkinter.set_appearance_mode("dark") customtkinter.set_appearance_mode("dark")
class App(customtkinter.CTk): class App(customtkinter.CTk):
def __init__(self, *args, **kwargs): def __init__(self, *args: Any, **kwargs: Any):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs: Any)
self.title("test filedialog") 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) label_3.pack(padx=20, pady=20)
app.mainloop() app.mainloop()

View File

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

View File

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

View File

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