From a9b44c180ea95a73e629f0c73a0099b3a8ee4626 Mon Sep 17 00:00:00 2001 From: demberto Date: Mon, 10 Apr 2023 17:41:14 +0530 Subject: [PATCH 1/2] rev. 1 --- customtkinter/__init__.py | 55 +++------ customtkinter/windows/__init__.py | 2 +- customtkinter/windows/ctk_input_dialog.py | 31 ++--- customtkinter/windows/ctk_tk.py | 49 ++++---- customtkinter/windows/ctk_toplevel.py | 64 ++++++----- customtkinter/windows/widgets/__init__.py | 2 +- .../appearance_mode_base_class.py | 15 ++- .../appearance_mode_tracker.py | 32 ++++-- .../widgets/core_rendering/ctk_canvas.py | 20 ++-- .../widgets/core_rendering/draw_engine.py | 42 ++++--- .../widgets/core_widget_classes/__init__.py | 2 +- .../core_widget_classes/ctk_base_class.py | 67 ++++++----- .../core_widget_classes/dropdown_menu.py | 38 ++++--- customtkinter/windows/widgets/ctk_button.py | 106 ++++++++++-------- customtkinter/windows/widgets/ctk_checkbox.py | 88 +++++++-------- customtkinter/windows/widgets/ctk_combobox.py | 87 +++++++------- customtkinter/windows/widgets/ctk_entry.py | 65 ++++++----- customtkinter/windows/widgets/ctk_frame.py | 47 ++++---- customtkinter/windows/widgets/ctk_label.py | 43 +++---- .../windows/widgets/ctk_optionmenu.py | 74 ++++++------ .../windows/widgets/ctk_progressbar.py | 52 ++++----- .../windows/widgets/ctk_radiobutton.py | 77 ++++++------- .../windows/widgets/ctk_scrollable_frame.py | 75 +++++++------ .../windows/widgets/ctk_scrollbar.py | 65 ++++++----- .../windows/widgets/ctk_segmented_button.py | 73 ++++++------ customtkinter/windows/widgets/ctk_slider.py | 79 ++++++------- customtkinter/windows/widgets/ctk_switch.py | 81 ++++++------- customtkinter/windows/widgets/ctk_tabview.py | 57 +++++----- customtkinter/windows/widgets/ctk_textbox.py | 97 ++++++++-------- .../windows/widgets/font/__init__.py | 5 +- .../windows/widgets/font/ctk_font.py | 29 ++--- .../windows/widgets/font/font_manager.py | 13 ++- .../windows/widgets/image/ctk_image.py | 35 +++--- .../widgets/scaling/scaling_base_class.py | 30 +++-- .../widgets/scaling/scaling_tracker.py | 40 +++---- .../windows/widgets/theme/theme_manager.py | 23 ++-- .../windows/widgets/utility/__init__.py | 2 +- .../widgets/utility/utility_functions.py | 13 ++- examples/example_background_image.py | 6 +- examples/image_example.py | 9 +- examples/scrollable_frame_example.py | 18 +-- setup.cfg | 14 ++- .../test_ctk_toplevel.py | 6 +- .../test_filedialog.py | 5 +- test/manual_integration_tests/test_images.py | 1 - test/unit_tests/test_ctk.py | 1 - test/unit_tests/test_ctk_button.py | 1 - test/unit_tests/test_ctk_entry.py | 1 - 48 files changed, 968 insertions(+), 869 deletions(-) diff --git a/customtkinter/__init__.py b/customtkinter/__init__.py index 216a377..a52eee2 100644 --- a/customtkinter/__init__.py +++ b/customtkinter/__init__.py @@ -2,49 +2,30 @@ __version__ = "5.1.2" import os import sys -from tkinter import Variable, StringVar, IntVar, DoubleVar, BooleanVar -from tkinter.constants import * import tkinter.filedialog as filedialog - -# import manager classes -from .windows.widgets.appearance_mode import AppearanceModeTracker -from .windows.widgets.font import FontManager -from .windows.widgets.scaling import ScalingTracker -from .windows.widgets.theme import ThemeManager -from .windows.widgets.core_rendering import DrawEngine - -# import base widgets -from .windows.widgets.core_rendering import CTkCanvas -from .windows.widgets.core_widget_classes import CTkBaseClass - -# import widgets -from .windows.widgets import CTkButton -from .windows.widgets import CTkCheckBox -from .windows.widgets import CTkComboBox -from .windows.widgets import CTkEntry -from .windows.widgets import CTkFrame -from .windows.widgets import CTkLabel -from .windows.widgets import CTkOptionMenu -from .windows.widgets import CTkProgressBar -from .windows.widgets import CTkRadioButton -from .windows.widgets import CTkScrollbar -from .windows.widgets import CTkSegmentedButton -from .windows.widgets import CTkSlider -from .windows.widgets import CTkSwitch -from .windows.widgets import CTkTabview -from .windows.widgets import CTkTextbox -from .windows.widgets import CTkScrollableFrame +from tkinter import BooleanVar, DoubleVar, IntVar, StringVar, Variable +from tkinter.constants import * # import windows -from .windows import CTk -from .windows import CTkToplevel -from .windows import CTkInputDialog - +from .windows import CTk, CTkInputDialog, CTkToplevel +# import widgets +from .windows.widgets import (CTkButton, CTkCheckBox, CTkComboBox, CTkEntry, + CTkFrame, CTkLabel, CTkOptionMenu, + CTkProgressBar, CTkRadioButton, + CTkScrollableFrame, CTkScrollbar, + CTkSegmentedButton, CTkSlider, CTkSwitch, + CTkTabview, CTkTextbox) +# import manager classes +from .windows.widgets.appearance_mode import AppearanceModeTracker +# import base widgets +from .windows.widgets.core_rendering import CTkCanvas, DrawEngine +from .windows.widgets.core_widget_classes import CTkBaseClass # import font classes -from .windows.widgets.font import CTkFont - +from .windows.widgets.font import CTkFont, FontManager # import image classes from .windows.widgets.image import CTkImage +from .windows.widgets.scaling import ScalingTracker +from .windows.widgets.theme import ThemeManager _ = Variable, StringVar, IntVar, DoubleVar, BooleanVar, CENTER, filedialog # prevent IDE from removing unused imports diff --git a/customtkinter/windows/__init__.py b/customtkinter/windows/__init__.py index ca681b7..57213a1 100644 --- a/customtkinter/windows/__init__.py +++ b/customtkinter/windows/__init__.py @@ -1,3 +1,3 @@ +from .ctk_input_dialog import CTkInputDialog from .ctk_tk import CTk from .ctk_toplevel import CTkToplevel -from .ctk_input_dialog import CTkInputDialog diff --git a/customtkinter/windows/ctk_input_dialog.py b/customtkinter/windows/ctk_input_dialog.py index fc37527..49ea327 100644 --- a/customtkinter/windows/ctk_input_dialog.py +++ b/customtkinter/windows/ctk_input_dialog.py @@ -1,10 +1,11 @@ -from typing import Union, Tuple, Optional +from __future__ import annotations + +import tkinter +from typing import Any -from .widgets import CTkLabel -from .widgets import CTkEntry -from .widgets import CTkButton -from .widgets.theme import ThemeManager from .ctk_toplevel import CTkToplevel +from .widgets import CTkButton, CTkEntry, CTkLabel +from .widgets.theme import ThemeManager class CTkInputDialog(CTkToplevel): @@ -14,14 +15,14 @@ class CTkInputDialog(CTkToplevel): """ def __init__(self, - fg_color: Optional[Union[str, Tuple[str, str]]] = None, - text_color: Optional[Union[str, Tuple[str, str]]] = None, - button_fg_color: Optional[Union[str, Tuple[str, str]]] = None, - button_hover_color: Optional[Union[str, Tuple[str, str]]] = None, - button_text_color: Optional[Union[str, Tuple[str, str]]] = None, - entry_fg_color: Optional[Union[str, Tuple[str, str]]] = None, - entry_border_color: Optional[Union[str, Tuple[str, str]]] = None, - entry_text_color: Optional[Union[str, Tuple[str, str]]] = None, + fg_color: str | tuple[str, str] | None = None, + text_color: str | tuple[str, str] | None = None, + button_fg_color: str | tuple[str, str] | None = None, + button_hover_color: str | tuple[str, str] | None = None, + button_text_color: str | tuple[str, str] | None = None, + entry_fg_color: str | tuple[str, str] | None = None, + entry_border_color: str | tuple[str, str] | None = None, + entry_text_color: str | tuple[str, str] | None = None, title: str = "CTkDialog", text: str = "CTkDialog"): @@ -37,7 +38,7 @@ class CTkInputDialog(CTkToplevel): self._entry_border_color = ThemeManager.theme["CTkEntry"]["border_color"] if entry_border_color is None else self._check_color_type(entry_border_color) self._entry_text_color = ThemeManager.theme["CTkEntry"]["text_color"] if entry_text_color is None else self._check_color_type(entry_text_color) - self._user_input: Union[str, None] = None + self._user_input: str | None = None self._running: bool = False self._text = text @@ -92,7 +93,7 @@ class CTkInputDialog(CTkToplevel): self.after(150, lambda: self._entry.focus()) # set focus to entry with slight delay, otherwise it won't work self._entry.bind("", self._ok_event) - def _ok_event(self, event=None): + def _ok_event(self, event: tkinter.Event[Any] | None = None): self._user_input = self._entry.get() self.grab_release() self.destroy() diff --git a/customtkinter/windows/ctk_tk.py b/customtkinter/windows/ctk_tk.py index cc3c940..bc21cec 100644 --- a/customtkinter/windows/ctk_tk.py +++ b/customtkinter/windows/ctk_tk.py @@ -1,16 +1,19 @@ -import tkinter -from distutils.version import StrictVersion as Version -import sys +from __future__ import annotations + +import ctypes import os import platform -import ctypes -from typing import Union, Tuple, Optional +import sys +import tkinter +from distutils.version import StrictVersion as Version +from typing import Any + +from customtkinter.windows.widgets.utility.utility_functions import ( + check_kwargs_empty, pop_from_dict_by_set) -from .widgets.theme import ThemeManager -from .widgets.scaling import CTkScalingBaseClass from .widgets.appearance_mode import CTkAppearanceModeBaseClass - -from customtkinter.windows.widgets.utility.utility_functions import pop_from_dict_by_set, check_kwargs_empty +from .widgets.scaling import CTkScalingBaseClass +from .widgets.theme import ThemeManager class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass): @@ -19,9 +22,9 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass): For detailed information check out the documentation. """ - _valid_tk_constructor_arguments: set = {"screenName", "baseName", "className", "useTk", "sync", "use"} + _valid_tk_constructor_arguments: set[str] = {"screenName", "baseName", "className", "useTk", "sync", "use"} - _valid_tk_configure_arguments: set = {'bd', 'borderwidth', 'class', 'menu', 'relief', 'screen', + _valid_tk_configure_arguments: set[str] = {'bd', 'borderwidth', 'class', 'menu', 'relief', 'screen', 'use', 'container', 'cursor', 'height', 'highlightthickness', 'padx', 'pady', 'takefocus', 'visual', 'width'} @@ -29,8 +32,8 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass): _deactivate_windows_window_header_manipulation: bool = False def __init__(self, - fg_color: Optional[Union[str, Tuple[str, str]]] = None, - **kwargs): + fg_color: str | tuple[str, str] | None = None, + **kwargs: Any): self._enable_macos_dark_title_bar() @@ -46,7 +49,7 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass): self._min_height: int = 0 self._max_width: int = 1_000_000 self._max_height: int = 1_000_000 - self._last_resizable_args: Union[Tuple[list, dict], None] = None # (args, kwargs) + self._last_resizable_args: tuple[list, dict] | None = None # (args, kwargs) self._fg_color = ThemeManager.theme["CTk"]["fg_color"] if fg_color is None else self._check_color_type(fg_color) @@ -86,12 +89,12 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass): CTkAppearanceModeBaseClass.destroy(self) CTkScalingBaseClass.destroy(self) - def _focus_in_event(self, event): + def _focus_in_event(self, event: tkinter.Event[Any] | None = None): # sometimes window looses jumps back on macOS if window is selected from Mission Control, so has to be lifted again if sys.platform == "darwin": self.lift() - def _update_dimensions_event(self, event=None): + def _update_dimensions_event(self, event: tkinter.Event[Any] | None = None): if not self._block_update_dimensions_event: detected_width = super().winfo_width() # detect current window size @@ -104,7 +107,7 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass): self._current_width = self._reverse_window_scaling(detected_width) # adjust current size according to new size given by event self._current_height = self._reverse_window_scaling(detected_height) # _current_width and _current_height are independent of the scale - def _set_scaling(self, new_widget_scaling, new_window_scaling): + def _set_scaling(self, new_widget_scaling: float, new_window_scaling: float): super()._set_scaling(new_widget_scaling, new_window_scaling) # Force new dimensions on window by using min, max, and geometry. Without min, max it won't work. @@ -149,7 +152,7 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass): super().update() - def mainloop(self, *args, **kwargs): + def mainloop(self, *args: Any, **kwargs: Any): if not self._window_exists: if sys.platform.startswith("win"): self._windows_set_titlebar_color(self._get_appearance_mode()) @@ -171,7 +174,7 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass): return current_resizable_values - def minsize(self, width: int = None, height: int = None): + def minsize(self, width: int | None = None, height: int | None = None): self._min_width = width self._min_height = height if self._current_width < width: @@ -180,7 +183,7 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass): self._current_height = height super().minsize(self._apply_window_scaling(self._min_width), self._apply_window_scaling(self._min_height)) - def maxsize(self, width: int = None, height: int = None): + def maxsize(self, width: int | None = None, height: int | None = None): self._max_width = width self._max_height = height if self._current_width > width: @@ -189,7 +192,7 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass): self._current_height = height super().maxsize(self._apply_window_scaling(self._max_width), self._apply_window_scaling(self._max_height)) - def geometry(self, geometry_string: str = None): + def geometry(self, geometry_string: str | None = None): if geometry_string is not None: super().geometry(self._apply_geometry_scaling(geometry_string)) @@ -201,7 +204,7 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass): else: return self._reverse_geometry_scaling(super().geometry()) - def configure(self, **kwargs): + def configure(self, **kwargs: Any): if "fg_color" in kwargs: self._fg_color = self._check_color_type(kwargs.pop("fg_color")) super().configure(bg=self._apply_appearance_mode(self._fg_color)) @@ -215,7 +218,7 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass): super().configure(**pop_from_dict_by_set(kwargs, self._valid_tk_configure_arguments)) check_kwargs_empty(kwargs) - def cget(self, attribute_name: str) -> any: + def cget(self, attribute_name: str) -> Any: if attribute_name == "fg_color": return self._fg_color else: diff --git a/customtkinter/windows/ctk_toplevel.py b/customtkinter/windows/ctk_toplevel.py index a4eef57..180ede9 100644 --- a/customtkinter/windows/ctk_toplevel.py +++ b/customtkinter/windows/ctk_toplevel.py @@ -1,16 +1,24 @@ -import tkinter -from distutils.version import StrictVersion as Version -import sys +from __future__ import annotations + +import ctypes import os import platform -import ctypes -from typing import Union, Tuple, Optional +import sys +import tkinter +from distutils.version import StrictVersion as Version +from typing import Any + +if sys.version_info >= (3, 8): + from typing import Literal +else: + from typing_extensions import Literal + +from customtkinter.windows.widgets.utility.utility_functions import ( + check_kwargs_empty, pop_from_dict_by_set) -from .widgets.theme import ThemeManager -from .widgets.scaling import CTkScalingBaseClass from .widgets.appearance_mode import CTkAppearanceModeBaseClass - -from customtkinter.windows.widgets.utility.utility_functions import pop_from_dict_by_set, check_kwargs_empty +from .widgets.scaling import CTkScalingBaseClass +from .widgets.theme import ThemeManager class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseClass): @@ -19,16 +27,16 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl For detailed information check out the documentation. """ - _valid_tk_toplevel_arguments: set = {"bd", "borderwidth", "class", "container", "cursor", "height", - "highlightbackground", "highlightthickness", "menu", "relief", - "screen", "takefocus", "use", "visual", "width"} + _valid_tk_toplevel_arguments: set[str] = {"bd", "borderwidth", "class", "container", "cursor", "height", + "highlightbackground", "highlightthickness", "menu", "relief", + "screen", "takefocus", "use", "visual", "width"} _deactivate_macos_window_header_manipulation: bool = False _deactivate_windows_window_header_manipulation: bool = False - def __init__(self, *args, - fg_color: Optional[Union[str, Tuple[str, str]]] = None, - **kwargs): + def __init__(self, *args: Any, + fg_color: str | tuple[str, str] | None = None, + **kwargs: Any): self._enable_macos_dark_title_bar() @@ -52,7 +60,7 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl self._min_height: int = 0 self._max_width: int = 1_000_000 self._max_height: int = 1_000_000 - self._last_resizable_args: Union[Tuple[list, dict], None] = None # (args, kwargs) + self._last_resizable_args: tuple[list, dict] | None = None # (args, kwargs) self._fg_color = ThemeManager.theme["CTkToplevel"]["fg_color"] if fg_color is None else self._check_color_type(fg_color) @@ -92,12 +100,12 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl CTkAppearanceModeBaseClass.destroy(self) CTkScalingBaseClass.destroy(self) - def _focus_in_event(self, event): + def _focus_in_event(self, event: tkinter.Event[Any] | None = None): # sometimes window looses jumps back on macOS if window is selected from Mission Control, so has to be lifted again if sys.platform == "darwin": self.lift() - def _update_dimensions_event(self, event=None): + def _update_dimensions_event(self, event: tkinter.Event[Any] | None = None): if not self._block_update_dimensions_event: detected_width = self.winfo_width() # detect current window size detected_height = self.winfo_height() @@ -106,7 +114,7 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl self._current_width = self._reverse_window_scaling(detected_width) # adjust current size according to new size given by event self._current_height = self._reverse_window_scaling(detected_height) # _current_width and _current_height are independent of the scale - def _set_scaling(self, new_widget_scaling, new_window_scaling): + def _set_scaling(self, new_widget_scaling: float, new_window_scaling: float): super()._set_scaling(new_widget_scaling, new_window_scaling) # Force new dimensions on window by using min, max, and geometry. Without min, max it won't work. @@ -130,7 +138,7 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl if self._max_width is not None or self._max_height is not None: super().maxsize(self._apply_window_scaling(self._max_width), self._apply_window_scaling(self._max_height)) - def geometry(self, geometry_string: str = None): + def geometry(self, geometry_string: str | None = None): if geometry_string is not None: super().geometry(self._apply_geometry_scaling(geometry_string)) @@ -152,7 +160,7 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl self._iconify_called_after_windows_set_titlebar_color = True super().iconify() - def resizable(self, width: bool = None, height: bool = None): + def resizable(self, width: bool | None = None, height: bool | None = None): current_resizable_values = super().resizable(width, height) self._last_resizable_args = ([], {"width": width, "height": height}) @@ -161,7 +169,7 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl return current_resizable_values - def minsize(self, width=None, height=None): + def minsize(self, width: int | None = None, height: int | None = None): self._min_width = width self._min_height = height if self._current_width < width: @@ -170,7 +178,7 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl self._current_height = height super().minsize(self._apply_window_scaling(self._min_width), self._apply_window_scaling(self._min_height)) - def maxsize(self, width=None, height=None): + def maxsize(self, width: int | None = None, height: int | None = None): self._max_width = width self._max_height = height if self._current_width > width: @@ -179,7 +187,7 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl self._current_height = height super().maxsize(self._apply_window_scaling(self._max_width), self._apply_window_scaling(self._max_height)) - def configure(self, **kwargs): + def configure(self, **kwargs: Any): if "fg_color" in kwargs: self._fg_color = self._check_color_type(kwargs.pop("fg_color")) super().configure(bg=self._apply_appearance_mode(self._fg_color)) @@ -193,15 +201,15 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl super().configure(**pop_from_dict_by_set(kwargs, self._valid_tk_toplevel_arguments)) check_kwargs_empty(kwargs) - def cget(self, attribute_name: str) -> any: + def cget(self, attribute_name: str) -> Any: if attribute_name == "fg_color": return self._fg_color else: return super().cget(attribute_name) - def wm_iconbitmap(self, bitmap=None, default=None): + def wm_iconbitmap(self, bitmap: Any = None, default: Any = None): self._iconbitmap_method_called = True - super().wm_iconbitmap(bitmap, default) + super().wm_iconbitmap(bitmap, default) # type: ignore def _windows_set_titlebar_icon(self): try: @@ -298,7 +306,7 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl self._withdraw_called_after_windows_set_titlebar_color = False self._iconify_called_after_windows_set_titlebar_color = False - def _set_appearance_mode(self, mode_string): + def _set_appearance_mode(self, mode_string: Literal["light", "dark"]): super()._set_appearance_mode(mode_string) if sys.platform.startswith("win"): diff --git a/customtkinter/windows/widgets/__init__.py b/customtkinter/windows/widgets/__init__.py index a75c63d..7f5e9fe 100644 --- a/customtkinter/windows/widgets/__init__.py +++ b/customtkinter/windows/widgets/__init__.py @@ -7,10 +7,10 @@ from .ctk_label import CTkLabel from .ctk_optionmenu import CTkOptionMenu from .ctk_progressbar import CTkProgressBar from .ctk_radiobutton import CTkRadioButton +from .ctk_scrollable_frame import CTkScrollableFrame from .ctk_scrollbar import CTkScrollbar from .ctk_segmented_button import CTkSegmentedButton from .ctk_slider import CTkSlider from .ctk_switch import CTkSwitch from .ctk_tabview import CTkTabview from .ctk_textbox import CTkTextbox -from .ctk_scrollable_frame import CTkScrollableFrame diff --git a/customtkinter/windows/widgets/appearance_mode/appearance_mode_base_class.py b/customtkinter/windows/widgets/appearance_mode/appearance_mode_base_class.py index b7f757a..60212c8 100644 --- a/customtkinter/windows/widgets/appearance_mode/appearance_mode_base_class.py +++ b/customtkinter/windows/widgets/appearance_mode/appearance_mode_base_class.py @@ -1,4 +1,11 @@ -from typing import Union, Tuple, List +from __future__ import annotations + +import sys + +if sys.version_info >= (3, 8): + from typing import Literal +else: + from typing_extensions import Literal from .appearance_mode_tracker import AppearanceModeTracker @@ -19,7 +26,7 @@ class CTkAppearanceModeBaseClass: def destroy(self): AppearanceModeTracker.remove(self._set_appearance_mode) - def _set_appearance_mode(self, mode_string: str): + def _set_appearance_mode(self, mode_string: Literal["light", "dark"]): """ can be overridden but super method must be called at the beginning """ if mode_string.lower() == "dark": self.__appearance_mode = 1 @@ -33,7 +40,7 @@ class CTkAppearanceModeBaseClass: else: return "dark" - def _apply_appearance_mode(self, color: Union[str, Tuple[str, str], List[str]]) -> str: + def _apply_appearance_mode(self, color: str | tuple[str, str] | list[str]) -> str: """ color can be either a single hex color string or a color name or it can be a tuple color with (light_color, dark_color). The functions returns @@ -46,7 +53,7 @@ class CTkAppearanceModeBaseClass: return color @staticmethod - def _check_color_type(color: any, transparency: bool = False): + def _check_color_type(color: Literal["transparent"] | str | tuple[str, str] | None, transparency: bool = False): if color is None: raise ValueError(f"color is None, for transparency set color='transparent'") elif isinstance(color, (tuple, list)) and (color[0] == "transparent" or color[1] == "transparent"): diff --git a/customtkinter/windows/widgets/appearance_mode/appearance_mode_tracker.py b/customtkinter/windows/widgets/appearance_mode/appearance_mode_tracker.py index 4958b93..1aa0f80 100644 --- a/customtkinter/windows/widgets/appearance_mode/appearance_mode_tracker.py +++ b/customtkinter/windows/widgets/appearance_mode/appearance_mode_tracker.py @@ -1,10 +1,20 @@ +from __future__ import annotations + import sys import tkinter from distutils.version import StrictVersion as Version -from typing import Callable +from typing import Callable, TYPE_CHECKING + +if sys.version_info >= (3, 8): + from typing import Literal +else: + from typing_extensions import Literal + +if TYPE_CHECKING: + from customtkinter.windows.ctk_tk import CTk try: - import darkdetect + import darkdetect # type: ignore if Version(darkdetect.__version__) < Version("0.3.1"): sys.stderr.write("WARNING: You have to upgrade the darkdetect library: pip3 install --upgrade darkdetect\n") @@ -18,13 +28,13 @@ except Exception: class AppearanceModeTracker: - callback_list = [] - app_list = [] + callback_list: list[Callable[[str], None]] = [] + app_list: list[CTk] = [] update_loop_running = False update_loop_interval = 30 # milliseconds - appearance_mode_set_by = "system" - appearance_mode = 0 # Light (standard) + appearance_mode_set_by: Literal["system", "user"] = "system" + appearance_mode: Literal[0, 1] = 0 # 0 = Light, 1 = Dark @classmethod def init_appearance_mode(cls): @@ -36,7 +46,7 @@ class AppearanceModeTracker: cls.update_callbacks() @classmethod - def add(cls, callback: Callable, widget=None): + def add(cls, callback: Callable[[str], None], widget: CTk | None = None): cls.callback_list.append(callback) if widget is not None: @@ -49,16 +59,16 @@ class AppearanceModeTracker: cls.update_loop_running = True @classmethod - def remove(cls, callback: Callable): + def remove(cls, callback: Callable[[str], None]): try: cls.callback_list.remove(callback) except ValueError: return @staticmethod - def detect_appearance_mode() -> int: + def detect_appearance_mode() -> Literal[0, 1]: try: - if darkdetect.theme() == "Dark": + if darkdetect.theme() == "Dark": # type: ignore return 1 # Dark else: return 0 # Light @@ -66,7 +76,7 @@ class AppearanceModeTracker: return 0 # Light @classmethod - def get_tk_root_of_widget(cls, widget): + def get_tk_root_of_widget(cls, widget: CTk) -> CTk: current_widget = widget while isinstance(current_widget, tkinter.Tk) is False: diff --git a/customtkinter/windows/widgets/core_rendering/ctk_canvas.py b/customtkinter/windows/widgets/core_rendering/ctk_canvas.py index f291e2c..e32590e 100644 --- a/customtkinter/windows/widgets/core_rendering/ctk_canvas.py +++ b/customtkinter/windows/widgets/core_rendering/ctk_canvas.py @@ -1,6 +1,9 @@ -import tkinter +from __future__ import annotations + import sys -from typing import Union, Tuple +import tkinter + +from typing import Any class CTkCanvas(tkinter.Canvas): @@ -25,11 +28,11 @@ class CTkCanvas(tkinter.Canvas): not can be a problem when using only a single circle character. """ - radius_to_char_fine: dict = None # dict to map radius to font circle character + radius_to_char_fine: dict[int, str] = {} # dict to map radius to font circle character - def __init__(self, *args, **kwargs): + def __init__(self, *args: Any, **kwargs: Any): super().__init__(*args, **kwargs) - self._aa_circle_canvas_ids = set() + self._aa_circle_canvas_ids: set[str | int] = set() @classmethod def init_font_character_mapping(cls): @@ -71,7 +74,7 @@ class CTkCanvas(tkinter.Canvas): return self.radius_to_char_fine[radius] def create_aa_circle(self, x_pos: int, y_pos: int, radius: int, angle: int = 0, fill: str = "white", - tags: Union[str, Tuple[str, ...]] = "", anchor: str = tkinter.CENTER) -> int: + tags: str | tuple[str, ...] = "", anchor: str = tkinter.CENTER) -> int: # create a circle with a font element circle_1 = self.create_text(x_pos, y_pos, text=self._get_char_from_radius(radius), anchor=anchor, fill=fill, font=("CustomTkinter_shapes_font", -radius * 2), tags=tags, angle=angle) @@ -80,8 +83,7 @@ class CTkCanvas(tkinter.Canvas): return circle_1 - def coords(self, tag_or_id, *args): - + def coords(self, tag_or_id: int | str, *args: Any): if type(tag_or_id) == str and "ctk_aa_circle_font_element" in self.gettags(tag_or_id): coords_id = self.find_withtag(tag_or_id)[0] # take the lowest id for the given tag super().coords(coords_id, *args[:2]) @@ -98,7 +100,7 @@ class CTkCanvas(tkinter.Canvas): else: super().coords(tag_or_id, *args) - def itemconfig(self, tag_or_id, *args, **kwargs): + def itemconfig(self, tag_or_id: int | str, *args: Any, **kwargs: Any): kwargs_except_outline = kwargs.copy() if "outline" in kwargs_except_outline: del kwargs_except_outline["outline"] diff --git a/customtkinter/windows/widgets/core_rendering/draw_engine.py b/customtkinter/windows/widgets/core_rendering/draw_engine.py index 5acea56..31675dc 100644 --- a/customtkinter/windows/widgets/core_rendering/draw_engine.py +++ b/customtkinter/windows/widgets/core_rendering/draw_engine.py @@ -1,8 +1,14 @@ from __future__ import annotations -import sys + import math +import sys import tkinter -from typing import Union, TYPE_CHECKING +from typing import TYPE_CHECKING + +if sys.version_info >= (3, 8): + from typing import Literal +else: + from typing_extensions import Literal if TYPE_CHECKING: from ..core_rendering import CTkCanvas @@ -26,7 +32,7 @@ class DrawEngine: """ - preferred_drawing_method: str = None # 'polygon_shapes', 'font_shapes', 'circle_shapes' + preferred_drawing_method: Literal["polygon_shapes", "font_shapes", "circle_shapes"] = None def __init__(self, canvas: CTkCanvas): self._canvas = canvas @@ -37,7 +43,7 @@ class DrawEngine: self._round_width_to_even_numbers: bool = round_width_to_even_numbers self._round_height_to_even_numbers: bool = round_height_to_even_numbers - def __calc_optimal_corner_radius(self, user_corner_radius: Union[float, int]) -> Union[float, int]: + def __calc_optimal_corner_radius(self, user_corner_radius: float | int) -> float | int: # optimize for drawing with polygon shapes if self.preferred_drawing_method == "polygon_shapes": if sys.platform == "darwin": @@ -61,7 +67,7 @@ class DrawEngine: else: return user_corner_radius - def draw_background_corners(self, width: Union[float, int], height: Union[float, int], ): + def draw_background_corners(self, width: float | int, height: float | int): if self._round_width_to_even_numbers: width = math.floor(width / 2) * 2 # round (floor) _current_width and _current_height and restrict them to even values only if self._round_height_to_even_numbers: @@ -93,8 +99,8 @@ class DrawEngine: return requires_recoloring - def draw_rounded_rect_with_border(self, width: Union[float, int], height: Union[float, int], corner_radius: Union[float, int], - border_width: Union[float, int], overwrite_preferred_drawing_method: str = None) -> bool: + def draw_rounded_rect_with_border(self, width: float | int, height: float | int, corner_radius: float | int, + border_width: float | int, overwrite_preferred_drawing_method: str | None = None) -> bool: """ Draws a rounded rectangle with a corner_radius and border_width on the canvas. The border elements have a 'border_parts' tag, the main foreground elements have an 'inner_parts' tag to color the elements accordingly. @@ -184,7 +190,7 @@ class DrawEngine: return requires_recoloring def __draw_rounded_rect_with_border_font_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int, - exclude_parts: tuple) -> bool: + exclude_parts: tuple[str, ...]) -> bool: requires_recoloring = False # create border button parts @@ -396,8 +402,8 @@ class DrawEngine: return requires_recoloring - def draw_rounded_rect_with_border_vertical_split(self, width: Union[float, int], height: Union[float, int], corner_radius: Union[float, int], - border_width: Union[float, int], left_section_width: Union[float, int]) -> bool: + def draw_rounded_rect_with_border_vertical_split(self, width: float | int, height: float | int, corner_radius: float | int, + border_width: float | int, left_section_width: float | int) -> bool: """ Draws a rounded rectangle with a corner_radius and border_width on the canvas which is split at left_section_width. The border elements have the tags 'border_parts_left', 'border_parts_lright', the main foreground elements have an 'inner_parts_left' and inner_parts_right' tag, @@ -690,8 +696,8 @@ class DrawEngine: return requires_recoloring - def draw_rounded_progress_bar_with_border(self, width: Union[float, int], height: Union[float, int], corner_radius: Union[float, int], - border_width: Union[float, int], progress_value_1: float, progress_value_2: float, orientation: str) -> bool: + def draw_rounded_progress_bar_with_border(self, width: float | int, height: float | int, corner_radius: float | int, + border_width: float | int, progress_value_1: float, progress_value_2: float, orientation: str) -> bool: """ Draws a rounded bar on the canvas, and onntop sits a progress bar from value 1 to value 2 (range 0-1, left to right, bottom to top). The border elements get the 'border_parts' tag", the main elements get the 'inner_parts' tag and the progress elements get the 'progress_parts' tag. The 'orientation' argument defines from which direction the progress starts (n, w, s, e). @@ -868,8 +874,8 @@ class DrawEngine: return requires_recoloring or requires_recoloring_2 - def draw_rounded_slider_with_border_and_button(self, width: Union[float, int], height: Union[float, int], corner_radius: Union[float, int], - border_width: Union[float, int], button_length: Union[float, int], button_corner_radius: Union[float, int], + def draw_rounded_slider_with_border_and_button(self, width: float | int, height: float | int, corner_radius: float | int, + border_width: float | int, button_length: float | int, button_corner_radius: float | int, slider_value: float, orientation: str) -> bool: if self._round_width_to_even_numbers: @@ -1028,8 +1034,8 @@ class DrawEngine: return requires_recoloring - def draw_rounded_scrollbar(self, width: Union[float, int], height: Union[float, int], corner_radius: Union[float, int], - border_spacing: Union[float, int], start_value: float, end_value: float, orientation: str) -> bool: + def draw_rounded_scrollbar(self, width: float | int, height: float | int, corner_radius: float | int, + border_spacing: float | int, start_value: float, end_value: float, orientation: str) -> bool: if self._round_width_to_even_numbers: width = math.floor(width / 2) * 2 # round _current_width and _current_height and restrict them to even values only @@ -1171,7 +1177,7 @@ class DrawEngine: return requires_recoloring - def draw_checkmark(self, width: Union[float, int], height: Union[float, int], size: Union[int, float]) -> bool: + def draw_checkmark(self, width: float | int, height: float | int, size: int | float) -> bool: """ Draws a rounded rectangle with a corner_radius and border_width on the canvas. The border elements have a 'border_parts' tag, the main foreground elements have an 'inner_parts' tag to color the elements accordingly. @@ -1201,7 +1207,7 @@ class DrawEngine: return requires_recoloring - def draw_dropdown_arrow(self, x_position: Union[int, float], y_position: Union[int, float], size: Union[int, float]) -> bool: + def draw_dropdown_arrow(self, x_position: int | float, y_position: int | float, size: int | float) -> bool: """ Draws a dropdown bottom facing arrow at (x_position, y_position) in a given size returns bool if recoloring is necessary """ diff --git a/customtkinter/windows/widgets/core_widget_classes/__init__.py b/customtkinter/windows/widgets/core_widget_classes/__init__.py index 75e2d84..930e7d0 100644 --- a/customtkinter/windows/widgets/core_widget_classes/__init__.py +++ b/customtkinter/windows/widgets/core_widget_classes/__init__.py @@ -1,2 +1,2 @@ -from .dropdown_menu import DropdownMenu from .ctk_base_class import CTkBaseClass +from .dropdown_menu import DropdownMenu diff --git a/customtkinter/windows/widgets/core_widget_classes/ctk_base_class.py b/customtkinter/windows/widgets/core_widget_classes/ctk_base_class.py index 610f46a..d07e3f4 100644 --- a/customtkinter/windows/widgets/core_widget_classes/ctk_base_class.py +++ b/customtkinter/windows/widgets/core_widget_classes/ctk_base_class.py @@ -1,8 +1,9 @@ -import sys -import warnings +from __future__ import annotations + import tkinter import tkinter.ttk as ttk -from typing import Union, Callable, Tuple +import warnings +from typing import Any, Callable try: from typing import TypedDict @@ -10,14 +11,12 @@ except ImportError: from typing_extensions import TypedDict from .... import windows # import windows for isinstance checks - -from ..theme import ThemeManager +from ..appearance_mode import CTkAppearanceModeBaseClass from ..font import CTkFont from ..image import CTkImage -from ..appearance_mode import CTkAppearanceModeBaseClass from ..scaling import CTkScalingBaseClass - -from ..utility import pop_from_dict_by_set, check_kwargs_empty +from ..theme import ThemeManager +from ..utility import check_kwargs_empty, pop_from_dict_by_set class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClass): @@ -25,17 +24,17 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas appearance_mode changes, scaling, bg changes of master if master is not a CTk widget """ # attributes that are passed to and managed by the tkinter frame only: - _valid_tk_frame_attributes: set = {"cursor"} + _valid_tk_frame_attributes: set[str] = {"cursor"} _cursor_manipulation_enabled: bool = True def __init__(self, - master: any, - width: int = 0, - height: int = 0, + master: Any, + width: int | str = 0, + height: int | str = 0, - bg_color: Union[str, Tuple[str, str]] = "transparent", - **kwargs): + bg_color: str | tuple[str, str] = "transparent", + **kwargs: Any): # call init methods of super classes tkinter.Frame.__init__(self, master=master, width=width, height=height, **pop_from_dict_by_set(kwargs, self._valid_tk_frame_attributes)) @@ -57,12 +56,12 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas # save latest geometry function and kwargs class GeometryCallDict(TypedDict): - function: Callable - kwargs: dict - self._last_geometry_manager_call: Union[GeometryCallDict, None] = None + function: Callable[..., None] + kwargs: dict[Any, Any] + self._last_geometry_manager_call: GeometryCallDict | None = None # background color - self._bg_color: Union[str, Tuple[str, str]] = self._detect_color_of_master() if bg_color == "transparent" else self._check_color_type(bg_color, transparency=True) + self._bg_color: str | tuple[str, str] = self._detect_color_of_master() if bg_color == "transparent" else self._check_color_type(bg_color, transparency=True) # set bg color of tkinter.Frame super().configure(bg=self._apply_appearance_mode(self._bg_color)) @@ -74,7 +73,7 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas if isinstance(self.master, (tkinter.Tk, tkinter.Toplevel, tkinter.Frame, tkinter.LabelFrame, ttk.Frame, ttk.LabelFrame, ttk.Notebook)) and not isinstance(self.master, (CTkBaseClass, CTkAppearanceModeBaseClass)): master_old_configure = self.master.config - def new_configure(*args, **kwargs): + def new_configure(*args, **kwargs: Any): if "bg" in kwargs: self.configure(bg_color=kwargs["bg"]) elif "background" in kwargs: @@ -107,10 +106,10 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas # super().configure(bg=self._apply_appearance_mode(self._bg_color)) pass - def config(self, *args, **kwargs): + def config(self, *args: Any, **kwargs: Any): raise AttributeError("'config' is not implemented for CTk widgets. For consistency, always use 'configure' instead.") - def configure(self, require_redraw=False, **kwargs): + def configure(self, require_redraw: bool =False, **kwargs: Any): """ basic configure with bg_color, width, height support, calls configure of tkinter.Frame, updates in the end """ if "width" in kwargs: @@ -150,7 +149,7 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas else: raise ValueError(f"'{attribute_name}' is not a supported argument. Look at the documentation for supported arguments.") - def _check_font_type(self, font: any): + def _check_font_type(self, font: CTkFont | tuple[Any, ...]): """ check font type when passed to widget """ if isinstance(font, CTkFont): return font @@ -169,7 +168,7 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas f"font=customtkinter.CTkFont(family='', size=)\n" + f"font=('', )\n") - def _check_image_type(self, image: any): + def _check_image_type(self, image: Any): """ check image type when passed to widget """ if image is None: return image @@ -179,7 +178,7 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas warnings.warn(f"{type(self).__name__} Warning: Given image is not CTkImage but {type(image)}. Image can not be scaled on HighDPI displays, use CTkImage instead.\n") return image - def _update_dimensions_event(self, event): + def _update_dimensions_event(self, event: tkinter.Event[Any] | None = None): # only redraw if dimensions changed (for performance), independent of scaling if round(self._current_width) != round(self._reverse_widget_scaling(event.width)) or round(self._current_height) != round(self._reverse_widget_scaling(event.height)): self._current_width = self._reverse_widget_scaling(event.width) # adjust current size according to new size given by event @@ -187,7 +186,7 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas self._draw(no_color_updates=True) # faster drawing without color changes - def _detect_color_of_master(self, master_widget=None) -> Union[str, Tuple[str, str]]: + def _detect_color_of_master(self, master_widget=None) -> str | tuple[str, str]: """ detect foreground color of master widget for bg_color and transparent color """ if master_widget is None: @@ -222,7 +221,7 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas self._draw() super().update_idletasks() - def _set_scaling(self, new_widget_scaling, new_window_scaling): + def _set_scaling(self, new_widget_scaling: float, new_window_scaling: float): super()._set_scaling(new_widget_scaling, new_window_scaling) super().configure(width=self._apply_widget_scaling(self._desired_width), @@ -231,7 +230,7 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas if self._last_geometry_manager_call is not None: self._last_geometry_manager_call["function"](**self._apply_argument_scaling(self._last_geometry_manager_call["kwargs"])) - def _set_dimensions(self, width=None, height=None): + def _set_dimensions(self, width: int | None = None, height: int | None = None): if width is not None: self._desired_width = width if height is not None: @@ -240,19 +239,19 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas super().configure(width=self._apply_widget_scaling(self._desired_width), height=self._apply_widget_scaling(self._desired_height)) - def bind(self, sequence=None, command=None, add=None): + def bind(self, sequence: str, command: Callable[..., None], add: bool = False): raise NotImplementedError - def unbind(self, sequence=None, funcid=None): + def unbind(self, sequence: str, funcid: str | None = None): raise NotImplementedError - def unbind_all(self, sequence): + def unbind_all(self, sequence: str): raise AttributeError("'unbind_all' is not allowed, because it would delete necessary internal callbacks for all widgets") - def bind_all(self, sequence=None, func=None, add=None): + def bind_all(self, sequence: str, func: Callable[..., None], add: bool = False): raise AttributeError("'bind_all' is not allowed, could result in undefined behavior") - def place(self, **kwargs): + def place(self, **kwargs: Any): """ Place a widget in the parent widget. Use as options: in=master - master relative to which the widget is placed @@ -278,7 +277,7 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas self._last_geometry_manager_call = None return super().place_forget() - def pack(self, **kwargs): + def pack(self, **kwargs: Any): """ Pack a widget in the parent widget. Use as options: after=widget - pack it after you have packed widget @@ -302,7 +301,7 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas self._last_geometry_manager_call = None return super().pack_forget() - def grid(self, **kwargs): + def grid(self, **kwargs: Any): """ Position a widget in the parent widget in a grid. Use as options: column=number - use cell identified with given column (starting with 0) diff --git a/customtkinter/windows/widgets/core_widget_classes/dropdown_menu.py b/customtkinter/windows/widgets/core_widget_classes/dropdown_menu.py index a6b8186..2cd6b1e 100644 --- a/customtkinter/windows/widgets/core_widget_classes/dropdown_menu.py +++ b/customtkinter/windows/widgets/core_widget_classes/dropdown_menu.py @@ -1,25 +1,27 @@ -import tkinter -import sys -from typing import Union, Tuple, Callable, List, Optional +from __future__ import annotations + +import sys +import tkinter +from typing import Any, Callable -from ..theme import ThemeManager -from ..font import CTkFont from ..appearance_mode import CTkAppearanceModeBaseClass +from ..font import CTkFont from ..scaling import CTkScalingBaseClass +from ..theme import ThemeManager class DropdownMenu(tkinter.Menu, CTkAppearanceModeBaseClass, CTkScalingBaseClass): - def __init__(self, *args, + def __init__(self, *args: Any, min_character_width: int = 18, - fg_color: Optional[Union[str, Tuple[str, str]]] = None, - hover_color: Optional[Union[str, Tuple[str, str]]] = None, - text_color: Optional[Union[str, Tuple[str, str]]] = None, + fg_color: str | tuple[str, str] | None = None, + hover_color: str | tuple[str, str] | None = None, + text_color: str | tuple[str, str] | None = None, - font: Optional[Union[tuple, CTkFont]] = None, - command: Union[Callable, None] = None, - values: Optional[List[str]] = None, - **kwargs): + font: tuple[Any, ...] | CTkFont | None = None, + command: Callable[..., None] | None = None, + values: list[str] | None = None, + **kwargs: Any): # call init methods of super classes tkinter.Menu.__init__(self, *args, **kwargs) @@ -105,7 +107,7 @@ class DropdownMenu(tkinter.Menu, CTkAppearanceModeBaseClass, CTkScalingBaseClass if self._command is not None: self._command(value) - def open(self, x: Union[int, float], y: Union[int, float]): + def open(self, x: int | float, y: int | float): if sys.platform == "darwin": y += self._apply_widget_scaling(8) @@ -117,7 +119,7 @@ class DropdownMenu(tkinter.Menu, CTkAppearanceModeBaseClass, CTkScalingBaseClass else: # Linux self.tk_popup(int(x), int(y)) - def configure(self, **kwargs): + def configure(self, **kwargs: Any): if "fg_color" in kwargs: self._fg_color = self._check_color_type(kwargs.pop("fg_color")) super().configure(bg=self._apply_appearance_mode(self._fg_color)) @@ -148,7 +150,7 @@ class DropdownMenu(tkinter.Menu, CTkAppearanceModeBaseClass, CTkScalingBaseClass super().configure(**kwargs) - def cget(self, attribute_name: str) -> any: + def cget(self, attribute_name: str) -> Any: if attribute_name == "min_character_width": return self._min_character_width @@ -170,7 +172,7 @@ class DropdownMenu(tkinter.Menu, CTkAppearanceModeBaseClass, CTkScalingBaseClass return super().cget(attribute_name) @staticmethod - def _check_font_type(font: any): + def _check_font_type(font: Any): if isinstance(font, CTkFont): return font @@ -188,7 +190,7 @@ class DropdownMenu(tkinter.Menu, CTkAppearanceModeBaseClass, CTkScalingBaseClass f"font=customtkinter.CTkFont(family='', size=)\n" + f"font=('', )\n") - def _set_scaling(self, new_widget_scaling, new_window_scaling): + def _set_scaling(self, new_widget_scaling: float, new_window_scaling: float): super()._set_scaling(new_widget_scaling, new_window_scaling) self._configure_menu_for_platforms() diff --git a/customtkinter/windows/widgets/ctk_button.py b/customtkinter/windows/widgets/ctk_button.py index d79a944..99115d0 100644 --- a/customtkinter/windows/widgets/ctk_button.py +++ b/customtkinter/windows/widgets/ctk_button.py @@ -1,14 +1,22 @@ -import tkinter -import sys -from typing import Union, Tuple, Callable, Optional +from __future__ import annotations -from .core_rendering import CTkCanvas -from .theme import ThemeManager -from .core_rendering import DrawEngine +import sys +import tkinter +from typing import Any, Callable, TYPE_CHECKING + +if sys.version_info >= (3, 8): + from typing import Literal +else: + from typing_extensions import Literal + +from .core_rendering import CTkCanvas, DrawEngine from .core_widget_classes import CTkBaseClass from .font import CTkFont from .image import CTkImage +from .theme import ThemeManager +if TYPE_CHECKING: + from PIL import ImageTk class CTkButton(CTkBaseClass): """ @@ -19,34 +27,34 @@ class CTkButton(CTkBaseClass): _image_label_spacing: int = 6 def __init__(self, - master: any, + master: CTkBaseClass, width: int = 140, height: int = 28, - corner_radius: Optional[int] = None, - border_width: Optional[int] = None, + corner_radius: int | None = None, + border_width: int | None = None, border_spacing: int = 2, - bg_color: Union[str, Tuple[str, str]] = "transparent", - fg_color: Optional[Union[str, Tuple[str, str]]] = None, - hover_color: Optional[Union[str, Tuple[str, str]]] = None, - border_color: Optional[Union[str, Tuple[str, str]]] = None, - text_color: Optional[Union[str, Tuple[str, str]]] = None, - text_color_disabled: Optional[Union[str, Tuple[str, str]]] = None, + bg_color: str | tuple[str, str] = "transparent", + fg_color: str | tuple[str, str] | None = None, + hover_color: str | tuple[str, str] | None = None, + border_color: str | tuple[str, str] | None = None, + text_color: str | tuple[str, str] | None = None, + text_color_disabled: str | tuple[str, str] | None = None, - background_corner_colors: Union[Tuple[Union[str, Tuple[str, str]]], None] = None, + background_corner_colors: tuple[str | tuple[str, str]] | None = None, round_width_to_even_numbers: bool = True, round_height_to_even_numbers: bool = True, text: str = "CTkButton", - font: Optional[Union[tuple, CTkFont]] = None, - textvariable: Union[tkinter.Variable, None] = None, - image: Union[CTkImage, "ImageTk.PhotoImage", None] = None, + font: tuple[Any, ...] | CTkFont | None = None, + textvariable: tkinter.Variable | None = None, + image: CTkImage | ImageTk.PhotoImage | None = None, state: str = "normal", hover: bool = True, - command: Union[Callable[[], None], None] = None, + command: Callable[[], None] | None = None, compound: str = "left", anchor: str = "center", - **kwargs): + **kwargs: Any): # transfer basic functionality (bg_color, size, appearance_mode, scaling) to CTkBaseClass super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs) @@ -58,38 +66,38 @@ class CTkButton(CTkBaseClass): self._border_spacing: int = border_spacing # color - self._fg_color: Union[str, Tuple[str, str]] = ThemeManager.theme["CTkButton"]["fg_color"] if fg_color is None else self._check_color_type(fg_color, transparency=True) - self._hover_color: Union[str, Tuple[str, str]] = ThemeManager.theme["CTkButton"]["hover_color"] if hover_color is None else self._check_color_type(hover_color) - self._border_color: Union[str, Tuple[str, str]] = ThemeManager.theme["CTkButton"]["border_color"] if border_color is None else self._check_color_type(border_color) - self._text_color: Union[str, Tuple[str, str]] = ThemeManager.theme["CTkButton"]["text_color"] if text_color is None else self._check_color_type(text_color) - self._text_color_disabled: Union[str, Tuple[str, str]] = ThemeManager.theme["CTkButton"]["text_color_disabled"] if text_color_disabled is None else self._check_color_type(text_color_disabled) + self._fg_color: str | tuple[str, str] = ThemeManager.theme["CTkButton"]["fg_color"] if fg_color is None else self._check_color_type(fg_color, transparency=True) + self._hover_color: str | tuple[str, str] = ThemeManager.theme["CTkButton"]["hover_color"] if hover_color is None else self._check_color_type(hover_color) + self._border_color: str | tuple[str, str] = ThemeManager.theme["CTkButton"]["border_color"] if border_color is None else self._check_color_type(border_color) + self._text_color: str | tuple[str, str] = ThemeManager.theme["CTkButton"]["text_color"] if text_color is None else self._check_color_type(text_color) + self._text_color_disabled: str | tuple[str, str] = ThemeManager.theme["CTkButton"]["text_color_disabled"] if text_color_disabled is None else self._check_color_type(text_color_disabled) # rendering options - self._background_corner_colors: Union[Tuple[Union[str, Tuple[str, str]]], None] = background_corner_colors # rendering options for DrawEngine + self._background_corner_colors: tuple[str | tuple[str, str]] | None = background_corner_colors # rendering options for DrawEngine self._round_width_to_even_numbers: bool = round_width_to_even_numbers # rendering options for DrawEngine self._round_height_to_even_numbers: bool = round_height_to_even_numbers # rendering options for DrawEngine # text, font self._text = text - self._text_label: Union[tkinter.Label, None] = None - self._textvariable: tkinter.Variable = textvariable - self._font: Union[tuple, CTkFont] = CTkFont() if font is None else self._check_font_type(font) + self._text_label: tkinter.Label | None = None + self._textvariable = textvariable + self._font = CTkFont() if font is None else self._check_font_type(font) if isinstance(self._font, CTkFont): self._font.add_size_configure_callback(self._update_font) # image self._image = self._check_image_type(image) - self._image_label: Union[tkinter.Label, None] = None + self._image_label: tkinter.Label | None = None if isinstance(self._image, CTkImage): self._image.add_configure_callback(self._update_image) # other - self._state: str = state - self._hover: bool = hover - self._command: Callable = command - self._compound: str = compound - self._anchor: str = anchor - self._click_animation_running: bool = False + self._state = state + self._hover = hover + self._command = command + self._compound = compound + self._anchor = anchor + self._click_animation_running = False # canvas and draw engine self._canvas = CTkCanvas(master=self, @@ -105,7 +113,7 @@ class CTkButton(CTkBaseClass): self._set_cursor() self._draw() - def _create_bindings(self, sequence: Optional[str] = None): + def _create_bindings(self, sequence: str | None = None): """ set necessary bindings for functionality of widget, will overwrite other bindings """ if sequence is None or sequence == "": @@ -132,7 +140,7 @@ class CTkButton(CTkBaseClass): if self._image_label is not None: self._image_label.bind("", self._clicked) - def _set_scaling(self, *args, **kwargs): + def _set_scaling(self, *args: Any, **kwargs: Any): super()._set_scaling(*args, **kwargs) self._create_grid() @@ -146,11 +154,11 @@ class CTkButton(CTkBaseClass): height=self._apply_widget_scaling(self._desired_height)) self._draw(no_color_updates=True) - def _set_appearance_mode(self, mode_string): + def _set_appearance_mode(self, mode_string: Literal["light", "dark"]): super()._set_appearance_mode(mode_string) self._update_image() - def _set_dimensions(self, width: int = None, height: int = None): + def _set_dimensions(self, width: int | None = None, height: int | None = None): super()._set_dimensions(width, height) self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), @@ -180,7 +188,7 @@ class CTkButton(CTkBaseClass): self._font.remove_size_configure_callback(self._update_font) super().destroy() - def _draw(self, no_color_updates=False): + def _draw(self, no_color_updates: bool = False): super()._draw(no_color_updates) if self._background_corner_colors is not None: @@ -349,7 +357,7 @@ class CTkButton(CTkBaseClass): if self._text_label is not None: self._text_label.grid(row=1, column=2, sticky="s") - def configure(self, require_redraw=False, **kwargs): + def configure(self, require_redraw: bool = False, **kwargs: Any): if "corner_radius" in kwargs: self._corner_radius = kwargs.pop("corner_radius") self._create_grid() @@ -440,7 +448,7 @@ class CTkButton(CTkBaseClass): super().configure(require_redraw=require_redraw, **kwargs) - def cget(self, attribute_name: str) -> any: + def cget(self, attribute_name: str) -> Any: if attribute_name == "corner_radius": return self._corner_radius elif attribute_name == "border_width": @@ -496,7 +504,7 @@ class CTkButton(CTkBaseClass): elif sys.platform.startswith("win") and self._command is not None: self.configure(cursor="hand2") - def _on_enter(self, event=None): + def _on_enter(self, event: tkinter.Event[Any] | None = None): if self._hover is True and self._state == "normal": if self._hover_color is None: inner_parts_color = self._fg_color @@ -516,7 +524,7 @@ class CTkButton(CTkBaseClass): if self._image_label is not None: self._image_label.configure(bg=self._apply_appearance_mode(inner_parts_color)) - def _on_leave(self, event=None): + def _on_leave(self, event: tkinter.Event[Any] | None = None): self._click_animation_running = False if self._fg_color == "transparent": @@ -541,7 +549,7 @@ class CTkButton(CTkBaseClass): if self._click_animation_running: self._on_enter() - def _clicked(self, event=None): + def _clicked(self, event: tkinter.Event[Any] | None = None): if self._state != tkinter.DISABLED: # click animation: change color with .on_leave() and back to normal after 100ms with click_animation() @@ -558,7 +566,7 @@ class CTkButton(CTkBaseClass): if self._command is not None: return self._command() - def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True): + def bind(self, sequence: str, command: Callable[..., None] | None = None, add: str | bool = True): """ called on the tkinter.Canvas """ if not (add == "+" or add is True): raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks") @@ -569,7 +577,7 @@ class CTkButton(CTkBaseClass): if self._image_label is not None: self._image_label.bind(sequence, command, add=True) - def unbind(self, sequence: str = None, funcid: str = None): + def unbind(self, sequence: str, funcid: str | None = None): """ called on the tkinter.Label and tkinter.Canvas """ if funcid is not None: raise ValueError("'funcid' argument can only be None, because there is a bug in" + diff --git a/customtkinter/windows/widgets/ctk_checkbox.py b/customtkinter/windows/widgets/ctk_checkbox.py index 3d9b496..515d5f3 100644 --- a/customtkinter/windows/widgets/ctk_checkbox.py +++ b/customtkinter/windows/widgets/ctk_checkbox.py @@ -1,12 +1,13 @@ -import tkinter -import sys -from typing import Union, Tuple, Callable, Optional +from __future__ import annotations -from .core_rendering import CTkCanvas -from .theme import ThemeManager -from .core_rendering import DrawEngine +import sys +import tkinter +from typing import Any, Callable + +from .core_rendering import CTkCanvas, DrawEngine from .core_widget_classes import CTkBaseClass from .font import CTkFont +from .theme import ThemeManager class CTkCheckBox(CTkBaseClass): @@ -16,32 +17,32 @@ class CTkCheckBox(CTkBaseClass): """ def __init__(self, - master: any, + master: CTkBaseClass, width: int = 100, height: int = 24, checkbox_width: int = 24, checkbox_height: int = 24, - corner_radius: Optional[int] = None, - border_width: Optional[int] = None, + corner_radius: int | None = None, + border_width: int | None = None, - bg_color: Union[str, Tuple[str, str]] = "transparent", - fg_color: Optional[Union[str, Tuple[str, str]]] = None, - hover_color: Optional[Union[str, Tuple[str, str]]] = None, - border_color: Optional[Union[str, Tuple[str, str]]] = None, - checkmark_color: Optional[Union[str, Tuple[str, str]]] = None, - text_color: Optional[Union[str, Tuple[str, str]]] = None, - text_color_disabled: Optional[Union[str, Tuple[str, str]]] = None, + bg_color: str | tuple[str, str] = "transparent", + fg_color: str | tuple[str, str] | None = None, + hover_color: str | tuple[str, str] | None = None, + border_color: str | tuple[str, str] | None = None, + checkmark_color: str | tuple[str, str] | None = None, + text_color: str | tuple[str, str] | None = None, + text_color_disabled: str | tuple[str, str] | None = None, text: str = "CTkCheckBox", - font: Optional[Union[tuple, CTkFont]] = None, - textvariable: Union[tkinter.Variable, None] = None, + font: tuple[Any, ...] | CTkFont | None = None, + textvariable: tkinter.Variable | None = None, state: str = tkinter.NORMAL, hover: bool = True, - command: Union[Callable[[], None], None] = None, - onvalue: Union[int, str] = 1, - offvalue: Union[int, str] = 0, - variable: Union[tkinter.Variable, None] = None, - **kwargs): + command: Callable[[], None] | None = None, + onvalue: int | str = 1, + offvalue: int | str = 0, + variable: tkinter.Variable | None = None, + **kwargs: Any): # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs) @@ -62,7 +63,6 @@ class CTkCheckBox(CTkBaseClass): # text self._text = text - self._text_label: Union[tkinter.Label, None] = None self._text_color = ThemeManager.theme["CTkCheckbox"]["text_color"] if text_color is None else self._check_color_type(text_color) self._text_color_disabled = ThemeManager.theme["CTkCheckbox"]["text_color_disabled"] if text_color_disabled is None else self._check_color_type(text_color_disabled) @@ -79,9 +79,9 @@ class CTkCheckBox(CTkBaseClass): self._onvalue = onvalue self._offvalue = offvalue - self._variable: tkinter.Variable = variable + self._variable = variable self._variable_callback_blocked = False - self._textvariable: tkinter.Variable = textvariable + self._textvariable = textvariable self._variable_callback_name = None # configure grid system (1x3) @@ -123,7 +123,7 @@ class CTkCheckBox(CTkBaseClass): self._set_cursor() self._draw() - def _create_bindings(self, sequence: Optional[str] = None): + def _create_bindings(self, sequence: str | None = None): """ set necessary bindings for functionality of widget, will overwrite other bindings """ if sequence is None or sequence == "": self._canvas.bind("", self._on_enter) @@ -135,7 +135,7 @@ class CTkCheckBox(CTkBaseClass): self._canvas.bind("", self.toggle) self._text_label.bind("", self.toggle) - def _set_scaling(self, *args, **kwargs): + def _set_scaling(self, *args: Any, **kwargs: Any): super()._set_scaling(*args, **kwargs) self.grid_columnconfigure(1, weight=0, minsize=self._apply_widget_scaling(6)) @@ -148,7 +148,7 @@ class CTkCheckBox(CTkBaseClass): height=self._apply_widget_scaling(self._checkbox_height)) self._draw(no_color_updates=True) - def _set_dimensions(self, width: int = None, height: int = None): + def _set_dimensions(self, width: int | None = None, height: int | None = None): super()._set_dimensions(width, height) self._bg_canvas.configure(width=self._apply_widget_scaling(self._desired_width), @@ -173,7 +173,7 @@ class CTkCheckBox(CTkBaseClass): super().destroy() - def _draw(self, no_color_updates=False): + def _draw(self, no_color_updates: bool = False): super()._draw(no_color_updates) requires_recoloring_1 = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._checkbox_width), @@ -220,22 +220,22 @@ class CTkCheckBox(CTkBaseClass): self._text_label.configure(bg=self._apply_appearance_mode(self._bg_color)) - def configure(self, require_redraw=False, **kwargs): + def configure(self, require_redraw: bool = False, **kwargs: Any): if "corner_radius" in kwargs: self._corner_radius = kwargs.pop("corner_radius") require_redraw = True if "border_width" in kwargs: - self._border_width = kwargs.pop("border_width") + self._border_width: int = kwargs.pop("border_width") require_redraw = True if "checkbox_width" in kwargs: - self._checkbox_width = kwargs.pop("checkbox_width") + self._checkbox_width: int = kwargs.pop("checkbox_width") self._canvas.configure(width=self._apply_widget_scaling(self._checkbox_width)) require_redraw = True if "checkbox_height" in kwargs: - self._checkbox_height = kwargs.pop("checkbox_height") + self._checkbox_height: int = kwargs.pop("checkbox_height") self._canvas.configure(height=self._apply_widget_scaling(self._checkbox_height)) require_redraw = True @@ -296,7 +296,7 @@ class CTkCheckBox(CTkBaseClass): super().configure(require_redraw=require_redraw, **kwargs) - def cget(self, attribute_name: str) -> any: + def cget(self, attribute_name: str) -> Any: if attribute_name == "corner_radius": return self._corner_radius elif attribute_name == "border_width": @@ -360,7 +360,7 @@ class CTkCheckBox(CTkBaseClass): if self._text_label is not None: self._text_label.configure(cursor="hand2") - def _on_enter(self, event=0): + def _on_enter(self, event: tkinter.Event[Any] | None = None): if self._hover is True and self._state == tkinter.NORMAL: if self._check_state is True: self._canvas.itemconfig("inner_parts", @@ -374,7 +374,7 @@ class CTkCheckBox(CTkBaseClass): fill=self._apply_appearance_mode(self._hover_color), outline=self._apply_appearance_mode(self._hover_color)) - def _on_leave(self, event=0): + def _on_leave(self, event: tkinter.Event[Any] | None = None): if self._check_state is True: self._canvas.itemconfig("inner_parts", fill=self._apply_appearance_mode(self._fg_color), @@ -390,14 +390,14 @@ class CTkCheckBox(CTkBaseClass): fill=self._apply_appearance_mode(self._border_color), outline=self._apply_appearance_mode(self._border_color)) - def _variable_callback(self, var_name, index, mode): + def _variable_callback(self, var_name: str, index: int, mode: Any): if not self._variable_callback_blocked: if self._variable.get() == self._onvalue: self.select(from_variable_callback=True) elif self._variable.get() == self._offvalue: self.deselect(from_variable_callback=True) - def toggle(self, event=0): + def toggle(self, event: tkinter.Event[Any] | None = None): if self._state == tkinter.NORMAL: if self._check_state is True: self._check_state = False @@ -414,7 +414,7 @@ class CTkCheckBox(CTkBaseClass): if self._command is not None: self._command() - def select(self, from_variable_callback=False): + def select(self, from_variable_callback: bool =False): self._check_state = True self._draw() @@ -423,7 +423,7 @@ class CTkCheckBox(CTkBaseClass): self._variable.set(self._onvalue) self._variable_callback_blocked = False - def deselect(self, from_variable_callback=False): + def deselect(self, from_variable_callback: bool = False): self._check_state = False self._draw() @@ -432,17 +432,17 @@ class CTkCheckBox(CTkBaseClass): self._variable.set(self._offvalue) self._variable_callback_blocked = False - def get(self) -> Union[int, str]: + def get(self) -> int | str: return self._onvalue if self._check_state is True else self._offvalue - def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True): + def bind(self, sequence: str, command: Callable[..., None] | None = None, add: str | bool = True): """ called on the tkinter.Canvas """ if not (add == "+" or add is True): raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks") self._canvas.bind(sequence, command, add=True) self._text_label.bind(sequence, command, add=True) - def unbind(self, sequence: str = None, funcid: str = None): + def unbind(self, sequence: str, funcid: str | None = None): """ called on the tkinter.Label and tkinter.Canvas """ if funcid is not None: raise ValueError("'funcid' argument can only be None, because there is a bug in" + diff --git a/customtkinter/windows/widgets/ctk_combobox.py b/customtkinter/windows/widgets/ctk_combobox.py index aa95337..1b25a2a 100644 --- a/customtkinter/windows/widgets/ctk_combobox.py +++ b/customtkinter/windows/widgets/ctk_combobox.py @@ -1,14 +1,19 @@ -import tkinter -import sys -import copy -from typing import Union, Tuple, Callable, List, Optional +from __future__ import annotations -from .core_widget_classes import DropdownMenu -from .core_rendering import CTkCanvas -from .theme import ThemeManager -from .core_rendering import DrawEngine -from .core_widget_classes import CTkBaseClass +import copy +import sys +import tkinter +from typing import Any, Callable + +if sys.version_info >= (3, 8): + from typing import Literal +else: + from typing_extensions import Literal + +from .core_rendering import CTkCanvas, DrawEngine +from .core_widget_classes import CTkBaseClass, DropdownMenu from .font import CTkFont +from .theme import ThemeManager class CTkComboBox(CTkBaseClass): @@ -18,32 +23,32 @@ class CTkComboBox(CTkBaseClass): """ def __init__(self, - master: any, + master: CTkBaseClass, width: int = 140, height: int = 28, - corner_radius: Optional[int] = None, - border_width: Optional[int] = None, + corner_radius: int | None = None, + border_width: int | None = None, - bg_color: Union[str, Tuple[str, str]] = "transparent", - fg_color: Optional[Union[str, Tuple[str, str]]] = None, - border_color: Optional[Union[str, Tuple[str, str]]] = None, - button_color: Optional[Union[str, Tuple[str, str]]] = None, - button_hover_color: Optional[Union[str, Tuple[str, str]]] = None, - dropdown_fg_color: Optional[Union[str, Tuple[str, str]]] = None, - dropdown_hover_color: Optional[Union[str, Tuple[str, str]]] = None, - dropdown_text_color: Optional[Union[str, Tuple[str, str]]] = None, - text_color: Optional[Union[str, Tuple[str, str]]] = None, - text_color_disabled: Optional[Union[str, Tuple[str, str]]] = None, + bg_color: str | tuple[str, str] = "transparent", + fg_color: str | tuple[str, str] | None = None, + border_color: str | tuple[str, str] | None = None, + button_color: str | tuple[str, str] | None = None, + button_hover_color: str | tuple[str, str] | None = None, + dropdown_fg_color: str | tuple[str, str] | None = None, + dropdown_hover_color: str | tuple[str, str] | None = None, + dropdown_text_color: str | tuple[str, str] | None = None, + text_color: str | tuple[str, str] | None = None, + text_color_disabled: str | tuple[str, str] | None = None, - font: Optional[Union[tuple, CTkFont]] = None, - dropdown_font: Optional[Union[tuple, CTkFont]] = None, - values: Optional[List[str]] = None, + font: tuple[Any, ...] | CTkFont | None = None, + dropdown_font: tuple[Any, ...] | CTkFont | None = None, + values: list[str] | None = None, state: str = tkinter.NORMAL, hover: bool = True, - variable: Union[tkinter.Variable, None] = None, - command: Union[Callable[[str], None], None] = None, + variable: tkinter.Variable | None = None, + command: Callable[[str], None] | None = None, justify: str = "left", - **kwargs): + **kwargs: Any): # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs) @@ -70,11 +75,7 @@ class CTkComboBox(CTkBaseClass): self._variable = variable self._state = state self._hover = hover - - if values is None: - self._values = ["CTkComboBox"] - else: - self._values = values + self._values = values or ["CTkComboBox"] self._dropdown_menu = DropdownMenu(master=self, values=self._values, @@ -116,7 +117,7 @@ class CTkComboBox(CTkBaseClass): else: self._entry.insert(0, "CTkComboBox") - def _create_bindings(self, sequence: Optional[str] = None): + def _create_bindings(self, sequence: str | None = None): """ set necessary bindings for functionality of widget, will overwrite other bindings """ if sequence is None: self._canvas.tag_bind("right_parts", "", self._on_enter) @@ -135,7 +136,7 @@ class CTkComboBox(CTkBaseClass): max(self._apply_widget_scaling(self._current_width - left_section_width + 3), self._apply_widget_scaling(3))), pady=self._apply_widget_scaling(self._border_width)) - def _set_scaling(self, *args, **kwargs): + def _set_scaling(self, *args: Any, **kwargs: Any): super()._set_scaling(*args, **kwargs) # change entry font size and grid padding @@ -146,7 +147,7 @@ class CTkComboBox(CTkBaseClass): height=self._apply_widget_scaling(self._desired_height)) self._draw(no_color_updates=True) - def _set_dimensions(self, width: int = None, height: int = None): + def _set_dimensions(self, width: int | None = None, height: int | None = None): super()._set_dimensions(width, height) self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), @@ -168,7 +169,7 @@ class CTkComboBox(CTkBaseClass): super().destroy() - def _draw(self, no_color_updates=False): + def _draw(self, no_color_updates: bool = False): super()._draw(no_color_updates) left_section_width = self._current_width - self._current_height @@ -218,7 +219,7 @@ class CTkComboBox(CTkBaseClass): self._dropdown_menu.open(self.winfo_rootx(), self.winfo_rooty() + self._apply_widget_scaling(self._current_height + 0)) - def configure(self, require_redraw=False, **kwargs): + def configure(self, require_redraw: bool = False, **kwargs: Any): if "corner_radius" in kwargs: self._corner_radius = kwargs.pop("corner_radius") require_redraw = True @@ -297,7 +298,7 @@ class CTkComboBox(CTkBaseClass): super().configure(require_redraw=require_redraw, **kwargs) - def cget(self, attribute_name: str) -> any: + def cget(self, attribute_name: str) -> Any: if attribute_name == "corner_radius": return self._corner_radius elif attribute_name == "border_width": @@ -341,7 +342,7 @@ class CTkComboBox(CTkBaseClass): else: return super().cget(attribute_name) - def _on_enter(self, event=0): + def _on_enter(self, event: tkinter.Event[Any] | None = None): if self._hover is True and self._state == tkinter.NORMAL and len(self._values) > 0: if sys.platform == "darwin" and len(self._values) > 0 and self._cursor_manipulation_enabled: self._canvas.configure(cursor="pointinghand") @@ -356,7 +357,7 @@ class CTkComboBox(CTkBaseClass): outline=self._apply_appearance_mode(self._button_hover_color), fill=self._apply_appearance_mode(self._button_hover_color)) - def _on_leave(self, event=0): + def _on_leave(self, event: tkinter.Event[Any] | None = None): if sys.platform == "darwin" and len(self._values) > 0 and self._cursor_manipulation_enabled: self._canvas.configure(cursor="arrow") elif sys.platform.startswith("win") and len(self._values) > 0 and self._cursor_manipulation_enabled: @@ -396,11 +397,11 @@ class CTkComboBox(CTkBaseClass): def get(self) -> str: return self._entry.get() - def _clicked(self, event=None): + def _clicked(self, event: tkinter.Event[Any] | None = None): if self._state is not tkinter.DISABLED and len(self._values) > 0: self._open_dropdown_menu() - def bind(self, sequence=None, command=None, add=True): + def bind(self, sequence=None, command=None, add: Literal["+", True] = True): """ called on the tkinter.Entry """ if not (add == "+" or add is True): raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks") diff --git a/customtkinter/windows/widgets/ctk_entry.py b/customtkinter/windows/widgets/ctk_entry.py index db54386..2066bed 100644 --- a/customtkinter/windows/widgets/ctk_entry.py +++ b/customtkinter/windows/widgets/ctk_entry.py @@ -1,12 +1,19 @@ -import tkinter -from typing import Union, Tuple, Optional +from __future__ import annotations -from .core_rendering import CTkCanvas -from .theme import ThemeManager -from .core_rendering import DrawEngine +import sys +import tkinter +from typing import Any + +if sys.version_info >= (3, 8): + from typing import Literal +else: + from typing_extensions import Literal + +from .core_rendering import CTkCanvas, DrawEngine from .core_widget_classes import CTkBaseClass from .font import CTkFont -from .utility import pop_from_dict_by_set, check_kwargs_empty +from .theme import ThemeManager +from .utility import check_kwargs_empty, pop_from_dict_by_set class CTkEntry(CTkBaseClass): @@ -23,23 +30,23 @@ class CTkEntry(CTkBaseClass): "show", "takefocus", "validate", "validatecommand", "xscrollcommand"} def __init__(self, - master: any, + master: CTkBaseClass, width: int = 140, height: int = 28, - corner_radius: Optional[int] = None, - border_width: Optional[int] = None, + corner_radius: int | None = None, + border_width: int | None = None, - bg_color: Union[str, Tuple[str, str]] = "transparent", - fg_color: Optional[Union[str, Tuple[str, str]]] = None, - border_color: Optional[Union[str, Tuple[str, str]]] = None, - text_color: Optional[Union[str, Tuple[str, str]]] = None, - placeholder_text_color: Optional[Union[str, Tuple[str, str]]] = None, + bg_color: str | tuple[str, str] = "transparent", + fg_color: str | tuple[str, str] | None = None, + border_color: str | tuple[str, str] | None = None, + text_color: str | tuple[str, str] | None = None, + placeholder_text_color: str | tuple[str, str] | None = None, - textvariable: Union[tkinter.Variable, None] = None, - placeholder_text: Union[str, None] = None, - font: Optional[Union[tuple, CTkFont]] = None, + textvariable: tkinter.Variable | None = None, + placeholder_text: str | None = None, + font: tuple[Any, ...] | CTkFont | None = None, state: str = tkinter.NORMAL, - **kwargs): + **kwargs: Any): # transfer basic functionality (bg_color, size, appearance_mode, scaling) to CTkBaseClass super().__init__(master=master, bg_color=bg_color, width=width, height=height) @@ -97,7 +104,7 @@ class CTkEntry(CTkBaseClass): self._create_bindings() self._draw() - def _create_bindings(self, sequence: Optional[str] = None): + def _create_bindings(self, sequence: str | None = None): """ set necessary bindings for functionality of widget, will overwrite other bindings """ if sequence is None or sequence == "": self._entry.bind("", self._entry_focus_in) @@ -116,11 +123,11 @@ class CTkEntry(CTkBaseClass): padx=self._apply_widget_scaling(self._minimum_x_padding), pady=(self._apply_widget_scaling(self._border_width), self._apply_widget_scaling(self._border_width + 1))) - def _textvariable_callback(self, var_name, index, mode): + def _textvariable_callback(self, var_name: str, index: int, mode: Any): if self._textvariable.get() == "": self._activate_placeholder() - def _set_scaling(self, *args, **kwargs): + def _set_scaling(self, *args: Any, **kwargs: Any): super()._set_scaling(*args, **kwargs) self._entry.configure(font=self._apply_font_scaling(self._font)) @@ -128,7 +135,7 @@ class CTkEntry(CTkBaseClass): self._create_grid() self._draw(no_color_updates=True) - def _set_dimensions(self, width=None, height=None): + def _set_dimensions(self, width: int | None = None, height: int | None = None): super()._set_dimensions(width, height) self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), @@ -150,7 +157,7 @@ class CTkEntry(CTkBaseClass): super().destroy() - def _draw(self, no_color_updates=False): + def _draw(self, no_color_updates: bool = False): super()._draw(no_color_updates) requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._current_width), @@ -191,7 +198,7 @@ class CTkEntry(CTkBaseClass): disabledforeground=self._apply_appearance_mode(self._text_color), insertbackground=self._apply_appearance_mode(self._text_color)) - def configure(self, require_redraw=False, **kwargs): + def configure(self, require_redraw: bool = False, **kwargs: Any): if "state" in kwargs: self._state = kwargs.pop("state") self._entry.configure(state=self._state) @@ -252,7 +259,7 @@ class CTkEntry(CTkBaseClass): self._entry.configure(**pop_from_dict_by_set(kwargs, self._valid_tk_entry_attributes)) # configure Tkinter.Entry super().configure(require_redraw=require_redraw, **kwargs) # configure CTkBaseClass - def cget(self, attribute_name: str) -> any: + def cget(self, attribute_name: str) -> Any: if attribute_name == "corner_radius": return self._corner_radius elif attribute_name == "border_width": @@ -281,7 +288,7 @@ class CTkEntry(CTkBaseClass): else: return super().cget(attribute_name) # cget of CTkBaseClass - def bind(self, sequence=None, command=None, add=True): + def bind(self, sequence=None, command=None, add: Literal["+", True] = True): """ called on the tkinter.Entry """ if not (add == "+" or add is True): raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks") @@ -316,11 +323,11 @@ class CTkEntry(CTkBaseClass): for argument, value in self._pre_placeholder_arguments.items(): self._entry[argument] = value - def _entry_focus_out(self, event=None): + def _entry_focus_out(self, event: tkinter.Event[Any] | None = None): self._activate_placeholder() self._is_focused = False - def _entry_focus_in(self, event=None): + def _entry_focus_in(self, event: tkinter.Event[Any] | None = None): self._deactivate_placeholder() self._is_focused = True @@ -330,7 +337,7 @@ class CTkEntry(CTkBaseClass): if not self._is_focused and self._entry.get() == "": self._activate_placeholder() - def insert(self, index, string): + def insert(self, index: int, string): self._deactivate_placeholder() return self._entry.insert(index, string) diff --git a/customtkinter/windows/widgets/ctk_frame.py b/customtkinter/windows/widgets/ctk_frame.py index 67bf161..9202090 100644 --- a/customtkinter/windows/widgets/ctk_frame.py +++ b/customtkinter/windows/widgets/ctk_frame.py @@ -1,9 +1,16 @@ -from typing import Union, Tuple, List, Optional +from __future__ import annotations -from .core_rendering import CTkCanvas -from .theme import ThemeManager -from .core_rendering import DrawEngine +import sys +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 .theme import ThemeManager class CTkFrame(CTkBaseClass): @@ -15,19 +22,19 @@ class CTkFrame(CTkBaseClass): """ def __init__(self, - master: any, + master: CTkBaseClass, width: int = 200, height: int = 200, - corner_radius: Optional[Union[int, str]] = None, - border_width: Optional[Union[int, str]] = None, + corner_radius: int | str | None = None, + border_width: int | str | None = None, - bg_color: Union[str, Tuple[str, str]] = "transparent", - fg_color: Optional[Union[str, Tuple[str, str]]] = None, - border_color: Optional[Union[str, Tuple[str, str]]] = None, + bg_color: str | tuple[str, str] = "transparent", + fg_color: str | tuple[str, str] | None = None, + border_color: str | tuple[str, str] | None = None, - background_corner_colors: Union[Tuple[Union[str, Tuple[str, str]]], None] = None, - overwrite_preferred_drawing_method: Union[str, None] = None, - **kwargs): + background_corner_colors: tuple[str | tuple[str, str]] | None = None, + overwrite_preferred_drawing_method: str | None = None, + **kwargs: Any): # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs) @@ -64,7 +71,7 @@ class CTkFrame(CTkBaseClass): self._draw(no_color_updates=True) - def winfo_children(self) -> List[any]: + def winfo_children(self) -> list[Any]: """ winfo_children of CTkFrame without self.canvas widget, because it's not a child but part of the CTkFrame itself @@ -77,21 +84,21 @@ class CTkFrame(CTkBaseClass): except ValueError: return child_widgets - def _set_scaling(self, *args, **kwargs): + def _set_scaling(self, *args: Any, **kwargs: Any): super()._set_scaling(*args, **kwargs) self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), height=self._apply_widget_scaling(self._desired_height)) self._draw() - def _set_dimensions(self, width=None, height=None): + def _set_dimensions(self, width: int | None = None, height: int | None = None): super()._set_dimensions(width, height) self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), height=self._apply_widget_scaling(self._desired_height)) self._draw() - def _draw(self, no_color_updates=False): + def _draw(self, no_color_updates: bool = False): super()._draw(no_color_updates) if not self._canvas.winfo_exists(): @@ -131,7 +138,7 @@ class CTkFrame(CTkBaseClass): # self._canvas.tag_lower("inner_parts") # maybe unnecessary, I don't know ??? # self._canvas.tag_lower("border_parts") - def configure(self, require_redraw=False, **kwargs): + def configure(self, require_redraw: bool = False, **kwargs: Any): if "fg_color" in kwargs: self._fg_color = self._check_color_type(kwargs.pop("fg_color"), transparency=True) require_redraw = True @@ -166,7 +173,7 @@ class CTkFrame(CTkBaseClass): super().configure(require_redraw=require_redraw, **kwargs) - def cget(self, attribute_name: str) -> any: + def cget(self, attribute_name: str) -> Any: if attribute_name == "corner_radius": return self._corner_radius elif attribute_name == "border_width": @@ -182,7 +189,7 @@ class CTkFrame(CTkBaseClass): else: return super().cget(attribute_name) - def bind(self, sequence=None, command=None, add=True): + def bind(self, sequence=None, command=None, add: Literal["+", True] = True): """ called on the tkinter.Canvas """ if not (add == "+" or add is True): raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks") diff --git a/customtkinter/windows/widgets/ctk_label.py b/customtkinter/windows/widgets/ctk_label.py index 800c58d..433adc9 100644 --- a/customtkinter/windows/widgets/ctk_label.py +++ b/customtkinter/windows/widgets/ctk_label.py @@ -1,13 +1,14 @@ -import tkinter -from typing import Union, Tuple, Callable, Optional +from __future__ import annotations -from .core_rendering import CTkCanvas -from .theme import ThemeManager -from .core_rendering import DrawEngine +import tkinter +from typing import Any, Callable + +from .core_rendering import CTkCanvas, DrawEngine from .core_widget_classes import CTkBaseClass from .font import CTkFont from .image import CTkImage -from .utility import pop_from_dict_by_set, check_kwargs_empty +from .theme import ThemeManager +from .utility import check_kwargs_empty, pop_from_dict_by_set class CTkLabel(CTkBaseClass): @@ -21,22 +22,22 @@ class CTkLabel(CTkBaseClass): "textvariable", "state", "takefocus", "underline"} def __init__(self, - master: any, + master: CTkBaseClass, width: int = 0, height: int = 28, - corner_radius: Optional[int] = None, + corner_radius: int | None = None, - bg_color: Union[str, Tuple[str, str]] = "transparent", - fg_color: Optional[Union[str, Tuple[str, str]]] = None, - text_color: Optional[Union[str, Tuple[str, str]]] = None, + bg_color: str | tuple[str, str] = "transparent", + fg_color: str | tuple[str, str] | None = None, + text_color: str | tuple[str, str] | None = None, text: str = "CTkLabel", - font: Optional[Union[tuple, CTkFont]] = None, - image: Union[CTkImage, None] = None, + font: tuple[Any, ...] | CTkFont | None = None, + image: CTkImage | None = None, compound: str = "center", anchor: str = "center", # label anchor: center, n, e, s, w wraplength: int = 0, - **kwargs): + **kwargs: Any): # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass super().__init__(master=master, bg_color=bg_color, width=width, height=height) @@ -93,7 +94,7 @@ class CTkLabel(CTkBaseClass): self._update_image() self._draw() - def _set_scaling(self, *args, **kwargs): + def _set_scaling(self, *args: Any, **kwargs: Any): super()._set_scaling(*args, **kwargs) self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), height=self._apply_widget_scaling(self._desired_height)) @@ -108,7 +109,7 @@ class CTkLabel(CTkBaseClass): super()._set_appearance_mode(mode_string) self._update_image() - def _set_dimensions(self, width=None, height=None): + def _set_dimensions(self, width: int | None = None, height: int | None = None): super()._set_dimensions(width, height) self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), @@ -144,7 +145,7 @@ class CTkLabel(CTkBaseClass): self._label.grid(row=0, column=0, sticky=text_label_grid_sticky, padx=self._apply_widget_scaling(min(self._corner_radius, round(self._current_height / 2)))) - def _draw(self, no_color_updates=False): + def _draw(self, no_color_updates: bool = False): super()._draw(no_color_updates) requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._current_width), @@ -170,7 +171,7 @@ class CTkLabel(CTkBaseClass): self._canvas.configure(bg=self._apply_appearance_mode(self._bg_color)) - def configure(self, require_redraw=False, **kwargs): + def configure(self, require_redraw: bool = False, **kwargs: Any): if "corner_radius" in kwargs: self._corner_radius = kwargs.pop("corner_radius") self._create_grid() @@ -220,7 +221,7 @@ class CTkLabel(CTkBaseClass): self._label.configure(**pop_from_dict_by_set(kwargs, self._valid_tk_label_attributes)) # configure tkinter.Label super().configure(require_redraw=require_redraw, **kwargs) # configure CTkBaseClass - def cget(self, attribute_name: str) -> any: + def cget(self, attribute_name: str) -> Any: if attribute_name == "corner_radius": return self._corner_radius @@ -247,14 +248,14 @@ class CTkLabel(CTkBaseClass): else: return super().cget(attribute_name) # cget of CTkBaseClass - def bind(self, sequence: str = None, command: Callable = None, add: str = True): + def bind(self, sequence: str, command: Callable[..., None] | None = None, add: str = True): """ called on the tkinter.Label and tkinter.Canvas """ if not (add == "+" or add is True): raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks") self._canvas.bind(sequence, command, add=True) self._label.bind(sequence, command, add=True) - def unbind(self, sequence: str = None, funcid: Optional[str] = None): + def unbind(self, sequence: str, funcid: str | None = None): """ called on the tkinter.Label and tkinter.Canvas """ if funcid is not None: raise ValueError("'funcid' argument can only be None, because there is a bug in" + diff --git a/customtkinter/windows/widgets/ctk_optionmenu.py b/customtkinter/windows/widgets/ctk_optionmenu.py index e9fa96c..2cb6ebc 100644 --- a/customtkinter/windows/widgets/ctk_optionmenu.py +++ b/customtkinter/windows/widgets/ctk_optionmenu.py @@ -1,14 +1,14 @@ -import tkinter +from __future__ import annotations + import copy import sys -from typing import Union, Tuple, Callable, Optional +import tkinter +from typing import Any, Callable -from .core_rendering import CTkCanvas -from .theme import ThemeManager -from .core_rendering import DrawEngine -from .core_widget_classes import CTkBaseClass -from .core_widget_classes import DropdownMenu +from .core_rendering import CTkCanvas, DrawEngine +from .core_widget_classes import CTkBaseClass, DropdownMenu from .font import CTkFont +from .theme import ThemeManager class CTkOptionMenu(CTkBaseClass): @@ -18,31 +18,31 @@ class CTkOptionMenu(CTkBaseClass): """ def __init__(self, - master: any, + master: CTkBaseClass, width: int = 140, height: int = 28, - corner_radius: Optional[Union[int]] = None, + corner_radius: int | None = None, - bg_color: Union[str, Tuple[str, str]] = "transparent", - fg_color: Optional[Union[str, Tuple[str, str]]] = None, - button_color: Optional[Union[str, Tuple[str, str]]] = None, - button_hover_color: Optional[Union[str, Tuple[str, str]]] = None, - text_color: Optional[Union[str, Tuple[str, str]]] = None, - text_color_disabled: Optional[Union[str, Tuple[str, str]]] = None, - dropdown_fg_color: Optional[Union[str, Tuple[str, str]]] = None, - dropdown_hover_color: Optional[Union[str, Tuple[str, str]]] = None, - dropdown_text_color: Optional[Union[str, Tuple[str, str]]] = None, + bg_color: str | tuple[str, str] = "transparent", + fg_color: str | tuple[str, str] | None = None, + button_color: str | tuple[str, str] | None = None, + button_hover_color: str | tuple[str, str] | None = None, + text_color: str | tuple[str, str] | None = None, + text_color_disabled: str | tuple[str, str] | None = None, + dropdown_fg_color: str | tuple[str, str] | None = None, + dropdown_hover_color: str | tuple[str, str] | None = None, + dropdown_text_color: str | tuple[str, str] | None = None, - font: Optional[Union[tuple, CTkFont]] = None, - dropdown_font: Optional[Union[tuple, CTkFont]] = None, - values: Optional[list] = None, - variable: Union[tkinter.Variable, None] = None, + font: tuple[Any, ...] | CTkFont | None = None, + dropdown_font: tuple[Any, ...] | CTkFont | None = None, + values: list | None = None, + variable: tkinter.Variable | None = None, state: str = tkinter.NORMAL, hover: bool = True, - command: Union[Callable[[str], None], None] = None, + command: Callable[[str], None] | None = None, dynamic_resizing: bool = True, anchor: str = "w", - **kwargs): + **kwargs: Any): # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs) @@ -68,7 +68,7 @@ class CTkOptionMenu(CTkBaseClass): self._command = command self._variable = variable self._variable_callback_blocked: bool = False - self._variable_callback_name: Union[str, None] = None + self._variable_callback_name: str | None = None self._state = state self._hover = hover self._dynamic_resizing = dynamic_resizing @@ -127,7 +127,7 @@ class CTkOptionMenu(CTkBaseClass): self._current_value = self._variable.get() self._text_label.configure(text=self._current_value) - def _create_bindings(self, sequence: Optional[str] = None): + def _create_bindings(self, sequence: str | None = None): """ set necessary bindings for functionality of widget, will overwrite other bindings """ if sequence is None or sequence == "": self._canvas.bind("", self._on_enter) @@ -147,7 +147,7 @@ class CTkOptionMenu(CTkBaseClass): padx=(max(self._apply_widget_scaling(self._corner_radius), self._apply_widget_scaling(3)), max(self._apply_widget_scaling(self._current_width - left_section_width + 3), self._apply_widget_scaling(3)))) - def _set_scaling(self, *args, **kwargs): + def _set_scaling(self, *args: Any, **kwargs: Any): super()._set_scaling(*args, **kwargs) # change label font size and grid padding @@ -157,7 +157,7 @@ class CTkOptionMenu(CTkBaseClass): self._create_grid() self._draw(no_color_updates=True) - def _set_dimensions(self, width: int = None, height: int = None): + def _set_dimensions(self, width: int | None = None, height: int | None = None): super()._set_dimensions(width, height) self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), @@ -182,7 +182,7 @@ class CTkOptionMenu(CTkBaseClass): super().destroy() - def _draw(self, no_color_updates=False): + def _draw(self, no_color_updates: bool = False): super()._draw(no_color_updates) left_section_width = self._current_width - self._current_height @@ -221,7 +221,7 @@ class CTkOptionMenu(CTkBaseClass): self._canvas.update_idletasks() - def configure(self, require_redraw=False, **kwargs): + def configure(self, require_redraw: bool = False, **kwargs: Any): if "corner_radius" in kwargs: self._corner_radius = kwargs.pop("corner_radius") self._create_grid() @@ -303,7 +303,7 @@ class CTkOptionMenu(CTkBaseClass): super().configure(require_redraw=require_redraw, **kwargs) - def cget(self, attribute_name: str) -> any: + def cget(self, attribute_name: str) -> Any: if attribute_name == "corner_radius": return self._corner_radius @@ -350,20 +350,20 @@ class CTkOptionMenu(CTkBaseClass): self._dropdown_menu.open(self.winfo_rootx(), self.winfo_rooty() + self._apply_widget_scaling(self._current_height + 0)) - def _on_enter(self, event=0): + def _on_enter(self, event: tkinter.Event[Any] | None = None): if self._hover is True and self._state == tkinter.NORMAL and len(self._values) > 0: # set color of inner button parts to hover color self._canvas.itemconfig("inner_parts_right", outline=self._apply_appearance_mode(self._button_hover_color), fill=self._apply_appearance_mode(self._button_hover_color)) - def _on_leave(self, event=0): + def _on_leave(self, event: tkinter.Event[Any] | None = None): # set color of inner button parts self._canvas.itemconfig("inner_parts_right", outline=self._apply_appearance_mode(self._button_color), fill=self._apply_appearance_mode(self._button_color)) - def _variable_callback(self, var_name, index, mode): + def _variable_callback(self, var_name: str, index: int, mode: Any): if not self._variable_callback_blocked: self._current_value = self._variable.get() self._text_label.configure(text=self._current_value) @@ -392,18 +392,18 @@ class CTkOptionMenu(CTkBaseClass): def get(self) -> str: return self._current_value - def _clicked(self, event=0): + def _clicked(self, event: tkinter.Event[Any] | None = None): if self._state is not tkinter.DISABLED and len(self._values) > 0: self._open_dropdown_menu() - def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True): + def bind(self, sequence: str, command: Callable[..., None] | None = None, add: str | bool = True): """ called on the tkinter.Canvas """ if not (add == "+" or add is True): raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks") self._canvas.bind(sequence, command, add=True) self._text_label.bind(sequence, command, add=True) - def unbind(self, sequence: str = None, funcid: str = None): + def unbind(self, sequence: str, funcid: str | None = None): """ called on the tkinter.Label and tkinter.Canvas """ if funcid is not None: raise ValueError("'funcid' argument can only be None, because there is a bug in" + diff --git a/customtkinter/windows/widgets/ctk_progressbar.py b/customtkinter/windows/widgets/ctk_progressbar.py index 084354d..134710a 100644 --- a/customtkinter/windows/widgets/ctk_progressbar.py +++ b/customtkinter/windows/widgets/ctk_progressbar.py @@ -1,15 +1,17 @@ -import tkinter +from __future__ import annotations + import math -from typing import Union, Tuple, Optional, Callable +import tkinter +from typing import Any, Callable + try: from typing import Literal except ImportError: from typing_extensions import Literal -from .core_rendering import CTkCanvas -from .theme import ThemeManager -from .core_rendering import DrawEngine +from .core_rendering import CTkCanvas, DrawEngine from .core_widget_classes import CTkBaseClass +from .theme import ThemeManager class CTkProgressBar(CTkBaseClass): @@ -20,23 +22,23 @@ class CTkProgressBar(CTkBaseClass): """ def __init__(self, - master: any, - width: Optional[int] = None, - height: Optional[int] = None, - corner_radius: Optional[int] = None, - border_width: Optional[int] = None, + master: CTkBaseClass, + width: int | None = None, + height: int | None = None, + corner_radius: int | None = None, + border_width: int | None = None, - bg_color: Union[str, Tuple[str, str]] = "transparent", - fg_color: Optional[Union[str, Tuple[str, str]]] = None, - border_color: Optional[Union[str, Tuple[str, str]]] = None, - progress_color: Optional[Union[str, Tuple[str, str]]] = None, + bg_color: str | tuple[str, str] = "transparent", + fg_color: str | tuple[str, str] | None = None, + border_color: str | tuple[str, str] | None = None, + progress_color: str | tuple[str, str] | None = None, - variable: Union[tkinter.Variable, None] = None, + variable: tkinter.Variable | None = None, orientation: str = "horizontal", mode: Literal["determinate", "indeterminate"] = "determinate", determinate_speed: float = 1, indeterminate_speed: float = 1, - **kwargs): + **kwargs: Any): # set default dimensions according to orientation if width is None: @@ -94,14 +96,14 @@ class CTkProgressBar(CTkBaseClass): self.set(self._variable.get(), from_variable_callback=True) self._variable_callback_blocked = False - def _set_scaling(self, *args, **kwargs): + def _set_scaling(self, *args: Any, **kwargs: Any): super()._set_scaling(*args, **kwargs) self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), height=self._apply_widget_scaling(self._desired_height)) self._draw(no_color_updates=True) - def _set_dimensions(self, width=None, height=None): + def _set_dimensions(self, width: int | None = None, height: int | None = None): super()._set_dimensions(width, height) self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), @@ -114,7 +116,7 @@ class CTkProgressBar(CTkBaseClass): super().destroy() - def _draw(self, no_color_updates=False): + def _draw(self, no_color_updates: bool = False): super()._draw(no_color_updates) if self._orientation.lower() == "horizontal": @@ -157,7 +159,7 @@ class CTkProgressBar(CTkBaseClass): fill=self._apply_appearance_mode(self._progress_color), outline=self._apply_appearance_mode(self._progress_color)) - def configure(self, require_redraw=False, **kwargs): + def configure(self, require_redraw: bool = False, **kwargs: Any): if "corner_radius" in kwargs: self._corner_radius = kwargs.pop("corner_radius") require_redraw = True @@ -202,7 +204,7 @@ class CTkProgressBar(CTkBaseClass): super().configure(require_redraw=require_redraw, **kwargs) - def cget(self, attribute_name: str) -> any: + def cget(self, attribute_name: str) -> Any: if attribute_name == "corner_radius": return self._corner_radius elif attribute_name == "border_width": @@ -229,11 +231,11 @@ class CTkProgressBar(CTkBaseClass): else: return super().cget(attribute_name) - def _variable_callback(self, var_name, index, mode): + def _variable_callback(self, var_name: str, index: int, mode: Any): if not self._variable_callback_blocked: self.set(self._variable.get(), from_variable_callback=True) - def set(self, value, from_variable_callback=False): + def set(self, value, from_variable_callback: bool = False): """ set determinate value """ self._determinate_value = value @@ -289,13 +291,13 @@ class CTkProgressBar(CTkBaseClass): self._indeterminate_value += self._indeterminate_speed self._draw() - def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True): + def bind(self, sequence: str, command: Callable[..., None] | None = None, add: str | bool = True): """ called on the tkinter.Canvas """ if not (add == "+" or add is True): raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks") self._canvas.bind(sequence, command, add=True) - def unbind(self, sequence: str = None, funcid: str = None): + def unbind(self, sequence: str, funcid: str | None = None): """ called on the tkinter.Label and tkinter.Canvas """ if funcid is not None: raise ValueError("'funcid' argument can only be None, because there is a bug in" + diff --git a/customtkinter/windows/widgets/ctk_radiobutton.py b/customtkinter/windows/widgets/ctk_radiobutton.py index d797be9..0e10172 100644 --- a/customtkinter/windows/widgets/ctk_radiobutton.py +++ b/customtkinter/windows/widgets/ctk_radiobutton.py @@ -1,12 +1,13 @@ -import tkinter -import sys -from typing import Union, Tuple, Callable, Optional +from __future__ import annotations -from .core_rendering import CTkCanvas -from .theme import ThemeManager -from .core_rendering import DrawEngine +import sys +import tkinter +from typing import Any, Callable + +from .core_rendering import CTkCanvas, DrawEngine from .core_widget_classes import CTkBaseClass from .font import CTkFont +from .theme import ThemeManager class CTkRadioButton(CTkBaseClass): @@ -16,31 +17,31 @@ class CTkRadioButton(CTkBaseClass): """ def __init__(self, - master: any, + master: CTkBaseClass, width: int = 100, height: int = 22, radiobutton_width: int = 22, radiobutton_height: int = 22, - corner_radius: Optional[int] = None, - border_width_unchecked: Optional[int] = None, - border_width_checked: Optional[int] = None, + corner_radius: int | None = None, + border_width_unchecked: int | None = None, + border_width_checked: int | None = None, - bg_color: Union[str, Tuple[str, str]] = "transparent", - fg_color: Optional[Union[str, Tuple[str, str]]] = None, - hover_color: Optional[Union[str, Tuple[str, str]]] = None, - border_color: Optional[Union[str, Tuple[str, str]]] = None, - text_color: Optional[Union[str, Tuple[str, str]]] = None, - text_color_disabled: Optional[Union[str, Tuple[str, str]]] = None, + bg_color: str | tuple[str, str] = "transparent", + fg_color: str | tuple[str, str] | None = None, + hover_color: str | tuple[str, str] | None = None, + border_color: str | tuple[str, str] | None = None, + text_color: str | tuple[str, str] | None = None, + text_color_disabled: str | tuple[str, str] | None = None, text: str = "CTkRadioButton", - font: Optional[Union[tuple, CTkFont]] = None, - textvariable: Union[tkinter.Variable, None] = None, - variable: Union[tkinter.Variable, None] = None, - value: Union[int, str] = 0, + font: tuple[Any, ...] | CTkFont | None = None, + textvariable: tkinter.Variable | None = None, + variable: tkinter.Variable | None = None, + value: int | str = 0, state: str = tkinter.NORMAL, hover: bool = True, - command: Union[Callable, None] = None, - **kwargs): + command: Callable[..., None] | None = None, + **kwargs: Any): # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs) @@ -61,7 +62,7 @@ class CTkRadioButton(CTkBaseClass): # text self._text = text - self._text_label: Union[tkinter.Label, None] = None + self._text_label: tkinter.Label | None = None self._text_color = ThemeManager.theme["CTkRadiobutton"]["text_color"] if text_color is None else self._check_color_type(text_color) self._text_color_disabled = ThemeManager.theme["CTkRadiobutton"]["text_color_disabled"] if text_color_disabled is None else self._check_color_type(text_color_disabled) @@ -79,7 +80,7 @@ class CTkRadioButton(CTkBaseClass): self._variable: tkinter.Variable = variable self._variable_callback_blocked: bool = False self._textvariable = textvariable - self._variable_callback_name: Union[str, None] = None + self._variable_callback_name: str | None = None # configure grid system (3x1) self.grid_columnconfigure(0, weight=0) @@ -119,7 +120,7 @@ class CTkRadioButton(CTkBaseClass): self._set_cursor() self._draw() - def _create_bindings(self, sequence: Optional[str] = None): + def _create_bindings(self, sequence: str | None = None): """ set necessary bindings for functionality of widget, will overwrite other bindings """ if sequence is None or sequence == "": self._canvas.bind("", self._on_enter) @@ -131,7 +132,7 @@ class CTkRadioButton(CTkBaseClass): self._canvas.bind("", self.invoke) self._text_label.bind("", self.invoke) - def _set_scaling(self, *args, **kwargs): + def _set_scaling(self, *args: Any, **kwargs: Any): super()._set_scaling(*args, **kwargs) self.grid_columnconfigure(1, weight=0, minsize=self._apply_widget_scaling(6)) @@ -143,7 +144,7 @@ class CTkRadioButton(CTkBaseClass): height=self._apply_widget_scaling(self._radiobutton_height)) self._draw(no_color_updates=True) - def _set_dimensions(self, width: int = None, height: int = None): + def _set_dimensions(self, width: int | None = None, height: int | None = None): super()._set_dimensions(width, height) self._bg_canvas.configure(width=self._apply_widget_scaling(self._desired_width), @@ -167,7 +168,7 @@ class CTkRadioButton(CTkBaseClass): super().destroy() - def _draw(self, no_color_updates=False): + def _draw(self, no_color_updates: bool = False): super()._draw(no_color_updates) if self._check_state is True: @@ -205,7 +206,7 @@ class CTkRadioButton(CTkBaseClass): self._text_label.configure(bg=self._apply_appearance_mode(self._bg_color)) - def configure(self, require_redraw=False, **kwargs): + def configure(self, require_redraw: bool = False, **kwargs: Any): if "corner_radius" in kwargs: self._corner_radius = kwargs.pop("corner_radius") require_redraw = True @@ -289,7 +290,7 @@ class CTkRadioButton(CTkBaseClass): super().configure(require_redraw=require_redraw, **kwargs) - def cget(self, attribute_name: str) -> any: + def cget(self, attribute_name: str) -> Any: if attribute_name == "corner_radius": return self._corner_radius elif attribute_name == "border_width_unchecked": @@ -354,13 +355,13 @@ class CTkRadioButton(CTkBaseClass): if self._text_label is not None: self._text_label.configure(cursor="hand2") - def _on_enter(self, event=0): + def _on_enter(self, event: tkinter.Event[Any] | None = None): if self._hover is True and self._state == tkinter.NORMAL: self._canvas.itemconfig("border_parts", fill=self._apply_appearance_mode(self._hover_color), outline=self._apply_appearance_mode(self._hover_color)) - def _on_leave(self, event=0): + def _on_leave(self, event: tkinter.Event[Any] | None = None): if self._check_state is True: self._canvas.itemconfig("border_parts", fill=self._apply_appearance_mode(self._fg_color), @@ -370,14 +371,14 @@ class CTkRadioButton(CTkBaseClass): fill=self._apply_appearance_mode(self._border_color), outline=self._apply_appearance_mode(self._border_color)) - def _variable_callback(self, var_name, index, mode): + def _variable_callback(self, var_name: str, index: int, mode: Any): if not self._variable_callback_blocked: if self._variable.get() == self._value: self.select(from_variable_callback=True) else: self.deselect(from_variable_callback=True) - def invoke(self, event=0): + def invoke(self, event: tkinter.Event[Any] | None = None): if self._state == tkinter.NORMAL: if self._check_state is False: self._check_state = True @@ -386,7 +387,7 @@ class CTkRadioButton(CTkBaseClass): if self._command is not None: self._command() - def select(self, from_variable_callback=False): + def select(self, from_variable_callback: bool = False): self._check_state = True self._draw() @@ -395,7 +396,7 @@ class CTkRadioButton(CTkBaseClass): self._variable.set(self._value) self._variable_callback_blocked = False - def deselect(self, from_variable_callback=False): + def deselect(self, from_variable_callback: bool = False): self._check_state = False self._draw() @@ -404,14 +405,14 @@ class CTkRadioButton(CTkBaseClass): self._variable.set("") self._variable_callback_blocked = False - def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True): + def bind(self, sequence: str, command: Callable[..., None] | None = None, add: str | bool = True): """ called on the tkinter.Canvas """ if not (add == "+" or add is True): raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks") self._canvas.bind(sequence, command, add=True) self._text_label.bind(sequence, command, add=True) - def unbind(self, sequence: str = None, funcid: str = None): + def unbind(self, sequence: str, funcid: str | None = None): """ called on the tkinter.Label and tkinter.Canvas """ if funcid is not None: raise ValueError("'funcid' argument can only be None, because there is a bug in" + diff --git a/customtkinter/windows/widgets/ctk_scrollable_frame.py b/customtkinter/windows/widgets/ctk_scrollable_frame.py index 685deb2..7291bdc 100644 --- a/customtkinter/windows/widgets/ctk_scrollable_frame.py +++ b/customtkinter/windows/widgets/ctk_scrollable_frame.py @@ -1,40 +1,43 @@ -from typing import Union, Tuple, Optional +from __future__ import annotations + +import sys +import tkinter +from typing import Any + try: from typing import Literal except ImportError: from typing_extensions import Literal -import tkinter -import sys -from .ctk_frame import CTkFrame -from .ctk_scrollbar import CTkScrollbar from .appearance_mode import CTkAppearanceModeBaseClass -from .scaling import CTkScalingBaseClass from .core_widget_classes import CTkBaseClass +from .ctk_frame import CTkFrame from .ctk_label import CTkLabel +from .ctk_scrollbar import CTkScrollbar from .font import CTkFont +from .scaling import CTkScalingBaseClass from .theme import ThemeManager class CTkScrollableFrame(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClass): def __init__(self, - master: any, + master: CTkBaseClass, width: int = 200, height: int = 200, - corner_radius: Optional[Union[int, str]] = None, - border_width: Optional[Union[int, str]] = None, + corner_radius: int | str | None = None, + border_width: int | str | None = None, - bg_color: Union[str, Tuple[str, str]] = "transparent", - fg_color: Optional[Union[str, Tuple[str, str]]] = None, - border_color: Optional[Union[str, Tuple[str, str]]] = None, - scrollbar_fg_color: Optional[Union[str, Tuple[str, str]]] = None, - scrollbar_button_color: Optional[Union[str, Tuple[str, str]]] = None, - scrollbar_button_hover_color: Optional[Union[str, Tuple[str, str]]] = None, - label_fg_color: Optional[Union[str, Tuple[str, str]]] = None, - label_text_color: Optional[Union[str, Tuple[str, str]]] = None, + bg_color: str | tuple[str, str] = "transparent", + fg_color: str | tuple[str, str] | None = None, + border_color: str | tuple[str, str] | None = None, + scrollbar_fg_color: str | tuple[str, str] | None = None, + scrollbar_button_color: str | tuple[str, str] | None = None, + scrollbar_button_hover_color: str | tuple[str, str] | None = None, + label_fg_color: str | tuple[str, str] | None = None, + label_text_color: str | tuple[str, str] | None = None, label_text: str = "", - label_font: Optional[Union[tuple, CTkFont]] = None, + label_font: tuple[Any, ...] | CTkFont | None = None, label_anchor: str = "center", orientation: Literal["vertical", "horizontal"] = "vertical"): @@ -120,7 +123,7 @@ class CTkScrollableFrame(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBa else: self._label.grid_forget() - def _set_appearance_mode(self, mode_string): + def _set_appearance_mode(self, mode_string: Literal["light", "dark"]): super()._set_appearance_mode(mode_string) if self._parent_frame.cget("fg_color") == "transparent": @@ -130,13 +133,13 @@ class CTkScrollableFrame(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBa tkinter.Frame.configure(self, bg=self._apply_appearance_mode(self._parent_frame.cget("fg_color"))) self._parent_canvas.configure(bg=self._apply_appearance_mode(self._parent_frame.cget("fg_color"))) - def _set_scaling(self, new_widget_scaling, new_window_scaling): + def _set_scaling(self, new_widget_scaling: float, new_window_scaling: float): super()._set_scaling(new_widget_scaling, new_window_scaling) self._parent_canvas.configure(width=self._apply_widget_scaling(self._desired_width), height=self._apply_widget_scaling(self._desired_height)) - def _set_dimensions(self, width=None, height=None): + def _set_dimensions(self, width: int | None = None, height: int | None = None): if width is not None: self._desired_width = width if height is not None: @@ -145,7 +148,7 @@ class CTkScrollableFrame(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBa self._parent_canvas.configure(width=self._apply_widget_scaling(self._desired_width), height=self._apply_widget_scaling(self._desired_height)) - def configure(self, **kwargs): + def configure(self, **kwargs: Any): if "width" in kwargs: self._set_dimensions(width=kwargs.pop("width")) @@ -232,7 +235,7 @@ class CTkScrollableFrame(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBa else: return self._parent_frame.cget(attribute_name) - def _fit_frame_dimensions_to_canvas(self, event): + def _fit_frame_dimensions_to_canvas(self, event: tkinter.Event[Any] | None = None): if self._orientation == "horizontal": self._parent_canvas.itemconfigure(self._create_window_id, height=self._parent_canvas.winfo_height()) elif self._orientation == "vertical": @@ -244,7 +247,7 @@ class CTkScrollableFrame(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBa elif sys.platform == "darwin": self._parent_canvas.configure(xscrollincrement=4, yscrollincrement=8) - def _mouse_wheel_all(self, event): + def _mouse_wheel_all(self, event: tkinter.Event[Any] | None = None): if self.check_if_master_is_canvas(event.widget): if sys.platform.startswith("win"): if self._shift_pressed: @@ -268,10 +271,10 @@ class CTkScrollableFrame(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBa if self._parent_canvas.yview() != (0.0, 1.0): self._parent_canvas.yview("scroll", -event.delta, "units") - def _keyboard_shift_press_all(self, event): + def _keyboard_shift_press_all(self, event: tkinter.Event[Any] | None = None): self._shift_pressed = True - def _keyboard_shift_release_all(self, event): + def _keyboard_shift_release_all(self, event: tkinter.Event[Any] | None = None): self._shift_pressed = False def check_if_master_is_canvas(self, widget): @@ -282,35 +285,35 @@ class CTkScrollableFrame(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBa else: return False - def pack(self, **kwargs): + def pack(self, **kwargs: Any): self._parent_frame.pack(**kwargs) - def place(self, **kwargs): + def place(self, **kwargs: Any): self._parent_frame.place(**kwargs) - def grid(self, **kwargs): + def grid(self, **kwargs: Any): self._parent_frame.grid(**kwargs) def pack_forget(self): self._parent_frame.pack_forget() - def place_forget(self, **kwargs): + def place_forget(self, **kwargs: Any): self._parent_frame.place_forget() - def grid_forget(self, **kwargs): + def grid_forget(self, **kwargs: Any): self._parent_frame.grid_forget() - def grid_remove(self, **kwargs): + def grid_remove(self, **kwargs: Any): self._parent_frame.grid_remove() - def grid_propagate(self, **kwargs): + def grid_propagate(self, **kwargs: Any): self._parent_frame.grid_propagate() - def grid_info(self, **kwargs): + def grid_info(self, **kwargs: Any): return self._parent_frame.grid_info() - def lift(self, aboveThis=None): + def lift(self, aboveThis: Any =None): self._parent_frame.lift(aboveThis) - def lower(self, belowThis=None): + def lower(self, belowThis: Any = None): self._parent_frame.lower(belowThis) diff --git a/customtkinter/windows/widgets/ctk_scrollbar.py b/customtkinter/windows/widgets/ctk_scrollbar.py index 1038282..0a93b14 100644 --- a/customtkinter/windows/widgets/ctk_scrollbar.py +++ b/customtkinter/windows/widgets/ctk_scrollbar.py @@ -1,10 +1,17 @@ -import sys -from typing import Union, Tuple, Callable, Optional +from __future__ import annotations -from .core_rendering import CTkCanvas -from .theme import ThemeManager -from .core_rendering import DrawEngine +import sys +import tkinter +from typing import Any, Callable + +if sys.version_info >= (3, 8): + from typing import Literal +else: + from typing_extensions import Literal + +from .core_rendering import CTkCanvas, DrawEngine from .core_widget_classes import CTkBaseClass +from .theme import ThemeManager class CTkScrollbar(CTkBaseClass): @@ -15,22 +22,22 @@ class CTkScrollbar(CTkBaseClass): """ def __init__(self, - master: any, - width: Optional[Union[int, str]] = None, - height: Optional[Union[int, str]] = None, - corner_radius: Optional[int] = None, - border_spacing: Optional[int] = None, + master: CTkBaseClass, + width: int | str | None = None, + height: int | str | None = None, + corner_radius: int | None = None, + border_spacing: int | None = None, minimum_pixel_length: int = 20, - bg_color: Union[str, Tuple[str, str]] = "transparent", - fg_color: Optional[Union[str, Tuple[str, str]]] = None, - button_color: Optional[Union[str, Tuple[str, str]]] = None, - button_hover_color: Optional[Union[str, Tuple[str, str]]] = None, + bg_color: str | tuple[str, str] = "transparent", + fg_color: str | tuple[str, str] | None = None, + button_color: str | tuple[str, str] | None = None, + button_hover_color: str | tuple[str, str] | None = None, hover: bool = True, - command: Union[Callable, None] = None, + command: Callable[..., None] | None = None, orientation: str = "vertical", - **kwargs): + **kwargs: Any): # set default dimensions according to orientation if width is None: @@ -74,7 +81,7 @@ class CTkScrollbar(CTkBaseClass): self._create_bindings() self._draw() - def _create_bindings(self, sequence: Optional[str] = None): + def _create_bindings(self, sequence: str | None = None): """ set necessary bindings for functionality of widget, will overwrite other bindings """ if sequence is None: self._canvas.tag_bind("border_parts", "", self._clicked) @@ -87,14 +94,14 @@ class CTkScrollbar(CTkBaseClass): if sequence is None or sequence == "": self._canvas.bind("", self._mouse_scroll_event) - def _set_scaling(self, *args, **kwargs): + def _set_scaling(self, *args: Any, **kwargs: Any): super()._set_scaling(*args, **kwargs) self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), height=self._apply_widget_scaling(self._desired_height)) self._draw(no_color_updates=True) - def _set_dimensions(self, width=None, height=None): + def _set_dimensions(self, width: int | None = None, height: int | None = None): super()._set_dimensions(width, height) self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), @@ -125,7 +132,7 @@ class CTkScrollbar(CTkBaseClass): else: return self._start_value, self._end_value - def _draw(self, no_color_updates=False): + def _draw(self, no_color_updates: bool = False): super()._draw(no_color_updates) corrected_start_value, corrected_end_value = self._get_scrollbar_values_for_minimum_pixel_size() @@ -160,7 +167,7 @@ class CTkScrollbar(CTkBaseClass): self._canvas.update_idletasks() - def configure(self, require_redraw=False, **kwargs): + def configure(self, require_redraw: bool = False, **kwargs: Any): if "fg_color" in kwargs: self._fg_color = self._check_color_type(kwargs.pop("fg_color"), transparency=True) require_redraw = True @@ -189,7 +196,7 @@ class CTkScrollbar(CTkBaseClass): super().configure(require_redraw=require_redraw, **kwargs) - def cget(self, attribute_name: str) -> any: + def cget(self, attribute_name: str) -> Any: if attribute_name == "corner_radius": return self._corner_radius elif attribute_name == "border_spacing": @@ -214,24 +221,24 @@ class CTkScrollbar(CTkBaseClass): else: return super().cget(attribute_name) - def _on_enter(self, event=0): + def _on_enter(self, event: tkinter.Event[Any] | None = None): if self._hover is True: self._hover_state = True self._canvas.itemconfig("scrollbar_parts", outline=self._apply_appearance_mode(self._button_hover_color), fill=self._apply_appearance_mode(self._button_hover_color)) - def _on_leave(self, event=0): + def _on_leave(self, event: tkinter.Event[Any] | None = None): self._hover_state = False self._canvas.itemconfig("scrollbar_parts", outline=self._apply_appearance_mode(self._button_color), fill=self._apply_appearance_mode(self._button_color)) - def _clicked(self, event): + def _clicked(self, event: tkinter.Event[Any] | None = None): if self._orientation == "vertical": - value = self._reverse_widget_scaling(((event.y - self._border_spacing) / (self._current_height - 2 * self._border_spacing))) + value = self._reverse_widget_scaling((event.y - self._border_spacing) / (self._current_height - 2 * self._border_spacing)) else: - value = self._reverse_widget_scaling(((event.x - self._border_spacing) / (self._current_width - 2 * self._border_spacing))) + value = self._reverse_widget_scaling((event.x - self._border_spacing) / (self._current_width - 2 * self._border_spacing)) current_scrollbar_length = self._end_value - self._start_value value = max(current_scrollbar_length / 2, min(value, 1 - (current_scrollbar_length / 2))) @@ -242,7 +249,7 @@ class CTkScrollbar(CTkBaseClass): if self._command is not None: self._command('moveto', self._start_value) - def _mouse_scroll_event(self, event=None): + def _mouse_scroll_event(self, event: tkinter.Event[Any] | None = None): if self._command is not None: if sys.platform.startswith("win"): self._command('scroll', -int(event.delta/40), 'units') @@ -257,7 +264,7 @@ class CTkScrollbar(CTkBaseClass): def get(self): return self._start_value, self._end_value - def bind(self, sequence=None, command=None, add=True): + def bind(self, sequence=None, command: Callable[..., None] | None = None, add: Literal["+", True] = True): """ called on the tkinter.Canvas """ if not (add == "+" or add is True): raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks") diff --git a/customtkinter/windows/widgets/ctk_segmented_button.py b/customtkinter/windows/widgets/ctk_segmented_button.py index 5ea46ca..b749495 100644 --- a/customtkinter/windows/widgets/ctk_segmented_button.py +++ b/customtkinter/windows/widgets/ctk_segmented_button.py @@ -1,15 +1,13 @@ -import tkinter -import copy -from typing import Union, Tuple, List, Dict, Callable, Optional -try: - from typing import Literal -except ImportError: - from typing_extensions import Literal +from __future__ import annotations + +import copy +import tkinter +from typing import Any, Callable -from .theme import ThemeManager -from .font import CTkFont from .ctk_button import CTkButton from .ctk_frame import CTkFrame +from .font import CTkFont +from .theme import ThemeManager class CTkSegmentedButton(CTkFrame): @@ -19,29 +17,29 @@ class CTkSegmentedButton(CTkFrame): """ def __init__(self, - master: any, + master: CTkBaseClass, width: int = 140, height: int = 28, - corner_radius: Optional[int] = None, + corner_radius: int | None = None, border_width: int = 3, - bg_color: Union[str, Tuple[str, str]] = "transparent", - fg_color: Optional[Union[str, Tuple[str, str]]] = None, - selected_color: Optional[Union[str, Tuple[str, str]]] = None, - selected_hover_color: Optional[Union[str, Tuple[str, str]]] = None, - unselected_color: Optional[Union[str, Tuple[str, str]]] = None, - unselected_hover_color: Optional[Union[str, Tuple[str, str]]] = None, - text_color: Optional[Union[str, Tuple[str, str]]] = None, - text_color_disabled: Optional[Union[str, Tuple[str, str]]] = None, - background_corner_colors: Union[Tuple[Union[str, Tuple[str, str]]], None] = None, + bg_color: str | tuple[str, str] = "transparent", + fg_color: str | tuple[str, str] | None = None, + selected_color: str | tuple[str, str] | None = None, + selected_hover_color: str | tuple[str, str] | None = None, + unselected_color: str | tuple[str, str] | None = None, + unselected_hover_color: str | tuple[str, str] | None = None, + text_color: str | tuple[str, str] | None = None, + text_color_disabled: str | tuple[str, str] | None = None, + background_corner_colors: tuple[str | tuple[str, str]] | None = None, - font: Optional[Union[tuple, CTkFont]] = None, - values: Optional[list] = None, - variable: Union[tkinter.Variable, None] = None, + font: tuple[Any, ...] | CTkFont | None = None, + values: list[str] | None = None, + variable: tkinter.Variable | None = None, dynamic_resizing: bool = True, - command: Union[Callable[[str], None], None] = None, + command: Callable[[str], None] | None = None, state: str = "normal", - **kwargs): + **kwargs: Any): super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs) @@ -61,15 +59,15 @@ class CTkSegmentedButton(CTkFrame): self._background_corner_colors = background_corner_colors # rendering options for DrawEngine - self._command: Callable[[str], None] = command + self._command = command self._font = CTkFont() if font is None else font self._state = state - self._buttons_dict: Dict[str, CTkButton] = {} # mapped from value to button object + self._buttons_dict: dict[str, CTkButton] = {} # mapped from value to button object if values is None: - self._value_list: List[str] = ["CTkSegmentedButton"] + self._value_list: list[str] = ["CTkSegmentedButton"] else: - self._value_list: List[str] = values # Values ordered like buttons rendered on widget + self._value_list: list[str] = values # Values ordered like buttons rendered on widget self._dynamic_resizing = dynamic_resizing if not self._dynamic_resizing: @@ -83,7 +81,7 @@ class CTkSegmentedButton(CTkFrame): self._variable = variable self._variable_callback_blocked: bool = False - self._variable_callback_name: Union[str, None] = None + self._variable_callback_name: str | None = None if self._variable is not None: self._variable_callback_name = self._variable.trace_add("write", self._variable_callback) @@ -97,13 +95,13 @@ class CTkSegmentedButton(CTkFrame): super().destroy() - def _set_dimensions(self, width: int = None, height: int = None): + def _set_dimensions(self, width: int | None = None, height: int | None = None): super()._set_dimensions(width, height) for button in self._buttons_dict.values(): button.configure(height=height) - def _variable_callback(self, var_name, index, mode): + def _variable_callback(self, var_name: str, index: int, mode: Any): if not self._variable_callback_blocked: self.set(self._variable.get(), from_variable_callback=True) @@ -172,7 +170,7 @@ class CTkSegmentedButton(CTkFrame): return new_button @staticmethod - def _check_unique_values(values: List[str]): + def _check_unique_values(values: list[str]): """ raises exception if values are not unique """ if len(values) != len(set(values)): raise ValueError("CTkSegmentedButton values are not unique") @@ -196,7 +194,7 @@ class CTkSegmentedButton(CTkFrame): self._buttons_dict[value] = self._create_button(index, value) self._configure_button_corners_for_index(index) - def configure(self, **kwargs): + def configure(self, **kwargs: Any): if "bg_color" in kwargs: super().configure(bg_color=kwargs.pop("bg_color")) @@ -298,7 +296,7 @@ class CTkSegmentedButton(CTkFrame): super().configure(**kwargs) - def cget(self, attribute_name: str) -> any: + def cget(self, attribute_name: str) -> Any: if attribute_name == "corner_radius": return self._sb_corner_radius elif attribute_name == "border_width": @@ -413,9 +411,8 @@ class CTkSegmentedButton(CTkFrame): else: raise ValueError(f"CTkSegmentedButton does not contain value '{value}'") - def bind(self, sequence=None, command=None, add=None): + def bind(self, sequence: Any =None, command: Any =None, add: Any = None): raise NotImplementedError - def unbind(self, sequence=None, funcid=None): + def unbind(self, sequence: Any = None, funcid: Any = None): raise NotImplementedError - diff --git a/customtkinter/windows/widgets/ctk_slider.py b/customtkinter/windows/widgets/ctk_slider.py index 3016a5c..20c5459 100644 --- a/customtkinter/windows/widgets/ctk_slider.py +++ b/customtkinter/windows/widgets/ctk_slider.py @@ -1,11 +1,12 @@ -import tkinter -import sys -from typing import Union, Tuple, Callable, Optional +from __future__ import annotations -from .core_rendering import CTkCanvas -from .theme import ThemeManager -from .core_rendering import DrawEngine +import sys +import tkinter +from typing import Any, Callable + +from .core_rendering import CTkCanvas, DrawEngine from .core_widget_classes import CTkBaseClass +from .theme import ThemeManager class CTkSlider(CTkBaseClass): @@ -15,30 +16,30 @@ class CTkSlider(CTkBaseClass): """ def __init__(self, - master: any, - width: Optional[int] = None, - height: Optional[int] = None, - corner_radius: Optional[int] = None, - button_corner_radius: Optional[int] = None, - border_width: Optional[int] = None, - button_length: Optional[int] = None, + master: CTkBaseClass, + width: int | None = None, + height: int | None = None, + corner_radius: int | None = None, + button_corner_radius: int | None = None, + border_width: int | None = None, + button_length: int | None = None, - bg_color: Union[str, Tuple[str, str]] = "transparent", - fg_color: Optional[Union[str, Tuple[str, str]]] = None, - border_color: Union[str, Tuple[str, str]] = "transparent", - progress_color: Optional[Union[str, Tuple[str, str]]] = None, - button_color: Optional[Union[str, Tuple[str, str]]] = None, - button_hover_color: Optional[Union[str, Tuple[str, str]]] = None, + bg_color: str | tuple[str, str] = "transparent", + fg_color: str | tuple[str, str] | None = None, + border_color: str | tuple[str, str] = "transparent", + progress_color: str | tuple[str, str] | None = None, + button_color: str | tuple[str, str] | None = None, + button_hover_color: str | tuple[str, str] | None = None, from_: int = 0, to: int = 1, state: str = "normal", - number_of_steps: Union[int, None] = None, + number_of_steps: int | None = None, hover: bool = True, - command: Union[Callable[[float], None], None] = None, - variable: Union[tkinter.Variable, None] = None, + command: Callable[[float], None] | None = None, + variable: tkinter.Variable | None = None, orientation: str = "horizontal", - **kwargs): + **kwargs: Any): # set default dimensions according to orientation if width is None: @@ -81,9 +82,9 @@ class CTkSlider(CTkBaseClass): # callback and control variables self._command = command - self._variable: tkinter.Variable = variable - self._variable_callback_blocked: bool = False - self._variable_callback_name: Union[bool, None] = None + self._variable = variable + self._variable_callback_blocked = False + self._variable_callback_name: bool | None = None self._state = state self.grid_rowconfigure(0, weight=1) @@ -106,7 +107,7 @@ class CTkSlider(CTkBaseClass): self.set(self._variable.get(), from_variable_callback=True) self._variable_callback_blocked = False - def _create_bindings(self, sequence: Optional[str] = None): + def _create_bindings(self, sequence: str | None = None): """ set necessary bindings for functionality of widget, will overwrite other bindings """ if sequence is None or sequence == "": self._canvas.bind("", self._on_enter) @@ -117,14 +118,14 @@ class CTkSlider(CTkBaseClass): if sequence is None or sequence == "": self._canvas.bind("", self._clicked) - def _set_scaling(self, *args, **kwargs): + def _set_scaling(self, *args: Any, **kwargs: Any): super()._set_scaling(*args, **kwargs) self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), height=self._apply_widget_scaling(self._desired_height)) self._draw(no_color_updates=True) - def _set_dimensions(self, width=None, height=None): + def _set_dimensions(self, width: int | None = None, height: int | None = None): super()._set_dimensions(width, height) self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), @@ -151,7 +152,7 @@ class CTkSlider(CTkBaseClass): elif sys.platform.startswith("win"): self.configure(cursor="arrow") - def _draw(self, no_color_updates=False): + def _draw(self, no_color_updates: bool = False): super()._draw(no_color_updates) if self._orientation.lower() == "horizontal": @@ -198,7 +199,7 @@ class CTkSlider(CTkBaseClass): fill=self._apply_appearance_mode(self._button_color), outline=self._apply_appearance_mode(self._button_color)) - def configure(self, require_redraw=False, **kwargs): + def configure(self, require_redraw: bool = False, **kwargs: Any): if "state" in kwargs: self._state = kwargs.pop("state") self._set_cursor() @@ -257,7 +258,7 @@ class CTkSlider(CTkBaseClass): super().configure(require_redraw=require_redraw, **kwargs) - def cget(self, attribute_name: str) -> any: + def cget(self, attribute_name: str) -> Any: if attribute_name == "corner_radius": return self._corner_radius elif attribute_name == "button_corner_radius": @@ -298,7 +299,7 @@ class CTkSlider(CTkBaseClass): else: return super().cget(attribute_name) - def _clicked(self, event=None): + def _clicked(self, event: tkinter.Event[Any] | None = None): if self._state == "normal": if self._orientation.lower() == "horizontal": self._value = self._reverse_widget_scaling(event.x / self._current_width) @@ -323,14 +324,14 @@ class CTkSlider(CTkBaseClass): if self._command is not None: self._command(self._output_value) - def _on_enter(self, event=0): + def _on_enter(self, event: tkinter.Event[Any] | None = None): if self._hover is True and self._state == "normal": self._hover_state = True self._canvas.itemconfig("slider_parts", fill=self._apply_appearance_mode(self._button_hover_color), outline=self._apply_appearance_mode(self._button_hover_color)) - def _on_leave(self, event=0): + def _on_leave(self, event: tkinter.Event[Any] | None = None): self._hover_state = False self._canvas.itemconfig("slider_parts", fill=self._apply_appearance_mode(self._button_color), @@ -347,7 +348,7 @@ class CTkSlider(CTkBaseClass): def get(self) -> float: return self._output_value - def set(self, output_value, from_variable_callback=False): + def set(self, output_value, from_variable_callback: bool = False): if self._from_ < self._to: if output_value > self._to: output_value = self._to @@ -369,17 +370,17 @@ class CTkSlider(CTkBaseClass): self._variable.set(round(self._output_value) if isinstance(self._variable, tkinter.IntVar) else self._output_value) self._variable_callback_blocked = False - def _variable_callback(self, var_name, index, mode): + def _variable_callback(self, var_name: str, index: int, mode: Any): if not self._variable_callback_blocked: self.set(self._variable.get(), from_variable_callback=True) - def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True): + def bind(self, sequence: str, command: Callable[..., None] | None = None, add: str | bool = True): """ called on the tkinter.Canvas """ if not (add == "+" or add is True): raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks") self._canvas.bind(sequence, command, add=True) - def unbind(self, sequence: str = None, funcid: str = None): + def unbind(self, sequence: str, funcid: str | None = None): """ called on the tkinter.Label and tkinter.Canvas """ if funcid is not None: raise ValueError("'funcid' argument can only be None, because there is a bug in" + diff --git a/customtkinter/windows/widgets/ctk_switch.py b/customtkinter/windows/widgets/ctk_switch.py index 6e8de13..49f7398 100644 --- a/customtkinter/windows/widgets/ctk_switch.py +++ b/customtkinter/windows/widgets/ctk_switch.py @@ -1,12 +1,13 @@ -import tkinter -import sys -from typing import Union, Tuple, Callable, Optional +from __future__ import annotations -from .core_rendering import CTkCanvas -from .theme import ThemeManager -from .core_rendering import DrawEngine +import sys +import tkinter +from typing import Any, Callable + +from .core_rendering import CTkCanvas, DrawEngine from .core_widget_classes import CTkBaseClass from .font import CTkFont +from .theme import ThemeManager class CTkSwitch(CTkBaseClass): @@ -16,34 +17,34 @@ class CTkSwitch(CTkBaseClass): """ def __init__(self, - master: any, + master: CTkBaseClass, width: int = 100, height: int = 24, switch_width: int = 36, switch_height: int = 18, - corner_radius: Optional[int] = None, - border_width: Optional[int] = None, - button_length: Optional[int] = None, + corner_radius: int | None = None, + border_width: int | None = None, + button_length: int | None = None, - bg_color: Union[str, Tuple[str, str]] = "transparent", - fg_color: Optional[Union[str, Tuple[str, str]]] = None, - border_color: Union[str, Tuple[str, str]] = "transparent", - progress_color: Optional[Union[str, Tuple[str, str]]] = None, - button_color: Optional[Union[str, Tuple[str, str]]] = None, - button_hover_color: Optional[Union[str, Tuple[str, str]]] = None, - text_color: Optional[Union[str, Tuple[str, str]]] = None, - text_color_disabled: Optional[Union[str, Tuple[str, str]]] = None, + bg_color: str | tuple[str, str] = "transparent", + fg_color: str | tuple[str, str] | None = None, + border_color: str | tuple[str, str] = "transparent", + progress_color: str | tuple[str, str] | None = None, + button_color: str | tuple[str, str] | None = None, + button_hover_color: str | tuple[str, str] | None = None, + text_color: str | tuple[str, str] | None = None, + text_color_disabled: str | tuple[str, str] | None = None, text: str = "CTkSwitch", - font: Optional[Union[tuple, CTkFont]] = None, - textvariable: Union[tkinter.Variable, None] = None, - onvalue: Union[int, str] = 1, - offvalue: Union[int, str] = 0, - variable: Union[tkinter.Variable, None] = None, + font: tuple[Any, ...] | CTkFont | None = None, + textvariable: tkinter.Variable | None = None, + onvalue: int | str = 1, + offvalue: int | str = 0, + variable: tkinter.Variable | None = None, hover: bool = True, - command: Union[Callable, None] = None, + command: Callable[..., None] | None = None, state: str = tkinter.NORMAL, - **kwargs): + **kwargs: Any): # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs) @@ -126,7 +127,7 @@ class CTkSwitch(CTkBaseClass): self._set_cursor() self._draw() # initial draw - def _create_bindings(self, sequence: Optional[str] = None): + def _create_bindings(self, sequence: str | None = None): """ set necessary bindings for functionality of widget, will overwrite other bindings """ if sequence is None or sequence == "": self._canvas.bind("", self._on_enter) @@ -138,7 +139,7 @@ class CTkSwitch(CTkBaseClass): self._canvas.bind("", self.toggle) self._text_label.bind("", self.toggle) - def _set_scaling(self, *args, **kwargs): + def _set_scaling(self, *args: Any, **kwargs: Any): super()._set_scaling(*args, **kwargs) self.grid_columnconfigure(1, weight=0, minsize=self._apply_widget_scaling(6)) @@ -150,7 +151,7 @@ class CTkSwitch(CTkBaseClass): height=self._apply_widget_scaling(self._switch_height)) self._draw(no_color_updates=True) - def _set_dimensions(self, width: int = None, height: int = None): + def _set_dimensions(self, width: int | None = None, height: int | None = None): super()._set_dimensions(width, height) self._bg_canvas.configure(width=self._apply_widget_scaling(self._desired_width), @@ -197,7 +198,7 @@ class CTkSwitch(CTkBaseClass): if self._text_label is not None: self._text_label.configure(cursor="hand2") - def _draw(self, no_color_updates=False): + def _draw(self, no_color_updates: bool = False): super()._draw(no_color_updates) if self._check_state is True: @@ -254,7 +255,7 @@ class CTkSwitch(CTkBaseClass): self._text_label.configure(bg=self._apply_appearance_mode(self._bg_color)) - def configure(self, require_redraw=False, **kwargs): + def configure(self, require_redraw: bool = False, **kwargs: Any): if "corner_radius" in kwargs: self._corner_radius = kwargs.pop("corner_radius") require_redraw = True @@ -338,7 +339,7 @@ class CTkSwitch(CTkBaseClass): super().configure(require_redraw=require_redraw, **kwargs) - def cget(self, attribute_name: str) -> any: + def cget(self, attribute_name: str) -> Any: if attribute_name == "corner_radius": return self._corner_radius elif attribute_name == "border_width": @@ -387,7 +388,7 @@ class CTkSwitch(CTkBaseClass): else: return super().cget(attribute_name) - def toggle(self, event=None): + def toggle(self, event: tkinter.Event[Any] | None = None): if self._state is not tkinter.DISABLED: if self._check_state is True: self._check_state = False @@ -404,7 +405,7 @@ class CTkSwitch(CTkBaseClass): if self._command is not None: self._command() - def select(self, from_variable_callback=False): + def select(self, from_variable_callback: bool = False): if self._state is not tkinter.DISABLED or from_variable_callback: self._check_state = True @@ -415,7 +416,7 @@ class CTkSwitch(CTkBaseClass): self._variable.set(self._onvalue) self._variable_callback_blocked = False - def deselect(self, from_variable_callback=False): + def deselect(self, from_variable_callback: bool = False): if self._state is not tkinter.DISABLED or from_variable_callback: self._check_state = False @@ -426,37 +427,37 @@ class CTkSwitch(CTkBaseClass): self._variable.set(self._offvalue) self._variable_callback_blocked = False - def get(self) -> Union[int, str]: + def get(self) -> int | str: return self._onvalue if self._check_state is True else self._offvalue - def _on_enter(self, event=0): + def _on_enter(self, event: tkinter.Event[Any] | None = None): if self._hover is True and self._state == "normal": self._hover_state = True self._canvas.itemconfig("slider_parts", fill=self._apply_appearance_mode(self._button_hover_color), outline=self._apply_appearance_mode(self._button_hover_color)) - def _on_leave(self, event=0): + def _on_leave(self, event: tkinter.Event[Any] | None = None): self._hover_state = False self._canvas.itemconfig("slider_parts", fill=self._apply_appearance_mode(self._button_color), outline=self._apply_appearance_mode(self._button_color)) - def _variable_callback(self, var_name, index, mode): + def _variable_callback(self, var_name: str, index: int, mode: Any): if not self._variable_callback_blocked: if self._variable.get() == self._onvalue: self.select(from_variable_callback=True) elif self._variable.get() == self._offvalue: self.deselect(from_variable_callback=True) - def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True): + def bind(self, sequence: str, command: Callable[..., None] | None = None, add: str | bool = True): """ called on the tkinter.Canvas """ if not (add == "+" or add is True): raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks") self._canvas.bind(sequence, command, add=True) self._text_label.bind(sequence, command, add=True) - def unbind(self, sequence: str = None, funcid: str = None): + def unbind(self, sequence: str, funcid: str | None = None): """ called on the tkinter.Label and tkinter.Canvas """ if funcid is not None: raise ValueError("'funcid' argument can only be None, because there is a bug in" + diff --git a/customtkinter/windows/widgets/ctk_tabview.py b/customtkinter/windows/widgets/ctk_tabview.py index 75bde4b..822074b 100644 --- a/customtkinter/windows/widgets/ctk_tabview.py +++ b/customtkinter/windows/widgets/ctk_tabview.py @@ -1,12 +1,13 @@ -import tkinter -from typing import Union, Tuple, Dict, List, Callable, Optional +from __future__ import annotations -from .theme import ThemeManager -from .ctk_frame import CTkFrame -from .core_rendering import CTkCanvas -from .core_rendering import DrawEngine +import tkinter +from typing import Any, Callable + +from .core_rendering import CTkCanvas, DrawEngine from .core_widget_classes import CTkBaseClass +from .ctk_frame import CTkFrame from .ctk_segmented_button import CTkSegmentedButton +from .theme import ThemeManager class CTkTabview(CTkBaseClass): @@ -21,28 +22,28 @@ class CTkTabview(CTkBaseClass): _segmented_button_border_width: int = 3 def __init__(self, - master: any, + master: CTkBaseClass, width: int = 300, height: int = 250, - corner_radius: Optional[int] = None, - border_width: Optional[int] = None, + corner_radius: int | None = None, + border_width: int | None = None, - bg_color: Union[str, Tuple[str, str]] = "transparent", - fg_color: Optional[Union[str, Tuple[str, str]]] = None, - border_color: Optional[Union[str, Tuple[str, str]]] = None, + bg_color: str | tuple[str, str] = "transparent", + fg_color: str | tuple[str, str] | None = None, + border_color: str | tuple[str, str] | None = None, - segmented_button_fg_color: Optional[Union[str, Tuple[str, str]]] = None, - segmented_button_selected_color: Optional[Union[str, Tuple[str, str]]] = None, - segmented_button_selected_hover_color: Optional[Union[str, Tuple[str, str]]] = None, - segmented_button_unselected_color: Optional[Union[str, Tuple[str, str]]] = None, - segmented_button_unselected_hover_color: Optional[Union[str, Tuple[str, str]]] = None, + segmented_button_fg_color: str | tuple[str, str] | None = None, + segmented_button_selected_color: str | tuple[str, str] | None = None, + segmented_button_selected_hover_color: str | tuple[str, str] | None = None, + segmented_button_unselected_color: str | tuple[str, str] | None = None, + segmented_button_unselected_hover_color: str | tuple[str, str] | None = None, - text_color: Optional[Union[str, Tuple[str, str]]] = None, - text_color_disabled: Optional[Union[str, Tuple[str, str]]] = None, + text_color: str | tuple[str, str] | None = None, + text_color_disabled: str | tuple[str, str] | None = None, - command: Union[Callable, None] = None, + command: Callable[..., None] | None = None, state: str = "normal", - **kwargs): + **kwargs: Any): # transfer some functionality to CTkFrame super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs) @@ -91,14 +92,14 @@ class CTkTabview(CTkBaseClass): self._configure_grid() self._set_grid_canvas() - self._tab_dict: Dict[str, CTkFrame] = {} - self._name_list: List[str] = [] # list of unique tab names in order of tabs + self._tab_dict: dict[str, CTkFrame] = {} + self._name_list: list[str] = [] # list of unique tab names in order of tabs self._current_name: str = "" self._command = command self._draw() - def _segmented_button_callback(self, selected_name): + def _segmented_button_callback(self, selected_name: str): self._current_name = selected_name self._grid_forget_all_tabs() self._set_grid_tab_by_name(self._current_name) @@ -106,7 +107,7 @@ class CTkTabview(CTkBaseClass): if self._command is not None: self._command() - def winfo_children(self) -> List[any]: + def winfo_children(self) -> list[Any]: """ winfo_children of CTkTabview without canvas and segmented button widgets, because it's not a child but part of the CTkTabview itself @@ -120,7 +121,7 @@ class CTkTabview(CTkBaseClass): except ValueError: return child_widgets - def _set_scaling(self, *args, **kwargs): + def _set_scaling(self, *args: Any, **kwargs: Any): super()._set_scaling(*args, **kwargs) self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), @@ -128,7 +129,7 @@ class CTkTabview(CTkBaseClass): self._configure_grid() self._draw(no_color_updates=True) - def _set_dimensions(self, width=None, height=None): + def _set_dimensions(self, width: int | None = None, height: int | None = None): super()._set_dimensions(width, height) self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), @@ -211,7 +212,7 @@ class CTkTabview(CTkBaseClass): self._canvas.configure(bg=self._apply_appearance_mode(self._bg_color)) tkinter.Frame.configure(self, bg=self._apply_appearance_mode(self._bg_color)) # configure bg color of tkinter.Frame, cuase canvas does not fill frame - def configure(self, require_redraw=False, **kwargs): + def configure(self, require_redraw: bool = False, **kwargs: Any): if "corner_radius" in kwargs: self._corner_radius = kwargs.pop("corner_radius") require_redraw = True diff --git a/customtkinter/windows/widgets/ctk_textbox.py b/customtkinter/windows/widgets/ctk_textbox.py index eeee9ef..36035bd 100644 --- a/customtkinter/windows/widgets/ctk_textbox.py +++ b/customtkinter/windows/widgets/ctk_textbox.py @@ -1,13 +1,20 @@ -import tkinter -from typing import Union, Tuple, Optional, Callable +from __future__ import annotations -from .core_rendering import CTkCanvas -from .ctk_scrollbar import CTkScrollbar -from .theme import ThemeManager -from .core_rendering import DrawEngine +import sys +import tkinter +from typing import Any, Callable + +if sys.version_info >= (3, 8): + from typing import Literal +else: + from typing_extensions import Literal + +from .core_rendering import CTkCanvas, DrawEngine from .core_widget_classes import CTkBaseClass +from .ctk_scrollbar import CTkScrollbar from .font import CTkFont -from .utility import pop_from_dict_by_set, check_kwargs_empty +from .theme import ThemeManager +from .utility import check_kwargs_empty, pop_from_dict_by_set class CTkTextbox(CTkBaseClass): @@ -32,23 +39,23 @@ class CTkTextbox(CTkBaseClass): "xscrollcommand", "yscrollcommand"} def __init__(self, - master: any, + master: CTkBaseClass, width: int = 200, height: int = 200, - corner_radius: Optional[int] = None, - border_width: Optional[int] = None, + corner_radius: int | None = None, + border_width: int | None = None, border_spacing: int = 3, - bg_color: Union[str, Tuple[str, str]] = "transparent", - fg_color: Optional[Union[str, Tuple[str, str]]] = None, - border_color: Optional[Union[str, Tuple[str, str]]] = None, - text_color: Optional[Union[str, str]] = None, - scrollbar_button_color: Optional[Union[str, Tuple[str, str]]] = None, - scrollbar_button_hover_color: Optional[Union[str, Tuple[str, str]]] = None, + bg_color: str | tuple[str, str] = "transparent", + fg_color: str | tuple[str, str] | None = None, + border_color: str | tuple[str, str] | None = None, + text_color: str | str | None = None, + scrollbar_button_color: str | tuple[str, str] | None = None, + scrollbar_button_hover_color: str | tuple[str, str] | None = None, - font: Optional[Union[tuple, CTkFont]] = None, + font: tuple[Any, ...] | CTkFont | None = None, activate_scrollbars: bool = True, - **kwargs): + **kwargs: Any): # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass super().__init__(master=master, bg_color=bg_color, width=width, height=height) @@ -176,7 +183,7 @@ class CTkTextbox(CTkBaseClass): if self._textbox.winfo_exists() and continue_loop is True: self.after(self._scrollbar_update_time, lambda: self._check_if_scrollbars_needed(continue_loop=True)) - def _set_scaling(self, *args, **kwargs): + def _set_scaling(self, *args: Any, **kwargs: Any): super()._set_scaling(*args, **kwargs) self._textbox.configure(font=self._apply_font_scaling(self._font)) @@ -185,7 +192,7 @@ class CTkTextbox(CTkBaseClass): self._create_grid_for_text_and_scrollbars(re_grid_textbox=True, re_grid_x_scrollbar=True, re_grid_y_scrollbar=True) self._draw(no_color_updates=True) - def _set_dimensions(self, width=None, height=None): + def _set_dimensions(self, width: int | None = None, height: int | None = None): super()._set_dimensions(width, height) self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), @@ -207,7 +214,7 @@ class CTkTextbox(CTkBaseClass): super().destroy() - def _draw(self, no_color_updates=False): + def _draw(self, no_color_updates: bool = False): super()._draw(no_color_updates) if not self._canvas.winfo_exists(): @@ -250,7 +257,7 @@ class CTkTextbox(CTkBaseClass): self._canvas.tag_lower("inner_parts") self._canvas.tag_lower("border_parts") - def configure(self, require_redraw=False, **kwargs): + def configure(self, require_redraw: bool = False, **kwargs: Any): if "fg_color" in kwargs: self._fg_color = self._check_color_type(kwargs.pop("fg_color"), transparency=True) require_redraw = True @@ -305,7 +312,7 @@ class CTkTextbox(CTkBaseClass): self._textbox.configure(**pop_from_dict_by_set(kwargs, self._valid_tk_text_attributes)) super().configure(require_redraw=require_redraw, **kwargs) - def cget(self, attribute_name: str) -> any: + def cget(self, attribute_name: str) -> Any: if attribute_name == "corner_radius": return self._corner_radius elif attribute_name == "border_width": @@ -326,13 +333,13 @@ class CTkTextbox(CTkBaseClass): else: return super().cget(attribute_name) - def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True): + def bind(self, sequence: str, command: Callable[..., None] | None = None, add: str | bool = True): """ called on the tkinter.Canvas """ if not (add == "+" or add is True): raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks") self._textbox.bind(sequence, command, add=True) - def unbind(self, sequence: str = None, funcid: str = None): + def unbind(self, sequence: str, funcid: str | None = None): """ called on the tkinter.Label and tkinter.Canvas """ if funcid is not None: raise ValueError("'funcid' argument can only be None, because there is a bug in" + @@ -348,7 +355,7 @@ class CTkTextbox(CTkBaseClass): def focus_force(self): return self._textbox.focus_force() - def insert(self, index, text, tags=None): + def insert(self, index: int, text, tags=None): return self._textbox.insert(index, text, tags) def get(self, index1, index2=None): @@ -357,7 +364,7 @@ class CTkTextbox(CTkBaseClass): def bbox(self, index): return self._textbox.bbox(index) - def compare(self, index, op, index2): + def compare(self, index: int, op, index2): return self._textbox.compare(index, op, index2) def delete(self, index1, index2=None): @@ -383,10 +390,10 @@ class CTkTextbox(CTkBaseClass): self._check_if_scrollbars_needed() return self._textbox.edit_undo() - def image_create(self, index, **kwargs): + def image_create(self, index: int, **kwargs: Any): raise AttributeError("embedding images is forbidden, because would be incompatible with scaling") - def image_cget(self, index, option): + def image_cget(self, index: int, option): raise AttributeError("embedding images is forbidden, because would be incompatible with scaling") def image_configure(self, index): @@ -422,7 +429,7 @@ class CTkTextbox(CTkBaseClass): def scan_mark(self, x, y): return self._textbox.scan_mark(x, y) - def search(self, pattern, index, *args, **kwargs): + def search(self, pattern, index: int, *args: Any, **kwargs: Any): return self._textbox.search(pattern, index, *args, **kwargs) def see(self, index): @@ -437,57 +444,57 @@ class CTkTextbox(CTkBaseClass): def tag_cget(self, tagName, option): return self._textbox.tag_cget(tagName, option) - def tag_config(self, tagName, **kwargs): + def tag_config(self, tagName, **kwargs: Any): if "font" in kwargs: raise AttributeError("'font' option forbidden, because would be incompatible with scaling") return self._textbox.tag_config(tagName, **kwargs) - def tag_delete(self, *tagName): + def tag_delete(self, *tagName: str): return self._textbox.tag_delete(*tagName) - def tag_lower(self, tagName, belowThis=None): + def tag_lower(self, tagName: str, belowThis=None): return self._textbox.tag_lower(tagName, belowThis) def tag_names(self, index=None): return self._textbox.tag_names(index) - def tag_nextrange(self, tagName, index1, index2=None): + def tag_nextrange(self, tagName: str, index1, index2=None): return self._textbox.tag_nextrange(tagName, index1, index2) - def tag_prevrange(self, tagName, index1, index2=None): + def tag_prevrange(self, tagName: str, index1, index2=None): return self._textbox.tag_prevrange(tagName, index1, index2) - def tag_raise(self, tagName, aboveThis=None): + def tag_raise(self, tagName: str, aboveThis: str | None = None): return self._textbox.tag_raise(tagName, aboveThis) - def tag_ranges(self, tagName): + def tag_ranges(self, tagName: str): return self._textbox.tag_ranges(tagName) - def tag_remove(self, tagName, index1, index2=None): + def tag_remove(self, tagName: str, index1, index2=None): return self._textbox.tag_remove(tagName, index1, index2) - def tag_unbind(self, tagName, sequence, funcid=None): + def tag_unbind(self, tagName: str, sequence, funcid=None): return self._textbox.tag_unbind(tagName, sequence, funcid) - def window_cget(self, index, option): + def window_cget(self, index: int, option): 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): raise AttributeError("embedding widgets is forbidden, would probably cause all kinds of problems ;)") - def window_create(self, index, **kwargs): + def window_create(self, index: int, **kwargs: Any): raise AttributeError("embedding widgets is forbidden, would probably cause all kinds of problems ;)") def window_names(self): raise AttributeError("embedding widgets is forbidden, would probably cause all kinds of problems ;)") - def xview(self, *args): + def xview(self, *args: Any): return self._textbox.xview(*args) - def xview_moveto(self, fraction): + def xview_moveto(self, fraction: float): return self._textbox.xview_moveto(fraction) - def xview_scroll(self, n, what): + def xview_scroll(self, n: int, what: Literal["units", "pages"]): return self._textbox.xview_scroll(n, what) def yview(self, *args): diff --git a/customtkinter/windows/widgets/font/__init__.py b/customtkinter/windows/widgets/font/__init__.py index 64a49f1..f3dd9fd 100644 --- a/customtkinter/windows/widgets/font/__init__.py +++ b/customtkinter/windows/widgets/font/__init__.py @@ -1,11 +1,10 @@ import os import sys -from .ctk_font import CTkFont -from .font_manager import FontManager - # import DrawEngine to set preferred_drawing_method if loading shapes font fails from ..core_rendering import DrawEngine +from .ctk_font import CTkFont +from .font_manager import FontManager FontManager.init_font_manager() diff --git a/customtkinter/windows/widgets/font/ctk_font.py b/customtkinter/windows/widgets/font/ctk_font.py index 551b3a6..63c43bc 100644 --- a/customtkinter/windows/widgets/font/ctk_font.py +++ b/customtkinter/windows/widgets/font/ctk_font.py @@ -1,6 +1,9 @@ -from tkinter.font import Font +from __future__ import annotations + import copy -from typing import List, Callable, Tuple, Optional +from tkinter.font import Font +from typing import Any, Callable + try: from typing import Literal except ImportError: @@ -25,14 +28,14 @@ class CTkFont(Font): """ def __init__(self, - family: Optional[str] = None, - size: Optional[int] = None, - weight: Literal["normal", "bold"] = None, + family: str | None = None, + size: int | None = None, + weight: Literal["normal", "bold"] | None = None, slant: Literal["italic", "roman"] = "roman", underline: bool = False, overstrike: bool = False): - self._size_configure_callback_list: List[Callable] = [] + self._size_configure_callback_list: list[Callable[..., None]] = [] self._size = ThemeManager.theme["CTkFont"]["size"] if size is None else size @@ -46,22 +49,22 @@ class CTkFont(Font): self._family = super().cget("family") self._tuple_style_string = f"{super().cget('weight')} {slant} {'underline' if underline else ''} {'overstrike' if overstrike else ''}" - def add_size_configure_callback(self, callback: Callable): + def add_size_configure_callback(self, callback: Callable[..., None]): """ add function, that gets called when font got configured """ self._size_configure_callback_list.append(callback) - def remove_size_configure_callback(self, callback: Callable): + def remove_size_configure_callback(self, callback: Callable[..., None]): """ remove function, that gets called when font got configured """ self._size_configure_callback_list.remove(callback) - def create_scaled_tuple(self, font_scaling: float) -> Tuple[str, int, str]: + def create_scaled_tuple(self, font_scaling: float) -> tuple[str, int, str]: """ return scaled tuple representation of font in the form (family: str, size: int, style: str)""" return self._family, round(-abs(self._size) * font_scaling), self._tuple_style_string - def config(self, *args, **kwargs): + def config(self, *args: Any, **kwargs: Any): raise AttributeError("'config' is not implemented for CTk widgets. For consistency, always use 'configure' instead.") - def configure(self, **kwargs): + def configure(self, **kwargs: Any): if "size" in kwargs: self._size = kwargs.pop("size") super().configure(size=-abs(self._size)) @@ -79,7 +82,7 @@ class CTkFont(Font): for callback in self._size_configure_callback_list: callback() - def cget(self, attribute_name: str) -> any: + def cget(self, attribute_name: str) -> Any: if attribute_name == "size": return self._size if attribute_name == "family": @@ -87,5 +90,5 @@ class CTkFont(Font): else: return super().cget(attribute_name) - def copy(self) -> "CTkFont": + def copy(self) -> CTkFont: return copy.deepcopy(self) diff --git a/customtkinter/windows/widgets/font/font_manager.py b/customtkinter/windows/widgets/font/font_manager.py index b3ef369..0e6eefc 100644 --- a/customtkinter/windows/widgets/font/font_manager.py +++ b/customtkinter/windows/widgets/font/font_manager.py @@ -1,11 +1,11 @@ -import sys +from __future__ import annotations + import os import shutil -from typing import Union +import sys class FontManager: - linux_font_path = "~/.fonts/" @classmethod @@ -25,10 +25,11 @@ class FontManager: return True @classmethod - def windows_load_font(cls, font_path: Union[str, bytes], private: bool = True, enumerable: bool = False) -> bool: - """ Function taken from: https://stackoverflow.com/questions/11993290/truly-custom-font-in-tkinter/30631309#30631309 """ + def windows_load_font(cls, font_path: str | bytes, private: bool = True, enumerable: bool = False) -> bool: + """ Function taken from: https://stackoverflow.com/a/30631309/ """ - from ctypes import windll, byref, create_unicode_buffer, create_string_buffer + from ctypes import (byref, create_string_buffer, create_unicode_buffer, + windll) FR_PRIVATE = 0x10 FR_NOT_ENUM = 0x20 diff --git a/customtkinter/windows/widgets/image/ctk_image.py b/customtkinter/windows/widgets/image/ctk_image.py index 0247cdd..2fb8582 100644 --- a/customtkinter/windows/widgets/image/ctk_image.py +++ b/customtkinter/windows/widgets/image/ctk_image.py @@ -1,4 +1,7 @@ -from typing import Tuple, Dict, Callable, List +from __future__ import annotations + +from typing import Any, Callable + try: from PIL import Image, ImageTk except ImportError: @@ -19,9 +22,9 @@ class CTkImage: _checked_PIL_import = False def __init__(self, - light_image: "Image.Image" = None, - dark_image: "Image.Image" = None, - size: Tuple[int, int] = (20, 20)): + light_image: Image.Image | None = None, + dark_image: Image.Image | None = None, + size: tuple[int, int] = (20, 20)): if not self._checked_PIL_import: self._check_pil_import() @@ -31,9 +34,9 @@ class CTkImage: self._check_images() self._size = size - self._configure_callback_list: List[Callable] = [] - self._scaled_light_photo_images: Dict[Tuple[int, int], ImageTk.PhotoImage] = {} - self._scaled_dark_photo_images: Dict[Tuple[int, int], ImageTk.PhotoImage] = {} + self._configure_callback_list: list[Callable[..., None]] = [] + self._scaled_light_photo_images: dict[tuple[int, int], ImageTk.PhotoImage] = {} + self._scaled_dark_photo_images: dict[tuple[int, int], ImageTk.PhotoImage] = {} @classmethod def _check_pil_import(cls): @@ -42,15 +45,15 @@ class CTkImage: except NameError: raise ImportError("PIL.Image and PIL.ImageTk couldn't be imported") - def add_configure_callback(self, callback: Callable): + def add_configure_callback(self, callback: Callable[..., None]): """ add function, that gets called when image got configured """ self._configure_callback_list.append(callback) - def remove_configure_callback(self, callback: Callable): + def remove_configure_callback(self, callback: Callable[..., None]): """ remove function, that gets called when image got configured """ self._configure_callback_list.remove(callback) - def configure(self, **kwargs): + def configure(self, **kwargs: Any): if "light_image" in kwargs: self._light_image = kwargs.pop("light_image") self._scaled_light_photo_images = {} @@ -66,7 +69,7 @@ class CTkImage: for callback in self._configure_callback_list: callback() - def cget(self, attribute_name: str) -> any: + def cget(self, attribute_name: str) -> Any: if attribute_name == "light_image": return self._light_image if attribute_name == "dark_image": @@ -89,24 +92,24 @@ class CTkImage: if self._light_image is not None and self._dark_image is not None and self._light_image.size != self._dark_image.size: raise ValueError(f"CTkImage: light_image size {self._light_image.size} must be the same as dark_image size {self._dark_image.size}.") - def _get_scaled_size(self, widget_scaling: float) -> Tuple[int, int]: + def _get_scaled_size(self, widget_scaling: float) -> tuple[int, int]: return round(self._size[0] * widget_scaling), round(self._size[1] * widget_scaling) - def _get_scaled_light_photo_image(self, scaled_size: Tuple[int, int]) -> "ImageTk.PhotoImage": + def _get_scaled_light_photo_image(self, scaled_size: tuple[int, int]) -> ImageTk.PhotoImage: if scaled_size in self._scaled_light_photo_images: return self._scaled_light_photo_images[scaled_size] else: self._scaled_light_photo_images[scaled_size] = ImageTk.PhotoImage(self._light_image.resize(scaled_size)) return self._scaled_light_photo_images[scaled_size] - def _get_scaled_dark_photo_image(self, scaled_size: Tuple[int, int]) -> "ImageTk.PhotoImage": + def _get_scaled_dark_photo_image(self, scaled_size: tuple[int, int]) -> ImageTk.PhotoImage: if scaled_size in self._scaled_dark_photo_images: return self._scaled_dark_photo_images[scaled_size] else: self._scaled_dark_photo_images[scaled_size] = ImageTk.PhotoImage(self._dark_image.resize(scaled_size)) return self._scaled_dark_photo_images[scaled_size] - def create_scaled_photo_image(self, widget_scaling: float, appearance_mode: str) -> "ImageTk.PhotoImage": + def create_scaled_photo_image(self, widget_scaling: float, appearance_mode: str) -> ImageTk.PhotoImage: scaled_size = self._get_scaled_size(widget_scaling) if appearance_mode == "light" and self._light_image is not None: @@ -118,5 +121,3 @@ class CTkImage: return self._get_scaled_dark_photo_image(scaled_size) elif appearance_mode == "dark" and self._dark_image is None: return self._get_scaled_light_photo_image(scaled_size) - - diff --git a/customtkinter/windows/widgets/scaling/scaling_base_class.py b/customtkinter/windows/widgets/scaling/scaling_base_class.py index 0d7b29b..a23dcbd 100644 --- a/customtkinter/windows/widgets/scaling/scaling_base_class.py +++ b/customtkinter/windows/widgets/scaling/scaling_base_class.py @@ -1,14 +1,20 @@ -from typing import Union, Tuple +from __future__ import annotations + import copy import re -try: +import sys +from typing import Any, TypeVar + +if sys.version_info >= (3, 8): from typing import Literal -except ImportError: +else: from typing_extensions import Literal -from .scaling_tracker import ScalingTracker from ..font import CTkFont +from .scaling_tracker import ScalingTracker +KT = TypeVar("KT") +VT = TypeVar("VT") class CTkScalingBaseClass: """ @@ -46,7 +52,7 @@ class CTkScalingBaseClass: elif self.__scaling_type == "window": ScalingTracker.remove_window(self._set_scaling, self) - def _set_scaling(self, new_widget_scaling, new_window_scaling): + def _set_scaling(self, new_widget_scaling: float, new_window_scaling: float): """ can be overridden, but super method must be called at the beginning """ self.__widget_scaling = new_widget_scaling self.__window_scaling = new_window_scaling @@ -57,23 +63,23 @@ class CTkScalingBaseClass: def _get_window_scaling(self) -> float: return self.__window_scaling - def _apply_widget_scaling(self, value: Union[int, float]) -> Union[float]: + def _apply_widget_scaling(self, value: int | float) -> float: assert self.__scaling_type == "widget" return value * self.__widget_scaling - def _reverse_widget_scaling(self, value: Union[int, float]) -> Union[float]: + def _reverse_widget_scaling(self, value: int | float) -> float: assert self.__scaling_type == "widget" return value / self.__widget_scaling - def _apply_window_scaling(self, value: Union[int, float]) -> int: + def _apply_window_scaling(self, value: int | float) -> int: assert self.__scaling_type == "window" return int(value * self.__window_scaling) - def _reverse_window_scaling(self, scaled_value: Union[int, float]) -> int: + def _reverse_window_scaling(self, scaled_value: int | float) -> int: assert self.__scaling_type == "window" return int(scaled_value / self.__window_scaling) - def _apply_font_scaling(self, font: Union[Tuple, CTkFont]) -> tuple: + def _apply_font_scaling(self, font: tuple[Any, ...] | CTkFont) -> tuple[str, int, str]: """ Takes CTkFont object and returns tuple font with scaled size, has to be called again for every change of font object """ assert self.__scaling_type == "widget" @@ -92,7 +98,7 @@ class CTkScalingBaseClass: else: raise ValueError(f"Can not scale font '{font}' of type {type(font)}. font needs to be tuple or instance of CTkFont") - def _apply_argument_scaling(self, kwargs: dict) -> dict: + def _apply_argument_scaling(self, kwargs: dict[KT, VT]) -> dict[KT, VT]: assert self.__scaling_type == "widget" scaled_kwargs = copy.copy(kwargs) @@ -118,7 +124,7 @@ class CTkScalingBaseClass: return scaled_kwargs @staticmethod - def _parse_geometry_string(geometry_string: str) -> tuple: + def _parse_geometry_string(geometry_string: str) -> tuple[int | None, int | None, int | None, int | None]: # index: 1 2 3 4 5 6 # regex group structure: ('x', '', '', '+-+-', '-', '-') result = re.search(r"((\d+)x(\d+)){0,1}(\+{0,1}([+-]{0,1}\d+)\+{0,1}([+-]{0,1}\d+)){0,1}", geometry_string) diff --git a/customtkinter/windows/widgets/scaling/scaling_tracker.py b/customtkinter/windows/widgets/scaling/scaling_tracker.py index d3627c2..4d5fe01 100644 --- a/customtkinter/windows/widgets/scaling/scaling_tracker.py +++ b/customtkinter/windows/widgets/scaling/scaling_tracker.py @@ -1,13 +1,17 @@ -import tkinter -import sys -from typing import Callable +from __future__ import annotations +import sys +import tkinter +from typing import Callable, TYPE_CHECKING + +if TYPE_CHECKING: + from customtkinter.windows import CTk class ScalingTracker: deactivate_automatic_dpi_awareness = False - window_widgets_dict = {} # contains window objects as keys with list of widget callbacks as elements - window_dpi_scaling_dict = {} # contains window objects as keys and corresponding scaling factors + window_widgets_dict: dict[CTk, list[Callable[..., None]]] = {} # contains window objects as keys with list of widget callbacks as elements + window_dpi_scaling_dict: dict[CTk, float] = {} # contains window objects as keys and corresponding scaling factors widget_scaling = 1 # user values which multiply to detected window scaling factor window_scaling = 1 @@ -17,12 +21,12 @@ class ScalingTracker: loop_pause_after_new_scaling = 1500 # ms @classmethod - def get_widget_scaling(cls, widget) -> float: + def get_widget_scaling(cls, widget: CTk) -> float: window_root = cls.get_window_root_of_widget(widget) return cls.window_dpi_scaling_dict[window_root] * cls.widget_scaling @classmethod - def get_window_scaling(cls, window) -> float: + def get_window_scaling(cls, window: CTk) -> float: window_root = cls.get_window_root_of_widget(window) return cls.window_dpi_scaling_dict[window_root] * cls.window_scaling @@ -37,7 +41,7 @@ class ScalingTracker: cls.update_scaling_callbacks_all() @classmethod - def get_window_root_of_widget(cls, widget): + def get_window_root_of_widget(cls, widget: CTk): current_widget = widget while isinstance(current_widget, tkinter.Tk) is False and\ @@ -58,7 +62,7 @@ class ScalingTracker: cls.window_scaling) @classmethod - def update_scaling_callbacks_for_window(cls, window): + def update_scaling_callbacks_for_window(cls, window: CTk): for set_scaling_callback in cls.window_widgets_dict[window]: if not cls.deactivate_automatic_dpi_awareness: set_scaling_callback(cls.window_dpi_scaling_dict[window] * cls.widget_scaling, @@ -68,7 +72,7 @@ class ScalingTracker: cls.window_scaling) @classmethod - def add_widget(cls, widget_callback: Callable, widget): + def add_widget(cls, widget_callback: Callable[..., None], widget: CTk): window_root = cls.get_window_root_of_widget(widget) if window_root not in cls.window_widgets_dict: @@ -84,7 +88,7 @@ class ScalingTracker: cls.update_loop_running = True @classmethod - def remove_widget(cls, widget_callback, widget): + def remove_widget(cls, widget_callback: Callable[..., None], widget: CTk): window_root = cls.get_window_root_of_widget(widget) try: cls.window_widgets_dict[window_root].remove(widget_callback) @@ -92,14 +96,14 @@ class ScalingTracker: pass @classmethod - def remove_window(cls, window_callback, window): + def remove_window(cls, window_callback: Callable[..., None], window: CTk): try: del cls.window_widgets_dict[window] except: pass @classmethod - def add_window(cls, window_callback, window): + def add_window(cls, window_callback: Callable[..., None], window: CTk): if window not in cls.window_widgets_dict: cls.window_widgets_dict[window] = [window_callback] else: @@ -135,11 +139,9 @@ class ScalingTracker: # DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE = 18, # DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = 34 # } - # ctypes.windll.user32.SetProcessDpiAwarenessContext(34) # Non client area scaling at runtime (titlebar) # does not work with resizable(False, False), window starts growing on monitor with different scaling (weird tkinter bug...) # ctypes.windll.user32.EnableNonClientDpiScaling(hwnd) does not work for some reason (tested on Windows 11) - # It's too bad, that these Windows API methods don't work properly with tkinter. But I tested days with multiple monitor setups, # and I don't think there is anything left to do. So this is the best option at the moment: @@ -148,13 +150,13 @@ class ScalingTracker: pass # DPI awareness on Linux not implemented @classmethod - def get_window_dpi_scaling(cls, window) -> float: + def get_window_dpi_scaling(cls, window: CTk) -> float: if not cls.deactivate_automatic_dpi_awareness: if sys.platform == "darwin": return 1 # scaling works automatically on macOS elif sys.platform.startswith("win"): - from ctypes import windll, pointer, wintypes + from ctypes import pointer, windll, wintypes DPI100pc = 96 # DPI 96 is 100% scaling DPI_type = 0 # MDT_EFFECTIVE_DPI = 0, MDT_ANGULAR_DPI = 1, MDT_RAW_DPI = 2 @@ -181,14 +183,14 @@ class ScalingTracker: cls.window_dpi_scaling_dict[window] = current_dpi_scaling_value if sys.platform.startswith("win"): - window.attributes("-alpha", 0.15) + window.attributes("-alpha", 0.15) # type: ignore window.block_update_dimensions_event() cls.update_scaling_callbacks_for_window(window) window.unblock_update_dimensions_event() if sys.platform.startswith("win"): - window.attributes("-alpha", 1) + window.attributes("-alpha", 1) # type: ignore new_scaling_detected = True diff --git a/customtkinter/windows/widgets/theme/theme_manager.py b/customtkinter/windows/widgets/theme/theme_manager.py index e04b679..0320d1d 100644 --- a/customtkinter/windows/widgets/theme/theme_manager.py +++ b/customtkinter/windows/widgets/theme/theme_manager.py @@ -1,24 +1,25 @@ -import sys -import os +from __future__ import annotations + import json -from typing import List, Union +import os +import sys +from typing import Any class ThemeManager: - - theme: dict = {} # contains all the theme data - _built_in_themes: List[str] = ["blue", "green", "dark-blue", "sweetkind"] - _currently_loaded_theme: Union[str, None] = None + theme: dict[str, Any] = {} # contains all the theme data + _built_in_themes: list[str] = ["blue", "green", "dark-blue", "sweetkind"] + _currently_loaded_theme: str | None = None @classmethod def load_theme(cls, theme_name_or_path: str): script_directory = os.path.dirname(os.path.abspath(__file__)) if theme_name_or_path in cls._built_in_themes: - with open(os.path.join(script_directory, "../../../assets", "themes", f"{theme_name_or_path}.json"), "r") as f: + with open(os.path.join(script_directory, "../../../assets", "themes", f"{theme_name_or_path}.json")) as f: cls.theme = json.load(f) else: - with open(theme_name_or_path, "r") as f: + with open(theme_name_or_path) as f: cls.theme = json.load(f) # store theme path for saving @@ -39,9 +40,9 @@ class ThemeManager: def save_theme(cls): if cls._currently_loaded_theme is not None: if cls._currently_loaded_theme not in cls._built_in_themes: - with open(cls._currently_loaded_theme, "r") as f: + with open(cls._currently_loaded_theme) as f: json.dump(cls.theme, f, indent=2) else: raise ValueError(f"cannot modify builtin theme '{cls._currently_loaded_theme}'") else: - raise ValueError(f"cannot save theme, no theme is loaded") + raise ValueError("cannot save theme, no theme is loaded") diff --git a/customtkinter/windows/widgets/utility/__init__.py b/customtkinter/windows/widgets/utility/__init__.py index c4b6fe8..38c8c02 100644 --- a/customtkinter/windows/widgets/utility/__init__.py +++ b/customtkinter/windows/widgets/utility/__init__.py @@ -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 diff --git a/customtkinter/windows/widgets/utility/utility_functions.py b/customtkinter/windows/widgets/utility/utility_functions.py index a9968bb..57849ef 100644 --- a/customtkinter/windows/widgets/utility/utility_functions.py +++ b/customtkinter/windows/widgets/utility/utility_functions.py @@ -1,7 +1,14 @@ +from __future__ import annotations -def pop_from_dict_by_set(dictionary: dict, valid_keys: set) -> dict: +from typing import Any, TypeVar + +KT = TypeVar("KT") +VT = TypeVar("VT") + + +def pop_from_dict_by_set(dictionary: dict[KT, VT], valid_keys: set[KT]) -> dict[KT, VT]: """ remove and create new dict with key value pairs of dictionary, where key is in valid_keys """ - new_dictionary = {} + new_dictionary: dict[KT, VT] = {} for key in list(dictionary.keys()): if key in valid_keys: @@ -10,7 +17,7 @@ def pop_from_dict_by_set(dictionary: dict, valid_keys: set) -> dict: return new_dictionary -def check_kwargs_empty(kwargs_dict, raise_error=False) -> bool: +def check_kwargs_empty(kwargs_dict: dict[Any, Any], raise_error: bool =False) -> bool: """ returns True if kwargs are empty, False otherwise, raises error if not empty """ if len(kwargs_dict) > 0: diff --git a/examples/example_background_image.py b/examples/example_background_image.py index 6a95841..2bd63e3 100644 --- a/examples/example_background_image.py +++ b/examples/example_background_image.py @@ -1,3 +1,7 @@ +from __future__ import annotations + +from typing import Any + import customtkinter from PIL import Image import os @@ -9,7 +13,7 @@ class App(customtkinter.CTk): width = 900 height = 600 - def __init__(self, *args, **kwargs): + def __init__(self, *args: Any, **kwargs: Any): super().__init__(*args, **kwargs) self.title("CustomTkinter example_background_image.py") diff --git a/examples/image_example.py b/examples/image_example.py index c4064b8..7a252d2 100644 --- a/examples/image_example.py +++ b/examples/image_example.py @@ -1,3 +1,7 @@ +from __future__ import annotations + +from typing import Any + import customtkinter import os from PIL import Image @@ -79,7 +83,7 @@ class App(customtkinter.CTk): # select default frame self.select_frame_by_name("home") - def select_frame_by_name(self, name): + def select_frame_by_name(self, name: str): # set button color for selected button self.home_button.configure(fg_color=("gray75", "gray25") if name == "home" else "transparent") self.frame_2_button.configure(fg_color=("gray75", "gray25") if name == "frame_2" else "transparent") @@ -108,11 +112,10 @@ class App(customtkinter.CTk): def frame_3_button_event(self): self.select_frame_by_name("frame_3") - def change_appearance_mode_event(self, new_appearance_mode): + def change_appearance_mode_event(self, new_appearance_mode: Any): customtkinter.set_appearance_mode(new_appearance_mode) if __name__ == "__main__": app = App() app.mainloop() - diff --git a/examples/scrollable_frame_example.py b/examples/scrollable_frame_example.py index 9386f70..6c8b329 100644 --- a/examples/scrollable_frame_example.py +++ b/examples/scrollable_frame_example.py @@ -1,25 +1,29 @@ +from __future__ import annotations + +from typing import Any, Callable + import customtkinter import os from PIL import Image class ScrollableCheckBoxFrame(customtkinter.CTkScrollableFrame): - def __init__(self, master, item_list, command=None, **kwargs): + def __init__(self, master, item_list: list[str], command: Callable[..., None] | None = None, **kwargs: Any): super().__init__(master, **kwargs) self.command = command - self.checkbox_list = [] - for i, item in enumerate(item_list): + self.checkbox_list: list[customtkinter.CTkCheckBox] = [] + for item in item_list: self.add_item(item) - def add_item(self, item): + def add_item(self, item: str): checkbox = customtkinter.CTkCheckBox(self, text=item) if self.command is not None: checkbox.configure(command=self.command) checkbox.grid(row=len(self.checkbox_list), column=0, pady=(0, 10)) self.checkbox_list.append(checkbox) - def remove_item(self, item): + def remove_item(self, item: str): for checkbox in self.checkbox_list: if item == checkbox.cget("text"): checkbox.destroy() @@ -31,7 +35,7 @@ class ScrollableCheckBoxFrame(customtkinter.CTkScrollableFrame): class ScrollableRadiobuttonFrame(customtkinter.CTkScrollableFrame): - def __init__(self, master, item_list, command=None, **kwargs): + def __init__(self, master, item_list: list[str], command: Callable[..., None] | None = None, **kwargs: Any): super().__init__(master, **kwargs) self.command = command @@ -59,7 +63,7 @@ class ScrollableRadiobuttonFrame(customtkinter.CTkScrollableFrame): class ScrollableLabelButtonFrame(customtkinter.CTkScrollableFrame): - def __init__(self, master, command=None, **kwargs): + def __init__(self, master, command=None, **kwargs: Any): super().__init__(master, **kwargs) self.grid_columnconfigure(0, weight=1) diff --git a/setup.cfg b/setup.cfg index 0ed1020..ef08d19 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,19 +2,24 @@ name = customtkinter version = 5.1.2 description = Create modern looking GUIs with Python -long_description = A modern and customizable python UI-library based on Tkinter: https://github.com/TomSchimansky/CustomTkinter +long_description = file: Readme.md long_description_content_type = text/markdown url = https://github.com/TomSchimansky/CustomTkinter author = Tom Schimansky -license = Creative Commons Zero v1.0 Universal +license = MIT license_file = LICENSE classifiers = License :: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication + License :: OSI Approved :: MIT License Operating System :: OS Independent + Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 [options] -python_requires = >=3.7 packages = customtkinter customtkinter.windows @@ -29,5 +34,6 @@ packages = customtkinter.windows.widgets.utility install_requires = darkdetect - typing_extensions; python_version<="3.7" + typing-extensions;python_version=="3.7" +python_requires = >=3.7 include_package_data = True diff --git a/test/manual_integration_tests/test_ctk_toplevel.py b/test/manual_integration_tests/test_ctk_toplevel.py index 32bd490..fa9f1d1 100644 --- a/test/manual_integration_tests/test_ctk_toplevel.py +++ b/test/manual_integration_tests/test_ctk_toplevel.py @@ -1,10 +1,14 @@ +from __future__ import annotations + +from typing import Any, Callable + import customtkinter customtkinter.set_appearance_mode("dark") class ToplevelWindow(customtkinter.CTkToplevel): - def __init__(self, *args, closing_event=None, **kwargs): + def __init__(self, *args: Any, closing_event: Callable[[], None] | None = None, **kwargs: Any): super().__init__(*args, **kwargs) self.protocol("WM_DELETE_WINDOW", self.closing) self.geometry("500x300") diff --git a/test/manual_integration_tests/test_filedialog.py b/test/manual_integration_tests/test_filedialog.py index 4615da8..d9461bd 100644 --- a/test/manual_integration_tests/test_filedialog.py +++ b/test/manual_integration_tests/test_filedialog.py @@ -1,12 +1,11 @@ -import tkinter.messagebox import customtkinter customtkinter.set_appearance_mode("dark") class App(customtkinter.CTk): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) + def __init__(self, *args: Any, **kwargs: Any): + super().__init__(*args, **kwargs: Any) self.title("test filedialog") diff --git a/test/manual_integration_tests/test_images.py b/test/manual_integration_tests/test_images.py index e1f9545..6e4d5cd 100644 --- a/test/manual_integration_tests/test_images.py +++ b/test/manual_integration_tests/test_images.py @@ -40,4 +40,3 @@ label_3 = customtkinter.CTkLabel(app, image=ImageTk.PhotoImage(Image.open(file_p label_3.pack(padx=20, pady=20) app.mainloop() - diff --git a/test/unit_tests/test_ctk.py b/test/unit_tests/test_ctk.py index bb95d29..401b8c0 100644 --- a/test/unit_tests/test_ctk.py +++ b/test/unit_tests/test_ctk.py @@ -1,4 +1,3 @@ -import time import customtkinter diff --git a/test/unit_tests/test_ctk_button.py b/test/unit_tests/test_ctk_button.py index b126d76..4ff0d6d 100644 --- a/test/unit_tests/test_ctk_button.py +++ b/test/unit_tests/test_ctk_button.py @@ -1,4 +1,3 @@ -import time import customtkinter diff --git a/test/unit_tests/test_ctk_entry.py b/test/unit_tests/test_ctk_entry.py index 5c14c9a..f568bee 100644 --- a/test/unit_tests/test_ctk_entry.py +++ b/test/unit_tests/test_ctk_entry.py @@ -1,5 +1,4 @@ import customtkinter -import time app = customtkinter.CTk() From 79cd10ddfde1152171a885b88814ea52b4d17565 Mon Sep 17 00:00:00 2001 From: demberto Date: Tue, 18 Apr 2023 18:50:20 +0530 Subject: [PATCH 2/2] rev. 2 --- customtkinter/windows/ctk_input_dialog.py | 1 - customtkinter/windows/ctk_tk.py | 2 +- customtkinter/windows/ctk_toplevel.py | 10 +++++----- customtkinter/windows/widgets/ctk_checkbox.py | 7 ++++++- customtkinter/windows/widgets/ctk_combobox.py | 8 ++++---- customtkinter/windows/widgets/ctk_entry.py | 4 ++-- customtkinter/windows/widgets/ctk_frame.py | 7 ++++--- .../windows/widgets/ctk_optionmenu.py | 7 ++++++- .../windows/widgets/ctk_radiobutton.py | 7 ++++++- .../windows/widgets/ctk_scrollbar.py | 2 +- customtkinter/windows/widgets/ctk_slider.py | 4 ++-- customtkinter/windows/widgets/ctk_switch.py | 7 ++++++- customtkinter/windows/widgets/ctk_textbox.py | 20 +++++++++---------- 13 files changed, 53 insertions(+), 33 deletions(-) diff --git a/customtkinter/windows/ctk_input_dialog.py b/customtkinter/windows/ctk_input_dialog.py index 49ea327..b1d3358 100644 --- a/customtkinter/windows/ctk_input_dialog.py +++ b/customtkinter/windows/ctk_input_dialog.py @@ -51,7 +51,6 @@ class CTkInputDialog(CTkToplevel): self.grab_set() # make other windows not clickable def _create_widgets(self): - self.grid_columnconfigure((0, 1), weight=1) self.rowconfigure(0, weight=1) diff --git a/customtkinter/windows/ctk_tk.py b/customtkinter/windows/ctk_tk.py index bc21cec..a31809b 100644 --- a/customtkinter/windows/ctk_tk.py +++ b/customtkinter/windows/ctk_tk.py @@ -197,7 +197,7 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass): super().geometry(self._apply_geometry_scaling(geometry_string)) # update width and height attributes - width, height, x, y = self._parse_geometry_string(geometry_string) + width, height, *_ = self._parse_geometry_string(geometry_string) if width is not None and height is not None: self._current_width = max(self._min_width, min(width, self._max_width)) # bound value between min and max self._current_height = max(self._min_height, min(height, self._max_height)) diff --git a/customtkinter/windows/ctk_toplevel.py b/customtkinter/windows/ctk_toplevel.py index 180ede9..50d6b0b 100644 --- a/customtkinter/windows/ctk_toplevel.py +++ b/customtkinter/windows/ctk_toplevel.py @@ -56,11 +56,11 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl self._current_width = 200 # initial window size, always without scaling self._current_height = 200 - self._min_width: int = 0 - self._min_height: int = 0 - self._max_width: int = 1_000_000 - self._max_height: int = 1_000_000 - self._last_resizable_args: tuple[list, dict] | None = None # (args, kwargs) + self._min_width: int | None = 0 + self._min_height: int | None = 0 + self._max_width: int | None = 1_000_000 + self._max_height: int | None = 1_000_000 + self._last_resizable_args: tuple[list[int], dict[str, float]] | None = None # (args, kwargs) self._fg_color = ThemeManager.theme["CTkToplevel"]["fg_color"] if fg_color is None else self._check_color_type(fg_color) diff --git a/customtkinter/windows/widgets/ctk_checkbox.py b/customtkinter/windows/widgets/ctk_checkbox.py index 515d5f3..bbdd0a2 100644 --- a/customtkinter/windows/widgets/ctk_checkbox.py +++ b/customtkinter/windows/widgets/ctk_checkbox.py @@ -4,6 +4,11 @@ import sys import tkinter from typing import Any, Callable +if sys.version_info >= (3, 8): + from typing import Literal +else: + from typing_extensions import Literal + from .core_rendering import CTkCanvas, DrawEngine from .core_widget_classes import CTkBaseClass from .font import CTkFont @@ -36,7 +41,7 @@ class CTkCheckBox(CTkBaseClass): text: str = "CTkCheckBox", font: tuple[Any, ...] | CTkFont | None = None, textvariable: tkinter.Variable | None = None, - state: str = tkinter.NORMAL, + state: Literal["normal", "disabled", "readonly"] = "normal", hover: bool = True, command: Callable[[], None] | None = None, onvalue: int | str = 1, diff --git a/customtkinter/windows/widgets/ctk_combobox.py b/customtkinter/windows/widgets/ctk_combobox.py index 1b25a2a..a471b97 100644 --- a/customtkinter/windows/widgets/ctk_combobox.py +++ b/customtkinter/windows/widgets/ctk_combobox.py @@ -43,11 +43,11 @@ class CTkComboBox(CTkBaseClass): font: tuple[Any, ...] | CTkFont | None = None, dropdown_font: tuple[Any, ...] | CTkFont | None = None, values: list[str] | None = None, - state: str = tkinter.NORMAL, + state: Literal["normal", "disabled", "readonly"] = "normal", hover: bool = True, variable: tkinter.Variable | None = None, command: Callable[[str], None] | None = None, - justify: str = "left", + justify: Literal["left", "center", "right"] = "left", **kwargs: Any): # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass @@ -401,13 +401,13 @@ class CTkComboBox(CTkBaseClass): if self._state is not tkinter.DISABLED and len(self._values) > 0: self._open_dropdown_menu() - def bind(self, sequence=None, command=None, add: Literal["+", True] = True): + def bind(self, sequence: str | None = None, command=None, add: Literal["+"] | bool = True): """ called on the tkinter.Entry """ if not (add == "+" or add is True): raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks") self._entry.bind(sequence, command, add=True) - def unbind(self, sequence=None, funcid=None): + def unbind(self, sequence=None, funcid: str | None = None): """ called on the tkinter.Entry """ if funcid is not None: raise ValueError("'funcid' argument can only be None, because there is a bug in" + diff --git a/customtkinter/windows/widgets/ctk_entry.py b/customtkinter/windows/widgets/ctk_entry.py index 2066bed..09253fb 100644 --- a/customtkinter/windows/widgets/ctk_entry.py +++ b/customtkinter/windows/widgets/ctk_entry.py @@ -45,7 +45,7 @@ class CTkEntry(CTkBaseClass): textvariable: tkinter.Variable | None = None, placeholder_text: str | None = None, font: tuple[Any, ...] | CTkFont | None = None, - state: str = tkinter.NORMAL, + state: Literal["normal", "disabled", "readonly"] = "normal", **kwargs: Any): # transfer basic functionality (bg_color, size, appearance_mode, scaling) to CTkBaseClass @@ -288,7 +288,7 @@ class CTkEntry(CTkBaseClass): else: return super().cget(attribute_name) # cget of CTkBaseClass - def bind(self, sequence=None, command=None, add: Literal["+", True] = True): + def bind(self, sequence=None, command=None, add: Literal["+"] | bool = True): """ called on the tkinter.Entry """ if not (add == "+" or add is True): raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks") diff --git a/customtkinter/windows/widgets/ctk_frame.py b/customtkinter/windows/widgets/ctk_frame.py index 9202090..e4dd32d 100644 --- a/customtkinter/windows/widgets/ctk_frame.py +++ b/customtkinter/windows/widgets/ctk_frame.py @@ -1,7 +1,8 @@ from __future__ import annotations import sys -from typing import Any +import tkinter +from typing import Any, Callable if sys.version_info >= (3, 8): from typing import Literal @@ -189,13 +190,13 @@ class CTkFrame(CTkBaseClass): else: return super().cget(attribute_name) - def bind(self, sequence=None, command=None, add: Literal["+", True] = True): + def bind(self, sequence: str | None = None, command: Callable[[tkinter.Event[Any]], None] | None = None, add: Literal["+"] | bool = True): """ called on the tkinter.Canvas """ if not (add == "+" or add is True): raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks") self._canvas.bind(sequence, command, add=True) - def unbind(self, sequence=None, funcid=None): + def unbind(self, sequence=None, funcid: str | None = None): """ called on the tkinter.Canvas """ if funcid is not None: raise ValueError("'funcid' argument can only be None, because there is a bug in" + diff --git a/customtkinter/windows/widgets/ctk_optionmenu.py b/customtkinter/windows/widgets/ctk_optionmenu.py index 2cb6ebc..96ef05d 100644 --- a/customtkinter/windows/widgets/ctk_optionmenu.py +++ b/customtkinter/windows/widgets/ctk_optionmenu.py @@ -5,6 +5,11 @@ import sys import tkinter from typing import Any, Callable +if sys.version_info >= (3, 8): + from typing import Literal +else: + from typing_extensions import Literal + from .core_rendering import CTkCanvas, DrawEngine from .core_widget_classes import CTkBaseClass, DropdownMenu from .font import CTkFont @@ -37,7 +42,7 @@ class CTkOptionMenu(CTkBaseClass): dropdown_font: tuple[Any, ...] | CTkFont | None = None, values: list | None = None, variable: tkinter.Variable | None = None, - state: str = tkinter.NORMAL, + state: Literal["normal", "disabled", "readonly"] = "normal", hover: bool = True, command: Callable[[str], None] | None = None, dynamic_resizing: bool = True, diff --git a/customtkinter/windows/widgets/ctk_radiobutton.py b/customtkinter/windows/widgets/ctk_radiobutton.py index 0e10172..d6e516e 100644 --- a/customtkinter/windows/widgets/ctk_radiobutton.py +++ b/customtkinter/windows/widgets/ctk_radiobutton.py @@ -4,6 +4,11 @@ import sys import tkinter from typing import Any, Callable +if sys.version_info >= (3, 8): + from typing import Literal +else: + from typing_extensions import Literal + from .core_rendering import CTkCanvas, DrawEngine from .core_widget_classes import CTkBaseClass from .font import CTkFont @@ -38,7 +43,7 @@ class CTkRadioButton(CTkBaseClass): textvariable: tkinter.Variable | None = None, variable: tkinter.Variable | None = None, value: int | str = 0, - state: str = tkinter.NORMAL, + state: Literal["normal", "disabled", "readonly"] = "normal", hover: bool = True, command: Callable[..., None] | None = None, **kwargs: Any): diff --git a/customtkinter/windows/widgets/ctk_scrollbar.py b/customtkinter/windows/widgets/ctk_scrollbar.py index 0a93b14..9180474 100644 --- a/customtkinter/windows/widgets/ctk_scrollbar.py +++ b/customtkinter/windows/widgets/ctk_scrollbar.py @@ -264,7 +264,7 @@ class CTkScrollbar(CTkBaseClass): def get(self): return self._start_value, self._end_value - def bind(self, sequence=None, command: Callable[..., None] | None = None, add: Literal["+", True] = True): + def bind(self, sequence=None, command: Callable[..., None] | None = None, add: Literal["+"] | bool = True): """ called on the tkinter.Canvas """ if not (add == "+" or add is True): raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks") diff --git a/customtkinter/windows/widgets/ctk_slider.py b/customtkinter/windows/widgets/ctk_slider.py index 20c5459..d036613 100644 --- a/customtkinter/windows/widgets/ctk_slider.py +++ b/customtkinter/windows/widgets/ctk_slider.py @@ -337,7 +337,7 @@ class CTkSlider(CTkBaseClass): fill=self._apply_appearance_mode(self._button_color), outline=self._apply_appearance_mode(self._button_color)) - def _round_to_step_size(self, value) -> float: + def _round_to_step_size(self, value: float) -> float: if self._number_of_steps is not None: step_size = (self._to - self._from_) / self._number_of_steps value = self._to - (round((self._to - value) / step_size) * step_size) @@ -348,7 +348,7 @@ class CTkSlider(CTkBaseClass): def get(self) -> float: return self._output_value - def set(self, output_value, from_variable_callback: bool = False): + def set(self, output_value: float, from_variable_callback: bool = False): if self._from_ < self._to: if output_value > self._to: output_value = self._to diff --git a/customtkinter/windows/widgets/ctk_switch.py b/customtkinter/windows/widgets/ctk_switch.py index 49f7398..68b87bf 100644 --- a/customtkinter/windows/widgets/ctk_switch.py +++ b/customtkinter/windows/widgets/ctk_switch.py @@ -4,6 +4,11 @@ import sys import tkinter from typing import Any, Callable +if sys.version_info >= (3, 8): + from typing import Literal +else: + from typing_extensions import Literal + from .core_rendering import CTkCanvas, DrawEngine from .core_widget_classes import CTkBaseClass from .font import CTkFont @@ -43,7 +48,7 @@ class CTkSwitch(CTkBaseClass): variable: tkinter.Variable | None = None, hover: bool = True, command: Callable[..., None] | None = None, - state: str = tkinter.NORMAL, + state: Literal["normal", "disabled", "readonly"] = "normal", **kwargs: Any): # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass diff --git a/customtkinter/windows/widgets/ctk_textbox.py b/customtkinter/windows/widgets/ctk_textbox.py index 36035bd..b15d871 100644 --- a/customtkinter/windows/widgets/ctk_textbox.py +++ b/customtkinter/windows/widgets/ctk_textbox.py @@ -129,7 +129,7 @@ class CTkTextbox(CTkBaseClass): self.after(50, self._check_if_scrollbars_needed, None, True) self._draw() - def _create_grid_for_text_and_scrollbars(self, re_grid_textbox=False, re_grid_x_scrollbar=False, re_grid_y_scrollbar=False): + def _create_grid_for_text_and_scrollbars(self, re_grid_textbox: bool = False, re_grid_x_scrollbar: bool = False, re_grid_y_scrollbar: bool = False): # configure 2x2 grid self.grid_rowconfigure(0, weight=1) @@ -158,7 +158,7 @@ class CTkTextbox(CTkBaseClass): else: self._y_scrollbar.grid_forget() - def _check_if_scrollbars_needed(self, event=None, continue_loop: bool = False): + def _check_if_scrollbars_needed(self, event: tkinter.Event[Any] | None = None, continue_loop: bool = False): """ Method hides or places the scrollbars if they are needed on key release event of tkinter.text widget """ if self._scrollbars_activated: @@ -355,7 +355,7 @@ class CTkTextbox(CTkBaseClass): def focus_force(self): return self._textbox.focus_force() - def insert(self, index: int, text, tags=None): + def insert(self, index: int, text: str, tags=None): return self._textbox.insert(index, text, tags) def get(self, index1, index2=None): @@ -435,16 +435,16 @@ class CTkTextbox(CTkBaseClass): def see(self, index): return self._textbox.see(index) - def tag_add(self, tagName, index1, index2=None): + def tag_add(self, tagName: str, index1, index2=None): return self._textbox.tag_add(tagName, index1, index2) - def tag_bind(self, tagName, sequence, func, add=None): + def tag_bind(self, tagName: str, sequence: str, func, add: bool | Literal["", "+"] | None = None): return self._textbox.tag_bind(tagName, sequence, func, add) - def tag_cget(self, tagName, option): + def tag_cget(self, tagName: str, option: str): return self._textbox.tag_cget(tagName, option) - def tag_config(self, tagName, **kwargs: Any): + def tag_config(self, tagName: str, **kwargs: Any): if "font" in kwargs: raise AttributeError("'font' option forbidden, because would be incompatible with scaling") return self._textbox.tag_config(tagName, **kwargs) @@ -476,10 +476,10 @@ class CTkTextbox(CTkBaseClass): def tag_unbind(self, tagName: str, sequence, funcid=None): return self._textbox.tag_unbind(tagName, sequence, funcid) - def window_cget(self, index: int, option): + def window_cget(self, index: int, option: str): raise AttributeError("embedding widgets is forbidden, would probably cause all kinds of problems ;)") - def window_configure(self, index: int, option): + def window_configure(self, index: int, option: str): raise AttributeError("embedding widgets is forbidden, would probably cause all kinds of problems ;)") def window_create(self, index: int, **kwargs: Any): @@ -500,7 +500,7 @@ class CTkTextbox(CTkBaseClass): def yview(self, *args): return self._textbox.yview(*args) - def yview_moveto(self, fraction): + def yview_moveto(self, fraction: float): return self._textbox.yview_moveto(fraction) def yview_scroll(self, n, what):