Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 15 additions & 34 deletions customtkinter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,30 @@

import os
import sys
from tkinter import Variable, StringVar, IntVar, DoubleVar, BooleanVar
from tkinter.constants import *
import tkinter.filedialog as filedialog
from tkinter import BooleanVar, DoubleVar, IntVar, StringVar, Variable
from tkinter.constants import *

# import windows
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
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_rendering import CTkCanvas, DrawEngine
from .windows.widgets.core_widget_classes import CTkBaseClass

# import widgets
from .windows.widgets import CTkButton
from .windows.widgets import CTkCheckBox
from .windows.widgets import CTkComboBox
from .windows.widgets import CTkEntry
from .windows.widgets import CTkFrame
from .windows.widgets import CTkLabel
from .windows.widgets import CTkOptionMenu
from .windows.widgets import CTkProgressBar
from .windows.widgets import CTkRadioButton
from .windows.widgets import CTkScrollbar
from .windows.widgets import CTkSegmentedButton
from .windows.widgets import CTkSlider
from .windows.widgets import CTkSwitch
from .windows.widgets import CTkTabview
from .windows.widgets import CTkTextbox
from .windows.widgets import CTkScrollableFrame

# import windows
from .windows import CTk
from .windows import CTkToplevel
from .windows import CTkInputDialog

# 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

Expand Down
2 changes: 1 addition & 1 deletion customtkinter/windows/__init__.py
Original file line number Diff line number Diff line change
@@ -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
32 changes: 16 additions & 16 deletions customtkinter/windows/ctk_input_dialog.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -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"):
Expand All @@ -37,7 +38,7 @@ def __init__(self,
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

Expand All @@ -50,7 +51,6 @@ def __init__(self,
self.grab_set() # make other windows not clickable

def _create_widgets(self):

self.grid_columnconfigure((0, 1), weight=1)
self.rowconfigure(0, weight=1)

Expand Down Expand Up @@ -92,7 +92,7 @@ def _create_widgets(self):
self.after(150, lambda: self._entry.focus()) # set focus to entry with slight delay, otherwise it won't work
self._entry.bind("<Return>", self._ok_event)

def _ok_event(self, event=None):
def _ok_event(self, event: tkinter.Event[Any] | None = None):
self._user_input = self._entry.get()
self.grab_release()
self.destroy()
Expand Down
51 changes: 27 additions & 24 deletions customtkinter/windows/ctk_tk.py
Original file line number Diff line number Diff line change
@@ -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 .widgets.theme import ThemeManager
from .widgets.scaling import CTkScalingBaseClass
from .widgets.appearance_mode import CTkAppearanceModeBaseClass
from customtkinter.windows.widgets.utility.utility_functions import (
check_kwargs_empty, pop_from_dict_by_set)

from customtkinter.windows.widgets.utility.utility_functions import pop_from_dict_by_set, check_kwargs_empty
from .widgets.appearance_mode import CTkAppearanceModeBaseClass
from .widgets.scaling import CTkScalingBaseClass
from .widgets.theme import ThemeManager


class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
Expand All @@ -19,18 +22,18 @@ 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'}

_deactivate_macos_window_header_manipulation: bool = False
_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()

Expand All @@ -46,7 +49,7 @@ def __init__(self,
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)

Expand Down Expand Up @@ -86,12 +89,12 @@ def destroy(self):
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
Expand All @@ -104,7 +107,7 @@ def _update_dimensions_event(self, event=None):
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.
Expand Down Expand Up @@ -149,7 +152,7 @@ def update(self):

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())
Expand All @@ -171,7 +174,7 @@ def resizable(self, width: bool = None, height: bool = None):

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:
Expand All @@ -180,7 +183,7 @@ def minsize(self, width: int = None, height: int = None):
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:
Expand All @@ -189,19 +192,19 @@ def maxsize(self, width: int = None, height: int = None):
self._current_height = height
super().maxsize(self._apply_window_scaling(self._max_width), self._apply_window_scaling(self._max_height))

def geometry(self, geometry_string: str = None):
def geometry(self, geometry_string: str | None = None):
if geometry_string is not None:
super().geometry(self._apply_geometry_scaling(geometry_string))

# update width and height attributes
width, height, x, y = self._parse_geometry_string(geometry_string)
width, height, *_ = self._parse_geometry_string(geometry_string)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's this *_? I've never seen that before.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

x and y were unused in that context. Python uses the convention of naming unused variables as _. *_ does a tuple unpack operation and can be thought of as an *args parameter used in functions.

Copy link
Copy Markdown
Contributor Author

@demberto demberto Apr 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, there's multiple places in the code that require attention. Redundant None checks, incorrect default values to name a few. It prevents a complete type hinting of the codebase.
I am unfamiliar with the actual logic and considering that tkinter itself is a very poor API, I leave it as is

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I discovered this library a month ago and have been making simple fixes and formatting changes in my fork. I need to do a deeper dive to learn how it really works/is supposed to work.

if width is not None and height is not None:
self._current_width = max(self._min_width, min(width, self._max_width)) # bound value between min and max
self._current_height = max(self._min_height, min(height, self._max_height))
else:
return self._reverse_geometry_scaling(super().geometry())

def configure(self, **kwargs):
def configure(self, **kwargs: Any):
if "fg_color" in kwargs:
self._fg_color = self._check_color_type(kwargs.pop("fg_color"))
super().configure(bg=self._apply_appearance_mode(self._fg_color))
Expand All @@ -215,7 +218,7 @@ def configure(self, **kwargs):
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:
Expand Down
Loading