53 Commits

Author SHA1 Message Date
22b4dfb2d3 Bump to 4.4.1 2022-06-17 21:13:30 +02:00
9146e02718 updated CHANGELOG.md 2022-06-17 21:10:15 +02:00
3b259e4d01 refined dropdown word spacing for linux 2022-06-16 16:55:17 +02:00
7a99aa318c fixed disabled color for combobox 2022-06-15 18:31:20 -04:00
9ff6cc8268 optimized char mapping on canvas for Linux 2022-06-15 18:15:24 -04:00
3a1d12f8ea removed old dropdown menu 2022-06-15 23:26:21 +02:00
45e47f5970 fixed dropdown menu bug on Linux 2022-06-15 14:51:24 +02:00
0e510dec53 Bump to 4.4.0 2022-06-15 02:08:28 +02:00
fc952294f0 chnaged complex_example.py and fixed command-variable execution order in CTkSlider 2022-06-15 02:07:51 +02:00
d8b5104028 adopted new dropdown menu for combobox 2022-06-15 01:24:02 +02:00
20e16969f2 fixed dropdown_menu_fallback.py for Linux 2022-06-14 18:31:10 -04:00
a86dbd4d07 fixed dropdown_menu_fallback.py for macOS 2022-06-15 00:14:35 +02:00
413cedd093 refined dropdown_menu_fallback.py 2022-06-14 23:58:21 +02:00
91e7e3077c enhanced dropdown_menu_fallback.py 2022-06-13 15:08:13 +02:00
9c479bc1de Bump to 4.3.0 2022-06-02 01:25:21 +02:00
ecf6b8d9cf added combobox values to themes 2022-06-02 01:25:13 +02:00
807064a888 ComboBox and DropDown fixes for Windows 2022-06-02 00:15:24 +02:00
550653c6c3 small fixes for CTkComboBox 2022-06-01 23:50:50 +02:00
0aa9dfc70f removed combobox from simple_example.py 2022-05-31 23:16:13 +02:00
a9f51f1aa1 fixed CTkComboBox for Windows 2022-05-31 22:44:22 +02:00
2746e2a05f added CTkComboBox 2022-05-31 22:32:21 +02:00
f5fdd77584 Bump to 4.2.0 2022-05-30 17:30:27 +02:00
940ed128bd fixed geometry string scaling 2022-05-30 17:20:27 +02:00
60b13bf215 fixes for DropdownMenu 2022-05-30 16:46:36 +02:00
a50e2ea9ca added test_optionmenu.py 2022-05-30 15:48:41 +02:00
15558b4d0f added dropdown arrow with font files 2022-05-30 15:35:23 +02:00
aa46c56da9 small fixes in examples 2022-05-30 14:35:33 +02:00
cf6f513afc added click support for labels of CTkCheckBox, CTkRadioButton, CTkSwitch 2022-05-30 13:39:10 +02:00
9d618386e1 fixed scaling for DropdownMenu 2022-05-27 01:09:54 +02:00
9bd55cc159 implemented overwrite_preferred_drawing_method parameter in DrawEngine 2022-05-27 00:30:06 +02:00
8a87b6f926 updated color for blue theme 2022-05-27 00:12:37 +02:00
a1afc3056b removed scaling with ctypes shcore for Windows < 8.1 2022-05-26 19:16:01 +02:00
34da9505e9 DropDown fixes for Windows 2022-05-25 22:40:30 +02:00
91a8687736 added DropdownMenu and CTkOptionMenu 2022-05-25 22:14:38 +02:00
e96165d212 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	customtkinter/draw_engine.py
2022-05-25 18:42:42 +02:00
aa8c96a2c4 added overwrite_preferred_drawing_method parameter to DrawEngine 2022-05-25 18:40:07 +02:00
fd8135129c Merge remote-tracking branch 'origin/master'
# Conflicts:
#	test/manual_integration_tests/test_new_menu_design.py
2022-05-25 18:38:49 +02:00
5f88db11aa updated test_new_menu_design.py 2022-05-25 18:37:55 +02:00
1fed35a193 added draw_rounded_rect_with_border_vertical_split() function to DrawEngine 2022-05-25 17:04:00 +02:00
4b3b406250 updated test_new_menu_design.py 2022-05-24 13:50:34 +02:00
f49c83d2dc Bump to 4.1.0 2022-05-24 01:03:14 +02:00
4389c3e86b added configurable dimensions to some widgets 2022-05-24 01:00:58 +02:00
25297c2598 Bump to 4.0.4 2022-05-23 22:35:57 +02:00
4e155aedd6 fixed bug in fg_color detection of master 2022-05-23 22:35:38 +02:00
3a5d34cef6 Merge pull request #106 from bengy3d/linux-font-hotfix
Fixed loading fonts on linux
2022-05-23 16:27:01 +02:00
e42db49ca5 Fixed loading fonts on linux 2022-05-23 16:16:09 +02:00
a7c0fc2a3c Bump to 4.0.3 2022-05-23 11:01:38 +02:00
9be2a76b25 set minimum python version to 3.7 2022-05-23 11:01:29 +02:00
b1ac3b6d45 Merge pull request #102 from demberto/issue101-fix
Fixes #101
2022-05-23 10:42:32 +02:00
a6b563abb1 Fixes #101 2022-05-23 11:21:09 +05:30
152a2a9652 Bump to 4.0.2 2022-05-22 21:05:56 +02:00
0da9a17b55 added border_color, border_width to configure of CTkFrame 2022-05-22 21:05:46 +02:00
8cafed1b06 changed root_tk name to app in all exmaples and tests 2022-05-22 20:26:31 +02:00
55 changed files with 2215 additions and 767 deletions

View File

@ -4,14 +4,31 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [4.4.0] - 2022-06-14
### Changed
- Changed custom dropdown menu to normal tkinter.Menu because of multiple platform specific bugs
## [4.3.0] - 2022-06-1
### Added
- Added CTkComboBox
- Small fixes for new dropdown menu
## [4.2.0] - 2022-05-30
### Added
- CTkOptionMenu with custom dropdown menu
- Support for clicking on labels of CTkCheckBox, CTkRadioButton, CTkSwitch
## [4.1.0] - 2022-05-24
### Added
- Configure width and height for frame, button, label, progressbar, slider, entry
## [4.0.0] - 2022-05-22 ## [4.0.0] - 2022-05-22
### Added ### Added
- This changelog file - This changelog file
- Adopted semantic versioning - Adopted semantic versioning
- Added HighDPI scaling to all widgets and geometry managers (place, pack, grid) - Added HighDPI scaling to all widgets and geometry managers (place, pack, grid)
- Restructured CTkSettings and renamed a few manager classes - Restructured CTkSettings and renamed a few manager classes
- Orientation attribute for slider and progressbar
### Changed
### Removed ### Removed
- A few unnecessary tests - A few unnecessary tests

View File

@ -16,7 +16,8 @@ CustomTkinter is a python UI-library based on Tkinter, which provides new, moder
fully customizable widgets. They are created and used like normal Tkinter widgets and fully customizable widgets. They are created and used like normal Tkinter widgets and
can also be used in combination with normal Tkinter elements. The widgets can also be used in combination with normal Tkinter elements. The widgets
and the window colors either adapt to the system appearance or the manually set mode and the window colors either adapt to the system appearance or the manually set mode
('light', 'dark'). With CustomTkinter you'll get a consistent and modern look across all ('light', 'dark'), and all CustomTkinter widgets and windows support HighDPI scaling
(Windows, macOS). With CustomTkinter you'll get a consistent and modern look across all
desktop platforms (Windows, macOS, Linux). desktop platforms (Windows, macOS, Linux).
@ -41,17 +42,17 @@ import customtkinter
customtkinter.set_appearance_mode("System") # Modes: system (default), light, dark customtkinter.set_appearance_mode("System") # Modes: system (default), light, dark
customtkinter.set_default_color_theme("blue") # Themes: blue (default), dark-blue, green customtkinter.set_default_color_theme("blue") # Themes: blue (default), dark-blue, green
root_tk = customtkinter.CTk() # create CTk window like you do with the Tk window app = customtkinter.CTk() # create CTk window like you do with the Tk window
root_tk.geometry("400x240") app.geometry("400x240")
def button_function(): def button_function():
print("button pressed") print("button pressed")
# Use CTkButton instead of tkinter Button # Use CTkButton instead of tkinter Button
button = customtkinter.CTkButton(master=root_tk, text="CTkButton", command=button_function) button = customtkinter.CTkButton(master=app, text="CTkButton", command=button_function)
button.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER) button.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER)
root_tk.mainloop() app.mainloop()
``` ```
which gives the following (macOS dark mode on): which gives the following (macOS dark mode on):

View File

View File

@ -1,7 +1,9 @@
__version__ = "4.0.1" __version__ = "4.4.1"
import os import os
import sys import sys
from tkinter.constants import *
from tkinter import StringVar, IntVar, DoubleVar, BooleanVar
# import manager classes # import manager classes
from .settings import Settings from .settings import Settings
@ -21,13 +23,16 @@ if sys.platform == "darwin":
else: else:
DrawEngine.preferred_drawing_method = "font_shapes" DrawEngine.preferred_drawing_method = "font_shapes"
if sys.platform.startswith("win") and sys.getwindowsversion().build < 9000: # No automatic scaling on Windows < 8.1
ScalingTracker.deactivate_automatic_dpi_awareness = True
# load Roboto fonts (used on Windows/Linux) # load Roboto fonts (used on Windows/Linux)
script_directory = os.path.dirname(os.path.abspath(__file__)) script_directory = os.path.dirname(os.path.abspath(__file__))
FontManager.load_font(os.path.join(script_directory, "assets", "fonts", "Roboto", "Roboto-Regular.ttf")) FontManager.load_font(os.path.join(script_directory, "assets", "fonts", "Roboto", "Roboto-Regular.ttf"))
FontManager.load_font(os.path.join(script_directory, "assets", "fonts", "Roboto", "Roboto-Medium.ttf")) FontManager.load_font(os.path.join(script_directory, "assets", "fonts", "Roboto", "Roboto-Medium.ttf"))
# load font necessary for rendering the widgets (used on Windows/Linux) # load font necessary for rendering the widgets (used on Windows/Linux)
if FontManager.load_font(os.path.join(script_directory, "assets", "fonts", "CustomTkinter_shapes_font-fine.otf")) is False: if FontManager.load_font(os.path.join(script_directory, "assets", "fonts", "CustomTkinter_shapes_font.otf")) is False:
# change draw method if font loading failed # change draw method if font loading failed
if DrawEngine.preferred_drawing_method == "font_shapes": if DrawEngine.preferred_drawing_method == "font_shapes":
sys.stderr.write("customtkinter.__init__ warning: " + sys.stderr.write("customtkinter.__init__ warning: " +
@ -46,6 +51,8 @@ from .widgets.ctk_label import CTkLabel
from .widgets.ctk_radiobutton import CTkRadioButton from .widgets.ctk_radiobutton import CTkRadioButton
from .widgets.ctk_canvas import CTkCanvas from .widgets.ctk_canvas import CTkCanvas
from .widgets.ctk_switch import CTkSwitch from .widgets.ctk_switch import CTkSwitch
from .widgets.ctk_optionmenu import CTkOptionMenu
from .widgets.ctk_combobox import CTkComboBox
# import windows # import windows
from .windows.ctk_tk import CTk from .windows.ctk_tk import CTk

View File

@ -19,7 +19,7 @@ except Exception:
class AppearanceModeTracker: class AppearanceModeTracker:
callback_list = [] callback_list = []
root_tk_list = [] app_list = []
update_loop_running = False update_loop_running = False
update_loop_interval = 500 # milliseconds update_loop_interval = 500 # milliseconds
@ -40,12 +40,12 @@ class AppearanceModeTracker:
cls.callback_list.append(callback) cls.callback_list.append(callback)
if widget is not None: if widget is not None:
root_tk = cls.get_tk_root_of_widget(widget) app = cls.get_tk_root_of_widget(widget)
if root_tk not in cls.root_tk_list: if app not in cls.app_list:
cls.root_tk_list.append(root_tk) cls.app_list.append(app)
if not cls.update_loop_running: if not cls.update_loop_running:
root_tk.after(500, cls.update) app.after(500, cls.update)
cls.update_loop_running = True cls.update_loop_running = True
@classmethod @classmethod
@ -97,9 +97,9 @@ class AppearanceModeTracker:
cls.update_callbacks() cls.update_callbacks()
# find an existing tkinter.Tk object for the next call of .after() # find an existing tkinter.Tk object for the next call of .after()
for root_tk in cls.root_tk_list: for app in cls.app_list:
try: try:
root_tk.after(cls.update_loop_interval, cls.update) app.after(cls.update_loop_interval, cls.update)
return return
except Exception: except Exception:
continue continue

View File

@ -1,32 +1,39 @@
{ {
"color": { "color": {
"window_bg_color": ["gray95", "gray12"], "window_bg_color": ["#EBEBEC", "#212325"],
"button":["#5B97D3", "#3373B8"], "button": ["#3B8ED0", "#1F6AA5"],
"button_hover": ["#4A7BAD", "#1D538D"], "button_hover": ["#36719F", "#144870"],
"button_border": ["gray40", "#D5D9DE"], "button_border": ["#3E454A", "#949A9F"],
"checkbox_border": ["gray40", "#D5D9DE"], "checkbox_border": ["#3E454A", "#949A9F"],
"checkmark": ["white", "gray90"], "checkmark": ["white", "gray90"],
"entry": ["white", "gray24"], "entry": ["#F9F9FA", "#343638"],
"entry_border": ["gray70", "gray32"], "entry_border": ["#979DA2", "#565B5E"],
"entry_placeholder_text": ["gray52", "gray62"], "entry_placeholder_text": ["gray52", "gray62"],
"frame_border": ["#A7C2E0", "#5FB4DD"], "frame_border": ["#979DA2", "#1F2122"],
"frame_low": ["#E3E4E5", "gray16"], "frame_low": ["#D1D5D8", "#2A2D2E"],
"frame_high": ["#D7D8D9", "gray22"], "frame_high": ["#D7D8D9", "#343638"],
"label": [null, null], "label": [null, null],
"text": ["gray20", "#D5D9DE"], "text": ["gray10", "#DCE4EE"],
"text_disabled": ["gray60", "#777B80"], "text_disabled": ["gray60", "#777B80"],
"text_button_disabled": ["gray40", "gray74"], "text_button_disabled": ["gray40", "gray74"],
"progressbar": ["#6B6B6B", "gray0"], "progressbar": ["#939BA2", "#4A4D50"],
"progressbar_progress": ["#5B97D3", "#3373B8"], "progressbar_progress": ["#3B8ED0", "#1F6AA5"],
"progressbar_border": ["gray", "gray"], "progressbar_border": ["gray", "gray"],
"slider": ["#6B6B6B", "gray0"], "slider": ["#939BA2", "#4A4D50"],
"slider_progress": ["white", "gray40"], "slider_progress": ["white", "#AAB0B5"],
"slider_button": ["#5B97D3", "#3373B8"], "slider_button": ["#3B8ED0", "#1F6AA5"],
"slider_button_hover": ["#4A7BAD", "#1D538D"], "slider_button_hover": ["#36719F", "#144870"],
"switch": ["gray70", "gray35"], "switch": ["#939BA2", "#4A4D50"],
"switch_progress": ["#5B97D3", "#3373B8"], "switch_progress": ["#3B8ED0", "#1F6AA5"],
"switch_button": ["gray36", "#D5D9DE"], "switch_button": ["gray36", "#D5D9DE"],
"switch_button_hover": ["gray20", "gray100"] "switch_button_hover": ["gray20", "gray100"],
"optionmenu_button": ["#36719F", "#144870"],
"optionmenu_button_hover": ["#27577D", "#203A4F"],
"combobox_border": ["#979DA2", "#565B5E"],
"combobox_button_hover": ["#6E7174", "#7A848D"],
"dropdown_color": ["gray90", "gray20"],
"dropdown_hover": ["gray75", "gray28"],
"dropdown_text": ["gray10", "#DCE4EE"]
}, },
"text": { "text": {
"macOS": { "macOS": {
@ -43,21 +50,21 @@
} }
}, },
"shape": { "shape": {
"button_corner_radius": 8, "button_corner_radius": 6,
"button_border_width": 0, "button_border_width": 0,
"checkbox_corner_radius": 7, "checkbox_corner_radius": 6,
"checkbox_border_width": 3, "checkbox_border_width": 3,
"radiobutton_corner_radius": 1000, "radiobutton_corner_radius": 1000,
"radiobutton_border_width_unchecked": 3, "radiobutton_border_width_unchecked": 3,
"radiobutton_border_width_checked": 6, "radiobutton_border_width_checked": 6,
"entry_border_width": 2, "entry_border_width": 2,
"frame_corner_radius": 8, "frame_corner_radius": 6,
"frame_border_width": 0, "frame_border_width": 0,
"label_corner_radius": 8, "label_corner_radius": 8,
"progressbar_border_width": 0, "progressbar_border_width": 0,
"progressbar_corner_radius": 1000, "progressbar_corner_radius": 1000,
"slider_border_width": 6, "slider_border_width": 6,
"slider_corner_radius": 8, "slider_corner_radius": 1000,
"slider_button_length": 0, "slider_button_length": 0,
"slider_button_corner_radius": 1000, "slider_button_corner_radius": 1000,
"switch_border_width": 3, "switch_border_width": 3,

View File

@ -27,7 +27,13 @@
"switch_progress": ["#608BD5", "#395E9C"], "switch_progress": ["#608BD5", "#395E9C"],
"switch_button": ["gray38", "gray70"], "switch_button": ["gray38", "gray70"],
"switch_button_hover": ["gray30", "gray90"], "switch_button_hover": ["gray30", "gray90"],
"darken_factor": 0.8 "optionmenu_button": ["#36719F", "#144870"],
"optionmenu_button_hover": ["#27577D", "#203A4F"],
"combobox_border": ["gray70", "gray32"],
"combobox_button_hover": ["#6E7174", "#7A848D"],
"dropdown_color": ["gray90", "gray20"],
"dropdown_hover": ["gray75", "gray28"],
"dropdown_text": ["gray10", "#DCE4EE"]
}, },
"text": { "text": {
"macOS": { "macOS": {

View File

@ -27,7 +27,13 @@
"switch_progress": ["#72CF9F", "#11B384"], "switch_progress": ["#72CF9F", "#11B384"],
"switch_button": ["gray38", "gray70"], "switch_button": ["gray38", "gray70"],
"switch_button_hover": ["gray30", "gray90"], "switch_button_hover": ["gray30", "gray90"],
"darken_factor": 0.8 "optionmenu_button": ["#0E9670", "#0D8A66"],
"optionmenu_button_hover":["gray40", "gray70"],
"combobox_border": ["gray70", "gray32"],
"combobox_button_hover": ["#6E7174", "#7A848D"],
"dropdown_color": ["gray90", "gray20"],
"dropdown_hover": ["gray75", "gray28"],
"dropdown_text": ["gray10", "#DCE4EE"]
}, },
"text": { "text": {
"macOS": { "macOS": {
@ -66,4 +72,4 @@
"switch_button_corner_radius": 1000, "switch_button_corner_radius": 1000,
"switch_button_length": 0 "switch_button_length": 0
} }
} }

View File

@ -27,7 +27,13 @@
"switch_progress": ["#00e6c3", "#00e6c3"], "switch_progress": ["#00e6c3", "#00e6c3"],
"switch_button": ["#2e324a", "#2e324a"], "switch_button": ["#2e324a", "#2e324a"],
"switch_button_hover": ["#2e324a", "#2e324a"], "switch_button_hover": ["#2e324a", "#2e324a"],
"darken_factor": 0.1 "optionmenu_button": ["#36719F", "#144870"],
"optionmenu_button_hover": ["#27577D", "#203A4F"],
"combobox_border": ["#979DA2", "#565B5E"],
"combobox_button_hover": ["#6E7174", "#7A848D"],
"dropdown_color": ["gray90", "gray20"],
"dropdown_hover": ["gray75", "gray28"],
"dropdown_text": ["gray10", "#DCE4EE"]
}, },
"text": { "text": {
"macOS": { "macOS": {
@ -66,4 +72,4 @@
"switch_button_corner_radius": 1000, "switch_button_corner_radius": 1000,
"switch_button_length": 2 "switch_button_length": 2
} }
} }

View File

@ -17,9 +17,11 @@ class DrawEngine:
Functions: Functions:
- draw_rounded_rect_with_border() - draw_rounded_rect_with_border()
- draw_rounded_rect_with_border_vertical_split()
- draw_rounded_progress_bar_with_border() - draw_rounded_progress_bar_with_border()
- draw_rounded_slider_with_border_and_button() - draw_rounded_slider_with_border_and_button()
- draw_checkmark() - draw_checkmark()
- draw_dropdown_arrow()
""" """
@ -27,9 +29,8 @@ class DrawEngine:
def __init__(self, canvas: CTkCanvas): def __init__(self, canvas: CTkCanvas):
self._canvas = canvas self._canvas = canvas
self._existing_tags = set()
def _calc_optimal_corner_radius(self, user_corner_radius: Union[float, int]) -> Union[float, int]: def __calc_optimal_corner_radius(self, user_corner_radius: Union[float, int]) -> Union[float, int]:
# optimize for drawing with polygon shapes # optimize for drawing with polygon shapes
if self.preferred_drawing_method == "polygon_shapes": if self.preferred_drawing_method == "polygon_shapes":
if sys.platform == "darwin": if sys.platform == "darwin":
@ -53,13 +54,14 @@ class DrawEngine:
else: else:
return user_corner_radius return user_corner_radius
def draw_rounded_rect_with_border(self, width: int, height: int, corner_radius: Union[float, int], border_width: Union[float, int]) -> bool: 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:
""" Draws a rounded rectangle with a corner_radius and border_width on the canvas. The border elements have a 'border_parts' tag, """ Draws a rounded rectangle with a corner_radius and border_width on the canvas. The border elements have a 'border_parts' tag,
the main foreground elements have an 'inner_parts' tag to color the elements accordingly. the main foreground elements have an 'inner_parts' tag to color the elements accordingly.
returns bool if recoloring is necessary """ returns bool if recoloring is necessary """
width = math.floor(width / 2) * 2 # round (floor) current_width and current_height and restrict them to even values only width = math.floor(width / 2) * 2 # round (floor) _current_width and _current_height and restrict them to even values only
height = math.floor(height / 2) * 2 height = math.floor(height / 2) * 2
corner_radius = round(corner_radius) corner_radius = round(corner_radius)
@ -67,21 +69,26 @@ class DrawEngine:
corner_radius = min(width / 2, height / 2) corner_radius = min(width / 2, height / 2)
border_width = round(border_width) border_width = round(border_width)
corner_radius = self._calc_optimal_corner_radius(corner_radius) # optimize corner_radius for different drawing methods (different rounding) corner_radius = self.__calc_optimal_corner_radius(corner_radius) # optimize corner_radius for different drawing methods (different rounding)
if corner_radius >= border_width: if corner_radius >= border_width:
inner_corner_radius = corner_radius - border_width inner_corner_radius = corner_radius - border_width
else: else:
inner_corner_radius = 0 inner_corner_radius = 0
if self.preferred_drawing_method == "polygon_shapes": if overwrite_preferred_drawing_method is not None:
return self._draw_rounded_rect_with_border_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius) preferred_drawing_method = overwrite_preferred_drawing_method
elif self.preferred_drawing_method == "font_shapes": else:
return self._draw_rounded_rect_with_border_font_shapes(width, height, corner_radius, border_width, inner_corner_radius, ()) preferred_drawing_method = self.preferred_drawing_method
elif self.preferred_drawing_method == "circle_shapes":
return self._draw_rounded_rect_with_border_circle_shapes(width, height, corner_radius, border_width, inner_corner_radius)
def _draw_rounded_rect_with_border_polygon_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int) -> bool: if preferred_drawing_method == "polygon_shapes":
return self.__draw_rounded_rect_with_border_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius)
elif preferred_drawing_method == "font_shapes":
return self.__draw_rounded_rect_with_border_font_shapes(width, height, corner_radius, border_width, inner_corner_radius, ())
elif preferred_drawing_method == "circle_shapes":
return self.__draw_rounded_rect_with_border_circle_shapes(width, height, corner_radius, border_width, inner_corner_radius)
def __draw_rounded_rect_with_border_polygon_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int) -> bool:
requires_recoloring = False requires_recoloring = False
# create border button parts (only if border exists) # create border button parts (only if border exists)
@ -134,8 +141,8 @@ class DrawEngine:
return requires_recoloring return requires_recoloring
def _draw_rounded_rect_with_border_font_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int, def __draw_rounded_rect_with_border_font_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int,
exclude_parts: tuple) -> bool: exclude_parts: tuple) -> bool:
requires_recoloring = False requires_recoloring = False
# create border button parts # create border button parts
@ -272,7 +279,7 @@ class DrawEngine:
return requires_recoloring return requires_recoloring
def _draw_rounded_rect_with_border_circle_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int) -> bool: def __draw_rounded_rect_with_border_circle_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int) -> bool:
requires_recoloring = False requires_recoloring = False
# border button parts # border button parts
@ -346,22 +353,302 @@ class DrawEngine:
return requires_recoloring return requires_recoloring
def draw_rounded_progress_bar_with_border(self, width: int, height: int, corner_radius: Union[float, int], border_width: Union[float, int], def draw_rounded_rect_with_border_vertical_split(self, width: Union[float, int], height: Union[float, int], corner_radius: Union[float, int],
progress_value: float, orientation: str) -> bool: border_width: Union[float, int], left_section_width: Union[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,
to color the elements accordingly.
returns bool if recoloring is necessary """
left_section_width = round(left_section_width)
width = math.floor(width / 2) * 2 # round (floor) _current_width and _current_height and restrict them to even values only
height = math.floor(height / 2) * 2
corner_radius = round(corner_radius)
if corner_radius > width / 2 or corner_radius > height / 2: # restrict corner_radius if it's too larger
corner_radius = min(width / 2, height / 2)
border_width = round(border_width)
corner_radius = self.__calc_optimal_corner_radius(corner_radius) # optimize corner_radius for different drawing methods (different rounding)
if corner_radius >= border_width:
inner_corner_radius = corner_radius - border_width
else:
inner_corner_radius = 0
if left_section_width > width - corner_radius * 2:
left_section_width = width - corner_radius * 2
elif left_section_width < corner_radius * 2:
left_section_width = corner_radius * 2
if self.preferred_drawing_method == "polygon_shapes" or self.preferred_drawing_method == "circle_shapes":
return self.__draw_rounded_rect_with_border_vertical_split_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius, left_section_width)
elif self.preferred_drawing_method == "font_shapes":
return self.__draw_rounded_rect_with_border_vertical_split_font_shapes(width, height, corner_radius, border_width, inner_corner_radius, left_section_width, ())
def __draw_rounded_rect_with_border_vertical_split_polygon_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int,
left_section_width: int) -> bool:
requires_recoloring = False
# create border button parts (only if border exists)
if border_width > 0:
if not self._canvas.find_withtag("border_parts"):
self._canvas.create_polygon((0, 0, 0, 0), tags=("border_line_left_1", "border_parts_left", "border_parts", "left_parts"))
self._canvas.create_polygon((0, 0, 0, 0), tags=("border_line_right_1", "border_parts_right", "border_parts", "right_parts"))
self._canvas.create_rectangle((0, 0, 0, 0), tags=("border_rect_left_1", "border_parts_left", "border_parts", "left_parts"))
self._canvas.create_rectangle((0, 0, 0, 0), tags=("border_rect_right_1", "border_parts_right", "border_parts", "right_parts"))
requires_recoloring = True
self._canvas.coords("border_line_left_1",
(corner_radius,
corner_radius,
left_section_width - corner_radius,
corner_radius,
left_section_width - corner_radius,
height - corner_radius,
corner_radius,
height - corner_radius))
self._canvas.coords("border_line_right_1",
(left_section_width + corner_radius,
corner_radius,
width - corner_radius,
corner_radius,
width - corner_radius,
height - corner_radius,
left_section_width + corner_radius,
height - corner_radius))
self._canvas.coords("border_rect_left_1",
(left_section_width - corner_radius,
0,
left_section_width,
height))
self._canvas.coords("border_rect_right_1",
(left_section_width,
0,
left_section_width + corner_radius,
height))
self._canvas.itemconfig("border_line_left_1", joinstyle=tkinter.ROUND, width=corner_radius * 2)
self._canvas.itemconfig("border_line_right_1", joinstyle=tkinter.ROUND, width=corner_radius * 2)
else:
self._canvas.delete("border_parts")
# create inner button parts
if not self._canvas.find_withtag("inner_parts"):
self._canvas.create_polygon((0, 0, 0, 0), tags=("inner_line_left_1", "inner_parts_left", "inner_parts", "left_parts"), joinstyle=tkinter.ROUND)
self._canvas.create_polygon((0, 0, 0, 0), tags=("inner_line_right_1", "inner_parts_right", "inner_parts", "right_parts"), joinstyle=tkinter.ROUND)
self._canvas.create_rectangle((0, 0, 0, 0), tags=("inner_rect_left_1", "inner_parts_left", "inner_parts", "left_parts"), width=0)
self._canvas.create_rectangle((0, 0, 0, 0), tags=("inner_rect_right_1", "inner_parts_right", "inner_parts", "right_parts"), width=0)
requires_recoloring = True
self._canvas.coords("inner_line_left_1",
corner_radius,
corner_radius,
left_section_width - inner_corner_radius,
corner_radius,
left_section_width - inner_corner_radius,
height - corner_radius,
corner_radius,
height - corner_radius)
self._canvas.coords("inner_line_right_1",
left_section_width + inner_corner_radius,
corner_radius,
width - corner_radius,
corner_radius,
width - corner_radius,
height - corner_radius,
left_section_width + inner_corner_radius,
height - corner_radius)
self._canvas.coords("inner_rect_left_1",
(left_section_width - inner_corner_radius,
border_width,
left_section_width,
height - border_width))
self._canvas.coords("inner_rect_right_1",
(left_section_width,
border_width,
left_section_width + inner_corner_radius,
height - border_width))
self._canvas.itemconfig("inner_line_left_1", width=inner_corner_radius * 2)
self._canvas.itemconfig("inner_line_right_1", width=inner_corner_radius * 2)
if requires_recoloring: # new parts were added -> manage z-order
self._canvas.tag_lower("inner_parts")
self._canvas.tag_lower("border_parts")
return requires_recoloring
def __draw_rounded_rect_with_border_vertical_split_font_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int,
left_section_width: int, exclude_parts: tuple) -> bool:
requires_recoloring = False
# create border button parts
if border_width > 0:
if corner_radius > 0:
# create canvas border corner parts if not already created, but only if needed, and delete if not needed
if not self._canvas.find_withtag("border_oval_1_a") and "border_oval_1" not in exclude_parts:
self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_1_a", "border_corner_part", "border_parts_left", "border_parts", "left_parts"), anchor=tkinter.CENTER)
self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_1_b", "border_corner_part", "border_parts_left", "border_parts", "left_parts"), anchor=tkinter.CENTER, angle=180)
requires_recoloring = True
elif self._canvas.find_withtag("border_oval_1_a") and "border_oval_1" in exclude_parts:
self._canvas.delete("border_oval_1_a", "border_oval_1_b")
if not self._canvas.find_withtag("border_oval_2_a") and width > 2 * corner_radius and "border_oval_2" not in exclude_parts:
self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_2_a", "border_corner_part", "border_parts_right", "border_parts", "right_parts"), anchor=tkinter.CENTER)
self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_2_b", "border_corner_part", "border_parts_right", "border_parts", "right_parts"), anchor=tkinter.CENTER, angle=180)
requires_recoloring = True
elif self._canvas.find_withtag("border_oval_2_a") and (not width > 2 * corner_radius or "border_oval_2" in exclude_parts):
self._canvas.delete("border_oval_2_a", "border_oval_2_b")
if not self._canvas.find_withtag("border_oval_3_a") and height > 2 * corner_radius \
and width > 2 * corner_radius and "border_oval_3" not in exclude_parts:
self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_3_a", "border_corner_part", "border_parts_right", "border_parts", "right_parts"), anchor=tkinter.CENTER)
self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_3_b", "border_corner_part", "border_parts_right", "border_parts", "right_parts"), anchor=tkinter.CENTER, angle=180)
requires_recoloring = True
elif self._canvas.find_withtag("border_oval_3_a") and (not (height > 2 * corner_radius
and width > 2 * corner_radius) or "border_oval_3" in exclude_parts):
self._canvas.delete("border_oval_3_a", "border_oval_3_b")
if not self._canvas.find_withtag("border_oval_4_a") and height > 2 * corner_radius and "border_oval_4" not in exclude_parts:
self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_4_a", "border_corner_part", "border_parts_left", "border_parts", "left_parts"), anchor=tkinter.CENTER)
self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_4_b", "border_corner_part", "border_parts_left", "border_parts", "left_parts"), anchor=tkinter.CENTER, angle=180)
requires_recoloring = True
elif self._canvas.find_withtag("border_oval_4_a") and (not height > 2 * corner_radius or "border_oval_4" in exclude_parts):
self._canvas.delete("border_oval_4_a", "border_oval_4_b")
# change position of border corner parts
self._canvas.coords("border_oval_1_a", corner_radius, corner_radius, corner_radius)
self._canvas.coords("border_oval_1_b", corner_radius, corner_radius, corner_radius)
self._canvas.coords("border_oval_2_a", width - corner_radius, corner_radius, corner_radius)
self._canvas.coords("border_oval_2_b", width - corner_radius, corner_radius, corner_radius)
self._canvas.coords("border_oval_3_a", width - corner_radius, height - corner_radius, corner_radius)
self._canvas.coords("border_oval_3_b", width - corner_radius, height - corner_radius, corner_radius)
self._canvas.coords("border_oval_4_a", corner_radius, height - corner_radius, corner_radius)
self._canvas.coords("border_oval_4_b", corner_radius, height - corner_radius, corner_radius)
else:
self._canvas.delete("border_corner_part") # delete border corner parts if not needed
# create canvas border rectangle parts if not already created
if not self._canvas.find_withtag("border_rectangle_1"):
self._canvas.create_rectangle(0, 0, 0, 0, tags=("border_rectangle_left_1", "border_rectangle_part", "border_parts_left", "border_parts", "left_parts"), width=0)
self._canvas.create_rectangle(0, 0, 0, 0, tags=("border_rectangle_left_2", "border_rectangle_part", "border_parts_left", "border_parts", "left_parts"), width=0)
self._canvas.create_rectangle(0, 0, 0, 0, tags=("border_rectangle_right_1", "border_rectangle_part", "border_parts_right", "border_parts", "right_parts"), width=0)
self._canvas.create_rectangle(0, 0, 0, 0, tags=("border_rectangle_right_2", "border_rectangle_part", "border_parts_right", "border_parts", "right_parts"), width=0)
requires_recoloring = True
# change position of border rectangle parts
self._canvas.coords("border_rectangle_left_1", (0, corner_radius, left_section_width, height - corner_radius))
self._canvas.coords("border_rectangle_left_2", (corner_radius, 0, left_section_width, height))
self._canvas.coords("border_rectangle_right_1", (left_section_width, corner_radius, width, height - corner_radius))
self._canvas.coords("border_rectangle_right_2", (left_section_width, 0, width - corner_radius, height))
else:
self._canvas.delete("border_parts")
# create inner button parts
if inner_corner_radius > 0:
# create canvas border corner parts if not already created, but only if they're needed and delete if not needed
if not self._canvas.find_withtag("inner_oval_1_a") and "inner_oval_1" not in exclude_parts:
self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_1_a", "inner_corner_part", "inner_parts_left", "inner_parts", "left_parts"), anchor=tkinter.CENTER)
self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_1_b", "inner_corner_part", "inner_parts_left", "inner_parts", "left_parts"), anchor=tkinter.CENTER, angle=180)
requires_recoloring = True
elif self._canvas.find_withtag("inner_oval_1_a") and "inner_oval_1" in exclude_parts:
self._canvas.delete("inner_oval_1_a", "inner_oval_1_b")
if not self._canvas.find_withtag("inner_oval_2_a") and width - (2 * border_width) > 2 * inner_corner_radius and "inner_oval_2" not in exclude_parts:
self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_2_a", "inner_corner_part","inner_parts_right", "inner_parts", "right_parts"), anchor=tkinter.CENTER)
self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_2_b", "inner_corner_part", "inner_parts_right", "inner_parts", "right_parts"), anchor=tkinter.CENTER, angle=180)
requires_recoloring = True
elif self._canvas.find_withtag("inner_oval_2_a") and (not width - (2 * border_width) > 2 * inner_corner_radius or "inner_oval_2" in exclude_parts):
self._canvas.delete("inner_oval_2_a", "inner_oval_2_b")
if not self._canvas.find_withtag("inner_oval_3_a") and height - (2 * border_width) > 2 * inner_corner_radius \
and width - (2 * border_width) > 2 * inner_corner_radius and "inner_oval_3" not in exclude_parts:
self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_3_a", "inner_corner_part", "inner_parts_right", "inner_parts", "right_parts"), anchor=tkinter.CENTER)
self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_3_b", "inner_corner_part", "inner_parts_right", "inner_parts", "right_parts"), anchor=tkinter.CENTER, angle=180)
requires_recoloring = True
elif self._canvas.find_withtag("inner_oval_3_a") and (not (height - (2 * border_width) > 2 * inner_corner_radius
and width - (2 * border_width) > 2 * inner_corner_radius) or "inner_oval_3" in exclude_parts):
self._canvas.delete("inner_oval_3_a", "inner_oval_3_b")
if not self._canvas.find_withtag("inner_oval_4_a") and height - (2 * border_width) > 2 * inner_corner_radius and "inner_oval_4" not in exclude_parts:
self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_4_a", "inner_corner_part", "inner_parts_left", "inner_parts", "left_parts"), anchor=tkinter.CENTER)
self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_4_b", "inner_corner_part", "inner_parts_left", "inner_parts", "left_parts"), anchor=tkinter.CENTER, angle=180)
requires_recoloring = True
elif self._canvas.find_withtag("inner_oval_4_a") and (not height - (2 * border_width) > 2 * inner_corner_radius or "inner_oval_4" in exclude_parts):
self._canvas.delete("inner_oval_4_a", "inner_oval_4_b")
# change position of border corner parts
self._canvas.coords("inner_oval_1_a", border_width + inner_corner_radius, border_width + inner_corner_radius, inner_corner_radius)
self._canvas.coords("inner_oval_1_b", border_width + inner_corner_radius, border_width + inner_corner_radius, inner_corner_radius)
self._canvas.coords("inner_oval_2_a", width - border_width - inner_corner_radius, border_width + inner_corner_radius, inner_corner_radius)
self._canvas.coords("inner_oval_2_b", width - border_width - inner_corner_radius, border_width + inner_corner_radius, inner_corner_radius)
self._canvas.coords("inner_oval_3_a", width - border_width - inner_corner_radius, height - border_width - inner_corner_radius, inner_corner_radius)
self._canvas.coords("inner_oval_3_b", width - border_width - inner_corner_radius, height - border_width - inner_corner_radius, inner_corner_radius)
self._canvas.coords("inner_oval_4_a", border_width + inner_corner_radius, height - border_width - inner_corner_radius, inner_corner_radius)
self._canvas.coords("inner_oval_4_b", border_width + inner_corner_radius, height - border_width - inner_corner_radius, inner_corner_radius)
else:
self._canvas.delete("inner_corner_part") # delete inner corner parts if not needed
# create canvas inner rectangle parts if not already created
if not self._canvas.find_withtag("inner_rectangle_1"):
self._canvas.create_rectangle(0, 0, 0, 0, tags=("inner_rectangle_left_1", "inner_rectangle_part", "inner_parts_left", "inner_parts", "left_parts"), width=0)
self._canvas.create_rectangle(0, 0, 0, 0, tags=("inner_rectangle_right_1", "inner_rectangle_part", "inner_parts_right", "inner_parts", "right_parts"), width=0)
requires_recoloring = True
if not self._canvas.find_withtag("inner_rectangle_2") and inner_corner_radius * 2 < height - (border_width * 2):
self._canvas.create_rectangle(0, 0, 0, 0, tags=("inner_rectangle_left_2", "inner_rectangle_part", "inner_parts_left", "inner_parts", "left_parts"), width=0)
self._canvas.create_rectangle(0, 0, 0, 0, tags=("inner_rectangle_right_2", "inner_rectangle_part", "inner_parts_right", "inner_parts", "right_parts"), width=0)
requires_recoloring = True
elif self._canvas.find_withtag("inner_rectangle_2") and not inner_corner_radius * 2 < height - (border_width * 2):
self._canvas.delete("inner_rectangle_left_2")
self._canvas.delete("inner_rectangle_right_2")
# change position of inner rectangle parts
self._canvas.coords("inner_rectangle_left_1", (border_width + inner_corner_radius,
border_width,
left_section_width,
height - border_width))
self._canvas.coords("inner_rectangle_left_2", (border_width,
border_width + inner_corner_radius,
left_section_width,
height - inner_corner_radius - border_width))
self._canvas.coords("inner_rectangle_right_1", (left_section_width,
border_width,
width - border_width - inner_corner_radius,
height - border_width))
self._canvas.coords("inner_rectangle_right_2", (left_section_width,
border_width + inner_corner_radius,
width - border_width,
height - inner_corner_radius - border_width))
if requires_recoloring: # new parts were added -> manage z-order
self._canvas.tag_lower("inner_parts")
self._canvas.tag_lower("border_parts")
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: float, orientation: str) -> bool:
""" Draws a rounded bar on the canvas, which is split in half according to the argument 'progress_value' (0 - 1). """ Draws a rounded bar on the canvas, which is split in half according to the argument 'progress_value' (0 - 1).
The border elements get the 'border_parts' tag", the main elements get the 'inner_parts' tag and The border elements get the 'border_parts' tag", the main elements get the 'inner_parts' tag and
the progress elements get the 'progress_parts' tag. The 'orientation' argument defines from which direction the progress starts (n, w, s, e). the progress elements get the 'progress_parts' tag. The 'orientation' argument defines from which direction the progress starts (n, w, s, e).
returns bool if recoloring is necessary """ returns bool if recoloring is necessary """
width = math.floor(width / 2) * 2 # round current_width and current_height and restrict them to even values only width = math.floor(width / 2) * 2 # round _current_width and _current_height and restrict them to even values only
height = math.floor(height / 2) * 2 height = math.floor(height / 2) * 2
if corner_radius > width / 2 or corner_radius > height / 2: # restrict corner_radius if it's too larger if corner_radius > width / 2 or corner_radius > height / 2: # restrict corner_radius if it's too larger
corner_radius = min(width / 2, height / 2) corner_radius = min(width / 2, height / 2)
border_width = round(border_width) border_width = round(border_width)
corner_radius = self._calc_optimal_corner_radius(corner_radius) # optimize corner_radius for different drawing methods (different rounding) corner_radius = self.__calc_optimal_corner_radius(corner_radius) # optimize corner_radius for different drawing methods (different rounding)
if corner_radius >= border_width: if corner_radius >= border_width:
inner_corner_radius = corner_radius - border_width inner_corner_radius = corner_radius - border_width
@ -369,16 +656,16 @@ class DrawEngine:
inner_corner_radius = 0 inner_corner_radius = 0
if self.preferred_drawing_method == "polygon_shapes" or self.preferred_drawing_method == "circle_shapes": if self.preferred_drawing_method == "polygon_shapes" or self.preferred_drawing_method == "circle_shapes":
return self._draw_rounded_progress_bar_with_border_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius, return self.__draw_rounded_progress_bar_with_border_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius,
progress_value, orientation) progress_value, orientation)
elif self.preferred_drawing_method == "font_shapes": elif self.preferred_drawing_method == "font_shapes":
return self._draw_rounded_progress_bar_with_border_font_shapes(width, height, corner_radius, border_width, inner_corner_radius, return self.__draw_rounded_progress_bar_with_border_font_shapes(width, height, corner_radius, border_width, inner_corner_radius,
progress_value, orientation) progress_value, orientation)
def _draw_rounded_progress_bar_with_border_polygon_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int, def __draw_rounded_progress_bar_with_border_polygon_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int,
progress_value: float, orientation: str) -> bool: progress_value: float, orientation: str) -> bool:
requires_recoloring = self._draw_rounded_rect_with_border_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius) requires_recoloring = self.__draw_rounded_rect_with_border_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius)
if corner_radius <= border_width: if corner_radius <= border_width:
bottom_right_shift = 0 # weird canvas rendering inaccuracy that has to be corrected in some cases bottom_right_shift = 0 # weird canvas rendering inaccuracy that has to be corrected in some cases
@ -417,8 +704,8 @@ class DrawEngine:
return requires_recoloring return requires_recoloring
def _draw_rounded_progress_bar_with_border_font_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int, def __draw_rounded_progress_bar_with_border_font_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int,
progress_value: float, orientation: str) -> bool: progress_value: float, orientation: str) -> bool:
requires_recoloring, requires_recoloring_2 = False, False requires_recoloring, requires_recoloring_2 = False, False
@ -452,8 +739,8 @@ class DrawEngine:
# horizontal orientation from the bottom # horizontal orientation from the bottom
if orientation == "w": if orientation == "w":
requires_recoloring_2 = self._draw_rounded_rect_with_border_font_shapes(width, height, corner_radius, border_width, inner_corner_radius, requires_recoloring_2 = self.__draw_rounded_rect_with_border_font_shapes(width, height, corner_radius, border_width, inner_corner_radius,
("inner_oval_1", "inner_oval_4")) ("inner_oval_1", "inner_oval_4"))
# set positions of progress corner parts # set positions of progress corner parts
self._canvas.coords("progress_oval_1_a", border_width + inner_corner_radius, border_width + inner_corner_radius, inner_corner_radius) self._canvas.coords("progress_oval_1_a", border_width + inner_corner_radius, border_width + inner_corner_radius, inner_corner_radius)
@ -483,8 +770,8 @@ class DrawEngine:
# vertical orientation from the bottom # vertical orientation from the bottom
if orientation == "s": if orientation == "s":
requires_recoloring_2 = self._draw_rounded_rect_with_border_font_shapes(width, height, corner_radius, border_width, inner_corner_radius, requires_recoloring_2 = self.__draw_rounded_rect_with_border_font_shapes(width, height, corner_radius, border_width, inner_corner_radius,
("inner_oval_3", "inner_oval_4")) ("inner_oval_3", "inner_oval_4"))
# set positions of progress corner parts # set positions of progress corner parts
self._canvas.coords("progress_oval_1_a", border_width + inner_corner_radius, self._canvas.coords("progress_oval_1_a", border_width + inner_corner_radius,
@ -514,11 +801,11 @@ class DrawEngine:
return requires_recoloring or requires_recoloring_2 return requires_recoloring or requires_recoloring_2
def draw_rounded_slider_with_border_and_button(self, width: int, height: int, corner_radius: Union[float, int], border_width: Union[float, int], def draw_rounded_slider_with_border_and_button(self, width: Union[float, int], height: Union[float, int], corner_radius: Union[float, int],
button_length: Union[float, int], button_corner_radius: Union[float, int], slider_value: float, border_width: Union[float, int], button_length: Union[float, int], button_corner_radius: Union[float, int],
orientation: str) -> bool: slider_value: float, orientation: str) -> bool:
width = math.floor(width / 2) * 2 # round current_width and current_height and restrict them to even values only width = math.floor(width / 2) * 2 # round _current_width and _current_height and restrict them to even values only
height = math.floor(height / 2) * 2 height = math.floor(height / 2) * 2
if corner_radius > width / 2 or corner_radius > height / 2: # restrict corner_radius if it's too larger if corner_radius > width / 2 or corner_radius > height / 2: # restrict corner_radius if it's too larger
@ -530,7 +817,7 @@ class DrawEngine:
button_length = round(button_length) button_length = round(button_length)
border_width = round(border_width) border_width = round(border_width)
button_corner_radius = round(button_corner_radius) button_corner_radius = round(button_corner_radius)
corner_radius = self._calc_optimal_corner_radius(corner_radius) # optimize corner_radius for different drawing methods (different rounding) corner_radius = self.__calc_optimal_corner_radius(corner_radius) # optimize corner_radius for different drawing methods (different rounding)
if corner_radius >= border_width: if corner_radius >= border_width:
inner_corner_radius = corner_radius - border_width inner_corner_radius = corner_radius - border_width
@ -538,18 +825,18 @@ class DrawEngine:
inner_corner_radius = 0 inner_corner_radius = 0
if self.preferred_drawing_method == "polygon_shapes" or self.preferred_drawing_method == "circle_shapes": if self.preferred_drawing_method == "polygon_shapes" or self.preferred_drawing_method == "circle_shapes":
return self._draw_rounded_slider_with_border_and_button_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius, return self.__draw_rounded_slider_with_border_and_button_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius,
button_length, button_corner_radius, slider_value, orientation) button_length, button_corner_radius, slider_value, orientation)
elif self.preferred_drawing_method == "font_shapes": elif self.preferred_drawing_method == "font_shapes":
return self._draw_rounded_slider_with_border_and_button_font_shapes(width, height, corner_radius, border_width, inner_corner_radius, return self.__draw_rounded_slider_with_border_and_button_font_shapes(width, height, corner_radius, border_width, inner_corner_radius,
button_length, button_corner_radius, slider_value, orientation) button_length, button_corner_radius, slider_value, orientation)
def _draw_rounded_slider_with_border_and_button_polygon_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int, def __draw_rounded_slider_with_border_and_button_polygon_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int,
button_length: int, button_corner_radius: int, slider_value: float, orientation: str) -> bool: button_length: int, button_corner_radius: int, slider_value: float, orientation: str) -> bool:
# draw normal progressbar # draw normal progressbar
requires_recoloring = self._draw_rounded_progress_bar_with_border_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius, requires_recoloring = self.__draw_rounded_progress_bar_with_border_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius,
slider_value, orientation) slider_value, orientation)
# create slider button part # create slider button part
if not self._canvas.find_withtag("slider_parts"): if not self._canvas.find_withtag("slider_parts"):
@ -583,12 +870,12 @@ class DrawEngine:
return requires_recoloring return requires_recoloring
def _draw_rounded_slider_with_border_and_button_font_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int, def __draw_rounded_slider_with_border_and_button_font_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int,
button_length: int, button_corner_radius: int, slider_value: float, orientation: str) -> bool: button_length: int, button_corner_radius: int, slider_value: float, orientation: str) -> bool:
# draw normal progressbar # draw normal progressbar
requires_recoloring = self._draw_rounded_progress_bar_with_border_font_shapes(width, height, corner_radius, border_width, inner_corner_radius, requires_recoloring = self.__draw_rounded_progress_bar_with_border_font_shapes(width, height, corner_radius, border_width, inner_corner_radius,
slider_value, orientation) slider_value, orientation)
# create 4 circles (if not needed, then less) # create 4 circles (if not needed, then less)
if not self._canvas.find_withtag("slider_oval_1_a"): if not self._canvas.find_withtag("slider_oval_1_a"):
@ -672,7 +959,7 @@ class DrawEngine:
return requires_recoloring return requires_recoloring
def draw_checkmark(self, width: int, height: int, size: Union[int, float]) -> bool: def draw_checkmark(self, width: Union[float, int], height: Union[float, int], size: Union[int, float]) -> bool:
""" Draws a rounded rectangle with a corner_radius and border_width on the canvas. The border elements have a 'border_parts' tag, """ Draws a rounded rectangle with a corner_radius and border_width on the canvas. The border elements have a 'border_parts' tag,
the main foreground elements have an 'inner_parts' tag to color the elements accordingly. the main foreground elements have an 'inner_parts' tag to color the elements accordingly.
@ -701,3 +988,35 @@ class DrawEngine:
self._canvas.coords("checkmark", round(width / 2), round(height / 2)) self._canvas.coords("checkmark", round(width / 2), round(height / 2))
return requires_recoloring return requires_recoloring
def draw_dropdown_arrow(self, x_position: Union[int, float], y_position: Union[int, float], size: Union[int, float]) -> bool:
""" Draws a dropdown bottom facing arrow at (x_position, y_position) in a given size
returns bool if recoloring is necessary """
x_position, y_position, size = round(x_position), round(y_position), round(size)
requires_recoloring = False
if self.preferred_drawing_method == "polygon_shapes" or self.preferred_drawing_method == "circle_shapes":
if not self._canvas.find_withtag("dropdown_arrow"):
self._canvas.create_line(0, 0, 0, 0, tags="dropdown_arrow", width=round(size / 3), joinstyle=tkinter.ROUND, capstyle=tkinter.ROUND)
self._canvas.tag_raise("dropdown_arrow")
requires_recoloring = True
self._canvas.coords("dropdown_arrow",
x_position - (size / 2),
y_position - (size / 5),
x_position,
y_position + (size / 5),
x_position + (size / 2),
y_position - (size / 5))
elif self.preferred_drawing_method == "font_shapes":
if not self._canvas.find_withtag("dropdown_arrow"):
self._canvas.create_text(0, 0, text="Y", font=("CustomTkinter_shapes_font", -size), tags="dropdown_arrow", anchor=tkinter.CENTER)
self._canvas.tag_raise("dropdown_arrow")
requires_recoloring = True
self._canvas.coords("dropdown_arrow", x_position, y_position)
return requires_recoloring

View File

@ -51,7 +51,7 @@ class FontManager:
return cls.windows_load_font(font_path, private=True, enumerable=False) return cls.windows_load_font(font_path, private=True, enumerable=False)
# Linux # Linux
elif sys.platform.startswith("win"): elif sys.platform.startswith("linux"):
try: try:
shutil.copy(font_path, os.path.expanduser("~/.fonts/")) shutil.copy(font_path, os.path.expanduser("~/.fonts/"))
return True return True

View File

@ -124,7 +124,7 @@ class ScalingTracker:
@classmethod @classmethod
def activate_high_dpi_awareness(cls): def activate_high_dpi_awareness(cls):
""" make process DPI aware, customtkinter elemets will get scaled automatically, """ make process DPI aware, customtkinter elements will get scaled automatically,
only gets activated when CTk object is created """ only gets activated when CTk object is created """
if not cls.deactivate_automatic_dpi_awareness: if not cls.deactivate_automatic_dpi_awareness:
@ -135,42 +135,45 @@ class ScalingTracker:
from ctypes import windll from ctypes import windll
windll.shcore.SetProcessDpiAwareness(2) windll.shcore.SetProcessDpiAwareness(2)
# Microsoft Docs: https://docs.microsoft.com/en-us/windows/win32/api/shellscalingapi/ne-shellscalingapi-process_dpi_awareness # Microsoft Docs: https://docs.microsoft.com/en-us/windows/win32/api/shellscalingapi/ne-shellscalingapi-process_dpi_awareness
else: else:
pass # DPI awareness on Linux not implemented pass # DPI awareness on Linux not implemented
@classmethod @classmethod
def get_window_dpi_scaling(cls, window) -> float: def get_window_dpi_scaling(cls, window) -> float:
if sys.platform == "darwin": if not cls.deactivate_automatic_dpi_awareness:
return 1 # scaling works automatically on macOS if sys.platform == "darwin":
return 1 # scaling works automatically on macOS
elif sys.platform.startswith("win"): elif sys.platform.startswith("win"):
from ctypes import windll, pointer, wintypes from ctypes import windll, pointer, wintypes
DPI100pc = 96 # DPI 96 is 100% scaling DPI100pc = 96 # DPI 96 is 100% scaling
DPI_type = 0 # MDT_EFFECTIVE_DPI = 0, MDT_ANGULAR_DPI = 1, MDT_RAW_DPI = 2 DPI_type = 0 # MDT_EFFECTIVE_DPI = 0, MDT_ANGULAR_DPI = 1, MDT_RAW_DPI = 2
window_hwnd = wintypes.HWND(window.winfo_id()) window_hwnd = wintypes.HWND(window.winfo_id())
monitor_handle = windll.user32.MonitorFromWindow(window_hwnd, wintypes.DWORD(2)) # MONITOR_DEFAULTTONEAREST = 2 monitor_handle = windll.user32.MonitorFromWindow(window_hwnd, wintypes.DWORD(2)) # MONITOR_DEFAULTTONEAREST = 2
x_dpi, y_dpi = wintypes.UINT(), wintypes.UINT() x_dpi, y_dpi = wintypes.UINT(), wintypes.UINT()
windll.shcore.GetDpiForMonitor(monitor_handle, DPI_type, pointer(x_dpi), pointer(y_dpi)) windll.shcore.GetDpiForMonitor(monitor_handle, DPI_type, pointer(x_dpi), pointer(y_dpi))
return (x_dpi.value + y_dpi.value) / (2 * DPI100pc) return (x_dpi.value + y_dpi.value) / (2 * DPI100pc)
else:
return 1 # DPI awareness on Linux not implemented
else: else:
return 1 # DPI awareness on Linux not implemented return 1
@classmethod @classmethod
def check_dpi_scaling(cls): def check_dpi_scaling(cls):
# check for every window if scaling value changed # check for every window if scaling value changed
for window in cls.window_widgets_dict: for window in cls.window_widgets_dict:
current_dpi_scaling_value = cls.get_window_dpi_scaling(window) if window.winfo_exists():
if current_dpi_scaling_value != cls.window_dpi_scaling_dict[window]: current_dpi_scaling_value = cls.get_window_dpi_scaling(window)
cls.window_dpi_scaling_dict[window] = current_dpi_scaling_value if current_dpi_scaling_value != cls.window_dpi_scaling_dict[window]:
cls.update_scaling_callbacks_for_window(window) cls.window_dpi_scaling_dict[window] = current_dpi_scaling_value
cls.update_scaling_callbacks_for_window(window)
# find an existing tkinter object for the next call of .after() # find an existing tkinter object for the next call of .after()
for root_tk in cls.window_widgets_dict.keys(): for app in cls.window_widgets_dict.keys():
try: try:
root_tk.after(cls.update_loop_interval, cls.check_dpi_scaling) app.after(cls.update_loop_interval, cls.check_dpi_scaling)
return return
except Exception: except Exception:
continue continue

View File

@ -3,3 +3,4 @@ class Settings:
cursor_manipulation_enabled = True cursor_manipulation_enabled = True
deactivate_macos_window_header_manipulation = False deactivate_macos_window_header_manipulation = False
deactivate_windows_window_header_manipulation = False deactivate_windows_window_header_manipulation = False
use_dropdown_fallback = True

View File

@ -62,6 +62,19 @@ class ThemeManager:
return cls.rgb2hex(new_rgb) return cls.rgb2hex(new_rgb)
@classmethod
def get_minimal_darker(cls, color: str) -> str:
if color.startswith("#"):
color_rgb = cls.hex2rgb(color)
if color_rgb[0] > 0:
return cls.rgb2hex((color_rgb[0] - 1, color_rgb[1], color_rgb[2]))
elif color_rgb[1] > 0:
return cls.rgb2hex((color_rgb[0], color_rgb[1] - 1, color_rgb[2]))
elif color_rgb[2] > 0:
return cls.rgb2hex((color_rgb[0], color_rgb[1], color_rgb[2] - 1))
else:
return cls.rgb2hex((color_rgb[0] + 1, color_rgb[1], color_rgb[2] - 1)) # otherwise slightly lighter
@classmethod @classmethod
def multiply_hex_color(cls, hex_color: str, factor: float = 1.0) -> str: def multiply_hex_color(cls, hex_color: str, factor: float = 1.0) -> str:
try: try:

View File

@ -20,8 +20,8 @@ class CTkButton(CTkBaseClass):
border_width="default_theme", border_width="default_theme",
command=None, command=None,
textvariable=None, textvariable=None,
width=120, width=140,
height=30, height=28,
corner_radius="default_theme", corner_radius="default_theme",
text_font="default_theme", text_font="default_theme",
text_color="default_theme", text_color="default_theme",
@ -33,7 +33,7 @@ class CTkButton(CTkBaseClass):
state=tkinter.NORMAL, state=tkinter.NORMAL,
**kwargs): **kwargs):
# transfer basic functionality (bg_color, size, appearance_mode, scaling) to CTkBaseClass # transfer basic functionality (bg_color, size, _appearance_mode, scaling) to CTkBaseClass
super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs) super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs)
self.configure_basic_grid() self.configure_basic_grid()
@ -66,8 +66,8 @@ class CTkButton(CTkBaseClass):
self.canvas = CTkCanvas(master=self, self.canvas = CTkCanvas(master=self,
highlightthickness=0, highlightthickness=0,
width=self.apply_widget_scaling(self.desired_width), width=self.apply_widget_scaling(self._desired_width),
height=self.apply_widget_scaling(self.desired_height)) height=self.apply_widget_scaling(self._desired_height))
self.canvas.grid(row=0, column=0, rowspan=2, columnspan=2, sticky="nsew") self.canvas.grid(row=0, column=0, rowspan=2, columnspan=2, sticky="nsew")
self.draw_engine = DrawEngine(self.canvas) self.draw_engine = DrawEngine(self.canvas)
@ -98,34 +98,41 @@ class CTkButton(CTkBaseClass):
self.image_label.destroy() self.image_label.destroy()
self.image_label = None self.image_label = None
self.canvas.configure(width=self.apply_widget_scaling(self.desired_width), self.canvas.configure(width=self.apply_widget_scaling(self._desired_width),
height=self.apply_widget_scaling(self.desired_height)), height=self.apply_widget_scaling(self._desired_height))
self.draw()
def set_dimensions(self, width=None, height=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() self.draw()
def draw(self, no_color_updates=False): def draw(self, no_color_updates=False):
requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.apply_widget_scaling(self.current_width), requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.apply_widget_scaling(self._current_width),
self.apply_widget_scaling(self.current_height), self.apply_widget_scaling(self._current_height),
self.apply_widget_scaling(self.corner_radius), self.apply_widget_scaling(self.corner_radius),
self.apply_widget_scaling(self.border_width)) self.apply_widget_scaling(self.border_width))
if no_color_updates is False or requires_recoloring: if no_color_updates is False or requires_recoloring:
self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self.appearance_mode)) self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
# set color for the button border parts (outline) # set color for the button border parts (outline)
self.canvas.itemconfig("border_parts", self.canvas.itemconfig("border_parts",
outline=ThemeManager.single_color(self.border_color, self.appearance_mode), outline=ThemeManager.single_color(self.border_color, self._appearance_mode),
fill=ThemeManager.single_color(self.border_color, self.appearance_mode)) fill=ThemeManager.single_color(self.border_color, self._appearance_mode))
# set color for inner button parts # set color for inner button parts
if self.fg_color is None: if self.fg_color is None:
self.canvas.itemconfig("inner_parts", self.canvas.itemconfig("inner_parts",
outline=ThemeManager.single_color(self.bg_color, self.appearance_mode), outline=ThemeManager.single_color(self.bg_color, self._appearance_mode),
fill=ThemeManager.single_color(self.bg_color, self.appearance_mode)) fill=ThemeManager.single_color(self.bg_color, self._appearance_mode))
else: else:
self.canvas.itemconfig("inner_parts", self.canvas.itemconfig("inner_parts",
outline=ThemeManager.single_color(self.fg_color, self.appearance_mode), outline=ThemeManager.single_color(self.fg_color, self._appearance_mode),
fill=ThemeManager.single_color(self.fg_color, self.appearance_mode)) fill=ThemeManager.single_color(self.fg_color, self._appearance_mode))
# create text label if text given # create text label if text given
if self.text is not None and self.text != "": if self.text is not None and self.text != "":
@ -142,17 +149,17 @@ class CTkButton(CTkBaseClass):
if no_color_updates is False: if no_color_updates is False:
# set text_label fg color (text color) # set text_label fg color (text color)
self.text_label.configure(fg=ThemeManager.single_color(self.text_color, self.appearance_mode)) self.text_label.configure(fg=ThemeManager.single_color(self.text_color, self._appearance_mode))
if self.state == tkinter.DISABLED: if self.state == tkinter.DISABLED:
self.text_label.configure(fg=(ThemeManager.single_color(self.text_color_disabled, self.appearance_mode))) self.text_label.configure(fg=(ThemeManager.single_color(self.text_color_disabled, self._appearance_mode)))
else: else:
self.text_label.configure(fg=ThemeManager.single_color(self.text_color, self.appearance_mode)) self.text_label.configure(fg=ThemeManager.single_color(self.text_color, self._appearance_mode))
if self.fg_color is None: if self.fg_color is None:
self.text_label.configure(bg=ThemeManager.single_color(self.bg_color, self.appearance_mode)) self.text_label.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
else: else:
self.text_label.configure(bg=ThemeManager.single_color(self.fg_color, self.appearance_mode)) self.text_label.configure(bg=ThemeManager.single_color(self.fg_color, self._appearance_mode))
self.text_label.configure(text=self.text) # set text self.text_label.configure(text=self.text) # set text
@ -176,9 +183,9 @@ class CTkButton(CTkBaseClass):
if no_color_updates is False: if no_color_updates is False:
# set image_label bg color (background color of label) # set image_label bg color (background color of label)
if self.fg_color is None: if self.fg_color is None:
self.image_label.configure(bg=ThemeManager.single_color(self.bg_color, self.appearance_mode)) self.image_label.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
else: else:
self.image_label.configure(bg=ThemeManager.single_color(self.fg_color, self.appearance_mode)) self.image_label.configure(bg=ThemeManager.single_color(self.fg_color, self._appearance_mode))
self.image_label.configure(image=self.image) # set image self.image_label.configure(image=self.image) # set image
@ -290,6 +297,14 @@ class CTkButton(CTkBaseClass):
self.text_label.configure(textvariable=self.textvariable) self.text_label.configure(textvariable=self.textvariable)
del kwargs["textvariable"] del kwargs["textvariable"]
if "width" in kwargs:
self.set_dimensions(width=kwargs["width"])
del kwargs["width"]
if "height" in kwargs:
self.set_dimensions(height=kwargs["height"])
del kwargs["height"]
super().configure(*args, **kwargs) super().configure(*args, **kwargs)
if require_redraw: if require_redraw:
@ -326,16 +341,16 @@ class CTkButton(CTkBaseClass):
# set color of inner button parts to hover color # set color of inner button parts to hover color
self.canvas.itemconfig("inner_parts", self.canvas.itemconfig("inner_parts",
outline=ThemeManager.single_color(inner_parts_color, self.appearance_mode), outline=ThemeManager.single_color(inner_parts_color, self._appearance_mode),
fill=ThemeManager.single_color(inner_parts_color, self.appearance_mode)) fill=ThemeManager.single_color(inner_parts_color, self._appearance_mode))
# set text_label bg color to button hover color # set text_label bg color to button hover color
if self.text_label is not None: if self.text_label is not None:
self.text_label.configure(bg=ThemeManager.single_color(inner_parts_color, self.appearance_mode)) self.text_label.configure(bg=ThemeManager.single_color(inner_parts_color, self._appearance_mode))
# set image_label bg color to button hover color # set image_label bg color to button hover color
if self.image_label is not None: if self.image_label is not None:
self.image_label.configure(bg=ThemeManager.single_color(inner_parts_color, self.appearance_mode)) self.image_label.configure(bg=ThemeManager.single_color(inner_parts_color, self._appearance_mode))
def on_leave(self, event=0): def on_leave(self, event=0):
self.click_animation_running = False self.click_animation_running = False
@ -348,16 +363,16 @@ class CTkButton(CTkBaseClass):
# set color of inner button parts # set color of inner button parts
self.canvas.itemconfig("inner_parts", self.canvas.itemconfig("inner_parts",
outline=ThemeManager.single_color(inner_parts_color, self.appearance_mode), outline=ThemeManager.single_color(inner_parts_color, self._appearance_mode),
fill=ThemeManager.single_color(inner_parts_color, self.appearance_mode)) fill=ThemeManager.single_color(inner_parts_color, self._appearance_mode))
# set text_label bg color (label color) # set text_label bg color (label color)
if self.text_label is not None: if self.text_label is not None:
self.text_label.configure(bg=ThemeManager.single_color(inner_parts_color, self.appearance_mode)) self.text_label.configure(bg=ThemeManager.single_color(inner_parts_color, self._appearance_mode))
# set image_label bg color (image bg color) # set image_label bg color (image bg color)
if self.image_label is not None: if self.image_label is not None:
self.image_label.configure(bg=ThemeManager.single_color(inner_parts_color, self.appearance_mode)) self.image_label.configure(bg=ThemeManager.single_color(inner_parts_color, self._appearance_mode))
def click_animation(self): def click_animation(self):
if self.click_animation_running: if self.click_animation_running:

View File

@ -29,12 +29,19 @@ class CTkCanvas(tkinter.Canvas):
9: 'E', 8: 'F', 7: 'C', 6: 'I', 5: 'E', 4: 'G', 3: 'P', 2: 'R', 1: 'R', 9: 'E', 8: 'F', 7: 'C', 6: 'I', 5: 'E', 4: 'G', 3: 'P', 2: 'R', 1: 'R',
0: 'A'} 0: 'A'}
radius_to_char_fine_linux = {19: 'A', 18: 'A', 17: 'B', 16: 'B', 15: 'B', 14: 'B', 13: 'F', 12: 'C',
11: 'F', 10: 'C',
9: 'D', 8: 'G', 7: 'D', 6: 'F', 5: 'D', 4: 'G', 3: 'M', 2: 'H', 1: 'H',
0: 'A'}
if sys.platform.startswith("win"): if sys.platform.startswith("win"):
if sys.getwindowsversion().build > 20000: # Windows 11 if sys.getwindowsversion().build > 20000: # Windows 11
cls.radius_to_char_fine = radius_to_char_fine_windows_11 cls.radius_to_char_fine = radius_to_char_fine_windows_11
else: # < Windows 11 else: # < Windows 11
cls.radius_to_char_fine = radius_to_char_fine_windows_10 cls.radius_to_char_fine = radius_to_char_fine_windows_10
else: # macOS and Linux elif sys.platform.startswith("linux"): # Optimized on Kali Linux
cls.radius_to_char_fine = radius_to_char_fine_linux
else:
cls.radius_to_char_fine = radius_to_char_fine_windows_10 cls.radius_to_char_fine = radius_to_char_fine_windows_10
def get_char_from_radius(self, radius: int) -> str: def get_char_from_radius(self, radius: int) -> str:

View File

@ -1,5 +1,6 @@
import tkinter import tkinter
import sys import sys
from typing import Union
from .ctk_canvas import CTkCanvas from .ctk_canvas import CTkCanvas
from ..theme_manager import ThemeManager from ..theme_manager import ThemeManager
@ -34,7 +35,7 @@ class CTkCheckBox(CTkBaseClass):
textvariable=None, textvariable=None,
**kwargs): **kwargs):
# transfer basic functionality (bg_color, size, appearance_mode, scaling) to CTkBaseClass # transfer basic functionality (bg_color, size, _appearance_mode, scaling) to CTkBaseClass
super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs) super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs)
# color # color
@ -49,7 +50,7 @@ class CTkCheckBox(CTkBaseClass):
# text # text
self.text = text self.text = text
self.text_label = None self.text_label: Union[tkinter.Label, None] = None
self.text_color = ThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color self.text_color = ThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color
self.text_color_disabled = ThemeManager.theme["color"]["text_disabled"] if text_color_disabled == "default_theme" else text_color_disabled self.text_color_disabled = ThemeManager.theme["color"]["text_disabled"] if text_color_disabled == "default_theme" else text_color_disabled
self.text_font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font self.text_font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font
@ -74,22 +75,19 @@ class CTkCheckBox(CTkBaseClass):
self.bg_canvas = CTkCanvas(master=self, self.bg_canvas = CTkCanvas(master=self,
highlightthickness=0, highlightthickness=0,
width=self.apply_widget_scaling(self.desired_width), width=self.apply_widget_scaling(self._desired_width),
height=self.apply_widget_scaling(self.desired_height)) height=self.apply_widget_scaling(self._desired_height))
self.bg_canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=3, rowspan=1, sticky="nswe") self.bg_canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=3, rowspan=1, sticky="nswe")
self.canvas = CTkCanvas(master=self, self.canvas = CTkCanvas(master=self,
highlightthickness=0, highlightthickness=0,
width=self.apply_widget_scaling(self.desired_width), width=self.apply_widget_scaling(self._desired_width),
height=self.apply_widget_scaling(self.desired_height)) height=self.apply_widget_scaling(self._desired_height))
self.canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=1, rowspan=1) self.canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=1, rowspan=1)
self.draw_engine = DrawEngine(self.canvas) self.draw_engine = DrawEngine(self.canvas)
if self.hover is True: self.canvas.bind("<Enter>", self.on_enter)
self.canvas.bind("<Enter>", self.on_enter) self.canvas.bind("<Leave>", self.on_leave)
self.canvas.bind("<Leave>", self.on_leave)
self.canvas.bind("<Button-1>", self.toggle)
self.canvas.bind("<Button-1>", self.toggle) self.canvas.bind("<Button-1>", self.toggle)
# set select state according to variable # set select state according to variable
@ -100,8 +98,8 @@ class CTkCheckBox(CTkBaseClass):
elif self.variable.get() == self.offvalue: elif self.variable.get() == self.offvalue:
self.deselect(from_variable_callback=True) self.deselect(from_variable_callback=True)
self.set_cursor()
self.draw() # initial draw self.draw() # initial draw
self.set_cursor()
def set_scaling(self, *args, **kwargs): def set_scaling(self, *args, **kwargs):
super().set_scaling(*args, **kwargs) super().set_scaling(*args, **kwargs)
@ -109,8 +107,8 @@ class CTkCheckBox(CTkBaseClass):
self.grid_columnconfigure(1, weight=0, minsize=self.apply_widget_scaling(6)) self.grid_columnconfigure(1, weight=0, minsize=self.apply_widget_scaling(6))
self.text_label.configure(font=self.apply_font_scaling(self.text_font)) self.text_label.configure(font=self.apply_font_scaling(self.text_font))
self.bg_canvas.configure(width=self.apply_widget_scaling(self.desired_width), height=self.apply_widget_scaling(self.desired_height)) self.bg_canvas.configure(width=self.apply_widget_scaling(self._desired_width), height=self.apply_widget_scaling(self._desired_height))
self.canvas.configure(width=self.apply_widget_scaling(self.desired_width), height=self.apply_widget_scaling(self.desired_height)) self.canvas.configure(width=self.apply_widget_scaling(self._desired_width), height=self.apply_widget_scaling(self._desired_height))
self.draw() self.draw()
def destroy(self): def destroy(self):
@ -120,40 +118,40 @@ class CTkCheckBox(CTkBaseClass):
super().destroy() super().destroy()
def draw(self, no_color_updates=False): def draw(self, no_color_updates=False):
requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.apply_widget_scaling(self.current_width), requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.apply_widget_scaling(self._current_width),
self.apply_widget_scaling(self.current_height), self.apply_widget_scaling(self._current_height),
self.apply_widget_scaling(self.corner_radius), self.apply_widget_scaling(self.corner_radius),
self.apply_widget_scaling(self.border_width)) self.apply_widget_scaling(self.border_width))
if self.check_state is True: if self.check_state is True:
self.draw_engine.draw_checkmark(self.apply_widget_scaling(self.current_width), self.draw_engine.draw_checkmark(self.apply_widget_scaling(self._current_width),
self.apply_widget_scaling(self.current_height), self.apply_widget_scaling(self._current_height),
self.apply_widget_scaling(self.current_height * 0.58)) self.apply_widget_scaling(self._current_height * 0.58))
else: else:
self.canvas.delete("checkmark") self.canvas.delete("checkmark")
self.bg_canvas.configure(bg=ThemeManager.single_color(self.bg_color, self.appearance_mode)) self.bg_canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self.appearance_mode)) self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
if self.check_state is True: if self.check_state is True:
self.canvas.itemconfig("inner_parts", self.canvas.itemconfig("inner_parts",
outline=ThemeManager.single_color(self.fg_color, self.appearance_mode), outline=ThemeManager.single_color(self.fg_color, self._appearance_mode),
fill=ThemeManager.single_color(self.fg_color, self.appearance_mode)) fill=ThemeManager.single_color(self.fg_color, self._appearance_mode))
self.canvas.itemconfig("border_parts", self.canvas.itemconfig("border_parts",
outline=ThemeManager.single_color(self.fg_color, self.appearance_mode), outline=ThemeManager.single_color(self.fg_color, self._appearance_mode),
fill=ThemeManager.single_color(self.fg_color, self.appearance_mode)) fill=ThemeManager.single_color(self.fg_color, self._appearance_mode))
if "create_line" in self.canvas.gettags("checkmark"): if "create_line" in self.canvas.gettags("checkmark"):
self.canvas.itemconfig("checkmark", fill=ThemeManager.single_color(self.checkmark_color, self.appearance_mode)) self.canvas.itemconfig("checkmark", fill=ThemeManager.single_color(self.checkmark_color, self._appearance_mode))
else: else:
self.canvas.itemconfig("checkmark", fill=ThemeManager.single_color(self.checkmark_color, self.appearance_mode)) self.canvas.itemconfig("checkmark", fill=ThemeManager.single_color(self.checkmark_color, self._appearance_mode))
else: else:
self.canvas.itemconfig("inner_parts", self.canvas.itemconfig("inner_parts",
outline=ThemeManager.single_color(self.bg_color, self.appearance_mode), outline=ThemeManager.single_color(self.bg_color, self._appearance_mode),
fill=ThemeManager.single_color(self.bg_color, self.appearance_mode)) fill=ThemeManager.single_color(self.bg_color, self._appearance_mode))
self.canvas.itemconfig("border_parts", self.canvas.itemconfig("border_parts",
outline=ThemeManager.single_color(self.border_color, self.appearance_mode), outline=ThemeManager.single_color(self.border_color, self._appearance_mode),
fill=ThemeManager.single_color(self.border_color, self.appearance_mode)) fill=ThemeManager.single_color(self.border_color, self._appearance_mode))
if self.text_label is None: if self.text_label is None:
self.text_label = tkinter.Label(master=self, self.text_label = tkinter.Label(master=self,
@ -164,11 +162,15 @@ class CTkCheckBox(CTkBaseClass):
self.text_label.grid(row=0, column=2, padx=0, pady=0, sticky="w") self.text_label.grid(row=0, column=2, padx=0, pady=0, sticky="w")
self.text_label["anchor"] = "w" self.text_label["anchor"] = "w"
self.text_label.bind("<Enter>", self.on_enter)
self.text_label.bind("<Leave>", self.on_leave)
self.text_label.bind("<Button-1>", self.toggle)
if self.state == tkinter.DISABLED: if self.state == tkinter.DISABLED:
self.text_label.configure(fg=(ThemeManager.single_color(self.text_color_disabled, self.appearance_mode))) self.text_label.configure(fg=(ThemeManager.single_color(self.text_color_disabled, self._appearance_mode)))
else: else:
self.text_label.configure(fg=ThemeManager.single_color(self.text_color, self.appearance_mode)) self.text_label.configure(fg=ThemeManager.single_color(self.text_color, self._appearance_mode))
self.text_label.configure(bg=ThemeManager.single_color(self.bg_color, self.appearance_mode)) self.text_label.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
self.set_text(self.text) self.set_text(self.text)
@ -244,14 +246,22 @@ class CTkCheckBox(CTkBaseClass):
if self.state == tkinter.DISABLED: if self.state == tkinter.DISABLED:
if sys.platform == "darwin" and Settings.cursor_manipulation_enabled: if sys.platform == "darwin" and Settings.cursor_manipulation_enabled:
self.canvas.configure(cursor="arrow") self.canvas.configure(cursor="arrow")
if self.text_label is not None:
self.text_label.configure(cursor="arrow")
elif sys.platform.startswith("win") and Settings.cursor_manipulation_enabled: elif sys.platform.startswith("win") and Settings.cursor_manipulation_enabled:
self.canvas.configure(cursor="arrow") self.canvas.configure(cursor="arrow")
if self.text_label is not None:
self.text_label.configure(cursor="arrow")
elif self.state == tkinter.NORMAL: elif self.state == tkinter.NORMAL:
if sys.platform == "darwin" and Settings.cursor_manipulation_enabled: if sys.platform == "darwin" and Settings.cursor_manipulation_enabled:
self.canvas.configure(cursor="pointinghand") self.canvas.configure(cursor="pointinghand")
if self.text_label is not None:
self.text_label.configure(cursor="pointinghand")
elif sys.platform.startswith("win") and Settings.cursor_manipulation_enabled: elif sys.platform.startswith("win") and Settings.cursor_manipulation_enabled:
self.canvas.configure(cursor="hand2") self.canvas.configure(cursor="hand2")
if self.text_label is not None:
self.text_label.configure(cursor="hand2")
def set_text(self, text): def set_text(self, text):
self.text = text self.text = text
@ -264,32 +274,32 @@ class CTkCheckBox(CTkBaseClass):
if self.hover is True and self.state == tkinter.NORMAL: if self.hover is True and self.state == tkinter.NORMAL:
if self.check_state is True: if self.check_state is True:
self.canvas.itemconfig("inner_parts", self.canvas.itemconfig("inner_parts",
fill=ThemeManager.single_color(self.hover_color, self.appearance_mode), fill=ThemeManager.single_color(self.hover_color, self._appearance_mode),
outline=ThemeManager.single_color(self.hover_color, self.appearance_mode)) outline=ThemeManager.single_color(self.hover_color, self._appearance_mode))
self.canvas.itemconfig("border_parts", self.canvas.itemconfig("border_parts",
fill=ThemeManager.single_color(self.hover_color, self.appearance_mode), fill=ThemeManager.single_color(self.hover_color, self._appearance_mode),
outline=ThemeManager.single_color(self.hover_color, self.appearance_mode)) outline=ThemeManager.single_color(self.hover_color, self._appearance_mode))
else: else:
self.canvas.itemconfig("inner_parts", self.canvas.itemconfig("inner_parts",
fill=ThemeManager.single_color(self.hover_color, self.appearance_mode), fill=ThemeManager.single_color(self.hover_color, self._appearance_mode),
outline=ThemeManager.single_color(self.hover_color, self.appearance_mode)) outline=ThemeManager.single_color(self.hover_color, self._appearance_mode))
def on_leave(self, event=0): def on_leave(self, event=0):
if self.hover is True: if self.hover is True:
if self.check_state is True: if self.check_state is True:
self.canvas.itemconfig("inner_parts", self.canvas.itemconfig("inner_parts",
fill=ThemeManager.single_color(self.fg_color, self.appearance_mode), fill=ThemeManager.single_color(self.fg_color, self._appearance_mode),
outline=ThemeManager.single_color(self.fg_color, self.appearance_mode)) outline=ThemeManager.single_color(self.fg_color, self._appearance_mode))
self.canvas.itemconfig("border_parts", self.canvas.itemconfig("border_parts",
fill=ThemeManager.single_color(self.fg_color, self.appearance_mode), fill=ThemeManager.single_color(self.fg_color, self._appearance_mode),
outline=ThemeManager.single_color(self.fg_color, self.appearance_mode)) outline=ThemeManager.single_color(self.fg_color, self._appearance_mode))
else: else:
self.canvas.itemconfig("inner_parts", self.canvas.itemconfig("inner_parts",
fill=ThemeManager.single_color(self.bg_color, self.appearance_mode), fill=ThemeManager.single_color(self.bg_color, self._appearance_mode),
outline=ThemeManager.single_color(self.bg_color, self.appearance_mode)) outline=ThemeManager.single_color(self.bg_color, self._appearance_mode))
self.canvas.itemconfig("border_parts", self.canvas.itemconfig("border_parts",
fill=ThemeManager.single_color(self.border_color, self.appearance_mode), fill=ThemeManager.single_color(self.border_color, self._appearance_mode),
outline=ThemeManager.single_color(self.border_color, self.appearance_mode)) outline=ThemeManager.single_color(self.border_color, self._appearance_mode))
def variable_callback(self, var_name, index, mode): def variable_callback(self, var_name, index, mode):
if not self.variable_callback_blocked: if not self.variable_callback_blocked:
@ -325,10 +335,7 @@ class CTkCheckBox(CTkBaseClass):
self.variable_callback_blocked = False self.variable_callback_blocked = False
if self.function is not None: if self.function is not None:
try: self.function()
self.function()
except:
pass
def deselect(self, from_variable_callback=False): def deselect(self, from_variable_callback=False):
self.check_state = False self.check_state = False

View File

@ -0,0 +1,305 @@
import tkinter
import sys
from typing import Union
from .dropdown_menu import DropdownMenu
from .ctk_canvas import CTkCanvas
from ..theme_manager import ThemeManager
from ..settings import Settings
from ..draw_engine import DrawEngine
from .widget_base_class import CTkBaseClass
class CTkComboBox(CTkBaseClass):
def __init__(self, *args,
bg_color=None,
fg_color="default_theme",
border_color="default_theme",
button_color="default_theme",
button_hover_color="default_theme",
dropdown_color="default_theme",
dropdown_hover_color="default_theme",
dropdown_text_color="default_theme",
variable=None,
values=None,
command=None,
width=140,
height=28,
corner_radius="default_theme",
border_width="default_theme",
text_font="default_theme",
dropdown_text_font="default_theme",
text_color="default_theme",
text_color_disabled="default_theme",
hover=True,
state=tkinter.NORMAL,
**kwargs):
# transfer basic functionality (bg_color, size, _appearance_mode, scaling) to CTkBaseClass
super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs)
# color variables
self.fg_color = ThemeManager.theme["color"]["entry"] if fg_color == "default_theme" else fg_color
self.border_color = ThemeManager.theme["color"]["combobox_border"] if border_color == "default_theme" else border_color
self.button_color = ThemeManager.theme["color"]["combobox_border"] if button_color == "default_theme" else button_color
self.button_hover_color = ThemeManager.theme["color"]["combobox_button_hover"] if button_hover_color == "default_theme" else button_hover_color
# shape
self.corner_radius = ThemeManager.theme["shape"]["button_corner_radius"] if corner_radius == "default_theme" else corner_radius
self.border_width = ThemeManager.theme["shape"]["entry_border_width"] if border_width == "default_theme" else border_width
# text and font
self.text_color = ThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color
self.text_color_disabled = ThemeManager.theme["color"]["text_button_disabled"] if text_color_disabled == "default_theme" else text_color_disabled
self.text_font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font
# callback and hover functionality
self.function = command
self.variable = variable
self.state = state
self.hover = hover
if values is None:
self.values = ["CTkComboBox"]
else:
self.values = values
if len(self.values) > 0:
self.current_value = self.values[0]
else:
self.current_value = "CTkComboBox"
self.dropdown_menu = DropdownMenu(master=self,
values=self.values,
command=self.set,
fg_color=dropdown_color,
hover_color=dropdown_hover_color,
text_color=dropdown_text_color,
text_font=dropdown_text_font)
# configure grid system (1x1)
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
self.canvas = CTkCanvas(master=self,
highlightthickness=0,
width=self.apply_widget_scaling(self._desired_width),
height=self.apply_widget_scaling(self._desired_height))
self.canvas.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="nsew")
self.draw_engine = DrawEngine(self.canvas)
self.entry = tkinter.Entry(master=self,
state=self.state,
width=1,
bd=0,
highlightthickness=0,
font=self.apply_font_scaling(self.text_font))
left_section_width = self._current_width - self._current_height
self.entry.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="ew",
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))))
self.draw() # initial draw
# event bindings
self.canvas.tag_bind("right_parts", "<Enter>", self.on_enter)
self.canvas.tag_bind("dropdown_arrow", "<Enter>", self.on_enter)
self.canvas.tag_bind("right_parts", "<Leave>", self.on_leave)
self.canvas.tag_bind("dropdown_arrow", "<Leave>", self.on_leave)
self.canvas.tag_bind("right_parts", "<Button-1>", self.clicked)
self.canvas.tag_bind("dropdown_arrow", "<Button-1>", self.clicked)
self.bind('<Configure>', self.update_dimensions_event)
if self.variable is not None:
self.entry.configure(textvariable=self.variable)
def set_scaling(self, *args, **kwargs):
super().set_scaling(*args, **kwargs)
self.canvas.configure(width=self.apply_widget_scaling(self._desired_width),
height=self.apply_widget_scaling(self._desired_height))
self.draw()
def set_dimensions(self, width: int = None, height: int = 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):
left_section_width = self._current_width - self._current_height
requires_recoloring = self.draw_engine.draw_rounded_rect_with_border_vertical_split(self.apply_widget_scaling(self._current_width),
self.apply_widget_scaling(self._current_height),
self.apply_widget_scaling(self.corner_radius),
self.apply_widget_scaling(self.border_width),
self.apply_widget_scaling(left_section_width))
requires_recoloring_2 = self.draw_engine.draw_dropdown_arrow(self.apply_widget_scaling(self._current_width - (self._current_height / 2)),
self.apply_widget_scaling(self._current_height / 2),
self.apply_widget_scaling(self._current_height / 3))
if self.current_value is not None:
self.entry.delete(0, tkinter.END)
self.entry.insert(0, self.current_value)
if no_color_updates is False or requires_recoloring or requires_recoloring_2:
self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
self.canvas.itemconfig("inner_parts_left",
outline=ThemeManager.single_color(self.fg_color, self._appearance_mode),
fill=ThemeManager.single_color(self.fg_color, self._appearance_mode))
self.canvas.itemconfig("border_parts_left",
outline=ThemeManager.single_color(self.border_color, self._appearance_mode),
fill=ThemeManager.single_color(self.border_color, self._appearance_mode))
self.canvas.itemconfig("inner_parts_right",
outline=ThemeManager.single_color(self.border_color, self._appearance_mode),
fill=ThemeManager.single_color(self.border_color, self._appearance_mode))
self.canvas.itemconfig("border_parts_right",
outline=ThemeManager.single_color(self.border_color, self._appearance_mode),
fill=ThemeManager.single_color(self.border_color, self._appearance_mode))
self.entry.configure(bg=ThemeManager.single_color(self.fg_color, self._appearance_mode),
fg=ThemeManager.single_color(self.text_color, self._appearance_mode),
disabledforeground=ThemeManager.single_color(self.text_color_disabled, self._appearance_mode),
disabledbackground=ThemeManager.single_color(self.fg_color, self._appearance_mode))
if self.state == tkinter.DISABLED:
self.canvas.itemconfig("dropdown_arrow",
fill=ThemeManager.single_color(self.text_color_disabled, self._appearance_mode))
else:
self.canvas.itemconfig("dropdown_arrow",
fill=ThemeManager.single_color(self.text_color, self._appearance_mode))
def open_dropdown_menu(self):
self.dropdown_menu.open(self.winfo_rootx(),
self.winfo_rooty() + self.apply_widget_scaling(self._current_height + 0))
def configure(self, *args, **kwargs):
require_redraw = False # some attribute changes require a call of self.draw() at the end
if "state" in kwargs:
self.state = kwargs["state"]
self.entry.configure(state=self.state)
require_redraw = True
del kwargs["state"]
if "fg_color" in kwargs:
self.fg_color = kwargs["fg_color"]
require_redraw = True
del kwargs["fg_color"]
if "bg_color" in kwargs:
if kwargs["bg_color"] is None:
self.bg_color = self.detect_color_of_master()
else:
self.bg_color = kwargs["bg_color"]
require_redraw = True
del kwargs["bg_color"]
if "button_color" in kwargs:
self.button_color = kwargs["button_color"]
require_redraw = True
del kwargs["button_color"]
if "button_hover_color" in kwargs:
self.button_hover_color = kwargs["button_hover_color"]
require_redraw = True
del kwargs["button_hover_color"]
if "text_color" in kwargs:
self.text_color = kwargs["text_color"]
require_redraw = True
del kwargs["text_color"]
if "command" in kwargs:
self.function = kwargs["command"]
del kwargs["command"]
if "variable" in kwargs:
self.variable = kwargs["variable"]
self.entry.configure(textvariable=self.variable)
del kwargs["variable"]
if "width" in kwargs:
self.set_dimensions(width=kwargs["width"])
del kwargs["width"]
if "height" in kwargs:
self.set_dimensions(height=kwargs["height"])
del kwargs["height"]
if "values" in kwargs:
self.values = kwargs["values"]
del kwargs["values"]
self.dropdown_menu.configure(values=self.values)
if "dropdown_color" in kwargs:
self.dropdown_menu.configure(fg_color=kwargs["dropdown_color"])
del kwargs["dropdown_color"]
if "dropdown_hover_color" in kwargs:
self.dropdown_menu.configure(hover_color=kwargs["dropdown_hover_color"])
del kwargs["dropdown_hover_color"]
if "dropdown_text_color" in kwargs:
self.dropdown_menu.configure(text_color=kwargs["dropdown_text_color"])
del kwargs["dropdown_text_color"]
if "dropdown_text_font" in kwargs:
self.dropdown_menu.configure(text_font=kwargs["dropdown_text_font"])
del kwargs["dropdown_text_font"]
super().configure(*args, **kwargs)
if require_redraw:
self.draw()
def on_enter(self, event=0):
if self.hover is True and self.state == tkinter.NORMAL and len(self.values) > 0:
if sys.platform == "darwin" and len(self.values) > 0 and Settings.cursor_manipulation_enabled:
self.canvas.configure(cursor="pointinghand")
elif sys.platform.startswith("win") and len(self.values) > 0 and Settings.cursor_manipulation_enabled:
self.canvas.configure(cursor="hand2")
# set color of inner button parts to hover color
self.canvas.itemconfig("inner_parts_right",
outline=ThemeManager.single_color(self.button_hover_color, self._appearance_mode),
fill=ThemeManager.single_color(self.button_hover_color, self._appearance_mode))
self.canvas.itemconfig("border_parts_right",
outline=ThemeManager.single_color(self.button_hover_color, self._appearance_mode),
fill=ThemeManager.single_color(self.button_hover_color, self._appearance_mode))
def on_leave(self, event=0):
if self.hover is True:
if sys.platform == "darwin" and len(self.values) > 0 and Settings.cursor_manipulation_enabled:
self.canvas.configure(cursor="arrow")
elif sys.platform.startswith("win") and len(self.values) > 0 and Settings.cursor_manipulation_enabled:
self.canvas.configure(cursor="arrow")
# set color of inner button parts
self.canvas.itemconfig("inner_parts_right",
outline=ThemeManager.single_color(self.button_color, self._appearance_mode),
fill=ThemeManager.single_color(self.button_color, self._appearance_mode))
self.canvas.itemconfig("border_parts_right",
outline=ThemeManager.single_color(self.button_color, self._appearance_mode),
fill=ThemeManager.single_color(self.button_color, self._appearance_mode))
def set(self, value: str, from_variable_callback: bool = False):
self.current_value = value
self.entry.delete(0, tkinter.END)
self.entry.insert(0, self.current_value)
if not from_variable_callback:
if self.function is not None:
self.function(self.current_value)
def get(self) -> str:
return self.entry.get()
def clicked(self, event=0):
if self.state is not tkinter.DISABLED and len(self.values) > 0:
self.open_dropdown_menu()

View File

@ -17,12 +17,12 @@ class CTkEntry(CTkBaseClass):
corner_radius="default_theme", corner_radius="default_theme",
border_width="default_theme", border_width="default_theme",
border_color="default_theme", border_color="default_theme",
width=120, width=140,
height=30, height=28,
state=tkinter.NORMAL, state=tkinter.NORMAL,
**kwargs): **kwargs):
# transfer basic functionality (bg_color, size, appearance_mode, scaling) to CTkBaseClass # transfer basic functionality (bg_color, size, _appearance_mode, scaling) to CTkBaseClass
if "master" in kwargs: if "master" in kwargs:
super().__init__(*args, bg_color=bg_color, width=width, height=height, master=kwargs["master"]) super().__init__(*args, bg_color=bg_color, width=width, height=height, master=kwargs["master"])
del kwargs["master"] del kwargs["master"]
@ -49,9 +49,9 @@ class CTkEntry(CTkBaseClass):
self.canvas = CTkCanvas(master=self, self.canvas = CTkCanvas(master=self,
highlightthickness=0, highlightthickness=0,
width=self.apply_widget_scaling(self.current_width), width=self.apply_widget_scaling(self._current_width),
height=self.apply_widget_scaling(self.current_height)) height=self.apply_widget_scaling(self._current_height))
self.canvas.grid(column=0, row=0, sticky="we") self.canvas.grid(column=0, row=0, sticky="nswe")
self.draw_engine = DrawEngine(self.canvas) self.draw_engine = DrawEngine(self.canvas)
self.entry = tkinter.Entry(master=self, self.entry = tkinter.Entry(master=self,
@ -61,8 +61,9 @@ class CTkEntry(CTkBaseClass):
font=self.apply_font_scaling(self.text_font), font=self.apply_font_scaling(self.text_font),
state=self.state, state=self.state,
**kwargs) **kwargs)
self.entry.grid(column=0, row=0, sticky="we", self.entry.grid(column=0, row=0, sticky="nswe",
padx=self.apply_widget_scaling(self.corner_radius) if self.corner_radius >= 6 else self.apply_widget_scaling(6)) padx=self.apply_widget_scaling(self.corner_radius) if self.corner_radius >= 6 else self.apply_widget_scaling(6),
pady=(self.apply_widget_scaling(self.border_width), self.apply_widget_scaling(self.border_width + 1)))
super().bind('<Configure>', self.update_dimensions_event) super().bind('<Configure>', self.update_dimensions_event)
self.entry.bind('<FocusOut>', self.set_placeholder) self.entry.bind('<FocusOut>', self.set_placeholder)
@ -78,7 +79,14 @@ class CTkEntry(CTkBaseClass):
self.entry.grid(column=0, row=0, sticky="we", self.entry.grid(column=0, row=0, sticky="we",
padx=self.apply_widget_scaling(self.corner_radius) if self.corner_radius >= 6 else self.apply_widget_scaling(6)) padx=self.apply_widget_scaling(self.corner_radius) if self.corner_radius >= 6 else self.apply_widget_scaling(6))
self.canvas.configure(width=self.apply_widget_scaling(self.desired_width), height=self.apply_widget_scaling(self.desired_height)) self.canvas.configure(width=self.apply_widget_scaling(self._desired_width), height=self.apply_widget_scaling(self._desired_height))
self.draw()
def set_dimensions(self, width=None, height=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() self.draw()
def set_placeholder(self, event=None): def set_placeholder(self, event=None):
@ -86,54 +94,54 @@ class CTkEntry(CTkBaseClass):
if not self.placeholder_text_active and self.entry.get() == "": if not self.placeholder_text_active and self.entry.get() == "":
self.placeholder_text_active = True self.placeholder_text_active = True
self.pre_placeholder_arguments = {"show": self.entry.cget("show")} self.pre_placeholder_arguments = {"show": self.entry.cget("show")}
self.entry.config(fg=ThemeManager.single_color(self.placeholder_text_color, self.appearance_mode), show="") self.entry.config(fg=ThemeManager.single_color(self.placeholder_text_color, self._appearance_mode), show="")
self.entry.delete(0, tkinter.END) self.entry.delete(0, tkinter.END)
self.entry.insert(0, self.placeholder_text) self.entry.insert(0, self.placeholder_text)
def clear_placeholder(self, event=None): def clear_placeholder(self, event=None):
if self.placeholder_text_active: if self.placeholder_text_active:
self.placeholder_text_active = False self.placeholder_text_active = False
self.entry.config(fg=ThemeManager.single_color(self.text_color, self.appearance_mode)) self.entry.config(fg=ThemeManager.single_color(self.text_color, self._appearance_mode))
self.entry.delete(0, tkinter.END) self.entry.delete(0, tkinter.END)
for argument, value in self.pre_placeholder_arguments.items(): for argument, value in self.pre_placeholder_arguments.items():
self.entry[argument] = value self.entry[argument] = value
def draw(self, no_color_updates=False): def draw(self, no_color_updates=False):
self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self.appearance_mode)) self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.apply_widget_scaling(self.current_width), requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.apply_widget_scaling(self._current_width),
self.apply_widget_scaling(self.current_height), self.apply_widget_scaling(self._current_height),
self.apply_widget_scaling(self.corner_radius), self.apply_widget_scaling(self.corner_radius),
self.apply_widget_scaling(self.border_width)) self.apply_widget_scaling(self.border_width))
if requires_recoloring or no_color_updates is False: if requires_recoloring or no_color_updates is False:
if ThemeManager.single_color(self.fg_color, self.appearance_mode) is not None: if ThemeManager.single_color(self.fg_color, self._appearance_mode) is not None:
self.canvas.itemconfig("inner_parts", self.canvas.itemconfig("inner_parts",
fill=ThemeManager.single_color(self.fg_color, self.appearance_mode), fill=ThemeManager.single_color(self.fg_color, self._appearance_mode),
outline=ThemeManager.single_color(self.fg_color, self.appearance_mode)) outline=ThemeManager.single_color(self.fg_color, self._appearance_mode))
self.entry.configure(bg=ThemeManager.single_color(self.fg_color, self.appearance_mode), self.entry.configure(bg=ThemeManager.single_color(self.fg_color, self._appearance_mode),
disabledbackground=ThemeManager.single_color(self.fg_color, self.appearance_mode), disabledbackground=ThemeManager.single_color(self.fg_color, self._appearance_mode),
highlightcolor=ThemeManager.single_color(self.fg_color, self.appearance_mode), highlightcolor=ThemeManager.single_color(self.fg_color, self._appearance_mode),
fg=ThemeManager.single_color(self.text_color, self.appearance_mode), fg=ThemeManager.single_color(self.text_color, self._appearance_mode),
disabledforeground=ThemeManager.single_color(self.text_color, self.appearance_mode), disabledforeground=ThemeManager.single_color(self.text_color, self._appearance_mode),
insertbackground=ThemeManager.single_color(self.text_color, self.appearance_mode)) insertbackground=ThemeManager.single_color(self.text_color, self._appearance_mode))
else: else:
self.canvas.itemconfig("inner_parts", self.canvas.itemconfig("inner_parts",
fill=ThemeManager.single_color(self.bg_color, self.appearance_mode), fill=ThemeManager.single_color(self.bg_color, self._appearance_mode),
outline=ThemeManager.single_color(self.bg_color, self.appearance_mode)) outline=ThemeManager.single_color(self.bg_color, self._appearance_mode))
self.entry.configure(bg=ThemeManager.single_color(self.bg_color, self.appearance_mode), self.entry.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode),
disabledbackground=ThemeManager.single_color(self.bg_color, self.appearance_mode), disabledbackground=ThemeManager.single_color(self.bg_color, self._appearance_mode),
highlightcolor=ThemeManager.single_color(self.bg_color, self.appearance_mode), highlightcolor=ThemeManager.single_color(self.bg_color, self._appearance_mode),
fg=ThemeManager.single_color(self.text_color, self.appearance_mode), fg=ThemeManager.single_color(self.text_color, self._appearance_mode),
disabledforeground=ThemeManager.single_color(self.text_color, self.appearance_mode), disabledforeground=ThemeManager.single_color(self.text_color, self._appearance_mode),
insertbackground=ThemeManager.single_color(self.text_color, self.appearance_mode)) insertbackground=ThemeManager.single_color(self.text_color, self._appearance_mode))
self.canvas.itemconfig("border_parts", self.canvas.itemconfig("border_parts",
fill=ThemeManager.single_color(self.border_color, self.appearance_mode), fill=ThemeManager.single_color(self.border_color, self._appearance_mode),
outline=ThemeManager.single_color(self.border_color, self.appearance_mode)) outline=ThemeManager.single_color(self.border_color, self._appearance_mode))
if self.placeholder_text_active: if self.placeholder_text_active:
self.entry.config(fg=ThemeManager.single_color(self.placeholder_text_color, self.appearance_mode)) self.entry.config(fg=ThemeManager.single_color(self.placeholder_text_color, self._appearance_mode))
def bind(self, *args, **kwargs): def bind(self, *args, **kwargs):
self.entry.bind(*args, **kwargs) self.entry.bind(*args, **kwargs)
@ -172,15 +180,23 @@ class CTkEntry(CTkBaseClass):
if "corner_radius" in kwargs: if "corner_radius" in kwargs:
self.corner_radius = kwargs["corner_radius"] self.corner_radius = kwargs["corner_radius"]
if self.corner_radius * 2 > self.current_height: if self.corner_radius * 2 > self._current_height:
self.corner_radius = self.current_height / 2 self.corner_radius = self._current_height / 2
elif self.corner_radius * 2 > self.current_width: elif self.corner_radius * 2 > self._current_width:
self.corner_radius = self.current_width / 2 self.corner_radius = self._current_width / 2
self.entry.grid(column=0, row=0, sticky="we", padx=self.apply_widget_scaling(self.corner_radius) if self.corner_radius >= 6 else self.apply_widget_scaling(6)) self.entry.grid(column=0, row=0, sticky="we", padx=self.apply_widget_scaling(self.corner_radius) if self.corner_radius >= 6 else self.apply_widget_scaling(6))
del kwargs["corner_radius"] del kwargs["corner_radius"]
require_redraw = True require_redraw = True
if "width" in kwargs:
self.set_dimensions(width=kwargs["width"])
del kwargs["width"]
if "height" in kwargs:
self.set_dimensions(height=kwargs["height"])
del kwargs["height"]
if "placeholder_text" in kwargs: if "placeholder_text" in kwargs:
pass pass

View File

@ -1,8 +1,5 @@
import tkinter
from .ctk_canvas import CTkCanvas from .ctk_canvas import CTkCanvas
from ..theme_manager import ThemeManager from ..theme_manager import ThemeManager
from ..settings import Settings
from ..draw_engine import DrawEngine from ..draw_engine import DrawEngine
from .widget_base_class import CTkBaseClass from .widget_base_class import CTkBaseClass
@ -16,14 +13,16 @@ class CTkFrame(CTkBaseClass):
corner_radius="default_theme", corner_radius="default_theme",
width=200, width=200,
height=200, height=200,
overwrite_preferred_drawing_method: str = None,
**kwargs): **kwargs):
# transfer basic functionality (bg_color, size, appearance_mode, scaling) to CTkBaseClass # transfer basic functionality (bg_color, size, _appearance_mode, scaling) to CTkBaseClass
super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs) super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs)
# color # color
self.border_color = ThemeManager.theme["color"]["frame_border"] if border_color == "default_theme" else border_color self.border_color = ThemeManager.theme["color"]["frame_border"] if border_color == "default_theme" else border_color
# determine fg_color of frame
if fg_color == "default_theme": if fg_color == "default_theme":
if isinstance(self.master, CTkFrame): if isinstance(self.master, CTkFrame):
if self.master.fg_color == ThemeManager.theme["color"]["frame_low"]: if self.master.fg_color == ThemeManager.theme["color"]["frame_low"]:
@ -41,11 +40,12 @@ class CTkFrame(CTkBaseClass):
self.canvas = CTkCanvas(master=self, self.canvas = CTkCanvas(master=self,
highlightthickness=0, highlightthickness=0,
width=self.apply_widget_scaling(self.current_width), width=self.apply_widget_scaling(self._current_width),
height=self.apply_widget_scaling(self.current_height)) height=self.apply_widget_scaling(self._current_height))
self.canvas.place(x=0, y=0, relwidth=1, relheight=1) self.canvas.place(x=0, y=0, relwidth=1, relheight=1)
self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self.appearance_mode)) self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
self.draw_engine = DrawEngine(self.canvas) self.draw_engine = DrawEngine(self.canvas)
self._overwrite_preferred_drawing_method = overwrite_preferred_drawing_method
self.bind('<Configure>', self.update_dimensions_event) self.bind('<Configure>', self.update_dimensions_event)
@ -65,30 +65,38 @@ class CTkFrame(CTkBaseClass):
def set_scaling(self, *args, **kwargs): def set_scaling(self, *args, **kwargs):
super().set_scaling(*args, **kwargs) super().set_scaling(*args, **kwargs)
self.canvas.configure(width=self.apply_widget_scaling(self.desired_width), height=self.apply_widget_scaling(self.desired_height)) self.canvas.configure(width=self.apply_widget_scaling(self._desired_width), height=self.apply_widget_scaling(self._desired_height))
self.draw()
def set_dimensions(self, width=None, height=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() self.draw()
def draw(self, no_color_updates=False): def draw(self, no_color_updates=False):
requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.apply_widget_scaling(self.current_width), requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.apply_widget_scaling(self._current_width),
self.apply_widget_scaling(self.current_height), self.apply_widget_scaling(self._current_height),
self.apply_widget_scaling(self.corner_radius), self.apply_widget_scaling(self.corner_radius),
self.apply_widget_scaling(self.border_width)) self.apply_widget_scaling(self.border_width),
overwrite_preferred_drawing_method=self._overwrite_preferred_drawing_method)
if no_color_updates is False or requires_recoloring: if no_color_updates is False or requires_recoloring:
if self.fg_color is None: if self.fg_color is None:
self.canvas.itemconfig("inner_parts", self.canvas.itemconfig("inner_parts",
fill=ThemeManager.single_color(self.bg_color, self.appearance_mode), fill=ThemeManager.single_color(self.bg_color, self._appearance_mode),
outline=ThemeManager.single_color(self.bg_color, self.appearance_mode)) outline=ThemeManager.single_color(self.bg_color, self._appearance_mode))
else: else:
self.canvas.itemconfig("inner_parts", self.canvas.itemconfig("inner_parts",
fill=ThemeManager.single_color(self.fg_color, self.appearance_mode), fill=ThemeManager.single_color(self.fg_color, self._appearance_mode),
outline=ThemeManager.single_color(self.fg_color, self.appearance_mode)) outline=ThemeManager.single_color(self.fg_color, self._appearance_mode))
self.canvas.itemconfig("border_parts", self.canvas.itemconfig("border_parts",
fill=ThemeManager.single_color(self.border_color, self.appearance_mode), fill=ThemeManager.single_color(self.border_color, self._appearance_mode),
outline=ThemeManager.single_color(self.border_color, self.appearance_mode)) outline=ThemeManager.single_color(self.border_color, self._appearance_mode))
self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self.appearance_mode)) self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
self.canvas.tag_lower("inner_parts") self.canvas.tag_lower("inner_parts")
self.canvas.tag_lower("border_parts") self.canvas.tag_lower("border_parts")
@ -115,11 +123,29 @@ class CTkFrame(CTkBaseClass):
del kwargs["bg_color"] del kwargs["bg_color"]
if "border_color" in kwargs:
self.border_color = kwargs["border_color"]
require_redraw = True
del kwargs["border_color"]
if "corner_radius" in kwargs: if "corner_radius" in kwargs:
self.corner_radius = kwargs["corner_radius"] self.corner_radius = kwargs["corner_radius"]
require_redraw = True require_redraw = True
del kwargs["corner_radius"] del kwargs["corner_radius"]
if "border_width" in kwargs:
self.border_width = kwargs["border_width"]
require_redraw = True
del kwargs["border_width"]
if "width" in kwargs:
self.set_dimensions(width=kwargs["width"])
del kwargs["width"]
if "height" in kwargs:
self.set_dimensions(height=kwargs["height"])
del kwargs["height"]
super().configure(*args, **kwargs) super().configure(*args, **kwargs)
if require_redraw: if require_redraw:

View File

@ -12,13 +12,13 @@ class CTkLabel(CTkBaseClass):
fg_color="default_theme", fg_color="default_theme",
text_color="default_theme", text_color="default_theme",
corner_radius="default_theme", corner_radius="default_theme",
width=120, width=140,
height=25, height=28,
text="CTkLabel", text="CTkLabel",
text_font="default_theme", text_font="default_theme",
**kwargs): **kwargs):
# transfer basic functionality (bg_color, size, appearance_mode, scaling) to CTkBaseClass # transfer basic functionality (bg_color, size, _appearance_mode, scaling) to CTkBaseClass
if "master" in kwargs: if "master" in kwargs:
super().__init__(*args, bg_color=bg_color, width=width, height=height, master=kwargs["master"]) super().__init__(*args, bg_color=bg_color, width=width, height=height, master=kwargs["master"])
del kwargs["master"] del kwargs["master"]
@ -44,8 +44,8 @@ class CTkLabel(CTkBaseClass):
self.canvas = CTkCanvas(master=self, self.canvas = CTkCanvas(master=self,
highlightthickness=0, highlightthickness=0,
width=self.apply_widget_scaling(self.desired_width), width=self.apply_widget_scaling(self._desired_width),
height=self.apply_widget_scaling(self.desired_height)) height=self.apply_widget_scaling(self._desired_height))
self.canvas.grid(row=0, column=0, sticky="nswe") self.canvas.grid(row=0, column=0, sticky="nswe")
self.draw_engine = DrawEngine(self.canvas) self.draw_engine = DrawEngine(self.canvas)
@ -63,35 +63,42 @@ class CTkLabel(CTkBaseClass):
def set_scaling(self, *args, **kwargs): def set_scaling(self, *args, **kwargs):
super().set_scaling(*args, **kwargs) super().set_scaling(*args, **kwargs)
self.canvas.configure(width=self.apply_widget_scaling(self.desired_width), height=self.apply_widget_scaling(self.desired_height)) self.canvas.configure(width=self.apply_widget_scaling(self._desired_width), height=self.apply_widget_scaling(self._desired_height))
self.text_label.configure(font=self.apply_font_scaling(self.text_font)) self.text_label.configure(font=self.apply_font_scaling(self.text_font))
self.text_label.grid(row=0, column=0, padx=self.apply_widget_scaling(self.corner_radius)) self.text_label.grid(row=0, column=0, padx=self.apply_widget_scaling(self.corner_radius))
self.draw() self.draw()
def set_dimensions(self, width=None, height=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=False):
requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.apply_widget_scaling(self.current_width), requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.apply_widget_scaling(self._current_width),
self.apply_widget_scaling(self.current_height), self.apply_widget_scaling(self._current_height),
self.apply_widget_scaling(self.corner_radius), self.apply_widget_scaling(self.corner_radius),
0) 0)
if no_color_updates is False or requires_recoloring: if no_color_updates is False or requires_recoloring:
if ThemeManager.single_color(self.fg_color, self.appearance_mode) is not None: if ThemeManager.single_color(self.fg_color, self._appearance_mode) is not None:
self.canvas.itemconfig("inner_parts", self.canvas.itemconfig("inner_parts",
fill=ThemeManager.single_color(self.fg_color, self.appearance_mode), fill=ThemeManager.single_color(self.fg_color, self._appearance_mode),
outline=ThemeManager.single_color(self.fg_color, self.appearance_mode)) outline=ThemeManager.single_color(self.fg_color, self._appearance_mode))
self.text_label.configure(fg=ThemeManager.single_color(self.text_color, self.appearance_mode), self.text_label.configure(fg=ThemeManager.single_color(self.text_color, self._appearance_mode),
bg=ThemeManager.single_color(self.fg_color, self.appearance_mode)) bg=ThemeManager.single_color(self.fg_color, self._appearance_mode))
else: else:
self.canvas.itemconfig("inner_parts", self.canvas.itemconfig("inner_parts",
fill=ThemeManager.single_color(self.bg_color, self.appearance_mode), fill=ThemeManager.single_color(self.bg_color, self._appearance_mode),
outline=ThemeManager.single_color(self.bg_color, self.appearance_mode)) outline=ThemeManager.single_color(self.bg_color, self._appearance_mode))
self.text_label.configure(fg=ThemeManager.single_color(self.text_color, self.appearance_mode), self.text_label.configure(fg=ThemeManager.single_color(self.text_color, self._appearance_mode),
bg=ThemeManager.single_color(self.bg_color, self.appearance_mode)) bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self.appearance_mode)) self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
def configure(self, *args, **kwargs): def configure(self, *args, **kwargs):
require_redraw = False # some attribute changes require a call of self.draw() at the end require_redraw = False # some attribute changes require a call of self.draw() at the end
@ -118,6 +125,14 @@ class CTkLabel(CTkBaseClass):
require_redraw = True require_redraw = True
del kwargs["text_color"] del kwargs["text_color"]
if "width" in kwargs:
self.set_dimensions(width=kwargs["width"])
del kwargs["width"]
if "height" in kwargs:
self.set_dimensions(height=kwargs["height"])
del kwargs["height"]
self.text_label.configure(*args, **kwargs) self.text_label.configure(*args, **kwargs)
if require_redraw: if require_redraw:

View File

@ -0,0 +1,311 @@
import tkinter
import sys
from typing import Union
from .dropdown_menu import DropdownMenu
from .ctk_canvas import CTkCanvas
from ..theme_manager import ThemeManager
from ..settings import Settings
from ..draw_engine import DrawEngine
from .widget_base_class import CTkBaseClass
class CTkOptionMenu(CTkBaseClass):
def __init__(self, *args,
bg_color=None,
fg_color="default_theme",
button_color="default_theme",
button_hover_color="default_theme",
text_color="default_theme",
text_color_disabled="default_theme",
dropdown_color="default_theme",
dropdown_hover_color="default_theme",
dropdown_text_color="default_theme",
variable=None,
values=None,
command=None,
width=140,
height=28,
corner_radius="default_theme",
text_font="default_theme",
dropdown_text_font="default_theme",
hover=True,
state=tkinter.NORMAL,
**kwargs):
# transfer basic functionality (bg_color, size, _appearance_mode, scaling) to CTkBaseClass
super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs)
# color variables
self.fg_color = ThemeManager.theme["color"]["button"] if fg_color == "default_theme" else fg_color
self.button_color = ThemeManager.theme["color"]["optionmenu_button"] if button_color == "default_theme" else button_color
self.button_hover_color = ThemeManager.theme["color"]["optionmenu_button_hover"] if button_hover_color == "default_theme" else button_hover_color
# shape
self.corner_radius = ThemeManager.theme["shape"]["button_corner_radius"] if corner_radius == "default_theme" else corner_radius
# text and font
self.text_color = ThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color
self.text_color_disabled = ThemeManager.theme["color"]["text_button_disabled"] if text_color_disabled == "default_theme" else text_color_disabled
self.text_font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font
self.dropdown_text_font = dropdown_text_font
# callback and hover functionality
self.function = command
self.variable = variable
self.variable_callback_blocked = False
self.variable_callback_name = None
self.state = state
self.hover = hover
if values is None:
self.values = ["CTkOptionMenu"]
else:
self.values = values
if len(self.values) > 0:
self.current_value = self.values[0]
else:
self.current_value = "CTkOptionMenu"
self.dropdown_menu = DropdownMenu(master=self,
values=self.values,
command=self.set,
fg_color=dropdown_color,
hover_color=dropdown_hover_color,
text_color=dropdown_text_color,
text_font=dropdown_text_font)
# configure grid system (1x1)
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
self.canvas = CTkCanvas(master=self,
highlightthickness=0,
width=self.apply_widget_scaling(self._desired_width),
height=self.apply_widget_scaling(self._desired_height))
self.canvas.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="nsew")
self.draw_engine = DrawEngine(self.canvas)
left_section_width = self._current_width - self._current_height
self.text_label = tkinter.Label(master=self, font=self.apply_font_scaling(self.text_font))
self.text_label.grid(row=0, column=0, sticky="w",
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))))
if Settings.cursor_manipulation_enabled:
if sys.platform == "darwin":
self.configure(cursor="pointinghand")
elif sys.platform.startswith("win"):
self.configure(cursor="hand2")
# event bindings
self.canvas.bind("<Enter>", self.on_enter)
self.canvas.bind("<Leave>", self.on_leave)
self.canvas.bind("<Button-1>", self.clicked)
self.canvas.bind("<Button-1>", self.clicked)
self.text_label.bind("<Enter>", self.on_enter)
self.text_label.bind("<Leave>", self.on_leave)
self.text_label.bind("<Button-1>", self.clicked)
self.text_label.bind("<Button-1>", self.clicked)
self.bind('<Configure>', self.update_dimensions_event)
self.draw() # initial draw
if self.variable is not None:
self.variable_callback_name = self.variable.trace_add("write", self.variable_callback)
self.set(self.variable.get(), from_variable_callback=True)
def set_scaling(self, *args, **kwargs):
super().set_scaling(*args, **kwargs)
if self.text_label is not None:
self.text_label.destroy()
self.text_label = None
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: int = None, height: int = 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):
left_section_width = self._current_width - self._current_height
requires_recoloring = self.draw_engine.draw_rounded_rect_with_border_vertical_split(self.apply_widget_scaling(self._current_width),
self.apply_widget_scaling(self._current_height),
self.apply_widget_scaling(self.corner_radius),
0,
self.apply_widget_scaling(left_section_width))
requires_recoloring_2 = self.draw_engine.draw_dropdown_arrow(self.apply_widget_scaling(self._current_width - (self._current_height / 2)),
self.apply_widget_scaling(self._current_height / 2),
self.apply_widget_scaling(self._current_height / 3))
if self.current_value is not None:
self.text_label.configure(text=self.current_value)
if no_color_updates is False or requires_recoloring or requires_recoloring_2:
self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
self.canvas.itemconfig("inner_parts_left",
outline=ThemeManager.single_color(self.fg_color, self._appearance_mode),
fill=ThemeManager.single_color(self.fg_color, self._appearance_mode))
self.canvas.itemconfig("inner_parts_right",
outline=ThemeManager.single_color(self.button_color, self._appearance_mode),
fill=ThemeManager.single_color(self.button_color, self._appearance_mode))
self.text_label.configure(fg=ThemeManager.single_color(self.text_color, self._appearance_mode))
if self.state == tkinter.DISABLED:
self.text_label.configure(fg=(ThemeManager.single_color(self.text_color_disabled, self._appearance_mode)))
self.canvas.itemconfig("dropdown_arrow",
fill=ThemeManager.single_color(self.text_color_disabled, self._appearance_mode))
else:
self.text_label.configure(fg=ThemeManager.single_color(self.text_color, self._appearance_mode))
self.canvas.itemconfig("dropdown_arrow",
fill=ThemeManager.single_color(self.text_color, self._appearance_mode))
self.text_label.configure(bg=ThemeManager.single_color(self.fg_color, self._appearance_mode))
def open_dropdown_menu(self):
self.dropdown_menu.open(self.winfo_rootx(),
self.winfo_rooty() + self.apply_widget_scaling(self._current_height + 0))
def configure(self, *args, **kwargs):
require_redraw = False # some attribute changes require a call of self.draw() at the end
if "state" in kwargs:
self.state = kwargs["state"]
require_redraw = True
del kwargs["state"]
if "fg_color" in kwargs:
self.fg_color = kwargs["fg_color"]
require_redraw = True
del kwargs["fg_color"]
if "bg_color" in kwargs:
if kwargs["bg_color"] is None:
self.bg_color = self.detect_color_of_master()
else:
self.bg_color = kwargs["bg_color"]
require_redraw = True
del kwargs["bg_color"]
if "button_color" in kwargs:
self.button_color = kwargs["button_color"]
require_redraw = True
del kwargs["button_color"]
if "button_hover_color" in kwargs:
self.button_hover_color = kwargs["button_hover_color"]
require_redraw = True
del kwargs["button_hover_color"]
if "text_color" in kwargs:
self.text_color = kwargs["text_color"]
require_redraw = True
del kwargs["text_color"]
if "command" in kwargs:
self.function = kwargs["command"]
del kwargs["command"]
if "variable" in kwargs:
if self.variable is not None: # remove old callback
self.variable.trace_remove("write", self.variable_callback_name)
self.variable = kwargs["variable"]
if self.variable is not None and self.variable != "":
self.variable_callback_name = self.variable.trace_add("write", self.variable_callback)
self.set(self.variable.get(), from_variable_callback=True)
else:
self.variable = None
del kwargs["variable"]
if "width" in kwargs:
self.set_dimensions(width=kwargs["width"])
del kwargs["width"]
if "height" in kwargs:
self.set_dimensions(height=kwargs["height"])
del kwargs["height"]
if "values" in kwargs:
self.values = kwargs["values"]
del kwargs["values"]
self.dropdown_menu.configure(values=self.values)
if "dropdown_color" in kwargs:
self.dropdown_menu.configure(fg_color=kwargs["dropdown_color"])
del kwargs["dropdown_color"]
if "dropdown_hover_color" in kwargs:
self.dropdown_menu.configure(hover_color=kwargs["dropdown_hover_color"])
del kwargs["dropdown_hover_color"]
if "dropdown_text_color" in kwargs:
self.dropdown_menu.configure(text_color=kwargs["dropdown_text_color"])
del kwargs["dropdown_text_color"]
if "dropdown_text_font" in kwargs:
self.dropdown_menu.configure(text_font=kwargs["dropdown_text_font"])
del kwargs["dropdown_text_font"]
super().configure(*args, **kwargs)
if require_redraw:
self.draw()
def on_enter(self, event=0):
if self.hover is True and self.state == tkinter.NORMAL and len(self.values) > 0:
# set color of inner button parts to hover color
self.canvas.itemconfig("inner_parts_right",
outline=ThemeManager.single_color(self.button_hover_color, self._appearance_mode),
fill=ThemeManager.single_color(self.button_hover_color, self._appearance_mode))
def on_leave(self, event=0):
if self.hover is True:
# set color of inner button parts
self.canvas.itemconfig("inner_parts_right",
outline=ThemeManager.single_color(self.button_color, self._appearance_mode),
fill=ThemeManager.single_color(self.button_color, self._appearance_mode))
def variable_callback(self, var_name, index, mode):
if not self.variable_callback_blocked:
self.set(self.variable.get(), from_variable_callback=True)
def set(self, value: str, from_variable_callback: bool = False):
self.current_value = value
if self.text_label is not None:
self.text_label.configure(text=self.current_value)
else:
self.draw()
if self.variable is not None and not from_variable_callback:
self.variable_callback_blocked = True
self.variable.set(self.current_value)
self.variable_callback_blocked = False
if not from_variable_callback:
if self.function is not None:
self.function(self.current_value)
def get(self) -> str:
return self.current_value
def clicked(self, event=0):
if self.state is not tkinter.DISABLED and len(self.values) > 0:
self.open_dropdown_menu()

View File

@ -3,12 +3,11 @@ import tkinter
from .ctk_canvas import CTkCanvas from .ctk_canvas import CTkCanvas
from ..theme_manager import ThemeManager from ..theme_manager import ThemeManager
from ..draw_engine import DrawEngine from ..draw_engine import DrawEngine
from ..settings import Settings
from .widget_base_class import CTkBaseClass from .widget_base_class import CTkBaseClass
class CTkProgressBar(CTkBaseClass): class CTkProgressBar(CTkBaseClass):
""" tkinter custom progressbar, always horizontal, values are from 0 to 1 """ """ tkinter custom progressbar, values from 0 to 1 """
def __init__(self, *args, def __init__(self, *args,
variable=None, variable=None,
@ -35,7 +34,7 @@ class CTkProgressBar(CTkBaseClass):
else: else:
height = 8 height = 8
# transfer basic functionality (bg_color, size, appearance_mode, scaling) to CTkBaseClass # transfer basic functionality (bg_color, size, _appearance_mode, scaling) to CTkBaseClass
super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs) super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs)
# color # color
@ -59,8 +58,8 @@ class CTkProgressBar(CTkBaseClass):
self.canvas = CTkCanvas(master=self, self.canvas = CTkCanvas(master=self,
highlightthickness=0, highlightthickness=0,
width=self.apply_widget_scaling(self.desired_width), width=self.apply_widget_scaling(self._desired_width),
height=self.apply_widget_scaling(self.desired_height)) height=self.apply_widget_scaling(self._desired_height))
self.canvas.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="nswe") self.canvas.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="nswe")
self.draw_engine = DrawEngine(self.canvas) self.draw_engine = DrawEngine(self.canvas)
@ -78,7 +77,14 @@ class CTkProgressBar(CTkBaseClass):
def set_scaling(self, *args, **kwargs): def set_scaling(self, *args, **kwargs):
super().set_scaling(*args, **kwargs) super().set_scaling(*args, **kwargs)
self.canvas.configure(width=self.apply_widget_scaling(self.desired_width), height=self.apply_widget_scaling(self.desired_height)) self.canvas.configure(width=self.apply_widget_scaling(self._desired_width), height=self.apply_widget_scaling(self._desired_height))
self.draw()
def set_dimensions(self, width=None, height=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() self.draw()
def destroy(self): def destroy(self):
@ -95,24 +101,24 @@ class CTkProgressBar(CTkBaseClass):
else: else:
orientation = "w" orientation = "w"
requires_recoloring = self.draw_engine.draw_rounded_progress_bar_with_border(self.apply_widget_scaling(self.current_width), requires_recoloring = self.draw_engine.draw_rounded_progress_bar_with_border(self.apply_widget_scaling(self._current_width),
self.apply_widget_scaling(self.current_height), self.apply_widget_scaling(self._current_height),
self.apply_widget_scaling(self.corner_radius), self.apply_widget_scaling(self.corner_radius),
self.apply_widget_scaling(self.border_width), self.apply_widget_scaling(self.border_width),
self.value, self.value,
orientation) orientation)
if no_color_updates is False or requires_recoloring: if no_color_updates is False or requires_recoloring:
self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self.appearance_mode)) self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
self.canvas.itemconfig("border_parts", self.canvas.itemconfig("border_parts",
fill=ThemeManager.single_color(self.border_color, self.appearance_mode), fill=ThemeManager.single_color(self.border_color, self._appearance_mode),
outline=ThemeManager.single_color(self.border_color, self.appearance_mode)) outline=ThemeManager.single_color(self.border_color, self._appearance_mode))
self.canvas.itemconfig("inner_parts", self.canvas.itemconfig("inner_parts",
fill=ThemeManager.single_color(self.fg_color, self.appearance_mode), fill=ThemeManager.single_color(self.fg_color, self._appearance_mode),
outline=ThemeManager.single_color(self.fg_color, self.appearance_mode)) outline=ThemeManager.single_color(self.fg_color, self._appearance_mode))
self.canvas.itemconfig("progress_parts", self.canvas.itemconfig("progress_parts",
fill=ThemeManager.single_color(self.progress_color, self.appearance_mode), fill=ThemeManager.single_color(self.progress_color, self._appearance_mode),
outline=ThemeManager.single_color(self.progress_color, self.appearance_mode)) outline=ThemeManager.single_color(self.progress_color, self._appearance_mode))
def configure(self, *args, **kwargs): def configure(self, *args, **kwargs):
require_redraw = False # some attribute changes require a call of self.draw() at the end require_redraw = False # some attribute changes require a call of self.draw() at the end
@ -159,6 +165,14 @@ class CTkProgressBar(CTkBaseClass):
del kwargs["variable"] del kwargs["variable"]
if "width" in kwargs:
self.set_dimensions(width=kwargs["width"])
del kwargs["width"]
if "height" in kwargs:
self.set_dimensions(height=kwargs["height"])
del kwargs["height"]
super().configure(*args, **kwargs) super().configure(*args, **kwargs)
if require_redraw is True: if require_redraw is True:

View File

@ -1,5 +1,6 @@
import tkinter import tkinter
import sys import sys
from typing import Callable, Union
from .ctk_canvas import CTkCanvas from .ctk_canvas import CTkCanvas
from ..theme_manager import ThemeManager from ..theme_manager import ThemeManager
@ -31,7 +32,7 @@ class CTkRadioButton(CTkBaseClass):
textvariable=None, textvariable=None,
**kwargs): **kwargs):
# transfer basic functionality (bg_color, size, appearance_mode, scaling) to CTkBaseClass # transfer basic functionality (bg_color, size, _appearance_mode, scaling) to CTkBaseClass
super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs) super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs)
# color # color
@ -47,7 +48,7 @@ class CTkRadioButton(CTkBaseClass):
# text # text
self.text = text self.text = text
self.text_label = None self.text_label: Union[tkinter.Label, None] = None
self.text_color = ThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color self.text_color = ThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color
self.text_color_disabled = ThemeManager.theme["color"]["text_disabled"] if text_color_disabled == "default_theme" else text_color_disabled self.text_color_disabled = ThemeManager.theme["color"]["text_disabled"] if text_color_disabled == "default_theme" else text_color_disabled
self.text_font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font self.text_font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font
@ -70,24 +71,23 @@ class CTkRadioButton(CTkBaseClass):
self.bg_canvas = CTkCanvas(master=self, self.bg_canvas = CTkCanvas(master=self,
highlightthickness=0, highlightthickness=0,
width=self.apply_widget_scaling(self.current_width), width=self.apply_widget_scaling(self._current_width),
height=self.apply_widget_scaling(self.current_height)) height=self.apply_widget_scaling(self._current_height))
self.bg_canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=3, rowspan=1, sticky="nswe") self.bg_canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=3, rowspan=1, sticky="nswe")
self.canvas = CTkCanvas(master=self, self.canvas = CTkCanvas(master=self,
highlightthickness=0, highlightthickness=0,
width=self.apply_widget_scaling(self.current_width), width=self.apply_widget_scaling(self._current_width),
height=self.apply_widget_scaling(self.current_height)) height=self.apply_widget_scaling(self._current_height))
self.canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=1) self.canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=1)
self.draw_engine = DrawEngine(self.canvas) self.draw_engine = DrawEngine(self.canvas)
self.canvas.bind("<Enter>", self.on_enter) self.canvas.bind("<Enter>", self.on_enter)
self.canvas.bind("<Leave>", self.on_leave) self.canvas.bind("<Leave>", self.on_leave)
self.canvas.bind("<Button-1>", self.invoke) self.canvas.bind("<Button-1>", self.invoke)
self.canvas.bind("<Button-1>", self.invoke)
self.set_cursor()
self.draw() # initial draw self.draw() # initial draw
self.set_cursor()
if self.variable is not None: if self.variable is not None:
self.variable_callback_name = self.variable.trace_add("write", self.variable_callback) self.variable_callback_name = self.variable.trace_add("write", self.variable_callback)
@ -102,8 +102,8 @@ class CTkRadioButton(CTkBaseClass):
self.grid_columnconfigure(1, weight=0, minsize=self.apply_widget_scaling(6)) self.grid_columnconfigure(1, weight=0, minsize=self.apply_widget_scaling(6))
self.text_label.configure(font=self.apply_font_scaling(self.text_font)) self.text_label.configure(font=self.apply_font_scaling(self.text_font))
self.bg_canvas.configure(width=self.apply_widget_scaling(self.desired_width), height=self.apply_widget_scaling(self.desired_height)) self.bg_canvas.configure(width=self.apply_widget_scaling(self._desired_width), height=self.apply_widget_scaling(self._desired_height))
self.canvas.configure(width=self.apply_widget_scaling(self.desired_width), height=self.apply_widget_scaling(self.desired_height)) self.canvas.configure(width=self.apply_widget_scaling(self._desired_width), height=self.apply_widget_scaling(self._desired_height))
self.draw() self.draw()
def destroy(self): def destroy(self):
@ -113,26 +113,26 @@ class CTkRadioButton(CTkBaseClass):
super().destroy() super().destroy()
def draw(self, no_color_updates=False): def draw(self, no_color_updates=False):
requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.apply_widget_scaling(self.current_width), requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.apply_widget_scaling(self._current_width),
self.apply_widget_scaling(self.current_height), self.apply_widget_scaling(self._current_height),
self.apply_widget_scaling(self.corner_radius), self.apply_widget_scaling(self.corner_radius),
self.apply_widget_scaling(self.border_width)) self.apply_widget_scaling(self.border_width))
self.bg_canvas.configure(bg=ThemeManager.single_color(self.bg_color, self.appearance_mode)) self.bg_canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self.appearance_mode)) self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
if self.check_state is False: if self.check_state is False:
self.canvas.itemconfig("border_parts", self.canvas.itemconfig("border_parts",
outline=ThemeManager.single_color(self.border_color, self.appearance_mode), outline=ThemeManager.single_color(self.border_color, self._appearance_mode),
fill=ThemeManager.single_color(self.border_color, self.appearance_mode)) fill=ThemeManager.single_color(self.border_color, self._appearance_mode))
else: else:
self.canvas.itemconfig("border_parts", self.canvas.itemconfig("border_parts",
outline=ThemeManager.single_color(self.fg_color, self.appearance_mode), outline=ThemeManager.single_color(self.fg_color, self._appearance_mode),
fill=ThemeManager.single_color(self.fg_color, self.appearance_mode)) fill=ThemeManager.single_color(self.fg_color, self._appearance_mode))
self.canvas.itemconfig("inner_parts", self.canvas.itemconfig("inner_parts",
outline=ThemeManager.single_color(self.bg_color, self.appearance_mode), outline=ThemeManager.single_color(self.bg_color, self._appearance_mode),
fill=ThemeManager.single_color(self.bg_color, self.appearance_mode)) fill=ThemeManager.single_color(self.bg_color, self._appearance_mode))
if self.text_label is None: if self.text_label is None:
self.text_label = tkinter.Label(master=self, self.text_label = tkinter.Label(master=self,
@ -143,12 +143,16 @@ class CTkRadioButton(CTkBaseClass):
self.text_label.grid(row=0, column=2, padx=0, pady=0, sticky="w") self.text_label.grid(row=0, column=2, padx=0, pady=0, sticky="w")
self.text_label["anchor"] = "w" self.text_label["anchor"] = "w"
if self.state == tkinter.DISABLED: self.text_label.bind("<Enter>", self.on_enter)
self.text_label.configure(fg=ThemeManager.single_color(self.text_color_disabled, self.appearance_mode)) self.text_label.bind("<Leave>", self.on_leave)
else: self.text_label.bind("<Button-1>", self.invoke)
self.text_label.configure(fg=ThemeManager.single_color(self.text_color, self.appearance_mode))
self.text_label.configure(bg=ThemeManager.single_color(self.bg_color, self.appearance_mode)) if self.state == tkinter.DISABLED:
self.text_label.configure(fg=ThemeManager.single_color(self.text_color_disabled, self._appearance_mode))
else:
self.text_label.configure(fg=ThemeManager.single_color(self.text_color, self._appearance_mode))
self.text_label.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
self.set_text(self.text) self.set_text(self.text)
@ -229,14 +233,22 @@ class CTkRadioButton(CTkBaseClass):
if self.state == tkinter.DISABLED: if self.state == tkinter.DISABLED:
if sys.platform == "darwin" and Settings.cursor_manipulation_enabled: if sys.platform == "darwin" and Settings.cursor_manipulation_enabled:
self.canvas.configure(cursor="arrow") self.canvas.configure(cursor="arrow")
if self.text_label is not None:
self.text_label.configure(cursor="arrow")
elif sys.platform.startswith("win") and Settings.cursor_manipulation_enabled: elif sys.platform.startswith("win") and Settings.cursor_manipulation_enabled:
self.canvas.configure(cursor="arrow") self.canvas.configure(cursor="arrow")
if self.text_label is not None:
self.text_label.configure(cursor="arrow")
elif self.state == tkinter.NORMAL: elif self.state == tkinter.NORMAL:
if sys.platform == "darwin" and Settings.cursor_manipulation_enabled: if sys.platform == "darwin" and Settings.cursor_manipulation_enabled:
self.canvas.configure(cursor="pointinghand") self.canvas.configure(cursor="pointinghand")
if self.text_label is not None:
self.text_label.configure(cursor="pointinghand")
elif sys.platform.startswith("win") and Settings.cursor_manipulation_enabled: elif sys.platform.startswith("win") and Settings.cursor_manipulation_enabled:
self.canvas.configure(cursor="hand2") self.canvas.configure(cursor="hand2")
if self.text_label is not None:
self.text_label.configure(cursor="hand2")
def set_text(self, text): def set_text(self, text):
self.text = text self.text = text
@ -248,19 +260,19 @@ class CTkRadioButton(CTkBaseClass):
def on_enter(self, event=0): def on_enter(self, event=0):
if self.hover is True and self.state == tkinter.NORMAL: if self.hover is True and self.state == tkinter.NORMAL:
self.canvas.itemconfig("border_parts", self.canvas.itemconfig("border_parts",
fill=ThemeManager.single_color(self.hover_color, self.appearance_mode), fill=ThemeManager.single_color(self.hover_color, self._appearance_mode),
outline=ThemeManager.single_color(self.hover_color, self.appearance_mode)) outline=ThemeManager.single_color(self.hover_color, self._appearance_mode))
def on_leave(self, event=0): def on_leave(self, event=0):
if self.hover is True: if self.hover is True:
if self.check_state is True: if self.check_state is True:
self.canvas.itemconfig("border_parts", self.canvas.itemconfig("border_parts",
fill=ThemeManager.single_color(self.fg_color, self.appearance_mode), fill=ThemeManager.single_color(self.fg_color, self._appearance_mode),
outline=ThemeManager.single_color(self.fg_color, self.appearance_mode)) outline=ThemeManager.single_color(self.fg_color, self._appearance_mode))
else: else:
self.canvas.itemconfig("border_parts", self.canvas.itemconfig("border_parts",
fill=ThemeManager.single_color(self.border_color, self.appearance_mode), fill=ThemeManager.single_color(self.border_color, self._appearance_mode),
outline=ThemeManager.single_color(self.border_color, self.appearance_mode)) outline=ThemeManager.single_color(self.border_color, self._appearance_mode))
def variable_callback(self, var_name, index, mode): def variable_callback(self, var_name, index, mode):
if not self.variable_callback_blocked: if not self.variable_callback_blocked:

View File

@ -9,7 +9,7 @@ from .widget_base_class import CTkBaseClass
class CTkSlider(CTkBaseClass): class CTkSlider(CTkBaseClass):
""" tkinter custom slider, always horizontal """ """ tkinter custom slider"""
def __init__(self, *args, def __init__(self, *args,
bg_color=None, bg_color=None,
@ -30,6 +30,7 @@ class CTkSlider(CTkBaseClass):
command=None, command=None,
variable=None, variable=None,
orient="horizontal", orient="horizontal",
state="normal",
**kwargs): **kwargs):
# set default dimensions according to orientation # set default dimensions according to orientation
@ -44,7 +45,7 @@ class CTkSlider(CTkBaseClass):
else: else:
height = 16 height = 16
# transfer basic functionality (bg_color, size, appearance_mode, scaling) to CTkBaseClass # transfer basic functionality (bg_color, size, _appearance_mode, scaling) to CTkBaseClass
super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs) super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs)
# color # color
@ -75,14 +76,15 @@ class CTkSlider(CTkBaseClass):
self.variable: tkinter.Variable = variable self.variable: tkinter.Variable = variable
self.variable_callback_blocked = False self.variable_callback_blocked = False
self.variable_callback_name = None self.variable_callback_name = None
self.state = state
self.grid_rowconfigure(0, weight=1) self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1) self.grid_columnconfigure(0, weight=1)
self.canvas = CTkCanvas(master=self, self.canvas = CTkCanvas(master=self,
highlightthickness=0, highlightthickness=0,
width=self.apply_widget_scaling(self.desired_width), width=self.apply_widget_scaling(self._desired_width),
height=self.apply_widget_scaling(self.desired_height)) height=self.apply_widget_scaling(self._desired_height))
self.canvas.grid(column=0, row=0, rowspan=1, columnspan=1, sticky="nswe") self.canvas.grid(column=0, row=0, rowspan=1, columnspan=1, sticky="nswe")
self.draw_engine = DrawEngine(self.canvas) self.draw_engine = DrawEngine(self.canvas)
@ -106,7 +108,14 @@ class CTkSlider(CTkBaseClass):
def set_scaling(self, *args, **kwargs): def set_scaling(self, *args, **kwargs):
super().set_scaling(*args, **kwargs) super().set_scaling(*args, **kwargs)
self.canvas.configure(width=self.apply_widget_scaling(self.desired_width), height=self.apply_widget_scaling(self.desired_height)) self.canvas.configure(width=self.apply_widget_scaling(self._desired_width), height=self.apply_widget_scaling(self._desired_height))
self.draw()
def set_dimensions(self, width=None, height=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() self.draw()
def destroy(self): def destroy(self):
@ -117,12 +126,18 @@ class CTkSlider(CTkBaseClass):
super().destroy() super().destroy()
def set_cursor(self): def set_cursor(self):
if Settings.cursor_manipulation_enabled: if self.state == "normal" and Settings.cursor_manipulation_enabled:
if sys.platform == "darwin": if sys.platform == "darwin":
self.configure(cursor="pointinghand") self.configure(cursor="pointinghand")
elif sys.platform.startswith("win"): elif sys.platform.startswith("win"):
self.configure(cursor="hand2") self.configure(cursor="hand2")
elif self.state == "disabled" and Settings.cursor_manipulation_enabled:
if sys.platform == "darwin":
self.configure(cursor="arrow")
elif sys.platform.startswith("win"):
self.configure(cursor="arrow")
def draw(self, no_color_updates=False): def draw(self, no_color_updates=False):
if self.orient.lower() == "horizontal": if self.orient.lower() == "horizontal":
orientation = "w" orientation = "w"
@ -131,8 +146,8 @@ class CTkSlider(CTkBaseClass):
else: else:
orientation = "w" orientation = "w"
requires_recoloring = self.draw_engine.draw_rounded_slider_with_border_and_button(self.apply_widget_scaling(self.current_width), requires_recoloring = self.draw_engine.draw_rounded_slider_with_border_and_button(self.apply_widget_scaling(self._current_width),
self.apply_widget_scaling(self.current_height), self.apply_widget_scaling(self._current_height),
self.apply_widget_scaling(self.corner_radius), self.apply_widget_scaling(self.corner_radius),
self.apply_widget_scaling(self.border_width), self.apply_widget_scaling(self.border_width),
self.apply_widget_scaling(self.button_length), self.apply_widget_scaling(self.button_length),
@ -140,61 +155,71 @@ class CTkSlider(CTkBaseClass):
self.value, orientation) self.value, orientation)
if no_color_updates is False or requires_recoloring: if no_color_updates is False or requires_recoloring:
self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self.appearance_mode)) self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
if self.border_color is None: if self.border_color is None:
self.canvas.itemconfig("border_parts", fill=ThemeManager.single_color(self.bg_color, self.appearance_mode), self.canvas.itemconfig("border_parts", fill=ThemeManager.single_color(self.bg_color, self._appearance_mode),
outline=ThemeManager.single_color(self.bg_color, self.appearance_mode)) outline=ThemeManager.single_color(self.bg_color, self._appearance_mode))
else: else:
self.canvas.itemconfig("border_parts", fill=ThemeManager.single_color(self.border_color, self.appearance_mode), self.canvas.itemconfig("border_parts", fill=ThemeManager.single_color(self.border_color, self._appearance_mode),
outline=ThemeManager.single_color(self.border_color, self.appearance_mode)) outline=ThemeManager.single_color(self.border_color, self._appearance_mode))
self.canvas.itemconfig("inner_parts", fill=ThemeManager.single_color(self.fg_color, self.appearance_mode), self.canvas.itemconfig("inner_parts", fill=ThemeManager.single_color(self.fg_color, self._appearance_mode),
outline=ThemeManager.single_color(self.fg_color, self.appearance_mode)) outline=ThemeManager.single_color(self.fg_color, self._appearance_mode))
if self.progress_color is None: if self.progress_color is None:
self.canvas.itemconfig("progress_parts", fill=ThemeManager.single_color(self.fg_color, self.appearance_mode), self.canvas.itemconfig("progress_parts", fill=ThemeManager.single_color(self.fg_color, self._appearance_mode),
outline=ThemeManager.single_color(self.fg_color, self.appearance_mode)) outline=ThemeManager.single_color(self.fg_color, self._appearance_mode))
else: else:
self.canvas.itemconfig("progress_parts", fill=ThemeManager.single_color(self.progress_color, self.appearance_mode), self.canvas.itemconfig("progress_parts", fill=ThemeManager.single_color(self.progress_color, self._appearance_mode),
outline=ThemeManager.single_color(self.progress_color, self.appearance_mode)) outline=ThemeManager.single_color(self.progress_color, self._appearance_mode))
self.canvas.itemconfig("slider_parts", fill=ThemeManager.single_color(self.button_color, self.appearance_mode), if self.hover_state is True:
outline=ThemeManager.single_color(self.button_color, self.appearance_mode)) self.canvas.itemconfig("slider_parts",
fill=ThemeManager.single_color(self.button_hover_color, self._appearance_mode),
outline=ThemeManager.single_color(self.button_hover_color, self._appearance_mode))
else:
self.canvas.itemconfig("slider_parts",
fill=ThemeManager.single_color(self.button_color, self._appearance_mode),
outline=ThemeManager.single_color(self.button_color, self._appearance_mode))
def clicked(self, event=None): def clicked(self, event=None):
if self.orient.lower() == "horizontal": if self.state == "normal":
self.value = (event.x / self.current_width) / self.widget_scaling if self.orient.lower() == "horizontal":
else: self.value = (event.x / self._current_width) / self._widget_scaling
self.value = 1 - (event.y / self.current_height) / self.widget_scaling else:
self.value = 1 - (event.y / self._current_height) / self._widget_scaling
if self.value > 1: if self.value > 1:
self.value = 1 self.value = 1
if self.value < 0: if self.value < 0:
self.value = 0 self.value = 0
self.output_value = self.round_to_step_size(self.from_ + (self.value * (self.to - self.from_))) self.output_value = self.round_to_step_size(self.from_ + (self.value * (self.to - self.from_)))
self.value = (self.output_value - self.from_) / (self.to - self.from_) self.value = (self.output_value - self.from_) / (self.to - self.from_)
self.draw(no_color_updates=False) self.draw(no_color_updates=False)
if self.callback_function is not None: if self.variable is not None:
self.callback_function(self.output_value) self.variable_callback_blocked = True
self.variable.set(round(self.output_value) if isinstance(self.variable, tkinter.IntVar) else self.output_value)
self.variable_callback_blocked = False
if self.variable is not None: if self.callback_function is not None:
self.variable_callback_blocked = True self.callback_function(self.output_value)
self.variable.set(round(self.output_value) if isinstance(self.variable, tkinter.IntVar) else self.output_value)
self.variable_callback_blocked = False
def on_enter(self, event=0): def on_enter(self, event=0):
self.hover_state = True if self.state == "normal":
self.canvas.itemconfig("slider_parts", fill=ThemeManager.single_color(self.button_hover_color, self.appearance_mode), self.hover_state = True
outline=ThemeManager.single_color(self.button_hover_color, self.appearance_mode)) self.canvas.itemconfig("slider_parts",
fill=ThemeManager.single_color(self.button_hover_color, self._appearance_mode),
outline=ThemeManager.single_color(self.button_hover_color, self._appearance_mode))
def on_leave(self, event=0): def on_leave(self, event=0):
self.hover_state = False self.hover_state = False
self.canvas.itemconfig("slider_parts", fill=ThemeManager.single_color(self.button_color, self.appearance_mode), self.canvas.itemconfig("slider_parts",
outline=ThemeManager.single_color(self.button_color, self.appearance_mode)) fill=ThemeManager.single_color(self.button_color, self._appearance_mode),
outline=ThemeManager.single_color(self.button_color, self._appearance_mode))
def round_to_step_size(self, value): def round_to_step_size(self, value):
if self.number_of_steps is not None: if self.number_of_steps is not None:
@ -224,8 +249,8 @@ class CTkSlider(CTkBaseClass):
self.draw(no_color_updates=False) self.draw(no_color_updates=False)
if self.callback_function is not None: # if self.callback_function is not None and not from_variable_callback:
self.callback_function(self.output_value) # self.callback_function(self.output_value)
if self.variable is not None and not from_variable_callback: if self.variable is not None and not from_variable_callback:
self.variable_callback_blocked = True self.variable_callback_blocked = True
@ -239,6 +264,12 @@ class CTkSlider(CTkBaseClass):
def configure(self, *args, **kwargs): def configure(self, *args, **kwargs):
require_redraw = False # some attribute changes require a call of self.draw() at the end require_redraw = False # some attribute changes require a call of self.draw() at the end
if "state" in kwargs:
self.state = kwargs["state"]
self.set_cursor()
require_redraw = True
del kwargs["state"]
if "fg_color" in kwargs: if "fg_color" in kwargs:
self.fg_color = kwargs["fg_color"] self.fg_color = kwargs["fg_color"]
require_redraw = True require_redraw = True
@ -310,6 +341,14 @@ class CTkSlider(CTkBaseClass):
del kwargs["variable"] del kwargs["variable"]
if "width" in kwargs:
self.set_dimensions(width=kwargs["width"])
del kwargs["width"]
if "height" in kwargs:
self.set_dimensions(height=kwargs["height"])
del kwargs["height"]
super().configure(*args, **kwargs) super().configure(*args, **kwargs)
if require_redraw: if require_redraw:

View File

@ -34,7 +34,7 @@ class CTkSwitch(CTkBaseClass):
state=tkinter.NORMAL, state=tkinter.NORMAL,
**kwargs): **kwargs):
# transfer basic functionality (bg_color, size, appearance_mode, scaling) to CTkBaseClass # transfer basic functionality (bg_color, size, _appearance_mode, scaling) to CTkBaseClass
super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs) super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs)
# color # color
@ -79,14 +79,14 @@ class CTkSwitch(CTkBaseClass):
self.bg_canvas = CTkCanvas(master=self, self.bg_canvas = CTkCanvas(master=self,
highlightthickness=0, highlightthickness=0,
width=self.apply_widget_scaling(self.current_width), width=self.apply_widget_scaling(self._current_width),
height=self.apply_widget_scaling(self.current_height)) height=self.apply_widget_scaling(self._current_height))
self.bg_canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=3, rowspan=1, sticky="nswe") self.bg_canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=3, rowspan=1, sticky="nswe")
self.canvas = CTkCanvas(master=self, self.canvas = CTkCanvas(master=self,
highlightthickness=0, highlightthickness=0,
width=self.apply_widget_scaling(self.current_width), width=self.apply_widget_scaling(self._current_width),
height=self.apply_widget_scaling(self.current_height)) height=self.apply_widget_scaling(self._current_height))
self.canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=1, sticky="nswe") self.canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=1, sticky="nswe")
self.draw_engine = DrawEngine(self.canvas) self.draw_engine = DrawEngine(self.canvas)
@ -94,8 +94,8 @@ class CTkSwitch(CTkBaseClass):
self.canvas.bind("<Leave>", self.on_leave) self.canvas.bind("<Leave>", self.on_leave)
self.canvas.bind("<Button-1>", self.toggle) self.canvas.bind("<Button-1>", self.toggle)
self.set_cursor()
self.draw() # initial draw self.draw() # initial draw
self.set_cursor()
if self.variable is not None: if self.variable is not None:
self.variable_callback_name = self.variable.trace_add("write", self.variable_callback) self.variable_callback_name = self.variable.trace_add("write", self.variable_callback)
@ -110,8 +110,8 @@ class CTkSwitch(CTkBaseClass):
self.grid_columnconfigure(1, weight=0, minsize=self.apply_widget_scaling(6)) self.grid_columnconfigure(1, weight=0, minsize=self.apply_widget_scaling(6))
self.text_label.configure(font=self.apply_font_scaling(self.text_font)) self.text_label.configure(font=self.apply_font_scaling(self.text_font))
self.bg_canvas.configure(width=self.apply_widget_scaling(self.desired_width), height=self.apply_widget_scaling(self.desired_height)) self.bg_canvas.configure(width=self.apply_widget_scaling(self._desired_width), height=self.apply_widget_scaling(self._desired_height))
self.canvas.configure(width=self.apply_widget_scaling(self.desired_width), height=self.apply_widget_scaling(self.desired_height)) self.canvas.configure(width=self.apply_widget_scaling(self._desired_width), height=self.apply_widget_scaling(self._desired_height))
self.draw() self.draw()
def destroy(self): def destroy(self):
@ -126,27 +126,36 @@ class CTkSwitch(CTkBaseClass):
if self.state == tkinter.DISABLED: if self.state == tkinter.DISABLED:
if sys.platform == "darwin" and Settings.cursor_manipulation_enabled: if sys.platform == "darwin" and Settings.cursor_manipulation_enabled:
self.canvas.configure(cursor="arrow") self.canvas.configure(cursor="arrow")
if self.text_label is not None:
self.text_label.configure(cursor="arrow")
elif sys.platform.startswith("win") and Settings.cursor_manipulation_enabled: elif sys.platform.startswith("win") and Settings.cursor_manipulation_enabled:
self.canvas.configure(cursor="arrow") self.canvas.configure(cursor="arrow")
else: if self.text_label is not None:
self.text_label.configure(cursor="arrow")
elif self.state == tkinter.NORMAL:
if sys.platform == "darwin" and Settings.cursor_manipulation_enabled: if sys.platform == "darwin" and Settings.cursor_manipulation_enabled:
self.canvas.configure(cursor="pointinghand") self.canvas.configure(cursor="pointinghand")
if self.text_label is not None:
self.text_label.configure(cursor="pointinghand")
elif sys.platform.startswith("win") and Settings.cursor_manipulation_enabled: elif sys.platform.startswith("win") and Settings.cursor_manipulation_enabled:
self.canvas.configure(cursor="hand2") self.canvas.configure(cursor="hand2")
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=False):
if self.check_state is True: if self.check_state is True:
requires_recoloring = self.draw_engine.draw_rounded_slider_with_border_and_button(self.apply_widget_scaling(self.current_width), requires_recoloring = self.draw_engine.draw_rounded_slider_with_border_and_button(self.apply_widget_scaling(self._current_width),
self.apply_widget_scaling(self.current_height), self.apply_widget_scaling(self._current_height),
self.apply_widget_scaling(self.corner_radius), self.apply_widget_scaling(self.corner_radius),
self.apply_widget_scaling(self.border_width), self.apply_widget_scaling(self.border_width),
self.apply_widget_scaling(self.button_length), self.apply_widget_scaling(self.button_length),
self.apply_widget_scaling(self.corner_radius), self.apply_widget_scaling(self.corner_radius),
1, "w") 1, "w")
else: else:
requires_recoloring = self.draw_engine.draw_rounded_slider_with_border_and_button(self.apply_widget_scaling(self.current_width), requires_recoloring = self.draw_engine.draw_rounded_slider_with_border_and_button(self.apply_widget_scaling(self._current_width),
self.apply_widget_scaling(self.current_height), self.apply_widget_scaling(self._current_height),
self.apply_widget_scaling(self.corner_radius), self.apply_widget_scaling(self.corner_radius),
self.apply_widget_scaling(self.border_width), self.apply_widget_scaling(self.border_width),
self.apply_widget_scaling(self.button_length), self.apply_widget_scaling(self.button_length),
@ -154,28 +163,28 @@ class CTkSwitch(CTkBaseClass):
0, "w") 0, "w")
if no_color_updates is False or requires_recoloring: if no_color_updates is False or requires_recoloring:
self.bg_canvas.configure(bg=ThemeManager.single_color(self.bg_color, self.appearance_mode)) self.bg_canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self.appearance_mode)) self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
if self.border_color is None: if self.border_color is None:
self.canvas.itemconfig("border_parts", fill=ThemeManager.single_color(self.bg_color, self.appearance_mode), self.canvas.itemconfig("border_parts", fill=ThemeManager.single_color(self.bg_color, self._appearance_mode),
outline=ThemeManager.single_color(self.bg_color, self.appearance_mode)) outline=ThemeManager.single_color(self.bg_color, self._appearance_mode))
else: else:
self.canvas.itemconfig("border_parts", fill=ThemeManager.single_color(self.border_color, self.appearance_mode), self.canvas.itemconfig("border_parts", fill=ThemeManager.single_color(self.border_color, self._appearance_mode),
outline=ThemeManager.single_color(self.border_color, self.appearance_mode)) outline=ThemeManager.single_color(self.border_color, self._appearance_mode))
self.canvas.itemconfig("inner_parts", fill=ThemeManager.single_color(self.fg_color, self.appearance_mode), self.canvas.itemconfig("inner_parts", fill=ThemeManager.single_color(self.fg_color, self._appearance_mode),
outline=ThemeManager.single_color(self.fg_color, self.appearance_mode)) outline=ThemeManager.single_color(self.fg_color, self._appearance_mode))
if self.progress_color is None: if self.progress_color is None:
self.canvas.itemconfig("progress_parts", fill=ThemeManager.single_color(self.fg_color, self.appearance_mode), self.canvas.itemconfig("progress_parts", fill=ThemeManager.single_color(self.fg_color, self._appearance_mode),
outline=ThemeManager.single_color(self.fg_color, self.appearance_mode)) outline=ThemeManager.single_color(self.fg_color, self._appearance_mode))
else: else:
self.canvas.itemconfig("progress_parts", fill=ThemeManager.single_color(self.progress_color, self.appearance_mode), self.canvas.itemconfig("progress_parts", fill=ThemeManager.single_color(self.progress_color, self._appearance_mode),
outline=ThemeManager.single_color(self.progress_color, self.appearance_mode)) outline=ThemeManager.single_color(self.progress_color, self._appearance_mode))
self.canvas.itemconfig("slider_parts", fill=ThemeManager.single_color(self.button_color, self.appearance_mode), self.canvas.itemconfig("slider_parts", fill=ThemeManager.single_color(self.button_color, self._appearance_mode),
outline=ThemeManager.single_color(self.button_color, self.appearance_mode)) outline=ThemeManager.single_color(self.button_color, self._appearance_mode))
if self.text_label is None: if self.text_label is None:
self.text_label = tkinter.Label(master=self, self.text_label = tkinter.Label(master=self,
@ -185,15 +194,20 @@ class CTkSwitch(CTkBaseClass):
font=self.apply_font_scaling(self.text_font)) font=self.apply_font_scaling(self.text_font))
self.text_label.grid(row=0, column=2, padx=0, pady=0, sticky="w") self.text_label.grid(row=0, column=2, padx=0, pady=0, sticky="w")
self.text_label["anchor"] = "w" self.text_label["anchor"] = "w"
self.text_label.bind("<Enter>", self.on_enter)
self.text_label.bind("<Leave>", self.on_leave)
self.text_label.bind("<Button-1>", self.toggle)
if self.textvariable is not None: if self.textvariable is not None:
self.text_label.configure(textvariable=self.textvariable) self.text_label.configure(textvariable=self.textvariable)
if self.state == tkinter.DISABLED: if self.state == tkinter.DISABLED:
self.text_label.configure(fg=(ThemeManager.single_color(self.text_color_disabled, self.appearance_mode))) self.text_label.configure(fg=(ThemeManager.single_color(self.text_color_disabled, self._appearance_mode)))
else: else:
self.text_label.configure(fg=ThemeManager.single_color(self.text_color, self.appearance_mode)) self.text_label.configure(fg=ThemeManager.single_color(self.text_color, self._appearance_mode))
self.text_label.configure(bg=ThemeManager.single_color(self.bg_color, self.appearance_mode)) self.text_label.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
self.set_text(self.text) self.set_text(self.text)
@ -225,28 +239,28 @@ class CTkSwitch(CTkBaseClass):
self.draw(no_color_updates=True) self.draw(no_color_updates=True)
if self.callback_function is not None:
self.callback_function()
if self.variable is not None and not from_variable_callback: if self.variable is not None and not from_variable_callback:
self.variable_callback_blocked = True self.variable_callback_blocked = True
self.variable.set(self.onvalue) self.variable.set(self.onvalue)
self.variable_callback_blocked = False self.variable_callback_blocked = False
if self.callback_function is not None:
self.callback_function()
def deselect(self, from_variable_callback=False): def deselect(self, from_variable_callback=False):
if self.state is not tkinter.DISABLED or from_variable_callback: if self.state is not tkinter.DISABLED or from_variable_callback:
self.check_state = False self.check_state = False
self.draw(no_color_updates=True) self.draw(no_color_updates=True)
if self.callback_function is not None:
self.callback_function()
if self.variable is not None and not from_variable_callback: if self.variable is not None and not from_variable_callback:
self.variable_callback_blocked = True self.variable_callback_blocked = True
self.variable.set(self.offvalue) self.variable.set(self.offvalue)
self.variable_callback_blocked = False self.variable_callback_blocked = False
if self.callback_function is not None:
self.callback_function()
def get(self): def get(self):
return self.onvalue if self.check_state is True else self.offvalue return self.onvalue if self.check_state is True else self.offvalue
@ -254,13 +268,13 @@ class CTkSwitch(CTkBaseClass):
self.hover_state = True self.hover_state = True
if self.state is not tkinter.DISABLED: if self.state is not tkinter.DISABLED:
self.canvas.itemconfig("slider_parts", fill=ThemeManager.single_color(self.button_hover_color, self.appearance_mode), self.canvas.itemconfig("slider_parts", fill=ThemeManager.single_color(self.button_hover_color, self._appearance_mode),
outline=ThemeManager.single_color(self.button_hover_color, self.appearance_mode)) outline=ThemeManager.single_color(self.button_hover_color, self._appearance_mode))
def on_leave(self, event=0): def on_leave(self, event=0):
self.hover_state = False self.hover_state = False
self.canvas.itemconfig("slider_parts", fill=ThemeManager.single_color(self.button_color, self.appearance_mode), self.canvas.itemconfig("slider_parts", fill=ThemeManager.single_color(self.button_color, self._appearance_mode),
outline=ThemeManager.single_color(self.button_color, self.appearance_mode)) outline=ThemeManager.single_color(self.button_color, self._appearance_mode))
def variable_callback(self, var_name, index, mode): def variable_callback(self, var_name, index, mode):
if not self.variable_callback_blocked: if not self.variable_callback_blocked:

View File

@ -0,0 +1,173 @@
import tkinter
import sys
import copy
import re
from typing import Union
from ..theme_manager import ThemeManager
from ..appearance_mode_tracker import AppearanceModeTracker
from ..scaling_tracker import ScalingTracker
class DropdownMenu(tkinter.Menu):
def __init__(self, *args,
min_character_width=18,
fg_color="default_theme",
hover_color="default_theme",
text_color="default_theme",
text_font="default_theme",
command=None,
values=None,
**kwargs):
super().__init__(*args, **kwargs)
ScalingTracker.add_widget(self.set_scaling, self)
self._widget_scaling = ScalingTracker.get_widget_scaling(self)
self._spacing_scaling = ScalingTracker.get_spacing_scaling(self)
AppearanceModeTracker.add(self.set_appearance_mode, self)
self._appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
self.min_character_width = min_character_width
self.fg_color = ThemeManager.theme["color"]["dropdown_color"] if fg_color == "default_theme" else fg_color
self.hover_color = ThemeManager.theme["color"]["dropdown_hover"] if hover_color == "default_theme" else hover_color
self.text_color = ThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color
self.text_font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font
self.configure_menu_for_platforms()
self.values = values
self.command = command
self.add_menu_commands()
def configure_menu_for_platforms(self):
if sys.platform == "darwin":
self.configure(tearoff=False,
font=self.apply_font_scaling(self.text_font))
elif sys.platform.startswith("win"):
self.configure(tearoff=False,
relief="flat",
activebackground=ThemeManager.single_color(self.hover_color, self._appearance_mode),
borderwidth=0,
activeborderwidth=self.apply_widget_scaling(4),
bg=ThemeManager.single_color(self.fg_color, self._appearance_mode),
fg=ThemeManager.single_color(self.text_color, self._appearance_mode),
activeforeground=ThemeManager.single_color(self.text_color, self._appearance_mode),
font=self.apply_font_scaling(self.text_font),
cursor="hand2")
else:
self.configure(tearoff=False,
relief="flat",
activebackground=ThemeManager.single_color(self.hover_color, self._appearance_mode),
borderwidth=0,
activeborderwidth=0,
bg=ThemeManager.single_color(self.fg_color, self._appearance_mode),
fg=ThemeManager.single_color(self.text_color, self._appearance_mode),
activeforeground=ThemeManager.single_color(self.text_color, self._appearance_mode),
font=self.apply_font_scaling(self.text_font))
def add_menu_commands(self):
if sys.platform.startswith("linux"):
for value in self.values:
self.add_command(label=" " + value.ljust(self.min_character_width) + " ",
command=lambda v=value: self.button_callback(v),
compound="left")
else:
for value in self.values:
self.add_command(label=value.ljust(self.min_character_width),
command=lambda v=value: self.button_callback(v),
compound="left")
def open(self, x: Union[int, float], y: Union[int, float]):
if sys.platform == "darwin":
y += self.apply_widget_scaling(8)
else:
y += self.apply_widget_scaling(3)
if sys.platform == "darwin" or sys.platform.startswith("win"):
self.post(int(x), int(y))
else: # Linux
self.tk_popup(int(x), int(y))
def button_callback(self, value):
if self.command is not None:
self.command(value)
def configure(self, **kwargs):
if "values" in kwargs:
self.values = kwargs["values"]
del kwargs["values"]
self.delete(0, "end") # delete all old commands
self.add_menu_commands()
if "fg_color" in kwargs:
self.fg_color = kwargs["fg_color"]
del kwargs["fg_color"]
self.configure(bg=ThemeManager.single_color(self.fg_color, self._appearance_mode))
if "hover_color" in kwargs:
self.hover_color = kwargs["hover_color"]
del kwargs["hover_color"]
self.configure(activebackground=ThemeManager.single_color(self.hover_color, self._appearance_mode))
if "text_color" in kwargs:
self.text_color = kwargs["text_color"]
del kwargs["text_color"]
self.configure(fg=ThemeManager.single_color(self.text_color, self._appearance_mode))
if "text_font" in kwargs:
self.text_font = kwargs["text_font"]
del kwargs["text_font"]
self.configure(font=self.apply_font_scaling(self.text_font))
super().configure(**kwargs)
def apply_widget_scaling(self, value: Union[int, float, str]) -> Union[float, str]:
if isinstance(value, (int, float)):
return value * self._widget_scaling
else:
return value
def apply_font_scaling(self, font):
if type(font) == tuple or type(font) == list:
font_list = list(font)
for i in range(len(font_list)):
if (type(font_list[i]) == int or type(font_list[i]) == float) and font_list[i] < 0:
font_list[i] = int(font_list[i] * self._widget_scaling)
return tuple(font_list)
elif type(font) == str:
for negative_number in re.findall(r" -\d* ", font):
font = font.replace(negative_number, f" {int(int(negative_number) * self._widget_scaling)} ")
return font
elif isinstance(font, tkinter.font.Font):
new_font_object = copy.copy(font)
if font.cget("size") < 0:
new_font_object.config(size=int(font.cget("size") * self._widget_scaling))
return new_font_object
else:
return font
def set_scaling(self, new_widget_scaling, new_spacing_scaling, new_window_scaling):
self._widget_scaling = new_widget_scaling
self._spacing_scaling = new_spacing_scaling
self.configure(font=self.apply_font_scaling(self.text_font))
if sys.platform.startswith("win"):
self.configure(activeborderwidth=self.apply_widget_scaling(4))
def set_appearance_mode(self, mode_string):
""" colors won't update on appearance mode change when dropdown is open, because it's not necessary """
if mode_string.lower() == "dark":
self._appearance_mode = 1
elif mode_string.lower() == "light":
self._appearance_mode = 0
self.configure_menu_for_platforms()

View File

@ -2,7 +2,12 @@ import tkinter
import tkinter.ttk as ttk import tkinter.ttk as ttk
import copy import copy
import re import re
from typing import Callable, Union, TypedDict from typing import Callable, Union
try:
from typing import TypedDict
except ImportError:
from typing_extensions import TypedDict
from ..windows.ctk_tk import CTk from ..windows.ctk_tk import CTk
from ..windows.ctk_toplevel import CTkToplevel from ..windows.ctk_toplevel import CTkToplevel
@ -12,38 +17,48 @@ from ..theme_manager import ThemeManager
class CTkBaseClass(tkinter.Frame): class CTkBaseClass(tkinter.Frame):
def __init__(self, *args, bg_color=None, width, height, **kwargs): """ Base class of every Ctk widget, handles the dimensions, bg_color,
appearance_mode changes, scaling, bg changes of master if master is not a CTk widget """
def __init__(self,
*args,
bg_color: Union[str, tuple] = None,
width: int,
height: int,
**kwargs):
super().__init__(*args, width=width, height=height, **kwargs) # set desired size of underlying tkinter.Frame super().__init__(*args, width=width, height=height, **kwargs) # set desired size of underlying tkinter.Frame
self.bg_color = self.detect_color_of_master() if bg_color is None else bg_color # dimensions
self._current_width = width # _current_width and _current_height in pixel, represent current size of the widget
self._current_height = height # _current_width and _current_height are independent of the scale
self._desired_width = width # _desired_width and _desired_height, represent desired size set by width and height
self._desired_height = height
self.current_width = width # current_width and current_height in pixel, represent current size of the widget (not the desired size by init) # scaling
self.current_height = height # current_width and current_height are independent of the scale ScalingTracker.add_widget(self.set_scaling, self) # add callback for automatic scaling changes
self.desired_width = width self._widget_scaling = ScalingTracker.get_widget_scaling(self)
self.desired_height = height self._spacing_scaling = ScalingTracker.get_spacing_scaling(self)
# add set_scaling method to callback list of ScalingTracker for automatic scaling changes super().configure(width=self.apply_widget_scaling(self._desired_width),
ScalingTracker.add_widget(self.set_scaling, self) height=self.apply_widget_scaling(self._desired_height))
self.widget_scaling = ScalingTracker.get_widget_scaling(self)
self.spacing_scaling = ScalingTracker.get_spacing_scaling(self)
super().configure(width=self.apply_widget_scaling(self.desired_width),
height=self.apply_widget_scaling(self.desired_height))
# save latest geometry function and kwargs # save latest geometry function and kwargs
class GeometryCallDict(TypedDict): class GeometryCallDict(TypedDict):
function: Callable function: Callable
kwargs: dict kwargs: dict
self.last_geometry_manager_call: Union[GeometryCallDict, None] = None self._last_geometry_manager_call: Union[GeometryCallDict, None] = None
# add set_appearance_mode method to callback list of AppearanceModeTracker for appearance mode changes # add set_appearance_mode method to callback list of AppearanceModeTracker for appearance mode changes
AppearanceModeTracker.add(self.set_appearance_mode, self) AppearanceModeTracker.add(self.set_appearance_mode, self)
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark" self._appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
super().configure(bg=ThemeManager.single_color(self.bg_color, self.appearance_mode)) # background color
self.bg_color = self.detect_color_of_master() if bg_color is None else bg_color
# overwrite configure methods of master when master is tkinter widget, so that bg changes get applied on child CTk widget too super().configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
# overwrite configure methods of master when master is tkinter widget, so that bg changes get applied on child CTk widget as well
if isinstance(self.master, (tkinter.Tk, tkinter.Toplevel, tkinter.Frame)) and not isinstance(self.master, (CTkBaseClass, CTk, CTkToplevel)): if isinstance(self.master, (tkinter.Tk, tkinter.Toplevel, tkinter.Frame)) and not isinstance(self.master, (CTkBaseClass, CTk, CTkToplevel)):
master_old_configure = self.master.config master_old_configure = self.master.config
@ -69,15 +84,15 @@ class CTkBaseClass(tkinter.Frame):
super().destroy() super().destroy()
def place(self, **kwargs): def place(self, **kwargs):
self.last_geometry_manager_call = {"function": super().place, "kwargs": kwargs} self._last_geometry_manager_call = {"function": super().place, "kwargs": kwargs}
super().place(**self.apply_argument_scaling(kwargs)) super().place(**self.apply_argument_scaling(kwargs))
def pack(self, **kwargs): def pack(self, **kwargs):
self.last_geometry_manager_call = {"function": super().pack, "kwargs": kwargs} self._last_geometry_manager_call = {"function": super().pack, "kwargs": kwargs}
super().pack(**self.apply_argument_scaling(kwargs)) super().pack(**self.apply_argument_scaling(kwargs))
def grid(self, **kwargs): def grid(self, **kwargs):
self.last_geometry_manager_call = {"function": super().grid, "kwargs": kwargs} self._last_geometry_manager_call = {"function": super().grid, "kwargs": kwargs}
super().grid(**self.apply_argument_scaling(kwargs)) super().grid(**self.apply_argument_scaling(kwargs))
def apply_argument_scaling(self, kwargs: dict) -> dict: def apply_argument_scaling(self, kwargs: dict) -> dict:
@ -124,36 +139,44 @@ class CTkBaseClass(tkinter.Frame):
def update_dimensions_event(self, event): def update_dimensions_event(self, event):
# only redraw if dimensions changed (for performance) # only redraw if dimensions changed (for performance)
if round(self.current_width) != round(event.width / self.widget_scaling) or round(self.current_height) != round(event.height / self.widget_scaling): if round(self._current_width) != round(event.width / self._widget_scaling) or round(self._current_height) != round(event.height / self._widget_scaling):
self.current_width = (event.width / self.widget_scaling) # adjust current size according to new size given by event self._current_width = (event.width / self._widget_scaling) # adjust current size according to new size given by event
self.current_height = (event.height / self.widget_scaling) # current_width and current_height are independent of the scale self._current_height = (event.height / self._widget_scaling) # _current_width and _current_height are independent of the scale
self.draw(no_color_updates=True) # faster drawing without color changes self.draw(no_color_updates=True) # faster drawing without color changes
def detect_color_of_master(self): def detect_color_of_master(self, master_widget=None):
""" detect color of self.master widget to set correct bg_color """ """ detect color of self.master widget to set correct bg_color """
if isinstance(self.master, CTkBaseClass) and hasattr(self.master, "fg_color"): # master is CTkFrame if master_widget is None:
return self.master.fg_color master_widget = self.master
elif isinstance(self.master, (ttk.Frame, ttk.LabelFrame, ttk.Notebook)): # master is ttk widget if isinstance(master_widget, CTkBaseClass) and hasattr(master_widget, "fg_color"): # master is CTkFrame
if master_widget.fg_color is not None:
return master_widget.fg_color
# if fg_color of master is None, try to retrieve fg_color from master of master
elif hasattr(master_widget.master, "master"):
return self.detect_color_of_master(self.master.master)
elif isinstance(master_widget, (ttk.Frame, ttk.LabelFrame, ttk.Notebook)): # master is ttk widget
try: try:
ttk_style = ttk.Style() ttk_style = ttk.Style()
return ttk_style.lookup(self.master.winfo_class(), 'background') return ttk_style.lookup(master_widget.winfo_class(), 'background')
except Exception: except Exception:
return "#FFFFFF", "#000000" return "#FFFFFF", "#000000"
else: # master is normal tkinter widget else: # master is normal tkinter widget
try: try:
return self.master.cget("bg") # try to get bg color by .cget() method return master_widget.cget("bg") # try to get bg color by .cget() method
except Exception: except Exception:
return "#FFFFFF", "#000000" return "#FFFFFF", "#000000"
def set_appearance_mode(self, mode_string): def set_appearance_mode(self, mode_string):
if mode_string.lower() == "dark": if mode_string.lower() == "dark":
self.appearance_mode = 1 self._appearance_mode = 1
elif mode_string.lower() == "light": elif mode_string.lower() == "light":
self.appearance_mode = 0 self._appearance_mode = 0
if isinstance(self.master, (CTkBaseClass, CTk)) and hasattr(self.master, "fg_color"): if isinstance(self.master, (CTkBaseClass, CTk)) and hasattr(self.master, "fg_color"):
self.bg_color = self.master.fg_color self.bg_color = self.master.fg_color
@ -163,24 +186,33 @@ class CTkBaseClass(tkinter.Frame):
self.draw() self.draw()
def set_scaling(self, new_widget_scaling, new_spacing_scaling, new_window_scaling): def set_scaling(self, new_widget_scaling, new_spacing_scaling, new_window_scaling):
self.widget_scaling = new_widget_scaling self._widget_scaling = new_widget_scaling
self.spacing_scaling = new_spacing_scaling self._spacing_scaling = new_spacing_scaling
super().configure(width=self.apply_widget_scaling(self.desired_width), super().configure(width=self.apply_widget_scaling(self._desired_width),
height=self.apply_widget_scaling(self.desired_height)) height=self.apply_widget_scaling(self._desired_height))
if self.last_geometry_manager_call is not None: if self._last_geometry_manager_call is not None:
self.last_geometry_manager_call["function"](**self.apply_argument_scaling(self.last_geometry_manager_call["kwargs"])) self._last_geometry_manager_call["function"](**self.apply_argument_scaling(self._last_geometry_manager_call["kwargs"]))
def apply_widget_scaling(self, value): def set_dimensions(self, width=None, height=None):
if width is not None:
self._desired_width = width
if height is not None:
self._desired_height = height
super().configure(width=self.apply_widget_scaling(self._desired_width),
height=self.apply_widget_scaling(self._desired_height))
def apply_widget_scaling(self, value: Union[int, float, str]) -> Union[float, str]:
if isinstance(value, (int, float)): if isinstance(value, (int, float)):
return value * self.widget_scaling return value * self._widget_scaling
else: else:
return value return value
def apply_spacing_scaling(self, value): def apply_spacing_scaling(self, value: Union[int, float, str]) -> Union[float, str]:
if isinstance(value, (int, float)): if isinstance(value, (int, float)):
return value * self.spacing_scaling return value * self._spacing_scaling
else: else:
return value return value
@ -189,25 +221,23 @@ class CTkBaseClass(tkinter.Frame):
font_list = list(font) font_list = list(font)
for i in range(len(font_list)): for i in range(len(font_list)):
if (type(font_list[i]) == int or type(font_list[i]) == float) and font_list[i] < 0: if (type(font_list[i]) == int or type(font_list[i]) == float) and font_list[i] < 0:
font_list[i] = int(font_list[i] * self.widget_scaling) font_list[i] = int(font_list[i] * self._widget_scaling)
return tuple(font_list) return tuple(font_list)
elif type(font) == str: elif type(font) == str:
for negative_number in re.findall(r" -\d* ", font): for negative_number in re.findall(r" -\d* ", font):
font = font.replace(negative_number, f" {int(int(negative_number) * self.widget_scaling)} ") font = font.replace(negative_number, f" {int(int(negative_number) * self._widget_scaling)} ")
return font return font
elif isinstance(font, tkinter.font.Font): elif isinstance(font, tkinter.font.Font):
new_font_object = copy.copy(font) new_font_object = copy.copy(font)
if font.cget("size") < 0: if font.cget("size") < 0:
new_font_object.config(size=int(font.cget("size") * self.widget_scaling)) new_font_object.config(size=int(font.cget("size") * self._widget_scaling))
return new_font_object return new_font_object
else: else:
return font return font
def draw(self, no_color_updates=False): def draw(self, no_color_updates: bool = False):
""" abstract of draw method to be overridden """ """ abstract of draw method to be overridden """
pass pass

View File

@ -68,7 +68,7 @@ class CTk(tkinter.Tk):
if self.current_width != round(detected_width / self.window_scaling) or self.current_height != round(detected_height / self.window_scaling): if self.current_width != round(detected_width / self.window_scaling) or self.current_height != round(detected_height / self.window_scaling):
self.current_width = round(detected_width / self.window_scaling) # adjust current size according to new size given by event self.current_width = round(detected_width / self.window_scaling) # adjust current size according to new size given by event
self.current_height = round(detected_height / self.window_scaling) # current_width and current_height are independent of the scale self.current_height = round(detected_height / self.window_scaling) # _current_width and _current_height are independent of the scale
def set_scaling(self, new_widget_scaling, new_spacing_scaling, new_window_scaling): def set_scaling(self, new_widget_scaling, new_spacing_scaling, new_window_scaling):
self.window_scaling = new_window_scaling self.window_scaling = new_window_scaling
@ -138,18 +138,7 @@ class CTk(tkinter.Tk):
self.current_height = max(self.min_height, min(numbers[1], self.max_height)) self.current_height = max(self.min_height, min(numbers[1], self.max_height))
def apply_geometry_scaling(self, geometry_string): def apply_geometry_scaling(self, geometry_string):
numbers = list(map(int, re.split(r"[x+]", geometry_string))) # split geometry string into list of numbers return re.sub(re.compile("\d+"), lambda match_obj: str(round(int(match_obj.group(0)) * self.window_scaling)), geometry_string)
if len(numbers) == 2:
return f"{self.apply_window_scaling(numbers[0]):.0f}x" +\
f"{self.apply_window_scaling(numbers[1]):.0f}"
elif len(numbers) == 4:
return f"{self.apply_window_scaling(numbers[0]):.0f}x" +\
f"{self.apply_window_scaling(numbers[1]):.0f}+" +\
f"{self.apply_window_scaling(numbers[2]):.0f}+" +\
f"{self.apply_window_scaling(numbers[3]):.0f}"
else:
return geometry_string
def apply_window_scaling(self, value): def apply_window_scaling(self, value):
if isinstance(value, (int, float)): if isinstance(value, (int, float)):

View File

@ -47,7 +47,7 @@ class CTkToplevel(tkinter.Toplevel):
AppearanceModeTracker.add(self.set_appearance_mode, self) AppearanceModeTracker.add(self.set_appearance_mode, self)
super().configure(bg=ThemeManager.single_color(self.fg_color, self.appearance_mode)) super().configure(bg=ThemeManager.single_color(self.fg_color, self.appearance_mode))
super().title("CTkToplevel") super().title("CTkToplevel")
# self.geometry(f"{self.current_width}x{self.current_height}") # self.geometry(f"{self._current_width}x{self._current_height}")
if sys.platform.startswith("win"): if sys.platform.startswith("win"):
if self.appearance_mode == 1: if self.appearance_mode == 1:
@ -63,7 +63,7 @@ class CTkToplevel(tkinter.Toplevel):
if self.current_width != round(detected_width / self.window_scaling) or self.current_height != round(detected_height / self.window_scaling): if self.current_width != round(detected_width / self.window_scaling) or self.current_height != round(detected_height / self.window_scaling):
self.current_width = round(detected_width / self.window_scaling) # adjust current size according to new size given by event self.current_width = round(detected_width / self.window_scaling) # adjust current size according to new size given by event
self.current_height = round(detected_height / self.window_scaling) # current_width and current_height are independent of the scale self.current_height = round(detected_height / self.window_scaling) # _current_width and _current_height are independent of the scale
def set_scaling(self, new_widget_scaling, new_spacing_scaling, new_window_scaling): def set_scaling(self, new_widget_scaling, new_spacing_scaling, new_window_scaling):
self.window_scaling = new_window_scaling self.window_scaling = new_window_scaling

View File

@ -14,10 +14,8 @@ class App(customtkinter.CTk):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.title("CustomTkinter complex example") self.title("CustomTkinter complex_example.py")
self.geometry(f"{App.WIDTH}x{App.HEIGHT}") self.geometry(f"{App.WIDTH}x{App.HEIGHT}")
# self.minsize(App.WIDTH, App.HEIGHT)
self.protocol("WM_DELETE_WINDOW", self.on_closing) # call .on_closing() when app gets closed self.protocol("WM_DELETE_WINDOW", self.on_closing) # call .on_closing() when app gets closed
# ============ create two frames ============ # ============ create two frames ============
@ -48,30 +46,27 @@ class App(customtkinter.CTk):
self.label_1.grid(row=1, column=0, pady=10, padx=10) self.label_1.grid(row=1, column=0, pady=10, padx=10)
self.button_1 = customtkinter.CTkButton(master=self.frame_left, self.button_1 = customtkinter.CTkButton(master=self.frame_left,
text="CTkButton 1", text="CTkButton",
fg_color=("gray75", "gray30"), # <- custom tuple-color
command=self.button_event) command=self.button_event)
self.button_1.grid(row=2, column=0, pady=10, padx=20) self.button_1.grid(row=2, column=0, pady=10, padx=20)
self.button_2 = customtkinter.CTkButton(master=self.frame_left, self.button_2 = customtkinter.CTkButton(master=self.frame_left,
text="CTkButton 2", text="CTkButton",
fg_color=("gray75", "gray30"), # <- custom tuple-color
command=self.button_event) command=self.button_event)
self.button_2.grid(row=3, column=0, pady=10, padx=20) self.button_2.grid(row=3, column=0, pady=10, padx=20)
self.button_3 = customtkinter.CTkButton(master=self.frame_left, self.button_3 = customtkinter.CTkButton(master=self.frame_left,
text="CTkButton 3", text="CTkButton",
fg_color=("gray75", "gray30"), # <- custom tuple-color
command=self.button_event) command=self.button_event)
self.button_3.grid(row=4, column=0, pady=10, padx=20) self.button_3.grid(row=4, column=0, pady=10, padx=20)
self.switch_1 = customtkinter.CTkSwitch(master=self.frame_left) self.label_mode = customtkinter.CTkLabel(master=self.frame_left, text="Appearance Mode:")
self.switch_1.grid(row=9, column=0, pady=10, padx=20, sticky="w") self.label_mode.grid(row=9, column=0, pady=0, padx=20, sticky="w")
self.switch_2 = customtkinter.CTkSwitch(master=self.frame_left, self.optionmenu_1 = customtkinter.CTkOptionMenu(master=self.frame_left,
text="Dark Mode", values=["Light", "Dark", "System"],
command=self.change_mode) command=self.change_appearance_mode)
self.switch_2.grid(row=10, column=0, pady=10, padx=20, sticky="w") self.optionmenu_1.grid(row=10, column=0, pady=10, padx=20, sticky="w")
# ============ frame_right ============ # ============ frame_right ============
@ -136,25 +131,17 @@ class App(customtkinter.CTk):
command=self.progressbar.set) command=self.progressbar.set)
self.slider_2.grid(row=5, column=0, columnspan=2, pady=10, padx=20, sticky="we") self.slider_2.grid(row=5, column=0, columnspan=2, pady=10, padx=20, sticky="we")
self.slider_button_1 = customtkinter.CTkButton(master=self.frame_right, self.switch_1 = customtkinter.CTkSwitch(master=self.frame_right,
height=25, text="CTkSwitch")
text="CTkButton", self.switch_1.grid(row=4, column=2, columnspan=1, pady=10, padx=20, sticky="we")
command=self.button_event)
self.slider_button_1.grid(row=4, column=2, columnspan=1, pady=10, padx=20, sticky="we")
self.slider_button_2 = customtkinter.CTkButton(master=self.frame_right, self.switch_2 = customtkinter.CTkSwitch(master=self.frame_right,
height=25, text="CTkSwitch")
text="CTkButton", self.switch_2.grid(row=5, column=2, columnspan=1, pady=10, padx=20, sticky="we")
command=self.button_event)
self.slider_button_2.grid(row=5, column=2, columnspan=1, pady=10, padx=20, sticky="we")
self.checkbox_button_1 = customtkinter.CTkButton(master=self.frame_right, self.combobox_1 = customtkinter.CTkComboBox(master=self.frame_right,
height=25, values=["Value 1", "Value 2"])
text="CTkButton", self.combobox_1.grid(row=6, column=2, columnspan=1, pady=10, padx=20, sticky="we")
border_width=3, # <- custom border_width
fg_color=None, # <- no fg_color
command=self.button_event)
self.checkbox_button_1.grid(row=6, column=2, columnspan=1, pady=10, padx=20, sticky="we")
self.check_box_1 = customtkinter.CTkCheckBox(master=self.frame_right, self.check_box_1 = customtkinter.CTkCheckBox(master=self.frame_right,
text="CTkCheckBox") text="CTkCheckBox")
@ -171,16 +158,20 @@ class App(customtkinter.CTk):
self.button_5 = customtkinter.CTkButton(master=self.frame_right, self.button_5 = customtkinter.CTkButton(master=self.frame_right,
text="CTkButton", text="CTkButton",
border_width=2, # <- custom border_width
fg_color=None, # <- no fg_color
command=self.button_event) command=self.button_event)
self.button_5.grid(row=8, column=2, columnspan=1, pady=20, padx=20, sticky="we") self.button_5.grid(row=8, column=2, columnspan=1, pady=20, padx=20, sticky="we")
# set default values # set default values
self.optionmenu_1.set("Dark")
self.button_3.configure(state="disabled", text="Disabled CTkButton")
self.combobox_1.set("CTkCombobox")
self.radio_button_1.select() self.radio_button_1.select()
self.switch_2.select()
self.slider_1.set(0.2) self.slider_1.set(0.2)
self.slider_2.set(0.7) self.slider_2.set(0.7)
self.progressbar.set(0.5) self.progressbar.set(0.5)
self.slider_button_1.configure(state=tkinter.DISABLED, text="Disabled Button") self.switch_2.select()
self.radio_button_3.configure(state=tkinter.DISABLED) self.radio_button_3.configure(state=tkinter.DISABLED)
self.check_box_1.configure(state=tkinter.DISABLED, text="CheckBox disabled") self.check_box_1.configure(state=tkinter.DISABLED, text="CheckBox disabled")
self.check_box_2.select() self.check_box_2.select()
@ -188,19 +179,13 @@ class App(customtkinter.CTk):
def button_event(self): def button_event(self):
print("Button pressed") print("Button pressed")
def change_mode(self): def change_appearance_mode(self, new_appearance_mode):
if self.switch_2.get() == 1: customtkinter.set_appearance_mode(new_appearance_mode)
customtkinter.set_appearance_mode("dark")
else:
customtkinter.set_appearance_mode("light")
def on_closing(self, event=0): def on_closing(self, event=0):
self.destroy() self.destroy()
def start(self):
self.mainloop()
if __name__ == "__main__": if __name__ == "__main__":
app = App() app = App()
app.start() app.mainloop()

View File

@ -12,7 +12,7 @@ PATH = os.path.dirname(os.path.realpath(__file__))
class App(customtkinter.CTk): class App(customtkinter.CTk):
APP_NAME = "CustomTkinter background gradient image" APP_NAME = "CustomTkinter example_background_image.py"
WIDTH = 900 WIDTH = 900
HEIGHT = 600 HEIGHT = 600
@ -23,6 +23,7 @@ class App(customtkinter.CTk):
self.geometry(f"{App.WIDTH}x{App.HEIGHT}") self.geometry(f"{App.WIDTH}x{App.HEIGHT}")
self.minsize(App.WIDTH, App.HEIGHT) self.minsize(App.WIDTH, App.HEIGHT)
self.maxsize(App.WIDTH, App.HEIGHT) self.maxsize(App.WIDTH, App.HEIGHT)
self.resizable(False, False)
self.protocol("WM_DELETE_WINDOW", self.on_closing) self.protocol("WM_DELETE_WINDOW", self.on_closing)

View File

@ -1,5 +1,5 @@
import tkinter import tkinter
import customtkinter # <- import the CustomTkinter module import customtkinter
from PIL import Image, ImageTk # <- import PIL for the images from PIL import Image, ImageTk # <- import PIL for the images
import os import os
@ -8,9 +8,9 @@ PATH = os.path.dirname(os.path.realpath(__file__))
customtkinter.set_appearance_mode("System") # Modes: "System" (standard), "Dark", "Light" customtkinter.set_appearance_mode("System") # Modes: "System" (standard), "Dark", "Light"
customtkinter.set_default_color_theme("blue") # Themes: "blue" (standard), "green", "dark-blue" customtkinter.set_default_color_theme("blue") # Themes: "blue" (standard), "green", "dark-blue"
root_tk = customtkinter.CTk() # create CTk window like you do with the Tk window (you can also use normal tkinter.Tk window) app = customtkinter.CTk() # create CTk window like you do with the Tk window (you can also use normal tkinter.Tk window)
root_tk.geometry("450x260") app.geometry("450x260")
root_tk.title("CustomTkinter button images") app.title("CustomTkinter example_button_images.py")
def button_function(): def button_function():
@ -29,10 +29,10 @@ add_user_image = ImageTk.PhotoImage(Image.open(PATH + "/test_images/add-user.png
chat_image = ImageTk.PhotoImage(Image.open(PATH + "/test_images/chat.png").resize((image_size, image_size), Image.ANTIALIAS)) chat_image = ImageTk.PhotoImage(Image.open(PATH + "/test_images/chat.png").resize((image_size, image_size), Image.ANTIALIAS))
home_image = ImageTk.PhotoImage(Image.open(PATH + "/test_images/home.png").resize((image_size, image_size), Image.ANTIALIAS)) home_image = ImageTk.PhotoImage(Image.open(PATH + "/test_images/home.png").resize((image_size, image_size), Image.ANTIALIAS))
root_tk.grid_rowconfigure(0, weight=1) app.grid_rowconfigure(0, weight=1)
root_tk.grid_columnconfigure(0, weight=1, minsize=200) app.grid_columnconfigure(0, weight=1, minsize=200)
frame_1 = customtkinter.CTkFrame(master=root_tk, width=250, height=240, corner_radius=15) frame_1 = customtkinter.CTkFrame(master=app, width=250, height=240, corner_radius=15)
frame_1.grid(row=0, column=0, padx=20, pady=20, sticky="nsew") frame_1.grid(row=0, column=0, padx=20, pady=20, sticky="nsew")
frame_1.grid_columnconfigure(0, weight=1) frame_1.grid_columnconfigure(0, weight=1)
@ -56,9 +56,9 @@ button_4 = customtkinter.CTkButton(master=frame_1, image=home_image, text="", wi
corner_radius=10, fg_color="gray40", hover_color="gray25", command=button_function) corner_radius=10, fg_color="gray40", hover_color="gray25", command=button_function)
button_4.grid(row=3, column=1, columnspan=1, padx=20, pady=10, sticky="e") button_4.grid(row=3, column=1, columnspan=1, padx=20, pady=10, sticky="e")
button_5 = customtkinter.CTkButton(master=root_tk, image=add_user_image, text="Add User", width=130, height=70, border_width=3, button_5 = customtkinter.CTkButton(master=app, image=add_user_image, text="Add User", width=130, height=70, border_width=3,
corner_radius=10, compound="bottom", border_color="#D35B58", fg_color=("gray84", "gray25"), hover_color="#C77C78", corner_radius=10, compound="bottom", border_color="#D35B58", fg_color=("gray84", "gray25"), hover_color="#C77C78",
command=button_function) command=button_function)
button_5.grid(row=0, column=1, padx=20, pady=20) button_5.grid(row=0, column=1, padx=20, pady=20)
root_tk.mainloop() app.mainloop()

View File

@ -1,61 +1,61 @@
import tkinter import tkinter
import customtkinter # <- import the CustomTkinter module import customtkinter
customtkinter.set_appearance_mode("dark") # Modes: "System" (standard), "Dark", "Light" customtkinter.set_appearance_mode("dark") # Modes: "System" (standard), "Dark", "Light"
# customtkinter.set_default_color_theme("blue") # Themes: "blue" (standard), "green", "dark-blue" customtkinter.set_default_color_theme("blue") # Themes: "blue" (standard), "green", "dark-blue"
root_tk = customtkinter.CTk() # create CTk window like you do with the Tk window (you can also use normal tkinter.Tk window) app = customtkinter.CTk()
root_tk.geometry("400x480") app.geometry("400x580")
root_tk.title("CustomTkinter Test") app.title("CustomTkinter simple_example.py")
def button_function(): def button_callback():
print("Button click", label_1.text_label.cget("text")) print("Button click", combobox_1.get())
def slider_function(value): def slider_callback(value):
progressbar_1.set(value) progressbar_1.set(value)
def check_box_function(): frame_1 = customtkinter.CTkFrame(master=app)
print("checkbox_1:", checkbox_1.get())
y_padding = 13
frame_1 = customtkinter.CTkFrame(master=root_tk, corner_radius=15)
frame_1.pack(pady=20, padx=60, fill="both", expand=True) frame_1.pack(pady=20, padx=60, fill="both", expand=True)
label_1 = customtkinter.CTkLabel(master=frame_1, justify=tkinter.LEFT) label_1 = customtkinter.CTkLabel(master=frame_1, justify=tkinter.LEFT)
label_1.pack(pady=y_padding, padx=10) label_1.pack(pady=12, padx=10)
progressbar_1 = customtkinter.CTkProgressBar(master=frame_1) progressbar_1 = customtkinter.CTkProgressBar(master=frame_1)
progressbar_1.pack(pady=y_padding, padx=10) progressbar_1.pack(pady=12, padx=10)
button_1 = customtkinter.CTkButton(master=frame_1, corner_radius=8, command=button_function) button_1 = customtkinter.CTkButton(master=frame_1, command=button_callback)
button_1.pack(pady=y_padding, padx=10) button_1.pack(pady=12, padx=10)
slider_1 = customtkinter.CTkSlider(master=frame_1, command=slider_function, from_=0, to=1) slider_1 = customtkinter.CTkSlider(master=frame_1, command=slider_callback, from_=0, to=1)
slider_1.pack(pady=y_padding, padx=10) slider_1.pack(pady=12, padx=10)
slider_1.set(0.5) slider_1.set(0.5)
entry_1 = customtkinter.CTkEntry(master=frame_1, placeholder_text="CTkEntry") entry_1 = customtkinter.CTkEntry(master=frame_1, placeholder_text="CTkEntry")
entry_1.pack(pady=y_padding, padx=10) entry_1.pack(pady=12, padx=10)
checkbox_1 = customtkinter.CTkCheckBox(master=frame_1, command=check_box_function) optionmenu_1 = customtkinter.CTkOptionMenu(frame_1, values=["Option 1", "Option 2", "Option 42 long long long..."])
checkbox_1.pack(pady=y_padding, padx=10) optionmenu_1.pack(pady=12, padx=10)
optionmenu_1.set("CTkOptionMenu")
combobox_1 = customtkinter.CTkComboBox(frame_1, values=["Option 1", "Option 2", "Option 42 long long long..."])
combobox_1.pack(pady=12, padx=10)
optionmenu_1.set("CTkComboBox")
checkbox_1 = customtkinter.CTkCheckBox(master=frame_1)
checkbox_1.pack(pady=12, padx=10)
radiobutton_var = tkinter.IntVar(value=1) radiobutton_var = tkinter.IntVar(value=1)
radiobutton_1 = customtkinter.CTkRadioButton(master=frame_1, variable=radiobutton_var, value=1) radiobutton_1 = customtkinter.CTkRadioButton(master=frame_1, variable=radiobutton_var, value=1)
radiobutton_1.pack(pady=y_padding, padx=10) radiobutton_1.pack(pady=12, padx=10)
radiobutton_2 = customtkinter.CTkRadioButton(master=frame_1, variable=radiobutton_var, value=2) radiobutton_2 = customtkinter.CTkRadioButton(master=frame_1, variable=radiobutton_var, value=2)
radiobutton_2.pack(pady=y_padding, padx=10) radiobutton_2.pack(pady=12, padx=10)
s_var = tkinter.StringVar(value="on")
switch_1 = customtkinter.CTkSwitch(master=frame_1) switch_1 = customtkinter.CTkSwitch(master=frame_1)
switch_1.pack(pady=y_padding, padx=10) switch_1.pack(pady=12, padx=10)
root_tk.mainloop() app.mainloop()

View File

@ -3,7 +3,7 @@ import tkinter
app = tkinter.Tk() app = tkinter.Tk()
app.geometry("400x350") app.geometry("400x350")
app.title("Standard Tkinter Test") app.title("simple_example_standard_tkinter.py")
def button_function(): def button_function():

View File

@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
github_url = "https://github.com/TomSchimansky/CustomTkinter" github_url = "https://github.com/TomSchimansky/CustomTkinter"
[tool.tbump.version] [tool.tbump.version]
current = "4.0.1" current = "4.4.1"
# Example of a semver regexp. # Example of a semver regexp.
# Make sure this matches current_version before # Make sure this matches current_version before

View File

@ -1,8 +1,8 @@
[metadata] [metadata]
name = customtkinter name = customtkinter
version = 4.0.1 version = 4.4.1
description = Create modern looking GUIs with Python description = Create modern looking GUIs with Python
long_description = file: README.md long_description = CustomTkinter UI-Library\n\n[](https://github.com/TomSchimansky/CustomTkinter/blob/master/documentation_images/Windows_dark.png)\n\nMore Information: https://github.com/TomSchimansky/CustomTkinter
long_description_content_type = text/markdown long_description_content_type = text/markdown
url = https://github.com/TomSchimansky/CustomTkinter url = https://github.com/TomSchimansky/CustomTkinter
author = Tom Schimansky author = Tom Schimansky
@ -14,10 +14,12 @@ classifiers =
Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3 :: Only
[options] [options]
python_requires = >=3.7
packages = packages =
customtkinter customtkinter
customtkinter.widgets customtkinter.widgets
customtkinter.windows customtkinter.windows
install_requires = install_requires =
darkdetect darkdetect
typing_extensions; python_version<="3.7"
include_package_data = True include_package_data = True

View File

@ -1,9 +1,7 @@
from tkinter.constants import CENTER, LEFT
import tkinter import tkinter
import tkinter.messagebox import tkinter.messagebox
from tkinter import filedialog as fd from tkinter import filedialog as fd
import customtkinter # <- import the CustomTkinter module import customtkinter
import os
class App(customtkinter.CTk): class App(customtkinter.CTk):

View File

@ -1,5 +1,4 @@
import customtkinter import customtkinter
import tkinter
customtkinter.set_default_color_theme("blue") customtkinter.set_default_color_theme("blue")
customtkinter.set_appearance_mode("dark") customtkinter.set_appearance_mode("dark")

View File

@ -0,0 +1,36 @@
import customtkinter
import random
app = customtkinter.CTk()
app.geometry("400x400")
def button_callback():
button_1.configure(width=random.randint(30, 200), height=random.randint(30, 60))
frame_1.configure(width=random.randint(30, 200), height=random.randint(30, 200))
label_1.configure(width=random.randint(30, 200), height=random.randint(30, 40))
entry_1.configure(width=random.randint(30, 200), height=random.randint(30, 40))
progressbar_1.configure(width=random.randint(30, 200), height=random.randint(10, 16))
slider_1.configure(width=random.randint(30, 200), height=random.randint(14, 20))
button_1 = customtkinter.CTkButton(app, text="button_1", command=button_callback)
button_1.pack(pady=10)
frame_1 = customtkinter.CTkFrame(app)
frame_1.pack(pady=10)
label_1 = customtkinter.CTkLabel(app, fg_color="green")
label_1.pack(pady=10)
entry_1 = customtkinter.CTkEntry(app, placeholder_text="placeholder")
entry_1.pack(pady=10)
progressbar_1 = customtkinter.CTkProgressBar(app)
progressbar_1.pack(pady=10)
slider_1 = customtkinter.CTkSlider(app)
slider_1.pack(pady=10)
app.mainloop()

View File

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

View File

@ -1,22 +1,21 @@
import tkinter
import customtkinter import customtkinter
root_tk = customtkinter.CTk() app = customtkinter.CTk()
root_tk.geometry("400x240") app.geometry("400x240")
def button_function(): def button_function():
top = customtkinter.CTkToplevel(root_tk) top = customtkinter.CTkToplevel(app)
root_tk.after(1000, top.iconify) # hide toplevel app.after(1000, top.iconify) # hide toplevel
root_tk.after(1500, top.deiconify) # show toplevel app.after(1500, top.deiconify) # show toplevel
root_tk.after(2500, root_tk.iconify) # hide root_tk app.after(2500, app.iconify) # hide app
root_tk.after(3000, root_tk.deiconify) # show root_tk app.after(3000, app.deiconify) # show app
root_tk.after(4000, root_tk.destroy) # destroy everything app.after(4000, app.destroy) # destroy everything
button = customtkinter.CTkButton(root_tk, command=button_function) button = customtkinter.CTkButton(app, command=button_function)
button.pack(pady=20, padx=20) button.pack(pady=20, padx=20)
root_tk.mainloop() app.mainloop()

View File

@ -1,53 +1,71 @@
import customtkinter import customtkinter
import tkinter import tkinter
import sys
# customtkinter.set_appearance_mode("light")
root_tk = customtkinter.CTk()
root_tk.geometry("600x500")
menu = tkinter.Menu(tearoff=0, bd=0, relief=tkinter.FLAT, activeforeground="red")
menu.add_command(label="System")
menu.add_command(label="Light")
menu.add_command(label="Dark")
class CTkMenu(tkinter.Toplevel): class CTkMenu(tkinter.Toplevel):
def __init__(self, master, x, y, options): def __init__(self, master, x, y, options):
super().__init__(bg="black") super().__init__()
super().overrideredirect(True)
#self.wm_attributes("-transparentcolor", "black")
super().geometry(f"120x{len(options) * (25 + 4) + 4}+{x}+{y}")
super().lift()
super().transient(master)
self.resizable(False, False)
super().focus_force()
self.focus()
self.frame = customtkinter.CTkFrame(self, border_width=0, width=120, corner_radius=10, border_color="gray4", fg_color="#333740") self.overrideredirect(True)
self.frame.grid(row=0, column=0, sticky="nsew", rowspan=len(options) + 2, columnspan=1) self.geometry(f"120x{len(options) * (25 + 3) + 3}+{x}+{y}")
self.frame.grid_rowconfigure(0, minsize=2) if sys.platform.startswith("darwin"):
self.frame.grid_rowconfigure(len(options) + 1, minsize=2) self.overrideredirect(False)
self.wm_attributes("-transparent", True) # turn off shadow
self.config(bg='systemTransparent') # transparent bg
self.frame = customtkinter.CTkFrame(self, border_width=0, width=120, corner_radius=6, border_color="gray4", fg_color="#333740")
elif sys.platform.startswith("win"):
self.configure(bg="#FFFFF1")
self.wm_attributes("-transparent", "#FFFFF1")
self.focus()
self.frame = customtkinter.CTkFrame(self, border_width=0, width=120, corner_radius=6, border_color="gray4", fg_color="#333740",
overwrite_preferred_drawing_method="circle_shapes")
else:
self.configure(bg="#FFFFF1")
self.wm_attributes("-transparent", "#FFFFF1")
self.frame = customtkinter.CTkFrame(self, border_width=0, width=120, corner_radius=6, border_color="gray4", fg_color="#333740",
overwrite_preferred_drawing_method="circle_shapes")
self.frame.grid(row=0, column=0, sticky="nsew", rowspan=len(options) + 1, columnspan=1, ipadx=0, ipady=0)
self.frame.grid_rowconfigure(len(options) + 1, minsize=3)
self.frame.grid_columnconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1) self.grid_columnconfigure(0, weight=1)
self.buttons = [] self.buttons = []
for index, option in enumerate(options): for index, option in enumerate(options):
button = customtkinter.CTkButton(self.frame, height=25, width=108, fg_color="#333740", text_color="gray74", hover_color="#272A2E", corner_radius=8) button = customtkinter.CTkButton(self.frame, text=option, height=25, width=108, fg_color="#333740", text_color="gray74",
hover_color="gray28", corner_radius=4, command=self.button_click)
button.text_label.grid(row=0, column=0, rowspan=2, columnspan=2, sticky="w") button.text_label.grid(row=0, column=0, rowspan=2, columnspan=2, sticky="w")
button.grid(row=index + 1, column=0, padx=4, pady=2) button.grid(row=index, column=0, padx=(3, 3), pady=(3, 0), columnspan=1, rowspan=1, sticky="ew")
self.buttons.append(button) self.buttons.append(button)
# master.bind("<Configure>", self.window_drag()) self.bind("<FocusOut>", self.focus_loss_event)
self.frame.canvas.bind("<Button-1>", self.focus_loss_event)
def focus_loss_event(self, event):
print("focus loss")
self.destroy()
# self.update()
def button_click(self):
print("button press")
self.destroy()
# self.update()
app = customtkinter.CTk()
app.geometry("600x500")
def open_menu(): def open_menu():
menu = CTkMenu(root_tk, button.winfo_rootx(), button.winfo_rooty() + button.winfo_height() + 4, ["Option 1", "Option 2", "Point 3"]) menu = CTkMenu(app, button.winfo_rootx(), button.winfo_rooty() + button.winfo_height() + 4, ["Option 1", "Option 2", "Point 3"])
button = customtkinter.CTkButton(command=open_menu, height=50) button = customtkinter.CTkButton(command=open_menu, height=30, corner_radius=6)
button.pack(pady=20) button.pack(pady=20)
root_tk.mainloop() button_2 = customtkinter.CTkButton(command=open_menu, height=30, corner_radius=6)
button_2.pack(pady=60)
app.mainloop()

View File

@ -0,0 +1,32 @@
import tkinter
import tkinter.ttk as ttk
import customtkinter
app = customtkinter.CTk()
app.title('Test OptionMenu ComboBox.py')
app.geometry('400x300')
def select_callback(choice):
choice = variable.get()
print("display_selected", choice)
countries = ['Bahamas', 'Canada', 'Cuba', 'United States']
variable = tkinter.StringVar()
variable.set("test")
optionmenu_tk = tkinter.OptionMenu(app, variable, *countries, command=select_callback)
optionmenu_tk.pack(pady=10, padx=10)
optionmenu_1 = customtkinter.CTkOptionMenu(app, variable=variable, values=countries, command=select_callback)
optionmenu_1.pack(pady=20, padx=10)
combobox_tk = ttk.Combobox(app, values=countries)
combobox_tk.pack(pady=10, padx=10)
combobox_1 = customtkinter.CTkComboBox(app, variable=variable, values=countries, command=select_callback)
combobox_1.pack(pady=20, padx=10)
app.mainloop()

View File

@ -6,17 +6,17 @@ customtkinter.ScalingTracker.set_window_scaling(0.5)
customtkinter.set_appearance_mode("dark") # Modes: "System" (standard), "Dark", "Light" customtkinter.set_appearance_mode("dark") # Modes: "System" (standard), "Dark", "Light"
customtkinter.set_default_color_theme("blue") # Themes: "blue" (standard), "green", "dark-blue" customtkinter.set_default_color_theme("blue") # Themes: "blue" (standard), "green", "dark-blue"
root_tk = customtkinter.CTk() # create CTk window like you do with the Tk window (you can also use normal tkinter.Tk window) app = customtkinter.CTk() # create CTk window like you do with the Tk window (you can also use normal tkinter.Tk window)
root_tk.geometry("400x600") app.geometry("400x600")
root_tk.title("CustomTkinter manual scaling test") app.title("CustomTkinter manual scaling test")
#root_tk.minsize(200, 200) #app.minsize(200, 200)
#root_tk.maxsize(520, 520) #app.maxsize(520, 520)
#root_tk.resizable(True, False) #app.resizable(True, False)
def button_function(): def button_function():
root_tk.geometry(f"{200}x{200}") app.geometry(f"{200}x{200}")
print("Button click", label_1.text_label.cget("text")) print("Button click", label_1.text_label.cget("text"))
@ -29,7 +29,7 @@ def slider_function(value):
y_padding = 13 y_padding = 13
frame_1 = customtkinter.CTkFrame(master=root_tk, height=550, width=300) frame_1 = customtkinter.CTkFrame(master=app, height=550, width=300)
frame_1.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER) frame_1.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER)
label_1 = customtkinter.CTkLabel(master=frame_1, justify=tkinter.LEFT) label_1 = customtkinter.CTkLabel(master=frame_1, justify=tkinter.LEFT)
label_1.place(relx=0.5, y=50, anchor=tkinter.CENTER) label_1.place(relx=0.5, y=50, anchor=tkinter.CENTER)
@ -53,4 +53,4 @@ s_var = tkinter.StringVar(value="on")
switch_1 = customtkinter.CTkSwitch(master=frame_1) switch_1 = customtkinter.CTkSwitch(master=frame_1)
switch_1.place(relx=0.5, y=450, anchor=tkinter.CENTER) switch_1.place(relx=0.5, y=450, anchor=tkinter.CENTER)
root_tk.mainloop() app.mainloop()

View File

@ -6,20 +6,20 @@ customtkinter.ScalingTracker.set_window_scaling(0.5)
customtkinter.set_appearance_mode("dark") # Modes: "System" (standard), "Dark", "Light" customtkinter.set_appearance_mode("dark") # Modes: "System" (standard), "Dark", "Light"
customtkinter.set_default_color_theme("blue") # Themes: "blue" (standard), "green", "dark-blue" customtkinter.set_default_color_theme("blue") # Themes: "blue" (standard), "green", "dark-blue"
root_tk = customtkinter.CTk() # create CTk window like you do with the Tk window (you can also use normal tkinter.Tk window) app = customtkinter.CTk() # create CTk window like you do with the Tk window (you can also use normal tkinter.Tk window)
root_tk.geometry("400x480") app.geometry("400x480")
root_tk.title("CustomTkinter manual scaling test") app.title("CustomTkinter manual scaling test")
top_tk = customtkinter.CTkToplevel(root_tk) top_tk = customtkinter.CTkToplevel(app)
top_tk.geometry("500x500") top_tk.geometry("500x500")
#root_tk.minsize(200, 200) #app.minsize(200, 200)
#root_tk.maxsize(520, 520) #app.maxsize(520, 520)
#root_tk.resizable(True, False) #app.resizable(True, False)
def button_function(): def button_function():
root_tk.geometry(f"{200}x{200}") app.geometry(f"{200}x{200}")
print("Button click", label_1.text_label.cget("text")) print("Button click", label_1.text_label.cget("text"))
@ -32,7 +32,7 @@ def slider_function(value):
y_padding = 13 y_padding = 13
frame_1 = customtkinter.CTkFrame(master=root_tk) frame_1 = customtkinter.CTkFrame(master=app)
frame_1.pack(pady=20, padx=60, fill="both", expand=True) frame_1.pack(pady=20, padx=60, fill="both", expand=True)
label_1 = customtkinter.CTkLabel(master=frame_1, justify=tkinter.LEFT) label_1 = customtkinter.CTkLabel(master=frame_1, justify=tkinter.LEFT)
label_1.pack(pady=y_padding, padx=10) label_1.pack(pady=y_padding, padx=10)
@ -78,4 +78,4 @@ radiobutton_2.pack(pady=y_padding, padx=10)
switch_1 = customtkinter.CTkSwitch(master=top_tk) switch_1 = customtkinter.CTkSwitch(master=top_tk)
switch_1.pack(pady=y_padding, padx=10) switch_1.pack(pady=y_padding, padx=10)
root_tk.mainloop() app.mainloop()

View File

@ -0,0 +1,62 @@
import tkinter
import customtkinter
app = customtkinter.CTk()
app.geometry("400x900")
app.title("CustomTkinter Test")
def change_state(widget):
if widget.state == tkinter.NORMAL:
widget.configure(state=tkinter.DISABLED)
elif widget.state == tkinter.DISABLED:
widget.configure(state=tkinter.NORMAL)
def widget_click():
print("widget clicked")
button_1 = customtkinter.CTkButton(master=app, text="button_1", command=widget_click)
button_1.pack(padx=20, pady=(20, 10))
button_2 = customtkinter.CTkButton(master=app, text="Disable/Enable button_1", command=lambda: change_state(button_1))
button_2.pack(padx=20, pady=(10, 20))
switch_1 = customtkinter.CTkSwitch(master=app, text="switch_1", command=widget_click)
switch_1.pack(padx=20, pady=(20, 10))
button_2 = customtkinter.CTkButton(master=app, text="Disable/Enable switch_1", command=lambda: change_state(switch_1))
button_2.pack(padx=20, pady=(10, 20))
entry_1 = customtkinter.CTkEntry(master=app, placeholder_text="entry_1")
entry_1.pack(padx=20, pady=(20, 10))
button_3 = customtkinter.CTkButton(master=app, text="Disable/Enable entry_1", command=lambda: change_state(entry_1))
button_3.pack(padx=20, pady=(10, 20))
checkbox_1 = customtkinter.CTkCheckBox(master=app, text="checkbox_1")
checkbox_1.pack(padx=20, pady=(20, 10))
button_4 = customtkinter.CTkButton(master=app, text="Disable/Enable checkbox_1", command=lambda: change_state(checkbox_1))
button_4.pack(padx=20, pady=(10, 20))
radiobutton_1 = customtkinter.CTkRadioButton(master=app, text="radiobutton_1")
radiobutton_1.pack(padx=20, pady=(20, 10))
button_5 = customtkinter.CTkButton(master=app, text="Disable/Enable radiobutton_1", command=lambda: change_state(radiobutton_1))
button_5.pack(padx=20, pady=(10, 20))
optionmenu_1 = customtkinter.CTkOptionMenu(app, values=["test 1", "test 2"])
optionmenu_1.pack(pady=10, padx=10)
button_6 = customtkinter.CTkButton(master=app, text="Disable/Enable optionmenu_1", command=lambda: change_state(optionmenu_1))
button_6.pack(padx=20, pady=(10, 20))
combobox_1 = customtkinter.CTkComboBox(app, values=["test 1", "test 2"])
combobox_1.pack(pady=10, padx=10)
button_7 = customtkinter.CTkButton(master=app, text="Disable/Enable combobox_1", command=lambda: change_state(combobox_1))
button_7.pack(padx=20, pady=(10, 20))
slider_1 = customtkinter.CTkSlider(app)
slider_1.pack(pady=10, padx=10)
button_8 = customtkinter.CTkButton(master=app, text="Disable/Enable slider_1", command=lambda: change_state(slider_1))
button_8.pack(padx=20, pady=(10, 20))
app.mainloop()

View File

@ -1,5 +1,4 @@
import customtkinter import customtkinter
import tkinter
customtkinter.set_appearance_mode("dark") customtkinter.set_appearance_mode("dark")
customtkinter.set_default_color_theme("blue") customtkinter.set_default_color_theme("blue")
@ -22,8 +21,8 @@ def button_click_event():
button = customtkinter.CTkButton(app, text="Open Dialog", command=button_click_event) button = customtkinter.CTkButton(app, text="Open Dialog", command=button_click_event)
button.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER) button.place(relx=0.5, rely=0.5, anchor=customtkinter.CENTER)
c1 = customtkinter.CTkCheckBox(app, text="dark mode", command=change_mode) c1 = customtkinter.CTkCheckBox(app, text="dark mode", command=change_mode)
c1.place(relx=0.5, rely=0.8, anchor=tkinter.CENTER) c1.place(relx=0.5, rely=0.8, anchor=customtkinter.CENTER)
app.mainloop() app.mainloop()

View File

@ -1,74 +0,0 @@
import tkinter
import customtkinter # <- import the CustomTkinter module
TEST_CONFIGURE = True
TEST_REMOVING = False
root_tk = customtkinter.CTk() # create CTk window like you do with the Tk window (you can also use normal tkinter.Tk window)
root_tk.geometry("400x600")
root_tk.title("Tkinter Variable Test")
txt_var = tkinter.StringVar(value="")
entry_1 = customtkinter.CTkEntry(root_tk, width=200, textvariable=txt_var)
entry_1.pack(pady=15)
txt_var.set("new text wjkfjdshkjfb")
if TEST_CONFIGURE: entry_1.configure(textvariable=txt_var)
if TEST_REMOVING: entry_1.configure(textvariable="")
label_1 = customtkinter.CTkLabel(root_tk, width=200, textvariable=txt_var)
label_1.pack(pady=15)
if TEST_CONFIGURE: label_1.configure(textvariable=txt_var)
if TEST_REMOVING: label_1.configure(textvariable="")
button_1 = customtkinter.CTkButton(root_tk, width=200, textvariable=txt_var)
button_1.pack(pady=15)
int_var = tkinter.IntVar(value=10)
if TEST_CONFIGURE: button_1.configure(textvariable=int_var)
if TEST_REMOVING: button_1.configure(textvariable="")
slider_1 = customtkinter.CTkSlider(root_tk, width=200, from_=0, to=3, variable=int_var)
slider_1.pack(pady=15)
if TEST_CONFIGURE: slider_1.configure(variable=int_var)
if TEST_REMOVING: slider_1.configure(variable="")
int_var.set(2)
slider_2 = customtkinter.CTkSlider(root_tk, width=200, from_=0, to=3, variable=int_var)
slider_2.pack(pady=15)
if TEST_CONFIGURE: slider_2.configure(variable=int_var)
if TEST_REMOVING: slider_2.configure(variable="")
label_2 = customtkinter.CTkLabel(root_tk, width=200, textvariable=int_var)
label_2.pack(pady=15)
progress_1 = customtkinter.CTkProgressBar(root_tk, width=200, variable=int_var)
progress_1.pack(pady=15)
if TEST_CONFIGURE: progress_1.configure(variable=int_var)
if TEST_REMOVING: progress_1.configure(variable="")
check_var = tkinter.StringVar(value="on")
check_1 = customtkinter.CTkCheckBox(root_tk, text="check 1", variable=check_var, onvalue="on", offvalue="off")
check_1.pack(pady=15)
if TEST_CONFIGURE: check_1.configure(variable=check_var)
if TEST_REMOVING: check_1.configure(variable="")
print("check_1", check_1.get())
check_2 = customtkinter.CTkCheckBox(root_tk, text="check 2", variable=check_var, onvalue="on", offvalue="off")
check_2.pack(pady=15)
if TEST_CONFIGURE: check_2.configure(variable=check_var)
if TEST_REMOVING: check_2.configure(variable="")
label_3 = customtkinter.CTkLabel(root_tk, width=200, textvariable=check_var)
label_3.pack(pady=15)
label_3.configure(textvariable=check_var)
def switch_event():
print("switch event")
s_var = tkinter.StringVar(value="on")
switch_1 = customtkinter.CTkSwitch(master=root_tk, variable=s_var, textvariable=s_var, onvalue="on", offvalue="off", command=switch_event)
switch_1.pack(pady=20, padx=10)
switch_1 = customtkinter.CTkSwitch(master=root_tk, variable=s_var, textvariable=s_var, onvalue="on", offvalue="off")
switch_1.pack(pady=20, padx=10)
#switch_1.toggle()
root_tk.mainloop()

View File

@ -4,28 +4,28 @@ import customtkinter
customtkinter.set_appearance_mode("light") customtkinter.set_appearance_mode("light")
root_tk = customtkinter.CTk() app = customtkinter.CTk()
root_tk.geometry("1400x480") app.geometry("1400x480")
root_tk.title("CustomTkinter TTk Compatibility Test") app.title("CustomTkinter TTk Compatibility Test")
root_tk.grid_rowconfigure(0, weight=1) app.grid_rowconfigure(0, weight=1)
root_tk.grid_columnconfigure((0, 1, 2, 3, 5, 6), weight=1) app.grid_columnconfigure((0, 1, 2, 3, 5, 6), weight=1)
button_0 = customtkinter.CTkButton(root_tk) button_0 = customtkinter.CTkButton(app)
button_0.grid(padx=20, pady=20, row=0, column=0) button_0.grid(padx=20, pady=20, row=0, column=0)
frame_1 = tkinter.Frame(master=root_tk) frame_1 = tkinter.Frame(master=app)
frame_1.grid(padx=20, pady=20, row=0, column=1, sticky="nsew") frame_1.grid(padx=20, pady=20, row=0, column=1, sticky="nsew")
button_1 = customtkinter.CTkButton(frame_1, text="tkinter.Frame") button_1 = customtkinter.CTkButton(frame_1, text="tkinter.Frame")
button_1.pack(pady=20, padx=20) button_1.pack(pady=20, padx=20)
frame_2 = tkinter.LabelFrame(master=root_tk, text="Tkinter LabelFrame") frame_2 = tkinter.LabelFrame(master=app, text="Tkinter LabelFrame")
frame_2.grid(padx=20, pady=20, row=0, column=2, sticky="nsew") frame_2.grid(padx=20, pady=20, row=0, column=2, sticky="nsew")
button_2 = customtkinter.CTkButton(frame_2, text="tkinter.LabelFrame") button_2 = customtkinter.CTkButton(frame_2, text="tkinter.LabelFrame")
button_2.pack(pady=20, padx=20) button_2.pack(pady=20, padx=20)
frame_3 = customtkinter.CTkFrame(master=root_tk) frame_3 = customtkinter.CTkFrame(master=app)
frame_3.grid(padx=20, pady=20, row=0, column=3, sticky="nsew") frame_3.grid(padx=20, pady=20, row=0, column=3, sticky="nsew")
label_3 = customtkinter.CTkLabel(master=frame_3, text="CTkFrame Label", fg_color=("gray95", "gray15")) label_3 = customtkinter.CTkLabel(master=frame_3, text="CTkFrame Label", fg_color=("gray95", "gray15"))
label_3.grid(row=0, column=0, columnspan=1, padx=5, pady=5, sticky="ew") label_3.grid(row=0, column=0, columnspan=1, padx=5, pady=5, sticky="ew")
@ -34,17 +34,17 @@ button_3.grid(row=1, column=0, padx=20)
frame_3.grid_rowconfigure(1, weight=1) frame_3.grid_rowconfigure(1, weight=1)
frame_3.grid_columnconfigure((0, ), weight=1) frame_3.grid_columnconfigure((0, ), weight=1)
frame_4 = ttk.Frame(master=root_tk) frame_4 = ttk.Frame(master=app)
frame_4.grid(padx=20, pady=20, row=0, column=4, sticky="nsew") frame_4.grid(padx=20, pady=20, row=0, column=4, sticky="nsew")
button_4 = customtkinter.CTkButton(frame_4, text="ttk.Frame") button_4 = customtkinter.CTkButton(frame_4, text="ttk.Frame")
button_4.pack(pady=20, padx=20) button_4.pack(pady=20, padx=20)
frame_5 = ttk.LabelFrame(master=root_tk, text="TTk LabelFrame") frame_5 = ttk.LabelFrame(master=app, text="TTk LabelFrame")
frame_5.grid(padx=20, pady=20, row=0, column=5, sticky="nsew") frame_5.grid(padx=20, pady=20, row=0, column=5, sticky="nsew")
button_5 = customtkinter.CTkButton(frame_5) button_5 = customtkinter.CTkButton(frame_5)
button_5.pack(pady=20, padx=20) button_5.pack(pady=20, padx=20)
frame_6 = ttk.Notebook(master=root_tk) frame_6 = ttk.Notebook(master=app)
frame_6.grid(padx=20, pady=20, row=0, column=6, sticky="nsew") frame_6.grid(padx=20, pady=20, row=0, column=6, sticky="nsew")
button_6 = customtkinter.CTkButton(frame_6, text="ttk.Notebook") button_6 = customtkinter.CTkButton(frame_6, text="ttk.Notebook")
button_6.pack(pady=20, padx=20) button_6.pack(pady=20, padx=20)
@ -52,4 +52,4 @@ button_6.pack(pady=20, padx=20)
ttk_style = ttk.Style() ttk_style = ttk.Style()
ttk_style.configure(frame_3.winfo_class(), background='red') ttk_style.configure(frame_3.winfo_class(), background='red')
root_tk.mainloop() app.mainloop()

View File

@ -0,0 +1,80 @@
import tkinter
import customtkinter
TEST_CONFIGURE = True
TEST_REMOVING = False
app = customtkinter.CTk() # create CTk window like you do with the Tk window (you can also use normal tkinter.Tk window)
app.geometry("400x800")
app.title("Tkinter Variable Test")
txt_var = tkinter.StringVar(value="")
entry_1 = customtkinter.CTkEntry(app, width=200, textvariable=txt_var)
entry_1.pack(pady=15)
txt_var.set("new text test")
if TEST_CONFIGURE: entry_1.configure(textvariable=txt_var)
if TEST_REMOVING: entry_1.configure(textvariable="")
label_1 = customtkinter.CTkLabel(app, width=200, textvariable=txt_var)
label_1.pack(pady=15)
if TEST_CONFIGURE: label_1.configure(textvariable=txt_var)
if TEST_REMOVING: label_1.configure(textvariable="")
button_1 = customtkinter.CTkButton(app, width=200, textvariable=txt_var)
button_1.pack(pady=15)
int_var = tkinter.IntVar(value=10)
if TEST_CONFIGURE: button_1.configure(textvariable=int_var)
if TEST_REMOVING: button_1.configure(textvariable="")
slider_1 = customtkinter.CTkSlider(app, width=200, from_=0, to=3, variable=int_var)
slider_1.pack(pady=15)
if TEST_CONFIGURE: slider_1.configure(variable=int_var)
if TEST_REMOVING: slider_1.configure(variable="")
int_var.set(2)
slider_2 = customtkinter.CTkSlider(app, width=200, from_=0, to=3, variable=int_var)
slider_2.pack(pady=15)
if TEST_CONFIGURE: slider_2.configure(variable=int_var)
if TEST_REMOVING: slider_2.configure(variable="")
label_2 = customtkinter.CTkLabel(app, width=200, textvariable=int_var)
label_2.pack(pady=15)
progress_1 = customtkinter.CTkProgressBar(app, width=200, variable=int_var)
progress_1.pack(pady=15)
if TEST_CONFIGURE: progress_1.configure(variable=int_var)
if TEST_REMOVING: progress_1.configure(variable="")
check_var = tkinter.StringVar(value="on")
check_1 = customtkinter.CTkCheckBox(app, text="check 1", variable=check_var, onvalue="on", offvalue="off")
check_1.pack(pady=15)
if TEST_CONFIGURE: check_1.configure(variable=check_var)
if TEST_REMOVING: check_1.configure(variable="")
print("check_1", check_1.get())
check_2 = customtkinter.CTkCheckBox(app, text="check 2", variable=check_var, onvalue="on", offvalue="off")
check_2.pack(pady=15)
if TEST_CONFIGURE: check_2.configure(variable=check_var)
if TEST_REMOVING: check_2.configure(variable="")
label_3 = customtkinter.CTkLabel(app, width=200, textvariable=check_var)
label_3.pack(pady=15)
label_3.configure(textvariable=check_var)
def switch_event():
print("switch event")
s_var = tkinter.StringVar(value="on")
switch_1 = customtkinter.CTkSwitch(master=app, variable=s_var, textvariable=s_var, onvalue="on", offvalue="off", command=switch_event)
switch_1.pack(pady=20, padx=10)
switch_1 = customtkinter.CTkSwitch(master=app, variable=s_var, textvariable=s_var, onvalue="on", offvalue="off")
switch_1.pack(pady=20, padx=10)
optionmenu_var = tkinter.StringVar(value="test")
optionmenu_1 = customtkinter.CTkOptionMenu(master=app, variable=optionmenu_var, values=["Option 1", "Option 2", "Option 3"])
optionmenu_1.pack(pady=20, padx=10)
combobox_1 = customtkinter.CTkComboBox(master=app, values=["Option 1", "Option 2", "Option 3"])
combobox_1.pack(pady=20, padx=10)
combobox_1.configure(variable=optionmenu_var)
app.mainloop()

View File

@ -1,25 +1,25 @@
import customtkinter import customtkinter
root_tk = customtkinter.CTk() # create CTk window like you do with the Tk window (you can also use normal tkinter.Tk window) app = customtkinter.CTk() # create CTk window like you do with the Tk window (you can also use normal tkinter.Tk window)
root_tk.geometry("400x650") app.geometry("400x650")
root_tk.title("test_vertical_widgets") app.title("test_vertical_widgets")
root_tk.grid_columnconfigure(0, weight=1) app.grid_columnconfigure(0, weight=1)
root_tk.grid_rowconfigure((0, 1, 2, 3), weight=1) app.grid_rowconfigure((0, 1, 2, 3), weight=1)
progressbar_1 = customtkinter.CTkProgressBar(root_tk, orient="horizontal") progressbar_1 = customtkinter.CTkProgressBar(app, orient="horizontal")
progressbar_1.grid(row=0, column=0, pady=20, padx=20) progressbar_1.grid(row=0, column=0, pady=20, padx=20)
progressbar_2 = customtkinter.CTkProgressBar(root_tk, orient="vertical") progressbar_2 = customtkinter.CTkProgressBar(app, orient="vertical")
progressbar_2.grid(row=1, column=0, pady=20, padx=20) progressbar_2.grid(row=1, column=0, pady=20, padx=20)
slider_1 = customtkinter.CTkSlider(root_tk, orient="horizontal", command=progressbar_1.set, slider_1 = customtkinter.CTkSlider(app, orient="horizontal", command=progressbar_1.set,
button_corner_radius=3, button_length=20) button_corner_radius=3, button_length=20)
slider_1.grid(row=2, column=0, pady=20, padx=20) slider_1.grid(row=2, column=0, pady=20, padx=20)
slider_2 = customtkinter.CTkSlider(root_tk, orient="vertical", command=progressbar_2.set, slider_2 = customtkinter.CTkSlider(app, orient="vertical", command=progressbar_2.set,
button_corner_radius=3, button_length=20) button_corner_radius=3, button_length=20)
slider_2.grid(row=3, column=0, pady=20, padx=20) slider_2.grid(row=3, column=0, pady=20, padx=20)
root_tk.mainloop() app.mainloop()

View File

@ -1,47 +0,0 @@
import tkinter
import customtkinter
root_tk = customtkinter.CTk()
root_tk.geometry("400x800")
root_tk.title("CustomTkinter Test")
def change_state(widget):
if widget.state == tkinter.NORMAL:
widget.configure(state=tkinter.DISABLED)
elif widget.state == tkinter.DISABLED:
widget.configure(state=tkinter.NORMAL)
def widget_click():
print("widget clicked")
button_1 = customtkinter.CTkButton(master=root_tk, text="button_1", command=widget_click)
button_1.pack(padx=20, pady=(20, 10))
button_2 = customtkinter.CTkButton(master=root_tk, text="Disable/Enable button_1", command=lambda: change_state(button_1))
button_2.pack(padx=20, pady=(10, 20))
switch_1 = customtkinter.CTkSwitch(master=root_tk, text="switch_1", command=widget_click)
switch_1.pack(padx=20, pady=(20, 10))
button_2 = customtkinter.CTkButton(master=root_tk, text="Disable/Enable switch_1", command=lambda: change_state(switch_1))
button_2.pack(padx=20, pady=(10, 20))
entry_1 = customtkinter.CTkEntry(master=root_tk, placeholder_text="entry_1")
entry_1.pack(padx=20, pady=(20, 10))
button_3 = customtkinter.CTkButton(master=root_tk, text="Disable/Enable entry_1", command=lambda: change_state(entry_1))
button_3.pack(padx=20, pady=(10, 20))
checkbox_1 = customtkinter.CTkCheckBox(master=root_tk, text="checkbox_1")
checkbox_1.pack(padx=20, pady=(20, 10))
button_4 = customtkinter.CTkButton(master=root_tk, text="Disable/Enable checkbox_1", command=lambda: change_state(checkbox_1))
button_4.pack(padx=20, pady=(10, 20))
radiobutton_1 = customtkinter.CTkRadioButton(master=root_tk, text="radiobutton_1")
radiobutton_1.pack(padx=20, pady=(20, 10))
button_5 = customtkinter.CTkButton(master=root_tk, text="Disable/Enable entry_1", command=lambda: change_state(radiobutton_1))
button_5.pack(padx=20, pady=(10, 20))
root_tk.mainloop()