100 Commits

Author SHA1 Message Date
39f369a8d4 Bump to 4.5.11 2022-09-15 13:59:58 +02:00
423d0886c9 enhanced geometry test 2022-09-15 13:54:32 +02:00
d2f8fd012f added zoom and appearance mode behavior tests for CTkToplevel #66 2022-09-15 13:48:32 +02:00
dcde8d69d8 added CTk zoom behavior test 2022-09-15 13:39:03 +02:00
81f3f9a622 added tests for CTk window behavior when switchng appearance mode and hiding at program start #66 #277 2022-09-15 13:28:02 +02:00
65c45abe32 changed linux font directory to ~/.local/share/fonts/ #340 2022-09-15 12:31:43 +02:00
64c8b8345d fixed bug when configuring place_holder in CTkEntry widget #330 2022-09-13 21:47:50 +02:00
9a144bfc6b small fixes 2022-08-19 00:18:01 +02:00
d9db3b64af fixed withdraw and iconify functionality for CTk and CTkToplevel #277 #305 #302 2022-08-19 00:13:00 +02:00
2db46afaf0 fixed withdraw and iconify functionality for CTk window before mainloop or update #277 #305 #302 2022-08-16 18:14:30 +02:00
8c9183006c added text_font configuration for all CTk widgets #266 2022-08-16 14:05:15 +02:00
f39ee5764a fixed simple_example.py 2022-08-06 01:12:22 +02:00
69216469a4 refactored example_button_images.py to class structure 2022-08-06 00:55:47 +02:00
d890d243a5 Merge remote-tracking branch 'origin/master' 2022-08-05 20:38:10 +02:00
deebaa9163 enhanced geometry string parsing for CTk and CTkToplevel #345 #287 2022-08-05 20:38:05 +02:00
5a4c28b178 removed print from CTk class 2022-08-05 16:08:46 +02:00
73ab410a96 Merge remote-tracking branch 'origin/master' 2022-08-05 15:37:42 +02:00
9bdf2436f5 CTk scaling fixes for Windows 2022-08-05 15:37:23 +02:00
91efc0ffc1 Merge pull request #352 from splewdge/master
Fix for issue #351
2022-08-05 08:49:38 -04:00
99550ab7fd Merge remote-tracking branch 'origin/master' 2022-08-05 14:23:22 +02:00
46b20d6605 fixed bug in CTkBaseClass #354 2022-08-05 14:23:06 +02:00
013e186ca6 Update ctk_button.py 2022-08-04 12:27:27 +01:00
6df8a1f44a Merge pull request #347 from felipetesc/master
white version of sweetkind
2022-08-02 14:16:30 -04:00
FTE
4516a5edb1 added white version of sweetkind
added white version of sweetkind
2022-08-02 14:51:28 -03:00
FTE
bd19d2f3e6 added white variant 2022-08-02 14:48:31 -03:00
ec8cecb575 fixed window closing returning None of CTkInputDialog 2022-07-25 11:25:48 +02:00
156a1863f5 Bump to 4.5.10 2022-07-23 20:03:59 +02:00
e295674e00 fixed bug in CTkLabel with multiline strings 2022-07-23 19:09:53 +02:00
36702326fa fixed checkbox size bug when rescaling 2022-07-18 13:02:39 +02:00
3bee19f8ce Bump to 4.5.9 2022-07-18 12:34:43 +02:00
ac6fb661a4 removed print from CTk 2022-07-18 12:34:27 +02:00
1a57294ae9 Bump to 4.5.8 2022-07-17 21:45:45 +02:00
ddd49377d4 fixed geomtry method for CTk and CTkToplevel 2022-07-17 21:45:29 +02:00
6bfddda399 Bump to 4.5.7 2022-07-17 21:41:28 +02:00
67f2072e07 added return value of geometry method for CTk and CTkToplevel 2022-07-17 21:41:13 +02:00
b3c0388958 added corner radius to CTkLabel in complex_example.py 2022-07-17 20:37:24 +02:00
228729305b Bump to 4.5.6 2022-07-17 20:35:12 +02:00
d9ff3d998c fixed command function bug in CTkSwitch 2022-07-17 20:34:54 +02:00
6a43dfd9bf added corner_radius to .configure() of CTkButton 2022-07-14 13:57:02 +02:00
db4f5ec919 Bump to 4.5.5 2022-07-12 21:57:50 +02:00
78f4e1e2ee Merge remote-tracking branch 'origin/master' into develop 2022-07-12 21:54:29 +02:00
acaeceb96d added methods to CTkTextbox 2022-07-12 21:52:52 +02:00
be126c70ae Merge pull request #247 from Ripeey/Theme-Scrollbar-Patch
Add scrollbar default values in .json theme files
2022-07-10 15:10:43 -04:00
d45904b1e4 Update missing scrollbar configs in green and sweetkind themes. 2022-07-11 04:57:39 +05:30
4fbcce75a0 Update dark-blue.json 2022-07-10 23:19:14 +05:30
92de2c4183 fixed CTkButton fg_color type hint 2022-07-07 18:23:50 +02:00
1c5c3450f9 Bump to 4.5.4 2022-07-07 18:22:39 +02:00
c95c0b7050 added readonly state to CTkComboBox 2022-07-07 18:19:23 +02:00
de33629e7d fixed entry placeholder for textvariables and added test_entry_placeholder.py 2022-07-07 18:07:54 +02:00
162997c7da added type hints to CTkButton 2022-07-07 16:21:30 +02:00
767379462e enhanced .configure() process for all widgets 2022-07-07 16:02:51 +02:00
a2fcb5dee1 fixed variables for CTkCheckbox, CTkSwitch, CTkRadiobutton 2022-07-05 14:39:12 +02:00
039cb1d17c Merge pull request #157 from mohsen1365ir/master
Enhanced checkbox functionality
2022-07-02 08:16:00 -04:00
f9890ba3e9 Merge branch 'master' into master 2022-07-02 08:14:20 -04:00
cdaf8f5f5c changed default CTkLabel corner_radius for better positioning 2022-07-02 14:10:41 +02:00
7e8bbf2968 Bump to 4.5.3 2022-07-02 01:11:54 +02:00
a3fb12f7cf Fixed textvariable support for CTkCheckBox, CTkSwitch, CTkRadiobutton 2022-07-02 00:54:21 +02:00
bb6678ae15 added support for anchor attribute in CTkLabel 2022-07-01 22:09:45 +02:00
b30692d1af Bump to 4.5.2 2022-07-01 21:39:19 +02:00
21448d3a07 Merge branch 'master' into develop 2022-07-01 21:38:51 +02:00
1f030f04f9 fixed placeholder_text bug for CTkEntry 2022-07-01 21:30:31 +02:00
c9653e7793 fixed updating of bg_color with set_scaling in CTkBaseClass 2022-06-30 15:53:32 +02:00
f587109618 Bump to 4.5.1 2022-06-28 11:33:21 +02:00
8bfd763786 Merge branch 'master' into develop 2022-06-28 11:30:58 +02:00
2a0ae06426 made CTkBaseClass public 2022-06-28 11:28:11 +02:00
16b9ce3c5f added .focus() for CTkEntry 2022-06-28 11:26:43 +02:00
e15bc5933d fixed scaling issues for combobox and optionemnu, chatched error in appearance_mode_tracker 2022-06-28 11:16:28 +02:00
11c7363d28 updated CHANGELOG.md 2022-06-23 22:28:29 +02:00
d4d0cf1188 Merge branch 'develop'
# Conflicts:
#	customtkinter/__init__.py
#	pyproject.toml
#	setup.cfg
2022-06-23 22:22:11 +02:00
b891032e2e Bump to 4.5.0 2022-06-23 22:18:06 +02:00
28308065bc fixed horizontal scrollbar for Windows 2022-06-23 22:14:19 +02:00
d4ae8cab7d fixed optionmenu and combobox bugs 2022-06-23 21:05:44 +02:00
ec3fdc40ff fixed scrollbar for horizontal orientation 2022-06-23 17:41:12 +02:00
a7b175ae65 changed scrollbar colors, added custom exception message for pyinstaller file problem 2022-06-20 23:44:35 +02:00
43900c7fef fixed scrollbar for Windows 2022-06-19 22:12:19 +02:00
79ecd2e946 added CTkScrollbar 2022-06-19 21:16:19 +02:00
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
4cf6a9f5c9 ctk_radiobutton command blocked if disabled 2022-06-08 07:24:24 -04:00
6e36ec818e checkbox.check_state initialization fixed 2022-06-08 07:11:52 -04:00
0f7cb22b1b Enhanced checkbox functionality 2022-06-05 06:32:11 -04: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
55 changed files with 2932 additions and 1445 deletions

View File

@ -4,6 +4,24 @@ 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.5.0] - 2022-06-23
### Added
- CTkScrollbar (vertical, horizontal)
## [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 ## [4.1.0] - 2022-05-24
### Added ### Added
- Configure width and height for frame, button, label, progressbar, slider, entry - Configure width and height for frame, button, label, progressbar, slider, entry

View File

@ -1,4 +1,4 @@
__version__ = "4.2.0" __version__ = "4.5.11"
import os import os
import sys import sys
@ -14,7 +14,15 @@ from .font_manager import FontManager
from .draw_engine import DrawEngine from .draw_engine import DrawEngine
AppearanceModeTracker.init_appearance_mode() AppearanceModeTracker.init_appearance_mode()
ThemeManager.load_theme("blue") # load default theme
# load default blue theme
try:
ThemeManager.load_theme("blue")
except FileNotFoundError as err:
raise FileNotFoundError(f"{err}\n\nThe .json theme file for CustomTkinter could not be found.\n" +
f"If packaging with pyinstaller was used, have a look at the wiki:\n" +
f"https://github.com/TomSchimansky/CustomTkinter/wiki/Packaging#windows-pyinstaller-auto-py-to-exe")
FontManager.init_font_manager() FontManager.init_font_manager()
# determine draw method based on current platform # determine draw method based on current platform
@ -41,6 +49,7 @@ if FontManager.load_font(os.path.join(script_directory, "assets", "fonts", "Cust
DrawEngine.preferred_drawing_method = "circle_shapes" DrawEngine.preferred_drawing_method = "circle_shapes"
# import widgets # import widgets
from .widgets.widget_base_class import CTkBaseClass
from .widgets.ctk_button import CTkButton from .widgets.ctk_button import CTkButton
from .widgets.ctk_checkbox import CTkCheckBox from .widgets.ctk_checkbox import CTkCheckBox
from .widgets.ctk_entry import CTkEntry from .widgets.ctk_entry import CTkEntry
@ -52,6 +61,9 @@ 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_optionmenu import CTkOptionMenu
from .widgets.ctk_combobox import CTkComboBox
from .widgets.ctk_scrollbar import CTkScrollbar
from .widgets.ctk_textbox import CTkTextbox
# import windows # import windows
from .windows.ctk_tk import CTk from .windows.ctk_tk import CTk

View File

@ -45,12 +45,15 @@ class AppearanceModeTracker:
cls.app_list.append(app) cls.app_list.append(app)
if not cls.update_loop_running: if not cls.update_loop_running:
app.after(500, cls.update) app.after(cls.update_loop_interval, cls.update)
cls.update_loop_running = True cls.update_loop_running = True
@classmethod @classmethod
def remove(cls, callback: Callable): def remove(cls, callback: Callable):
cls.callback_list.remove(callback) try:
cls.callback_list.remove(callback)
except ValueError:
return
@staticmethod @staticmethod
def detect_appearance_mode() -> int: def detect_appearance_mode() -> int:

View File

@ -11,7 +11,7 @@
"entry_placeholder_text": ["gray52", "gray62"], "entry_placeholder_text": ["gray52", "gray62"],
"frame_border": ["#979DA2", "#1F2122"], "frame_border": ["#979DA2", "#1F2122"],
"frame_low": ["#D1D5D8", "#2A2D2E"], "frame_low": ["#D1D5D8", "#2A2D2E"],
"frame_high": ["#D7D8D9", "#343638"], "frame_high": ["#C0C2C5", "#343638"],
"label": [null, null], "label": [null, null],
"text": ["gray10", "#DCE4EE"], "text": ["gray10", "#DCE4EE"],
"text_disabled": ["gray60", "#777B80"], "text_disabled": ["gray60", "#777B80"],
@ -20,7 +20,7 @@
"progressbar_progress": ["#3B8ED0", "#1F6AA5"], "progressbar_progress": ["#3B8ED0", "#1F6AA5"],
"progressbar_border": ["gray", "gray"], "progressbar_border": ["gray", "gray"],
"slider": ["#939BA2", "#4A4D50"], "slider": ["#939BA2", "#4A4D50"],
"slider_progress": ["white", "#AAB0B5"], "slider_progress": ["gray40", "#AAB0B5"],
"slider_button": ["#3B8ED0", "#1F6AA5"], "slider_button": ["#3B8ED0", "#1F6AA5"],
"slider_button_hover": ["#36719F", "#144870"], "slider_button_hover": ["#36719F", "#144870"],
"switch": ["#939BA2", "#4A4D50"], "switch": ["#939BA2", "#4A4D50"],
@ -29,9 +29,13 @@
"switch_button_hover": ["gray20", "gray100"], "switch_button_hover": ["gray20", "gray100"],
"optionmenu_button": ["#36719F", "#144870"], "optionmenu_button": ["#36719F", "#144870"],
"optionmenu_button_hover": ["#27577D", "#203A4F"], "optionmenu_button_hover": ["#27577D", "#203A4F"],
"dropdown_color": ["#A8ACB1", "#535353"], "combobox_border": ["#979DA2", "#565B5E"],
"dropdown_hover": ["#D6DCE2", "#46484A"], "combobox_button_hover": ["#6E7174", "#7A848D"],
"dropdown_text": ["gray10", "#DCE4EE"] "dropdown_color": ["gray90", "gray20"],
"dropdown_hover": ["gray75", "gray28"],
"dropdown_text": ["gray10", "#DCE4EE"],
"scrollbar_button": ["gray55", "gray41"],
"scrollbar_button_hover": ["gray40", "gray53"]
}, },
"text": { "text": {
"macOS": { "macOS": {
@ -58,7 +62,7 @@
"entry_border_width": 2, "entry_border_width": 2,
"frame_corner_radius": 6, "frame_corner_radius": 6,
"frame_border_width": 0, "frame_border_width": 0,
"label_corner_radius": 8, "label_corner_radius": 0,
"progressbar_border_width": 0, "progressbar_border_width": 0,
"progressbar_corner_radius": 1000, "progressbar_corner_radius": 1000,
"slider_border_width": 6, "slider_border_width": 6,
@ -68,6 +72,8 @@
"switch_border_width": 3, "switch_border_width": 3,
"switch_corner_radius": 1000, "switch_corner_radius": 1000,
"switch_button_corner_radius": 1000, "switch_button_corner_radius": 1000,
"switch_button_length": 0 "switch_button_length": 0,
"scrollbar_corner_radius": 1000,
"scrollbar_border_spacing": 4
} }
} }

View File

@ -27,7 +27,15 @@
"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"],
"scrollbar_button": ["gray55", "gray41"],
"scrollbar_button_hover": ["gray40", "gray53"]
}, },
"text": { "text": {
"macOS": { "macOS": {
@ -54,7 +62,7 @@
"entry_border_width": 2, "entry_border_width": 2,
"frame_corner_radius": 10, "frame_corner_radius": 10,
"frame_border_width": 0, "frame_border_width": 0,
"label_corner_radius": 8, "label_corner_radius": 0,
"progressbar_border_width": 0, "progressbar_border_width": 0,
"progressbar_corner_radius": 1000, "progressbar_corner_radius": 1000,
"slider_border_width": 6, "slider_border_width": 6,
@ -64,6 +72,8 @@
"switch_border_width": 3, "switch_border_width": 3,
"switch_corner_radius": 1000, "switch_corner_radius": 1000,
"switch_button_corner_radius": 1000, "switch_button_corner_radius": 1000,
"switch_button_length": 0 "switch_button_length": 0,
"scrollbar_corner_radius": 1000,
"scrollbar_border_spacing": 4
} }
} }

View File

@ -27,7 +27,15 @@
"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"],
"scrollbar_button": ["gray55", "gray41"],
"scrollbar_button_hover": ["gray40", "gray53"]
}, },
"text": { "text": {
"macOS": { "macOS": {
@ -54,7 +62,7 @@
"entry_border_width": 2, "entry_border_width": 2,
"frame_corner_radius": 10, "frame_corner_radius": 10,
"frame_border_width": 0, "frame_border_width": 0,
"label_corner_radius": 8, "label_corner_radius": 0,
"progressbar_border_width": 0, "progressbar_border_width": 0,
"progressbar_corner_radius": 1000, "progressbar_corner_radius": 1000,
"slider_border_width": 6, "slider_border_width": 6,
@ -64,6 +72,8 @@
"switch_border_width": 3, "switch_border_width": 3,
"switch_corner_radius": 1000, "switch_corner_radius": 1000,
"switch_button_corner_radius": 1000, "switch_button_corner_radius": 1000,
"switch_button_length": 0 "switch_button_length": 0,
"scrollbar_corner_radius": 1000,
"scrollbar_border_spacing": 4
} }
} }

View File

@ -1,33 +1,41 @@
{ {
"color": { "color": {
"window_bg_color": ["#181b28", "#181b28"], "window_bg_color": ["#ebf0f5", "#181b28"],
"button": ["#212435", "#212435"], "button": ["#e46bff", "#212435"],
"button_hover": ["#171926", "#171926"], "button_hover": ["#8593d6", "#171926"],
"button_border": ["#080b12", "#080b12"], "button_border": ["#525983", "#080b12"],
"checkbox_border": ["#01e9c4", "#01e9c4"], "checkbox_border": ["#01e9c4", "#01e9c4"],
"checkmark": ["#01e9c4", "#01e9c4"], "checkmark": ["#01e9c4", "#01e9c4"],
"entry": ["#212435", "#212435"], "entry": ["#dee2e7", "#212435"],
"entry_border": ["#080b12", "#080b12"], "entry_border": ["#fa00d0", "#080b12"],
"entry_placeholder_text": ["#cdc8ce", "#cdc8ce"], "entry_placeholder_text": ["#cdc8ce", "#cdc8ce"],
"frame_border": ["#10121f", "#10121f"], "frame_border": ["#525983", "#10121f"],
"frame_low": ["#181b28", "#181b28"], "frame_low": ["#dee2e7", "#181b28"],
"frame_high": ["#181b28", "#181b28"], "frame_high": ["#dee2e7", "#1b1e2d"],
"label": [null, null], "label": [null, null],
"text": ["#cdc8ce", "#cdc8ce"], "text": ["#0c0e14", "#cdc8ce"],
"text_disabled": ["#7a8894", "#7a8894"], "text_disabled": ["#5e6062", "#7a8894"],
"text_button_disabled": ["#7a8894", "#7a8894"], "text_button_disabled": ["#7a8894", "#7a8894"],
"progressbar": ["#c452f8", "#c452f8"], "progressbar": ["#fa00d0", "#fa00d0"],
"progressbar_progress": ["#363844", "#363844"], "progressbar_progress": ["#363844", "#363844"],
"progressbar_border": ["#0d101f", "#0d101f"], "progressbar_border": ["#fa00d0", "#0d101f"],
"slider": ["#c452f8", "#c452f8"], "slider": ["#fa00d0", "#fa00d0"],
"slider_progress": ["#363844", "#363844"], "slider_progress": ["#0d101f", "#0d101f"],
"slider_button": ["#5b40c5", "#5b40c5"], "slider_button": ["#fa00d0", "#fa00d0"],
"slider_button_hover": ["#c452f8", "#c452f8"], "slider_button_hover": ["#e46bff", "#fa00d0"],
"switch": ["#1f2233", "#1f2233"], "switch": ["#7681be", "#1f2233"],
"switch_progress": ["#00e6c3", "#00e6c3"], "switch_progress": ["#00e6c3", "#00e6c3"],
"switch_button": ["#2e324a", "#2e324a"], "switch_button": ["#525983", "#2e324a"],
"switch_button_hover": ["#2e324a", "#2e324a"], "switch_button_hover": ["#fa00d0", "#2e324a"],
"darken_factor": 0.1 "optionmenu_button": ["#525983", "#080b12"],
"optionmenu_button_hover": ["#fa00d0", "#080b12"],
"combobox_border": ["#525983", "#080b12"],
"combobox_button_hover": ["#fa00d0", "#fa00d0"],
"dropdown_color": ["#dee2e7", "#212435"],
"dropdown_hover": ["#fa00d0", "#fa00d0"],
"dropdown_text": ["#0c0e14", "#cdc8ce"],
"scrollbar_button": ["#fa00d0", "#fa00d0"],
"scrollbar_button_hover": ["#9b45ff", "#9b45ff"]
}, },
"text": { "text": {
"macOS": { "macOS": {
@ -45,16 +53,16 @@
}, },
"shape": { "shape": {
"button_corner_radius": 8, "button_corner_radius": 8,
"button_border_width": 2, "button_border_width": 1,
"checkbox_corner_radius": 7, "checkbox_corner_radius": 7,
"checkbox_border_width": 3, "checkbox_border_width": 1,
"radiobutton_corner_radius": 1000, "radiobutton_corner_radius": 1000,
"radiobutton_border_width_unchecked": 3, "radiobutton_border_width_unchecked": 2,
"radiobutton_border_width_checked": 6, "radiobutton_border_width_checked": 6,
"entry_border_width": 2, "entry_border_width": 1,
"frame_corner_radius": 10, "frame_corner_radius": 10,
"frame_border_width": 2, "frame_border_width": 1,
"label_corner_radius": 8, "label_corner_radius": 3,
"progressbar_border_width": 2, "progressbar_border_width": 2,
"progressbar_corner_radius": 1000, "progressbar_corner_radius": 1000,
"slider_border_width": 6, "slider_border_width": 6,
@ -64,6 +72,8 @@
"switch_border_width": 3, "switch_border_width": 3,
"switch_corner_radius": 1000, "switch_corner_radius": 1000,
"switch_button_corner_radius": 1000, "switch_button_corner_radius": 1000,
"switch_button_length": 2 "switch_button_length": 2,
"scrollbar_corner_radius": 1000,
"scrollbar_border_spacing": 4
} }
} }

View File

@ -20,6 +20,7 @@ class DrawEngine:
- draw_rounded_rect_with_border_vertical_split() - 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_rounded_scrollbar()
- draw_checkmark() - draw_checkmark()
- draw_dropdown_arrow() - draw_dropdown_arrow()
@ -38,7 +39,7 @@ class DrawEngine:
else: else:
return round(user_corner_radius) return round(user_corner_radius)
# optimize forx drawing with antialiased font shapes # optimize for drawing with antialiased font shapes
elif self.preferred_drawing_method == "font_shapes": elif self.preferred_drawing_method == "font_shapes":
return round(user_corner_radius) return round(user_corner_radius)
@ -54,14 +55,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], def draw_rounded_rect_with_border(self, width: Union[float, int], height: Union[float, int], corner_radius: Union[float, int],
overwrite_preferred_drawing_method: str = None) -> bool: 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)
@ -353,8 +354,8 @@ class DrawEngine:
return requires_recoloring return requires_recoloring
def draw_rounded_rect_with_border_vertical_split(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],
left_section_width: int) -> 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. """ Draws a rounded rectangle with a corner_radius and border_width on the canvas which is split at left_section_width.
The border elements have the tags 'border_parts_left', 'border_parts_lright', The border elements have the tags 'border_parts_left', 'border_parts_lright',
the main foreground elements have an 'inner_parts_left' and inner_parts_right' tag, the main foreground elements have an 'inner_parts_left' and inner_parts_right' tag,
@ -362,7 +363,8 @@ class DrawEngine:
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 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 height = math.floor(height / 2) * 2
corner_radius = round(corner_radius) corner_radius = round(corner_radius)
@ -394,10 +396,10 @@ class DrawEngine:
# create border button parts (only if border exists) # create border button parts (only if border exists)
if border_width > 0: if border_width > 0:
if not self._canvas.find_withtag("border_parts"): 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")) 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")) 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")) self._canvas.create_rectangle((0, 0, 0, 0), tags=("border_rect_left_1", "border_parts_left", "border_parts", "left_parts"), width=0)
self._canvas.create_rectangle((0, 0, 0, 0), tags=("border_rect_right_1", "border_parts_right", "border_parts")) self._canvas.create_rectangle((0, 0, 0, 0), tags=("border_rect_right_1", "border_parts_right", "border_parts", "right_parts"), width=0)
requires_recoloring = True requires_recoloring = True
self._canvas.coords("border_line_left_1", self._canvas.coords("border_line_left_1",
@ -436,29 +438,29 @@ class DrawEngine:
# create inner button parts # create inner button parts
if not self._canvas.find_withtag("inner_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"), joinstyle=tkinter.ROUND) 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"), 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")) 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")) 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 requires_recoloring = True
self._canvas.coords("inner_line_left_1", self._canvas.coords("inner_line_left_1",
corner_radius, corner_radius,
corner_radius, corner_radius,
left_section_width - corner_radius, left_section_width - inner_corner_radius,
corner_radius, corner_radius,
left_section_width - corner_radius, left_section_width - inner_corner_radius,
height - corner_radius, height - corner_radius,
corner_radius, corner_radius,
height - corner_radius) height - corner_radius)
self._canvas.coords("inner_line_right_1", self._canvas.coords("inner_line_right_1",
left_section_width + corner_radius, left_section_width + inner_corner_radius,
corner_radius, corner_radius,
width - corner_radius, width - corner_radius,
corner_radius, corner_radius,
width - corner_radius, width - corner_radius,
height - corner_radius, height - corner_radius,
left_section_width + corner_radius, left_section_width + inner_corner_radius,
height - corner_radius) height - corner_radius)
self._canvas.coords("inner_rect_left_1", self._canvas.coords("inner_rect_left_1",
(left_section_width - inner_corner_radius, (left_section_width - inner_corner_radius,
@ -488,31 +490,37 @@ class DrawEngine:
if corner_radius > 0: if corner_radius > 0:
# create canvas border corner parts if not already created, but only if needed, and delete if not needed # 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: 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"), anchor=tkinter.CENTER) 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"), anchor=tkinter.CENTER, angle=180) 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 requires_recoloring = True
elif self._canvas.find_withtag("border_oval_1_a") and "border_oval_1" in exclude_parts: 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") 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: 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"), anchor=tkinter.CENTER) self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_2_a", "border_corner_part", "border_parts_right", "border_parts", "right_parts"),
self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_2_b", "border_corner_part", "border_parts_right", "border_parts"), anchor=tkinter.CENTER, angle=180) 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 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): 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") 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 \ 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: 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"), anchor=tkinter.CENTER) self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_3_a", "border_corner_part", "border_parts_right", "border_parts", "right_parts"),
self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_3_b", "border_corner_part", "border_parts_right", "border_parts"), anchor=tkinter.CENTER, angle=180) 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 requires_recoloring = True
elif self._canvas.find_withtag("border_oval_3_a") and (not (height > 2 * corner_radius 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): and width > 2 * corner_radius) or "border_oval_3" in exclude_parts):
self._canvas.delete("border_oval_3_a", "border_oval_3_b") 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: 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"), anchor=tkinter.CENTER) 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"), anchor=tkinter.CENTER, angle=180) 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 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): 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") self._canvas.delete("border_oval_4_a", "border_oval_4_b")
@ -532,17 +540,17 @@ class DrawEngine:
# create canvas border rectangle parts if not already created # create canvas border rectangle parts if not already created
if not self._canvas.find_withtag("border_rectangle_1"): 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"), width=0) 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"), 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"), 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"), 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 requires_recoloring = True
# change position of border rectangle parts # 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_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_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_1", (left_section_width, corner_radius, width, height - corner_radius))
self._canvas.coords("border_rectangle_right_2", (corner_radius, left_section_width, width - corner_radius, height)) self._canvas.coords("border_rectangle_right_2", (left_section_width, 0, width - corner_radius, height))
else: else:
self._canvas.delete("border_parts") self._canvas.delete("border_parts")
@ -552,31 +560,35 @@ class DrawEngine:
# create canvas border corner parts if not already created, but only if they're needed and delete if not needed # 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: 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"), anchor=tkinter.CENTER) 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"), anchor=tkinter.CENTER, angle=180) 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 requires_recoloring = True
elif self._canvas.find_withtag("inner_oval_1_a") and "inner_oval_1" in exclude_parts: 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") 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: 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"), anchor=tkinter.CENTER) 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"), anchor=tkinter.CENTER, angle=180) 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 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): 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") 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 \ 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: 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"), anchor=tkinter.CENTER) 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"), anchor=tkinter.CENTER, angle=180) 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 requires_recoloring = True
elif self._canvas.find_withtag("inner_oval_3_a") and (not (height - (2 * border_width) > 2 * inner_corner_radius 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): 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") 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: 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"), anchor=tkinter.CENTER) 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"), anchor=tkinter.CENTER, angle=180) 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 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): 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") self._canvas.delete("inner_oval_4_a", "inner_oval_4_b")
@ -595,13 +607,13 @@ class DrawEngine:
# create canvas inner rectangle parts if not already created # create canvas inner rectangle parts if not already created
if not self._canvas.find_withtag("inner_rectangle_1"): 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"), width=0) 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"), 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 requires_recoloring = True
if not self._canvas.find_withtag("inner_rectangle_2") and inner_corner_radius * 2 < height - (border_width * 2): 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"), width=0) 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"), 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 requires_recoloring = True
elif self._canvas.find_withtag("inner_rectangle_2") and not inner_corner_radius * 2 < height - (border_width * 2): elif self._canvas.find_withtag("inner_rectangle_2") and not inner_corner_radius * 2 < height - (border_width * 2):
@ -632,15 +644,15 @@ 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_progress_bar_with_border(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], 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
@ -800,11 +812,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
@ -958,7 +970,147 @@ class DrawEngine:
return requires_recoloring return requires_recoloring
def draw_checkmark(self, width: int, height: int, size: Union[int, float]) -> bool: def draw_rounded_scrollbar(self, width: Union[float, int], height: Union[float, int], corner_radius: Union[float, int],
border_spacing: Union[float, int], start_value: float, end_value: float, orientation: str) -> bool:
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
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_spacing = round(border_spacing)
corner_radius = self.__calc_optimal_corner_radius(corner_radius) # optimize corner_radius for different drawing methods (different rounding)
if corner_radius >= border_spacing:
inner_corner_radius = corner_radius - border_spacing
else:
inner_corner_radius = 0
if self.preferred_drawing_method == "polygon_shapes" or self.preferred_drawing_method == "circle_shapes":
return self.__draw_rounded_scrollbar_polygon_shapes(width, height, corner_radius, inner_corner_radius,
start_value, end_value, orientation)
elif self.preferred_drawing_method == "font_shapes":
return self.__draw_rounded_scrollbar_font_shapes(width, height, corner_radius, inner_corner_radius,
start_value, end_value, orientation)
def __draw_rounded_scrollbar_polygon_shapes(self, width: int, height: int, corner_radius: int, inner_corner_radius: int,
start_value: float, end_value: float, orientation: str) -> bool:
requires_recoloring = False
if not self._canvas.find_withtag("border_parts"):
self._canvas.create_rectangle(0, 0, 0, 0, tags=("border_rectangle_1", "border_parts"), width=0)
requires_recoloring = True
self._canvas.coords("border_rectangle_1", 0, 0, width, height)
if not self._canvas.find_withtag("scrollbar_parts"):
self._canvas.create_polygon((0, 0, 0, 0), tags=("scrollbar_polygon_1", "scrollbar_parts"), joinstyle=tkinter.ROUND)
self._canvas.tag_raise("scrollbar_parts", "border_parts")
requires_recoloring = True
if orientation == "vertical":
self._canvas.coords("scrollbar_polygon_1",
corner_radius, corner_radius + (height - 2 * corner_radius) * start_value,
width - corner_radius, corner_radius + (height - 2 * corner_radius) * start_value,
width - corner_radius, corner_radius + (height - 2 * corner_radius) * end_value,
corner_radius, corner_radius + (height - 2 * corner_radius) * end_value)
elif orientation == "horizontal":
self._canvas.coords("scrollbar_polygon_1",
corner_radius + (width - 2 * corner_radius) * start_value, corner_radius,
corner_radius + (width - 2 * corner_radius) * end_value, corner_radius,
corner_radius + (width - 2 * corner_radius) * end_value, height - corner_radius,
corner_radius + (width - 2 * corner_radius) * start_value, height - corner_radius,)
self._canvas.itemconfig("scrollbar_polygon_1", width=inner_corner_radius * 2)
return requires_recoloring
def __draw_rounded_scrollbar_font_shapes(self, width: int, height: int, corner_radius: int, inner_corner_radius: int,
start_value: float, end_value: float, orientation: str) -> bool:
requires_recoloring = False
if not self._canvas.find_withtag("border_parts"):
self._canvas.create_rectangle(0, 0, 0, 0, tags=("border_rectangle_1", "border_parts"), width=0)
requires_recoloring = True
self._canvas.coords("border_rectangle_1", 0, 0, width, height)
if inner_corner_radius > 0:
if not self._canvas.find_withtag("scrollbar_oval_1_a"):
self._canvas.create_aa_circle(0, 0, 0, tags=("scrollbar_oval_1_a", "scrollbar_corner_part", "scrollbar_parts"), anchor=tkinter.CENTER)
self._canvas.create_aa_circle(0, 0, 0, tags=("scrollbar_oval_1_b", "scrollbar_corner_part", "scrollbar_parts"), anchor=tkinter.CENTER, angle=180)
requires_recoloring = True
if not self._canvas.find_withtag("scrollbar_oval_2_a") and width > 2 * corner_radius:
self._canvas.create_aa_circle(0, 0, 0, tags=("scrollbar_oval_2_a", "scrollbar_corner_part", "scrollbar_parts"), anchor=tkinter.CENTER)
self._canvas.create_aa_circle(0, 0, 0, tags=("scrollbar_oval_2_b", "scrollbar_corner_part", "scrollbar_parts"), anchor=tkinter.CENTER, angle=180)
requires_recoloring = True
elif self._canvas.find_withtag("scrollbar_oval_2_a") and not width > 2 * corner_radius:
self._canvas.delete("scrollbar_oval_2_a", "scrollbar_oval_2_b")
if not self._canvas.find_withtag("scrollbar_oval_3_a") and height > 2 * corner_radius and width > 2 * corner_radius:
self._canvas.create_aa_circle(0, 0, 0, tags=("scrollbar_oval_3_a", "scrollbar_corner_part", "scrollbar_parts"), anchor=tkinter.CENTER)
self._canvas.create_aa_circle(0, 0, 0, tags=("scrollbar_oval_3_b", "scrollbar_corner_part", "scrollbar_parts"), anchor=tkinter.CENTER, angle=180)
requires_recoloring = True
elif self._canvas.find_withtag("scrollbar_oval_3_a") and not (height > 2 * corner_radius and width > 2 * corner_radius):
self._canvas.delete("scrollbar_oval_3_a", "scrollbar_oval_3_b")
if not self._canvas.find_withtag("scrollbar_oval_4_a") and height > 2 * corner_radius:
self._canvas.create_aa_circle(0, 0, 0, tags=("scrollbar_oval_4_a", "scrollbar_corner_part", "scrollbar_parts"), anchor=tkinter.CENTER)
self._canvas.create_aa_circle(0, 0, 0, tags=("scrollbar_oval_4_b", "scrollbar_corner_part", "scrollbar_parts"), anchor=tkinter.CENTER, angle=180)
requires_recoloring = True
elif self._canvas.find_withtag("scrollbar_oval_4_a") and not height > 2 * corner_radius:
self._canvas.delete("scrollbar_oval_4_a", "scrollbar_oval_4_b")
else:
self._canvas.delete("scrollbar_corner_part")
if not self._canvas.find_withtag("scrollbar_rectangle_1") and height > 2 * corner_radius:
self._canvas.create_rectangle(0, 0, 0, 0, tags=("scrollbar_rectangle_1", "scrollbar_rectangle_part", "scrollbar_parts"), width=0)
requires_recoloring = True
elif self._canvas.find_withtag("scrollbar_rectangle_1") and not height > 2 * corner_radius:
self._canvas.delete("scrollbar_rectangle_1")
if not self._canvas.find_withtag("scrollbar_rectangle_2") and width > 2 * corner_radius:
self._canvas.create_rectangle(0, 0, 0, 0, tags=("scrollbar_rectangle_2", "scrollbar_rectangle_part", "scrollbar_parts"), width=0)
requires_recoloring = True
elif self._canvas.find_withtag("scrollbar_rectangle_2") and not width > 2 * corner_radius:
self._canvas.delete("scrollbar_rectangle_2")
if orientation == "vertical":
self._canvas.coords("scrollbar_rectangle_1",
corner_radius - inner_corner_radius, corner_radius + (height - 2 * corner_radius) * start_value,
width - (corner_radius - inner_corner_radius), corner_radius + (height - 2 * corner_radius) * end_value)
self._canvas.coords("scrollbar_rectangle_2",
corner_radius, corner_radius - inner_corner_radius + (height - 2 * corner_radius) * start_value,
width - (corner_radius), corner_radius + inner_corner_radius + (height - 2 * corner_radius) * end_value)
self._canvas.coords("scrollbar_oval_1_a", corner_radius, corner_radius + (height - 2 * corner_radius) * start_value, inner_corner_radius)
self._canvas.coords("scrollbar_oval_1_b", corner_radius, corner_radius + (height - 2 * corner_radius) * start_value, inner_corner_radius)
self._canvas.coords("scrollbar_oval_2_a", width - corner_radius, corner_radius + (height - 2 * corner_radius) * start_value, inner_corner_radius)
self._canvas.coords("scrollbar_oval_2_b", width - corner_radius, corner_radius + (height - 2 * corner_radius) * start_value, inner_corner_radius)
self._canvas.coords("scrollbar_oval_3_a", width - corner_radius, corner_radius + (height - 2 * corner_radius) * end_value, inner_corner_radius)
self._canvas.coords("scrollbar_oval_3_b", width - corner_radius, corner_radius + (height - 2 * corner_radius) * end_value, inner_corner_radius)
self._canvas.coords("scrollbar_oval_4_a", corner_radius, corner_radius + (height - 2 * corner_radius) * end_value, inner_corner_radius)
self._canvas.coords("scrollbar_oval_4_b", corner_radius, corner_radius + (height - 2 * corner_radius) * end_value, inner_corner_radius)
if orientation == "horizontal":
self._canvas.coords("scrollbar_rectangle_1",
corner_radius - inner_corner_radius + (width - 2 * corner_radius) * start_value, corner_radius,
corner_radius + inner_corner_radius + (width - 2 * corner_radius) * end_value, height - corner_radius)
self._canvas.coords("scrollbar_rectangle_2",
corner_radius + (width - 2 * corner_radius) * start_value, corner_radius - inner_corner_radius,
corner_radius + (width - 2 * corner_radius) * end_value, height - (corner_radius - inner_corner_radius))
self._canvas.coords("scrollbar_oval_1_a", corner_radius + (width - 2 * corner_radius) * start_value, corner_radius, inner_corner_radius)
self._canvas.coords("scrollbar_oval_1_b", corner_radius + (width - 2 * corner_radius) * start_value, corner_radius, inner_corner_radius)
self._canvas.coords("scrollbar_oval_2_a", corner_radius + (width - 2 * corner_radius) * end_value, corner_radius, inner_corner_radius)
self._canvas.coords("scrollbar_oval_2_b", corner_radius + (width - 2 * corner_radius) * end_value, corner_radius, inner_corner_radius)
self._canvas.coords("scrollbar_oval_3_a", corner_radius + (width - 2 * corner_radius) * end_value, height - corner_radius, inner_corner_radius)
self._canvas.coords("scrollbar_oval_3_b", corner_radius + (width - 2 * corner_radius) * end_value, height - corner_radius, inner_corner_radius)
self._canvas.coords("scrollbar_oval_4_a", corner_radius + (width - 2 * corner_radius) * start_value, height - corner_radius, inner_corner_radius)
self._canvas.coords("scrollbar_oval_4_b", corner_radius + (width - 2 * corner_radius) * start_value, height - corner_radius, inner_corner_radius)
return requires_recoloring
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.

View File

@ -6,13 +6,15 @@ from typing import Union
class FontManager: class FontManager:
linux_font_path = "~/.local/share/fonts/"
@classmethod @classmethod
def init_font_manager(cls): def init_font_manager(cls):
# Linux # Linux
if sys.platform.startswith("linux"): if sys.platform.startswith("linux"):
try: try:
if not os.path.isdir(os.path.expanduser('~/.fonts/')): if not os.path.isdir(os.path.expanduser(cls.linux_font_path)):
os.mkdir(os.path.expanduser('~/.fonts/')) os.mkdir(os.path.expanduser(cls.linux_font_path))
return True return True
except Exception as err: except Exception as err:
sys.stderr.write("FontManager error: " + str(err) + "\n") sys.stderr.write("FontManager error: " + str(err) + "\n")
@ -53,7 +55,7 @@ class FontManager:
# Linux # Linux
elif sys.platform.startswith("linux"): elif sys.platform.startswith("linux"):
try: try:
shutil.copy(font_path, os.path.expanduser("~/.fonts/")) shutil.copy(font_path, os.path.expanduser(cls.linux_font_path))
return True return True
except Exception as err: except Exception as err:
sys.stderr.write("FontManager error: " + str(err) + "\n") sys.stderr.write("FontManager error: " + str(err) + "\n")

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

@ -1,6 +1,6 @@
import tkinter import tkinter
import sys import sys
import math from typing import Union, Tuple, Callable
from .ctk_canvas import CTkCanvas from .ctk_canvas import CTkCanvas
from ..theme_manager import ThemeManager from ..theme_manager import ThemeManager
@ -10,83 +10,82 @@ from .widget_base_class import CTkBaseClass
class CTkButton(CTkBaseClass): class CTkButton(CTkBaseClass):
""" tkinter custom button with border, rounded corners and hover effect """ """ button with border, rounded corners, hover effect, image support """
def __init__(self, *args, def __init__(self, *args,
bg_color=None, bg_color: Union[str, Tuple[str, str], None] = None,
fg_color="default_theme", fg_color: Union[str, Tuple[str, str], None] = "default_theme",
hover_color="default_theme", hover_color: Union[str, Tuple[str, str]] = "default_theme",
border_color="default_theme", border_color: Union[str, Tuple[str, str]] = "default_theme",
border_width="default_theme", text_color: Union[str, Tuple[str, str]] = "default_theme",
command=None, text_color_disabled: Union[str, Tuple[str, str]] = "default_theme",
textvariable=None, width: int = 140,
width=120, height: int = 28,
height=28, corner_radius: Union[int, str] = "default_theme",
corner_radius="default_theme", border_width: Union[int, str] = "default_theme",
text_font="default_theme", text: str = "CTkButton",
text_color="default_theme", textvariable: tkinter.Variable = None,
text_color_disabled="default_theme", text_font: any = "default_theme",
text="CTkButton", image: tkinter.PhotoImage = None,
hover=True, hover: bool = True,
image=None, compound: str = "left",
compound=tkinter.LEFT, state: str = "normal",
state=tkinter.NORMAL, command: Callable = 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)
self.configure_basic_grid() # color
# color variables
self.fg_color = ThemeManager.theme["color"]["button"] if fg_color == "default_theme" else fg_color self.fg_color = ThemeManager.theme["color"]["button"] if fg_color == "default_theme" else fg_color
self.hover_color = ThemeManager.theme["color"]["button_hover"] if hover_color == "default_theme" else hover_color self.hover_color = ThemeManager.theme["color"]["button_hover"] if hover_color == "default_theme" else hover_color
self.border_color = ThemeManager.theme["color"]["button_border"] if border_color == "default_theme" else border_color self.border_color = ThemeManager.theme["color"]["button_border"] if border_color == "default_theme" else border_color
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
# shape # shape
self.corner_radius = ThemeManager.theme["shape"]["button_corner_radius"] if corner_radius == "default_theme" else corner_radius self.corner_radius = ThemeManager.theme["shape"]["button_corner_radius"] if corner_radius == "default_theme" else corner_radius
self.border_width = ThemeManager.theme["shape"]["button_border_width"] if border_width == "default_theme" else border_width self.border_width = ThemeManager.theme["shape"]["button_border_width"] if border_width == "default_theme" else border_width
# text and font and image # text, font, image
self.image = image self.image = image
self.image_label = None self.image_label = None
self.text = text self.text = text
self.text_label = None self.text_label = None
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.text_font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font
# callback and hover functionality # callback and hover functionality
self.function = command self.command = command
self.textvariable = textvariable self.textvariable = textvariable
self.state = state self.state = state
self.hover = hover self.hover = hover
self.compound = compound self.compound = compound
self.click_animation_running = False self.click_animation_running = False
# configure grid system (2x2)
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(1, weight=1)
self.grid_columnconfigure(1, weight=1)
# canvas
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)
# event bindings # canvas event bindings
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.clicked) self.canvas.bind("<Button-1>", self.clicked)
self.canvas.bind("<Button-1>", self.clicked) self.canvas.bind("<Button-1>", self.clicked)
self.bind('<Configure>', self.update_dimensions_event) self.bind('<Configure>', self.update_dimensions_event)
# configure cursor and initial draw
self.set_cursor() self.set_cursor()
self.draw() # initial draw self.draw()
def configure_basic_grid(self):
# Configuration of a grid system (2x2) in which all parts of CTkButton are centered
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(1, weight=1)
self.grid_columnconfigure(1, weight=1)
def set_scaling(self, *args, **kwargs): def set_scaling(self, *args, **kwargs):
super().set_scaling(*args, **kwargs) super().set_scaling(*args, **kwargs)
@ -98,41 +97,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() self.draw()
def set_dimensions(self, width=None, height=None): def set_dimensions(self, width: int = None, height: int = None):
super().set_dimensions(width, height) super().set_dimensions(width, height)
self.canvas.configure(width=self.apply_widget_scaling(self.desired_width), self.canvas.configure(width=self.apply_widget_scaling(self._desired_width),
height=self.apply_widget_scaling(self.desired_height)) height=self.apply_widget_scaling(self._desired_height))
self.draw() self.draw()
def draw(self, no_color_updates=False): def draw(self, no_color_updates=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 != "":
@ -140,6 +139,7 @@ class CTkButton(CTkBaseClass):
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,
font=self.apply_font_scaling(self.text_font), font=self.apply_font_scaling(self.text_font),
text=self.text,
textvariable=self.textvariable) textvariable=self.textvariable)
self.text_label.bind("<Enter>", self.on_enter) self.text_label.bind("<Enter>", self.on_enter)
@ -149,19 +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
else: else:
# delete text_label if no text given # delete text_label if no text given
@ -183,9 +181,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
@ -237,102 +235,91 @@ class CTkButton(CTkBaseClass):
padx=max(self.apply_widget_scaling(self.corner_radius), self.apply_widget_scaling(self.border_width)), padx=max(self.apply_widget_scaling(self.corner_radius), self.apply_widget_scaling(self.border_width)),
pady=(self.apply_widget_scaling(self.border_width), 2)) pady=(self.apply_widget_scaling(self.border_width), 2))
def configure(self, *args, **kwargs): def configure(self, require_redraw=False, **kwargs):
require_redraw = False # some attribute changes require a call of self.draw() at the end
if "text" in kwargs: if "text" in kwargs:
self.set_text(kwargs["text"]) self.text = kwargs.pop("text")
del kwargs["text"] if self.text_label is None:
require_redraw = True # text_label will be created in .draw()
else:
self.text_label.configure(text=self.text)
if "text_font" in kwargs:
self.text_font = kwargs.pop("text_font")
if self.text_label is not None:
self.text_label.configure(font=self.apply_font_scaling(self.text_font))
if "state" in kwargs: if "state" in kwargs:
self.state = kwargs["state"] self.state = kwargs.pop("state")
self.set_cursor() self.set_cursor()
require_redraw = True require_redraw = True
del kwargs["state"]
if "image" in kwargs: if "image" in kwargs:
self.set_image(kwargs["image"]) self.image = kwargs.pop("image")
del kwargs["image"] require_redraw = True
if "corner_radius" in kwargs:
self.corner_radius = kwargs.pop("corner_radius")
require_redraw = True
if "compound" in kwargs: if "compound" in kwargs:
self.compound = kwargs["compound"] self.compound = kwargs.pop("compound")
require_redraw = True require_redraw = True
del kwargs["compound"]
if "fg_color" in kwargs: if "fg_color" in kwargs:
self.fg_color = kwargs["fg_color"] self.fg_color = kwargs.pop("fg_color")
require_redraw = True require_redraw = True
del kwargs["fg_color"]
if "border_color" in kwargs: if "border_color" in kwargs:
self.border_color = kwargs["border_color"] self.border_color = kwargs.pop("border_color")
require_redraw = True require_redraw = True
del kwargs["border_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 "hover_color" in kwargs: if "hover_color" in kwargs:
self.hover_color = kwargs["hover_color"] self.hover_color = kwargs.pop("hover_color")
require_redraw = True require_redraw = True
del kwargs["hover_color"]
if "text_color" in kwargs: if "text_color" in kwargs:
self.text_color = kwargs["text_color"] self.text_color = kwargs.pop("text_color")
require_redraw = True require_redraw = True
del kwargs["text_color"]
if "command" in kwargs: if "command" in kwargs:
self.function = kwargs["command"] self.command = kwargs.pop("command")
del kwargs["command"]
if "textvariable" in kwargs: if "textvariable" in kwargs:
self.textvariable = kwargs["textvariable"] self.textvariable = kwargs.pop("textvariable")
if self.text_label is not None: if self.text_label is not None:
self.text_label.configure(textvariable=self.textvariable) self.text_label.configure(textvariable=self.textvariable)
del kwargs["textvariable"]
if "width" in kwargs: if "width" in kwargs:
self.set_dimensions(width=kwargs["width"]) self.set_dimensions(width=kwargs.pop("width"))
del kwargs["width"]
if "height" in kwargs: if "height" in kwargs:
self.set_dimensions(height=kwargs["height"]) self.set_dimensions(height=kwargs.pop("height"))
del kwargs["height"]
super().configure(*args, **kwargs) super().configure(require_redraw=require_redraw, **kwargs)
if require_redraw:
self.draw()
def set_cursor(self): def set_cursor(self):
if Settings.cursor_manipulation_enabled: if Settings.cursor_manipulation_enabled:
if self.state == tkinter.DISABLED: if self.state == tkinter.DISABLED:
if sys.platform == "darwin" and self.function is not None and Settings.cursor_manipulation_enabled: if sys.platform == "darwin" and self.command is not None and Settings.cursor_manipulation_enabled:
self.configure(cursor="arrow") self.configure(cursor="arrow")
elif sys.platform.startswith("win") and self.function is not None and Settings.cursor_manipulation_enabled: elif sys.platform.startswith("win") and self.command is not None and Settings.cursor_manipulation_enabled:
self.configure(cursor="arrow") self.configure(cursor="arrow")
elif self.state == tkinter.NORMAL: elif self.state == tkinter.NORMAL:
if sys.platform == "darwin" and self.function is not None and Settings.cursor_manipulation_enabled: if sys.platform == "darwin" and self.command is not None and Settings.cursor_manipulation_enabled:
self.configure(cursor="pointinghand") self.configure(cursor="pointinghand")
elif sys.platform.startswith("win") and self.function is not None and Settings.cursor_manipulation_enabled: elif sys.platform.startswith("win") and self.command is not None and Settings.cursor_manipulation_enabled:
self.configure(cursor="hand2") self.configure(cursor="hand2")
def set_text(self, text):
self.text = text
self.draw()
def set_image(self, image): def set_image(self, image):
self.image = image """ will be removed in next major """
self.draw() self.configure(image=image)
def on_enter(self, event=0): def set_text(self, text):
""" will be removed in next major """
self.configure(text=text)
def on_enter(self, event=None):
if self.hover is True and self.state == tkinter.NORMAL: if self.hover is True and self.state == tkinter.NORMAL:
if self.hover_color is None: if self.hover_color is None:
inner_parts_color = self.fg_color inner_parts_color = self.fg_color
@ -341,18 +328,18 @@ 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=None):
self.click_animation_running = False self.click_animation_running = False
if self.hover is True: if self.hover is True:
@ -363,28 +350,28 @@ 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:
self.on_enter() self.on_enter()
def clicked(self, event=0): def clicked(self, event=None):
if self.function is not None: if self.command is not None:
if self.state is not tkinter.DISABLED: if self.state != tkinter.DISABLED:
# click animation: change color with .on_leave() and back to normal after 100ms with click_animation() # click animation: change color with .on_leave() and back to normal after 100ms with click_animation()
self.on_leave() self.on_leave()
self.click_animation_running = True self.click_animation_running = True
self.after(100, self.click_animation) self.after(100, self.click_animation)
self.function() self.command()

View File

@ -8,7 +8,6 @@ class CTkCanvas(tkinter.Canvas):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.aa_circle_canvas_ids = set() self.aa_circle_canvas_ids = set()
@classmethod @classmethod
@ -21,7 +20,7 @@ class CTkCanvas(tkinter.Canvas):
radius_to_char_fine_windows_10 = {19: 'A', 18: 'A', 17: 'B', 16: 'B', 15: 'B', 14: 'B', 13: 'C', 12: 'C', radius_to_char_fine_windows_10 = {19: 'A', 18: 'A', 17: 'B', 16: 'B', 15: 'B', 14: 'B', 13: 'C', 12: 'C',
11: 'C', 10: 'C', 11: 'C', 10: 'C',
9: 'D', 8: 'D', 7: 'D', 6: 'F', 5: 'D', 4: 'G', 3: 'G', 2: 'H', 1: 'H', 9: 'D', 8: 'D', 7: 'D', 6: 'C', 5: 'D', 4: 'G', 3: 'G', 2: 'H', 1: 'H',
0: 'A'} 0: 'A'}
radius_to_char_fine_windows_11 = {19: 'A', 18: 'A', 17: 'B', 16: 'B', 15: 'B', 14: 'B', 13: 'C', 12: 'C', radius_to_char_fine_windows_11 = {19: 'A', 18: 'A', 17: 'B', 16: 'B', 15: 'B', 14: 'B', 13: 'C', 12: 'C',
@ -29,12 +28,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

@ -35,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
@ -56,15 +56,16 @@ class CTkCheckBox(CTkBaseClass):
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
# callback and hover functionality # callback and hover functionality
self.function = command self.command = command
self.state = state self.state = state
self.hover = hover self.hover = hover
self.check_state = False self.check_state = False
self.onvalue = onvalue self.onvalue = onvalue
self.offvalue = offvalue self.offvalue = offvalue
self.variable: tkinter.Variable = variable self.variable: tkinter.Variable = variable
self.variable_callback_blocked = False self.variable_callback_blocked = False
self.textvariable = textvariable self.textvariable: tkinter.Variable = textvariable
self.variable_callback_name = None self.variable_callback_name = None
# configure grid system (1x3) # configure grid system (1x3)
@ -75,14 +76,14 @@ 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)
@ -90,13 +91,23 @@ class CTkCheckBox(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)
# set select state according to variable self.text_label = tkinter.Label(master=self,
if self.variable is not None: bd=0,
text=self.text,
justify=tkinter.LEFT,
font=self.apply_font_scaling(self.text_font),
textvariable=self.textvariable)
self.text_label.grid(row=0, column=2, padx=0, pady=0, sticky="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)
# register variable callback and set state according to variable
if self.variable is not None and self.variable != "":
self.variable_callback_name = self.variable.trace_add("write", self.variable_callback) self.variable_callback_name = self.variable.trace_add("write", self.variable_callback)
if self.variable.get() == self.onvalue: self.check_state = True if variable.get() == self.onvalue else False
self.select(from_variable_callback=True)
elif self.variable.get() == self.offvalue:
self.deselect(from_variable_callback=True)
self.draw() # initial draw self.draw() # initial draw
self.set_cursor() self.set_cursor()
@ -107,8 +118,9 @@ 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.canvas.delete("checkmark")
self.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.draw() self.draw()
def destroy(self): def destroy(self):
@ -118,128 +130,98 @@ 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:
self.text_label = tkinter.Label(master=self,
bd=0,
text=self.text,
justify=tkinter.LEFT,
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["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.set_text(self.text) self.text_label.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
def configure(self, *args, **kwargs):
require_redraw = False # some attribute changes require a call of self.draw()
def configure(self, require_redraw=False, **kwargs):
if "text" in kwargs: if "text" in kwargs:
self.set_text(kwargs["text"]) self.text = kwargs.pop("text")
del kwargs["text"] self.text_label.configure(text=self.text)
if "text_font" in kwargs:
self.text_font = kwargs.pop("text_font")
if self.text_label is not None:
self.text_label.configure(font=self.apply_font_scaling(self.text_font))
if "state" in kwargs: if "state" in kwargs:
self.state = kwargs["state"] self.state = kwargs.pop("state")
self.set_cursor() self.set_cursor()
require_redraw = True 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.pop("fg_color")
require_redraw = True 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 "hover_color" in kwargs: if "hover_color" in kwargs:
self.hover_color = kwargs["hover_color"] self.hover_color = kwargs.pop("hover_color")
require_redraw = True require_redraw = True
del kwargs["hover_color"]
if "text_color" in kwargs: if "text_color" in kwargs:
self.text_color = kwargs["text_color"] self.text_color = kwargs.pop("text_color")
require_redraw = True require_redraw = True
del kwargs["text_color"]
if "border_color" in kwargs: if "border_color" in kwargs:
self.border_color = kwargs["border_color"] self.border_color = kwargs.pop("border_color")
require_redraw = True require_redraw = True
del kwargs["border_color"]
if "command" in kwargs: if "command" in kwargs:
self.function = kwargs["command"] self.command = kwargs.pop("command")
del kwargs["command"]
if "textvariable" in kwargs:
self.textvariable = kwargs.pop("textvariable")
self.text_label.configure(textvariable=self.textvariable)
if "variable" in kwargs: if "variable" in kwargs:
if self.variable is not None: if self.variable is not None and self.variable != "":
self.variable.trace_remove("write", self.variable_callback_name) self.variable.trace_remove("write", self.variable_callback_name) # remove old variable callback
self.variable = kwargs["variable"] self.variable = kwargs.pop("variable")
if self.variable is not None and self.variable != "": if self.variable is not None and self.variable != "":
self.variable_callback_name = self.variable.trace_add("write", self.variable_callback) self.variable_callback_name = self.variable.trace_add("write", self.variable_callback)
if self.variable.get() == self.onvalue: self.check_state = True if self.variable.get() == self.onvalue else False
self.select(from_variable_callback=True) require_redraw = True
elif self.variable.get() == self.offvalue:
self.deselect(from_variable_callback=True)
else:
self.variable = None
del kwargs["variable"] super().configure(require_redraw=require_redraw, **kwargs)
super().configure(*args, **kwargs)
if require_redraw:
self.draw()
def set_cursor(self): def set_cursor(self):
if Settings.cursor_manipulation_enabled: if Settings.cursor_manipulation_enabled:
@ -263,43 +245,36 @@ class CTkCheckBox(CTkBaseClass):
if self.text_label is not None: if self.text_label is not None:
self.text_label.configure(cursor="hand2") self.text_label.configure(cursor="hand2")
def set_text(self, text):
self.text = text
if self.text_label is not None:
self.text_label.configure(text=self.text)
else:
sys.stderr.write("ERROR (CTkButton): Cant change text because checkbox has no text.")
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:
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:
@ -317,14 +292,14 @@ class CTkCheckBox(CTkBaseClass):
self.check_state = True self.check_state = True
self.draw() self.draw()
if self.function is not None:
self.function()
if self.variable is not None: if self.variable is not None:
self.variable_callback_blocked = True self.variable_callback_blocked = True
self.variable.set(self.onvalue if self.check_state is True else self.offvalue) self.variable.set(self.onvalue if self.check_state is True else self.offvalue)
self.variable_callback_blocked = False self.variable_callback_blocked = False
if self.command is not None:
self.command()
def select(self, from_variable_callback=False): def select(self, from_variable_callback=False):
self.check_state = True self.check_state = True
self.draw() self.draw()
@ -334,11 +309,8 @@ class CTkCheckBox(CTkBaseClass):
self.variable.set(self.onvalue) self.variable.set(self.onvalue)
self.variable_callback_blocked = False self.variable_callback_blocked = False
if self.function is not None: if self.command is not None:
try: self.command()
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
@ -349,11 +321,8 @@ class CTkCheckBox(CTkBaseClass):
self.variable.set(self.offvalue) self.variable.set(self.offvalue)
self.variable_callback_blocked = False self.variable_callback_blocked = False
if self.function is not None: if self.command is not None:
try: self.command()
self.function()
except:
pass
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

View File

@ -0,0 +1,292 @@
import tkinter
import sys
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.command = command
self.textvariable = 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.entry.delete(0, tkinter.END)
self.entry.insert(0, self.current_value)
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.textvariable is not None:
self.entry.configure(textvariable=self.textvariable)
def set_scaling(self, *args, **kwargs):
super().set_scaling(*args, **kwargs)
# change entry font size and grid padding
left_section_width = self._current_width - self._current_height
self.entry.configure(font=self.apply_font_scaling(self.text_font))
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.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 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, require_redraw=False, **kwargs):
if "state" in kwargs:
self.state = kwargs.pop("state")
self.entry.configure(state=self.state)
require_redraw = True
if "fg_color" in kwargs:
self.fg_color = kwargs.pop("fg_color")
require_redraw = True
if "button_color" in kwargs:
self.button_color = kwargs.pop("button_color")
require_redraw = True
if "button_hover_color" in kwargs:
self.button_hover_color = kwargs.pop("button_hover_color")
require_redraw = True
if "text_color" in kwargs:
self.text_color = kwargs.pop("text_color")
require_redraw = True
if "text_font" in kwargs:
self.text_font = kwargs.pop("text_font")
self.entry.configure(font=self.apply_font_scaling(self.text_font))
if "command" in kwargs:
self.command = kwargs.pop("command")
if "variable" in kwargs:
self.textvariable = kwargs.pop("variable")
self.entry.configure(textvariable=self.textvariable)
if "width" in kwargs:
self.set_dimensions(width=kwargs.pop("width"))
if "height" in kwargs:
self.set_dimensions(height=kwargs.pop("height"))
if "values" in kwargs:
self.values = kwargs.pop("values")
self.dropdown_menu.configure(values=self.values)
if "dropdown_color" in kwargs:
self.dropdown_menu.configure(fg_color=kwargs.pop("dropdown_color"))
if "dropdown_hover_color" in kwargs:
self.dropdown_menu.configure(hover_color=kwargs.pop("dropdown_hover_color"))
if "dropdown_text_color" in kwargs:
self.dropdown_menu.configure(text_color=kwargs.pop("dropdown_text_color"))
if "dropdown_text_font" in kwargs:
self.dropdown_menu.configure(text_font=kwargs.pop("dropdown_text_font"))
super().configure(require_redraw=require_redraw, **kwargs)
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
if self.state == "readonly":
self.entry.configure(state="normal")
self.entry.delete(0, tkinter.END)
self.entry.insert(0, self.current_value)
self.entry.configure(state="readonly")
else:
self.entry.delete(0, tkinter.END)
self.entry.insert(0, self.current_value)
if not from_variable_callback:
if self.command is not None:
self.command(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,41 +17,48 @@ 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=28, height=28,
state=tkinter.NORMAL, state=tkinter.NORMAL,
textvariable: tkinter.Variable = 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
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.pop("master"))
del kwargs["master"]
else: else:
super().__init__(*args, bg_color=bg_color, width=width, height=height) super().__init__(*args, bg_color=bg_color, width=width, height=height)
# configure grid system (1x1)
self.grid_rowconfigure(0, weight=1) self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1) self.grid_columnconfigure(0, weight=1)
# color
self.fg_color = ThemeManager.theme["color"]["entry"] if fg_color == "default_theme" else fg_color self.fg_color = ThemeManager.theme["color"]["entry"] if fg_color == "default_theme" else fg_color
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.placeholder_text_color = ThemeManager.theme["color"]["entry_placeholder_text"] if placeholder_text_color == "default_theme" else placeholder_text_color self.placeholder_text_color = ThemeManager.theme["color"]["entry_placeholder_text"] if placeholder_text_color == "default_theme" else placeholder_text_color
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
self.border_color = ThemeManager.theme["color"]["entry_border"] if border_color == "default_theme" else border_color self.border_color = ThemeManager.theme["color"]["entry_border"] if border_color == "default_theme" else border_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
# placeholder text
self.placeholder_text = placeholder_text self.placeholder_text = placeholder_text
self.placeholder_text_active = False self.placeholder_text_active = False
self.pre_placeholder_arguments = {} # some set arguments of the entry will be changed for placeholder and then set back self.pre_placeholder_arguments = {} # some set arguments of the entry will be changed for placeholder and then set back
self.state = state # textvariable
self.textvariable = textvariable
self.corner_radius = ThemeManager.theme["shape"]["button_corner_radius"] if corner_radius == "default_theme" else corner_radius self.state = state
self.border_width = ThemeManager.theme["shape"]["entry_border_width"] if border_width == "default_theme" else border_width
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,
@ -60,16 +67,18 @@ class CTkEntry(CTkBaseClass):
highlightthickness=0, highlightthickness=0,
font=self.apply_font_scaling(self.text_font), font=self.apply_font_scaling(self.text_font),
state=self.state, state=self.state,
textvariable=self.textvariable,
**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.entry_focus_out)
self.entry.bind('<FocusIn>', self.clear_placeholder) self.entry.bind('<FocusIn>', self.entry_focus_in)
self.activate_placeholder()
self.draw() self.draw()
self.set_placeholder()
def set_scaling(self, *args, **kwargs): def set_scaling(self, *args, **kwargs):
super().set_scaling( *args, **kwargs) super().set_scaling( *args, **kwargs)
@ -78,138 +87,156 @@ 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() self.draw()
def set_dimensions(self, width=None, height=None): def set_dimensions(self, width=None, height=None):
super().set_dimensions(width, height) super().set_dimensions(width, height)
self.canvas.configure(width=self.apply_widget_scaling(self.desired_width), self.canvas.configure(width=self.apply_widget_scaling(self._desired_width),
height=self.apply_widget_scaling(self.desired_height)) height=self.apply_widget_scaling(self._desired_height))
self.draw() self.draw()
def set_placeholder(self, event=None):
if self.placeholder_text is not None:
if not self.placeholder_text_active and self.entry.get() == "":
self.placeholder_text_active = True
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.delete(0, tkinter.END)
self.entry.insert(0, self.placeholder_text)
def clear_placeholder(self, event=None):
if self.placeholder_text_active:
self.placeholder_text_active = False
self.entry.config(fg=ThemeManager.single_color(self.text_color, self.appearance_mode))
self.entry.delete(0, tkinter.END)
for argument, value in self.pre_placeholder_arguments.items():
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)
def configure(self, *args, **kwargs): def configure(self, require_redraw=False, **kwargs):
require_redraw = False # some attribute changes require a call of self.draw() at the end
if "state" in kwargs: if "state" in kwargs:
self.state = kwargs["state"] self.state = kwargs.pop("state")
self.entry.configure(state=self.state) self.entry.configure(state=self.state)
del kwargs["state"]
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 "fg_color" in kwargs: if "fg_color" in kwargs:
self.fg_color = kwargs["fg_color"] self.fg_color = kwargs.pop("fg_color")
del kwargs["fg_color"]
require_redraw = True require_redraw = True
if "text_color" in kwargs: if "text_color" in kwargs:
self.text_color = kwargs["text_color"] self.text_color = kwargs.pop("text_color")
del kwargs["text_color"]
require_redraw = True require_redraw = True
if "border_color" in kwargs: if "border_color" in kwargs:
self.border_color = kwargs["border_color"] self.border_color = kwargs.pop("border_color")
del kwargs["border_color"]
require_redraw = True require_redraw = True
if "corner_radius" in kwargs: if "corner_radius" in kwargs:
self.corner_radius = kwargs["corner_radius"] self.corner_radius = kwargs.pop("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"]
require_redraw = True require_redraw = True
if "width" in kwargs: if "width" in kwargs:
self.set_dimensions(width=kwargs["width"]) self.set_dimensions(width=kwargs.pop("width"))
del kwargs["width"]
if "height" in kwargs: if "height" in kwargs:
self.set_dimensions(height=kwargs["height"]) self.set_dimensions(height=kwargs.pop("height"))
del kwargs["height"]
if "placeholder_text" in kwargs: if "placeholder_text" in kwargs:
pass self.placeholder_text = kwargs.pop("placeholder_text")
if self.placeholder_text_active:
self.entry.delete(0, tkinter.END)
self.entry.insert(0, self.placeholder_text)
else:
self.activate_placeholder()
self.entry.configure(*args, **kwargs) if "placeholder_text_color" in kwargs:
self.placeholder_text_color = kwargs.pop("placeholder_text_color")
require_redraw = True
if require_redraw is True: if "textvariable" in kwargs:
self.draw() self.textvariable = kwargs.pop("textvariable")
self.entry.configure(textvariable=self.textvariable)
if "text_font" in kwargs:
self.text_font = kwargs.pop("text_font")
self.entry.configure(font=self.apply_font_scaling(self.text_font))
if "show" in kwargs:
if self.placeholder_text_active:
self.pre_placeholder_arguments["show"] = kwargs.pop("show")
else:
self.entry.configure(show=kwargs.pop("show"))
if "bg_color" in kwargs:
super().configure(bg_color=kwargs.pop("bg_color"), require_redraw=require_redraw)
else:
super().configure(require_redraw=require_redraw)
self.entry.configure(**kwargs) # pass remaining kwargs to entry
def activate_placeholder(self):
if self.entry.get() == "" and self.placeholder_text is not None and (self.textvariable is None or self.textvariable == ""):
self.placeholder_text_active = True
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.delete(0, tkinter.END)
self.entry.insert(0, self.placeholder_text)
def deactivate_placeholder(self):
if self.placeholder_text_active:
self.placeholder_text_active = False
self.entry.config(fg=ThemeManager.single_color(self.text_color, self._appearance_mode))
self.entry.delete(0, tkinter.END)
for argument, value in self.pre_placeholder_arguments.items():
self.entry[argument] = value
def entry_focus_out(self, event=None):
self.activate_placeholder()
def entry_focus_in(self, event=None):
self.deactivate_placeholder()
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
self.entry.delete(*args, **kwargs) self.entry.delete(*args, **kwargs)
self.set_placeholder()
if self.entry.get() == "":
self.activate_placeholder()
def insert(self, *args, **kwargs): def insert(self, *args, **kwargs):
self.clear_placeholder() self.deactivate_placeholder()
return self.entry.insert(*args, **kwargs) return self.entry.insert(*args, **kwargs)
def get(self): def get(self):
@ -217,3 +244,9 @@ class CTkEntry(CTkBaseClass):
return "" return ""
else: else:
return self.entry.get() return self.entry.get()
def focus(self):
self.entry.focus()
def focus_force(self):
self.entry.focus_force()

View File

@ -16,7 +16,7 @@ class CTkFrame(CTkBaseClass):
overwrite_preferred_drawing_method: str = None, 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
@ -40,10 +40,10 @@ 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._overwrite_preferred_drawing_method = overwrite_preferred_drawing_method
@ -65,20 +65,20 @@ 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() self.draw()
def set_dimensions(self, width=None, height=None): def set_dimensions(self, width=None, height=None):
super().set_dimensions(width, height) super().set_dimensions(width, height)
self.canvas.configure(width=self.apply_widget_scaling(self.desired_width), self.canvas.configure(width=self.apply_widget_scaling(self._desired_width),
height=self.apply_widget_scaling(self.desired_height)) height=self.apply_widget_scaling(self._desired_height))
self.draw() self.draw()
def draw(self, no_color_updates=False): def draw(self, no_color_updates=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) overwrite_preferred_drawing_method=self._overwrite_preferred_drawing_method)
@ -86,67 +86,47 @@ class CTkFrame(CTkBaseClass):
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")
def configure(self, *args, **kwargs): def configure(self, require_redraw=False, **kwargs):
require_redraw = False # some attribute changes require a call of self.draw() at the end
if "fg_color" in kwargs: if "fg_color" in kwargs:
self.fg_color = kwargs["fg_color"] self.fg_color = kwargs.pop("fg_color")
require_redraw = True require_redraw = True
del kwargs["fg_color"]
# check if CTk widgets are children of the frame and change their bg_color to new frame fg_color # check if CTk widgets are children of the frame and change their bg_color to new frame fg_color
for child in self.winfo_children(): for child in self.winfo_children():
if isinstance(child, CTkBaseClass): if isinstance(child, CTkBaseClass):
child.configure(bg_color=self.fg_color) child.configure(bg_color=self.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 "border_color" in kwargs: if "border_color" in kwargs:
self.border_color = kwargs["border_color"] self.border_color = kwargs.pop("border_color")
require_redraw = True 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.pop("corner_radius")
require_redraw = True require_redraw = True
del kwargs["corner_radius"]
if "border_width" in kwargs: if "border_width" in kwargs:
self.border_width = kwargs["border_width"] self.border_width = kwargs.pop("border_width")
require_redraw = True require_redraw = True
del kwargs["border_width"]
if "width" in kwargs: if "width" in kwargs:
self.set_dimensions(width=kwargs["width"]) self.set_dimensions(width=kwargs.pop("width"))
del kwargs["width"]
if "height" in kwargs: if "height" in kwargs:
self.set_dimensions(height=kwargs["height"]) self.set_dimensions(height=kwargs.pop("height"))
del kwargs["height"]
super().configure(*args, **kwargs) super().configure(require_redraw=require_redraw, **kwargs)
if require_redraw:
self.draw()

View File

@ -1,3 +1,4 @@
import sys
import tkinter import tkinter
from .ctk_canvas import CTkCanvas from .ctk_canvas import CTkCanvas
@ -12,16 +13,16 @@ 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=28, height=28,
text="CTkLabel", text="CTkLabel",
text_font="default_theme", text_font="default_theme",
anchor="center", # label anchor: center, n, e, s, w
**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.pop("master"))
del kwargs["master"]
else: else:
super().__init__(*args, bg_color=bg_color, width=width, height=height) super().__init__(*args, bg_color=bg_color, width=width, height=height)
@ -35,6 +36,7 @@ class CTkLabel(CTkBaseClass):
self.corner_radius = ThemeManager.theme["shape"]["label_corner_radius"] if corner_radius == "default_theme" else corner_radius self.corner_radius = ThemeManager.theme["shape"]["label_corner_radius"] if corner_radius == "default_theme" else corner_radius
# text # text
self.anchor = anchor
self.text = text self.text = text
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
@ -44,18 +46,21 @@ 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)
self.text_label = tkinter.Label(master=self, self.text_label = tkinter.Label(master=self,
highlightthickness=0, highlightthickness=0,
bd=0, bd=0,
anchor=self.anchor,
text=self.text, text=self.text,
font=self.apply_font_scaling(self.text_font), font=self.apply_font_scaling(self.text_font),
**kwargs) **kwargs)
self.text_label.grid(row=0, column=0, padx=self.apply_widget_scaling(self.corner_radius)) text_label_grid_sticky = self.anchor if self.anchor != "center" else ""
self.text_label.grid(row=0, column=0, padx=self.apply_widget_scaling(self.corner_radius),
sticky=text_label_grid_sticky)
self.bind('<Configure>', self.update_dimensions_event) self.bind('<Configure>', self.update_dimensions_event)
self.draw() self.draw()
@ -63,63 +68,70 @@ 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)) text_label_grid_sticky = self.anchor if self.anchor != "center" else ""
self.text_label.grid(row=0, column=0, padx=self.apply_widget_scaling(self.corner_radius),
sticky=text_label_grid_sticky)
self.draw() self.draw()
def set_dimensions(self, width=None, height=None): def set_dimensions(self, width=None, height=None):
super().set_dimensions(width, height) super().set_dimensions(width, height)
self.canvas.configure(width=self.apply_widget_scaling(self.desired_width), self.canvas.configure(width=self.apply_widget_scaling(self._desired_width),
height=self.apply_widget_scaling(self.desired_height)) height=self.apply_widget_scaling(self._desired_height))
self.draw() self.draw()
def draw(self, no_color_updates=False): def draw(self, no_color_updates=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 config(self, **kwargs):
require_redraw = False # some attribute changes require a call of self.draw() at the end sys.stderr.write("Warning: Use .configure() instead of .config()")
self.configure(**kwargs)
def configure(self, require_redraw=False, **kwargs):
if "anchor" in kwargs:
self.anchor = kwargs.pop("anchor")
text_label_grid_sticky = self.anchor if self.anchor != "center" else ""
self.text_label.grid(row=0, column=0, padx=self.apply_widget_scaling(self.corner_radius),
sticky=text_label_grid_sticky)
if "text" in kwargs: if "text" in kwargs:
self.set_text(kwargs["text"]) self.text = kwargs["text"]
self.text_label.configure(text=self.text)
del kwargs["text"] del kwargs["text"]
if "text_font" in kwargs:
self.text_font = kwargs.pop("text_font")
self.text_label.configure(font=self.apply_font_scaling(self.text_font))
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
del kwargs["fg_color"] 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 "text_color" in kwargs: if "text_color" in kwargs:
self.text_color = kwargs["text_color"] self.text_color = kwargs["text_color"]
require_redraw = True require_redraw = True
@ -133,11 +145,15 @@ class CTkLabel(CTkBaseClass):
self.set_dimensions(height=kwargs["height"]) self.set_dimensions(height=kwargs["height"])
del kwargs["height"] del kwargs["height"]
self.text_label.configure(*args, **kwargs) if "bg_color" in kwargs:
super().configure(bg_color=kwargs.pop("bg_color"), require_redraw=require_redraw)
else:
super().configure(require_redraw=require_redraw)
if require_redraw: self.text_label.configure(**kwargs) # pass remaining kwargs to label
self.draw()
def set_text(self, text): def set_text(self, text):
""" Will be removed in the next major release """
self.text = text self.text = text
self.text_label.configure(text=self.text, width=len(self.text)) self.text_label.configure(text=self.text)

View File

@ -11,63 +11,72 @@ from .widget_base_class import CTkBaseClass
class CTkOptionMenu(CTkBaseClass): class CTkOptionMenu(CTkBaseClass):
def __init__(self, *args, def __init__(self, *args,
bg_color=None, bg_color=None,
fg_color="default_theme", fg_color="default_theme",
button_color="default_theme", button_color="default_theme",
button_hover_color="default_theme", button_hover_color="default_theme",
text_color="default_theme",
text_color_disabled="default_theme",
dropdown_color="default_theme", dropdown_color="default_theme",
dropdown_hover_color="default_theme", dropdown_hover_color="default_theme",
dropdown_text_color="default_theme", dropdown_text_color="default_theme",
variable=None, variable=None,
values=None, values=None,
command=None, command=None,
width=120, width=140,
height=28, height=28,
corner_radius="default_theme", corner_radius="default_theme",
text_font="default_theme", text_font="default_theme",
text_color="default_theme", dropdown_text_font="default_theme",
text_color_disabled="default_theme",
hover=True, hover=True,
state=tkinter.NORMAL, state=tkinter.NORMAL,
dynamic_resizing=True,
**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 variables # color variables
self.fg_color = ThemeManager.theme["color"]["button"] if fg_color == "default_theme" else fg_color 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_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 self.button_hover_color = ThemeManager.theme["color"]["optionmenu_button_hover"] if button_hover_color == "default_theme" else button_hover_color
self.dropdown_color = ThemeManager.theme["color"]["dropdown_color"] if dropdown_color == "default_theme" else dropdown_color
self.dropdown_hover_color = ThemeManager.theme["color"]["dropdown_hover"] if dropdown_hover_color == "default_theme" else dropdown_hover_color
self.dropdown_text_color = ThemeManager.theme["color"]["dropdown_text"] if dropdown_text_color == "default_theme" else dropdown_text_color
# shape # shape
self.corner_radius = ThemeManager.theme["shape"]["button_corner_radius"] if corner_radius == "default_theme" else corner_radius self.corner_radius = ThemeManager.theme["shape"]["button_corner_radius"] if corner_radius == "default_theme" else corner_radius
# text and font # text and font
self.text_label = 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_button_disabled"] if text_color_disabled == "default_theme" else text_color_disabled 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.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 # callback and hover functionality
self.function = command self.command = command
self.variable = variable self.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.state = state
self.hover = hover self.hover = hover
self.click_animation_running = False self.dynamic_resizing = dynamic_resizing
if values is None: if values is None:
self.values = ["CTkOptionMenu"] self.values = ["CTkOptionMenu"]
else: else:
self.values = values self.values = values
self.current_value = self.values[0]
self.dropdown_menu = None 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) # configure grid system (1x1)
self.grid_rowconfigure(0, weight=1) self.grid_rowconfigure(0, weight=1)
@ -75,19 +84,42 @@ class CTkOptionMenu(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="nsew") self.canvas.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="nsew")
self.draw_engine = DrawEngine(self.canvas) 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),
anchor="w",
text=self.current_value)
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 not self.dynamic_resizing:
self.grid_propagate(0)
if Settings.cursor_manipulation_enabled:
if sys.platform == "darwin":
self.configure(cursor="pointinghand")
elif sys.platform.startswith("win"):
self.configure(cursor="hand2")
# event bindings # event bindings
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.clicked) self.canvas.bind("<Button-1>", self.clicked)
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.bind('<Configure>', self.update_dimensions_event)
self.set_cursor()
self.draw() # initial draw self.draw() # initial draw
if self.variable is not None: if self.variable is not None:
@ -97,128 +129,99 @@ class CTkOptionMenu(CTkBaseClass):
def set_scaling(self, *args, **kwargs): def set_scaling(self, *args, **kwargs):
super().set_scaling(*args, **kwargs) super().set_scaling(*args, **kwargs)
if self.text_label is not None: # change label text size and grid padding
self.text_label.destroy() left_section_width = self._current_width - self._current_height
self.text_label = None self.text_label.configure(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))))
self.canvas.configure(width=self.apply_widget_scaling(self.desired_width), self.canvas.configure(width=self.apply_widget_scaling(self._desired_width),
height=self.apply_widget_scaling(self.desired_height)) height=self.apply_widget_scaling(self._desired_height))
self.draw() self.draw()
def set_dimensions(self, width: int = None, height: int = None): def set_dimensions(self, width: int = None, height: int = None):
super().set_dimensions(width, height) super().set_dimensions(width, height)
self.canvas.configure(width=self.apply_widget_scaling(self.desired_width), self.canvas.configure(width=self.apply_widget_scaling(self._desired_width),
height=self.apply_widget_scaling(self.desired_height)) height=self.apply_widget_scaling(self._desired_height))
self.draw() self.draw()
def draw(self, no_color_updates=False): def draw(self, no_color_updates=False):
left_section_width = self.current_width - self.current_height 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), 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._current_height),
self.apply_widget_scaling(self.corner_radius), self.apply_widget_scaling(self.corner_radius),
0, 0,
self.apply_widget_scaling(left_section_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)), 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 / 2),
self.apply_widget_scaling(self.current_height / 3)) self.apply_widget_scaling(self._current_height / 3))
if self.text_label is None:
self.text_label = tkinter.Label(master=self,
font=self.apply_font_scaling(self.text_font))
self.text_label.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="w",
padx=(max(self.apply_widget_scaling(self.corner_radius), 3),
max(self.current_width - left_section_width + 3, 3)))
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)
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: 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.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
self.canvas.itemconfig("inner_parts_left", self.canvas.itemconfig("inner_parts_left",
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_right", self.canvas.itemconfig("inner_parts_right",
outline=ThemeManager.single_color(self.button_color, self.appearance_mode), outline=ThemeManager.single_color(self.button_color, self._appearance_mode),
fill=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)) 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)))
self.canvas.itemconfig("dropdown_arrow", self.canvas.itemconfig("dropdown_arrow",
fill=ThemeManager.single_color(self.text_color_disabled, self.appearance_mode)) fill=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.canvas.itemconfig("dropdown_arrow", self.canvas.itemconfig("dropdown_arrow",
fill=ThemeManager.single_color(self.text_color, self.appearance_mode)) fill=ThemeManager.single_color(self.text_color, self._appearance_mode))
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.canvas.update_idletasks()
def open_dropdown_menu(self): def open_dropdown_menu(self):
self.dropdown_menu = DropdownMenu(x_position=self.winfo_rootx(), self.dropdown_menu.open(self.winfo_rootx(),
y_position=self.winfo_rooty() + self.apply_widget_scaling(self.current_height + 4), self.winfo_rooty() + self.apply_widget_scaling(self._current_height + 0))
width=self.current_width,
values=self.values,
command=self.set,
fg_color=self.dropdown_color,
button_hover_color=self.dropdown_hover_color,
button_color=self.dropdown_color,
text_color=self.dropdown_text_color)
def configure(self, *args, **kwargs):
require_redraw = False # some attribute changes require a call of self.draw() at the end
def configure(self, require_redraw=False, **kwargs):
if "state" in kwargs: if "state" in kwargs:
self.state = kwargs["state"] self.state = kwargs.pop("state")
self.set_cursor()
require_redraw = True 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.pop("fg_color")
require_redraw = True 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: if "button_color" in kwargs:
self.button_color = kwargs["button_color"] self.button_color = kwargs.pop("button_color")
require_redraw = True require_redraw = True
del kwargs["button_color"]
if "button_hover_color" in kwargs: if "button_hover_color" in kwargs:
self.button_hover_color = kwargs["button_hover_color"] self.button_hover_color = kwargs.pop("button_hover_color")
require_redraw = True require_redraw = True
del kwargs["button_hover_color"]
if "text_color" in kwargs: if "text_color" in kwargs:
self.text_color = kwargs["text_color"] self.text_color = kwargs.pop("text_color")
require_redraw = True require_redraw = True
del kwargs["text_color"]
if "text_font" in kwargs:
self.text_font = kwargs.pop("text_font")
self.text_label.configure(font=self.apply_font_scaling(self.text_font))
if "command" in kwargs: if "command" in kwargs:
self.function = kwargs["command"] self.command = kwargs.pop("command")
del kwargs["command"]
if "variable" in kwargs: if "variable" in kwargs:
if self.variable is not None: # remove old callback if self.variable is not None: # remove old callback
self.variable.trace_remove("write", self.variable_callback_name) self.variable.trace_remove("write", self.variable_callback_name)
self.variable = kwargs["variable"] self.variable = kwargs.pop("variable")
if self.variable is not None and self.variable != "": if self.variable is not None and self.variable != "":
self.variable_callback_name = self.variable.trace_add("write", self.variable_callback) self.variable_callback_name = self.variable.trace_add("write", self.variable_callback)
@ -226,54 +229,51 @@ class CTkOptionMenu(CTkBaseClass):
else: else:
self.variable = None self.variable = None
del kwargs["variable"]
if "width" in kwargs: if "width" in kwargs:
self.set_dimensions(width=kwargs["width"]) self.set_dimensions(width=kwargs.pop("width"))
del kwargs["width"]
if "height" in kwargs: if "height" in kwargs:
self.set_dimensions(height=kwargs["height"]) self.set_dimensions(height=kwargs.pop("height"))
del kwargs["height"]
super().configure(*args, **kwargs) if "values" in kwargs:
self.values = kwargs.pop("values")
self.dropdown_menu.configure(values=self.values)
if require_redraw: if "dropdown_color" in kwargs:
self.draw() self.dropdown_menu.configure(fg_color=kwargs.pop("dropdown_color"))
def set_cursor(self): if "dropdown_hover_color" in kwargs:
if Settings.cursor_manipulation_enabled: self.dropdown_menu.configure(hover_color=kwargs.pop("dropdown_hover_color"))
if self.state == tkinter.DISABLED:
if sys.platform == "darwin" and len(self.values) > 0 and Settings.cursor_manipulation_enabled:
self.configure(cursor="arrow")
elif sys.platform.startswith("win") and len(self.values) > 0 and Settings.cursor_manipulation_enabled:
self.configure(cursor="arrow")
elif self.state == tkinter.NORMAL: if "dropdown_text_color" in kwargs:
if sys.platform == "darwin" and len(self.values) > 0 and Settings.cursor_manipulation_enabled: self.dropdown_menu.configure(text_color=kwargs.pop("dropdown_text_color"))
self.configure(cursor="pointinghand")
elif sys.platform.startswith("win") and len(self.values) > 0 and Settings.cursor_manipulation_enabled: if "dropdown_text_font" in kwargs:
self.configure(cursor="hand2") self.dropdown_text_font = kwargs.pop("dropdown_text_font")
self.dropdown_menu.configure(text_font=self.dropdown_text_font)
if "dynamic_resizing" in kwargs:
self.dynamic_resizing = kwargs.pop("dynamic_resizing")
if not self.dynamic_resizing:
self.grid_propagate(0)
else:
self.grid_propagate(1)
super().configure(require_redraw=require_redraw, **kwargs)
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 and len(self.values) > 0:
# set color of inner button parts to hover color # set color of inner button parts to hover color
self.canvas.itemconfig("inner_parts_right", self.canvas.itemconfig("inner_parts_right",
outline=ThemeManager.single_color(self.button_hover_color, self.appearance_mode), outline=ThemeManager.single_color(self.button_hover_color, self._appearance_mode),
fill=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): def on_leave(self, event=0):
self.click_animation_running = False
if self.hover is True: if self.hover is True:
# set color of inner button parts # set color of inner button parts
self.canvas.itemconfig("inner_parts_right", self.canvas.itemconfig("inner_parts_right",
outline=ThemeManager.single_color(self.button_color, self.appearance_mode), outline=ThemeManager.single_color(self.button_color, self._appearance_mode),
fill=ThemeManager.single_color(self.button_color, self.appearance_mode)) fill=ThemeManager.single_color(self.button_color, self._appearance_mode))
def click_animation(self):
if self.click_animation_running:
self.on_enter()
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:
@ -282,10 +282,7 @@ class CTkOptionMenu(CTkBaseClass):
def set(self, value: str, from_variable_callback: bool = False): def set(self, value: str, from_variable_callback: bool = False):
self.current_value = value self.current_value = value
if self.text_label is not None: self.text_label.configure(text=self.current_value)
self.text_label.configure(text=self.current_value)
else:
self.draw()
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
@ -293,20 +290,12 @@ class CTkOptionMenu(CTkBaseClass):
self.variable_callback_blocked = False self.variable_callback_blocked = False
if not from_variable_callback: if not from_variable_callback:
if self.function is not None: if self.command is not None:
try: self.command(self.current_value)
self.function(self.current_value)
except Exception:
pass
def get(self) -> str: def get(self) -> str:
return self.current_value return self.current_value
def clicked(self, event=0): def clicked(self, event=0):
if self.state is not tkinter.DISABLED: if self.state is not tkinter.DISABLED and len(self.values) > 0:
self.open_dropdown_menu() self.open_dropdown_menu()
# click animation: change color with .on_leave() and back to normal after 100ms with click_animation()
self.on_leave()
self.click_animation_running = True
self.after(100, self.click_animation)

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,14 +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() self.draw()
def set_dimensions(self, width=None, height=None): def set_dimensions(self, width=None, height=None):
super().set_dimensions(width, height) super().set_dimensions(width, height)
self.canvas.configure(width=self.apply_widget_scaling(self.desired_width), self.canvas.configure(width=self.apply_widget_scaling(self._desired_width),
height=self.apply_widget_scaling(self.desired_height)) height=self.apply_widget_scaling(self._desired_height))
self.draw() self.draw()
def destroy(self): def destroy(self):
@ -102,36 +101,26 @@ 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):
require_redraw = False # some attribute changes require a call of self.draw() at the end
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"]
def configure(self, require_redraw=False, **kwargs):
if "fg_color" in kwargs: if "fg_color" in kwargs:
self.fg_color = kwargs["fg_color"] self.fg_color = kwargs["fg_color"]
del kwargs["fg_color"] del kwargs["fg_color"]
@ -174,10 +163,7 @@ class CTkProgressBar(CTkBaseClass):
self.set_dimensions(height=kwargs["height"]) self.set_dimensions(height=kwargs["height"])
del kwargs["height"] del kwargs["height"]
super().configure(*args, **kwargs) super().configure(require_redraw=require_redraw, **kwargs)
if require_redraw is True:
self.draw()
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

@ -1,6 +1,6 @@
import tkinter import tkinter
import sys import sys
from typing import Callable, Union from typing import Union
from .ctk_canvas import CTkCanvas from .ctk_canvas import CTkCanvas
from ..theme_manager import ThemeManager from ..theme_manager import ThemeManager
@ -32,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
@ -54,7 +54,7 @@ class CTkRadioButton(CTkBaseClass):
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
# callback and control variables # callback and control variables
self.function = command self.command = command
self.state = state self.state = state
self.hover = hover self.hover = hover
self.check_state = False self.check_state = False
@ -71,14 +71,14 @@ 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)
@ -86,15 +86,25 @@ class CTkRadioButton(CTkBaseClass):
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.draw() # initial draw self.text_label = tkinter.Label(master=self,
self.set_cursor() bd=0,
text=self.text,
justify=tkinter.LEFT,
font=self.apply_font_scaling(self.text_font),
textvariable=self.textvariable)
self.text_label.grid(row=0, column=2, padx=0, pady=0, sticky="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.invoke)
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)
if self.variable.get() == self.value: self.check_state = True if self.variable.get() == self.value else False
self.select(from_variable_callback=True)
else: self.draw() # initial draw
self.deselect(from_variable_callback=True) 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)
@ -102,8 +112,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,120 +123,87 @@ 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:
self.text_label = tkinter.Label(master=self,
bd=0,
text=self.text,
justify=tkinter.LEFT,
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["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.invoke)
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)
def configure(self, *args, **kwargs):
require_redraw = False # some attribute changes require a call of self.draw()
def configure(self, require_redraw=False, **kwargs):
if "text" in kwargs: if "text" in kwargs:
self.set_text(kwargs["text"]) self.text = kwargs.pop("text")
del kwargs["text"] self.text_label.configure(text=self.text)
if "text_font" in kwargs:
self.text_font = kwargs.pop("text_font")
self.text_label.configure(font=self.apply_font_scaling(self.text_font))
if "state" in kwargs: if "state" in kwargs:
self.state = kwargs["state"] self.state = kwargs.pop("state")
self.set_cursor() self.set_cursor()
require_redraw = True 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.pop("fg_color")
require_redraw = True 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 "hover_color" in kwargs: if "hover_color" in kwargs:
self.hover_color = kwargs["hover_color"] self.hover_color = kwargs.pop("hover_color")
require_redraw = True require_redraw = True
del kwargs["hover_color"]
if "text_color" in kwargs: if "text_color" in kwargs:
self.text_color = kwargs["text_color"] self.text_color = kwargs.pop("text_color")
require_redraw = True require_redraw = True
del kwargs["text_color"]
if "border_color" in kwargs: if "border_color" in kwargs:
self.border_color = kwargs["border_color"] self.border_color = kwargs.pop("border_color")
require_redraw = True require_redraw = True
del kwargs["border_color"]
if "border_width" in kwargs: if "border_width" in kwargs:
self.border_width = kwargs["border_width"] self.border_width = kwargs.pop("border_width")
require_redraw = True require_redraw = True
del kwargs["border_width"]
if "command" in kwargs: if "command" in kwargs:
self.function = kwargs["command"] self.command = kwargs.pop("command")
del kwargs["command"]
if "textvariable" in kwargs:
self.textvariable = kwargs.pop("textvariable")
self.text_label.configure(textvariable=self.textvariable)
if "variable" in kwargs: if "variable" in kwargs:
if self.variable is not None: if self.variable is not None:
self.variable.trace_remove("write", self.variable_callback_name) self.variable.trace_remove("write", self.variable_callback_name)
self.variable = kwargs["variable"] self.variable = kwargs.pop("variable")
if self.variable is not None and self.variable != "": if self.variable is not None and self.variable != "":
self.variable_callback_name = self.variable.trace_add("write", self.variable_callback) self.variable_callback_name = self.variable.trace_add("write", self.variable_callback)
if self.variable.get() == self.value: self.check_state = True if self.variable.get() == self.value else False
self.select(from_variable_callback=True) require_redraw = True
else:
self.deselect(from_variable_callback=True)
else:
self.variable = None
del kwargs["variable"] super().configure(require_redraw=require_redraw, **kwargs)
super().configure(*args, **kwargs)
if require_redraw:
self.draw()
def set_cursor(self): def set_cursor(self):
if Settings.cursor_manipulation_enabled: if Settings.cursor_manipulation_enabled:
@ -250,29 +227,22 @@ class CTkRadioButton(CTkBaseClass):
if self.text_label is not None: if self.text_label is not None:
self.text_label.configure(cursor="hand2") self.text_label.configure(cursor="hand2")
def set_text(self, text):
self.text = text
if self.text_label is not None:
self.text_label.configure(text=self.text)
else:
sys.stderr.write("ERROR (CTkButton): Cant change text because radiobutton has no text.")
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:
@ -287,11 +257,8 @@ class CTkRadioButton(CTkBaseClass):
self.check_state = True self.check_state = True
self.select() self.select()
if self.function is not None: if self.command is not None:
try: self.command()
self.function()
except:
pass
def select(self, from_variable_callback=False): def select(self, from_variable_callback=False):
self.check_state = True self.check_state = True

View File

@ -0,0 +1,225 @@
import sys
from .ctk_canvas import CTkCanvas
from ..theme_manager import ThemeManager
from ..draw_engine import DrawEngine
from .widget_base_class import CTkBaseClass
class CTkScrollbar(CTkBaseClass):
def __init__(self, *args,
bg_color=None,
fg_color="default_theme",
scrollbar_color="default_theme",
scrollbar_hover_color="default_theme",
border_spacing="default_theme",
corner_radius="default_theme",
width=None,
height=None,
minimum_pixel_length=20,
orientation="vertical",
command=None,
hover=True,
**kwargs):
# set default dimensions according to orientation
if width is None:
if orientation.lower() == "vertical":
width = 16
else:
width = 200
if height is None:
if orientation.lower() == "horizontal":
height = 16
else:
height = 200
# transfer basic functionality (bg_color, size, _appearance_mode, scaling) to CTkBaseClass
super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs)
# color
self.fg_color = ThemeManager.theme["color"]["frame_high"] if fg_color == "default_theme" else fg_color
self.scrollbar_color = ThemeManager.theme["color"]["scrollbar_button"] if scrollbar_color == "default_theme" else scrollbar_color
self.scrollbar_hover_color = ThemeManager.theme["color"]["scrollbar_button_hover"] if scrollbar_hover_color == "default_theme" else scrollbar_hover_color
# shape
self.corner_radius = ThemeManager.theme["shape"]["scrollbar_corner_radius"] if corner_radius == "default_theme" else corner_radius
self.border_spacing = ThemeManager.theme["shape"]["scrollbar_border_spacing"] if border_spacing == "default_theme" else border_spacing
self.hover = hover
self.hover_state = False
self.command = command
self.orientation = orientation
self.start_value: float = 0 # 0 to 1
self.end_value: float = 1 # 0 to 1
self.minimum_pixel_length = minimum_pixel_length
self.canvas = CTkCanvas(master=self,
highlightthickness=0,
width=self.apply_widget_scaling(self._current_width),
height=self.apply_widget_scaling(self._current_height))
self.canvas.place(x=0, y=0, relwidth=1, relheight=1)
self.draw_engine = DrawEngine(self.canvas)
self.canvas.bind("<Enter>", self.on_enter)
self.canvas.bind("<Leave>", self.on_leave)
self.canvas.tag_bind("border_parts", "<Button-1>", self.clicked)
self.canvas.bind("<B1-Motion>", self.clicked)
self.canvas.bind("<MouseWheel>", self.mouse_scroll_event)
self.bind('<Configure>', self.update_dimensions_event)
self.draw()
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(no_color_updates=True)
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(no_color_updates=True)
def get_scrollbar_values_for_minimum_pixel_size(self):
# correct scrollbar float values if scrollbar is too small
if self.orientation == "vertical":
scrollbar_pixel_length = (self.end_value - self.start_value) * self._current_height
if scrollbar_pixel_length < self.minimum_pixel_length and -scrollbar_pixel_length + self._current_height != 0:
# calculate how much to increase the float interval values so that the scrollbar width is self.minimum_pixel_length
interval_extend_factor = (-scrollbar_pixel_length + self.minimum_pixel_length) / (-scrollbar_pixel_length + self._current_height)
corrected_end_value = self.end_value + (1 - self.end_value) * interval_extend_factor
corrected_start_value = self.start_value - self.start_value * interval_extend_factor
return corrected_start_value, corrected_end_value
else:
return self.start_value, self.end_value
else:
scrollbar_pixel_length = (self.end_value - self.start_value) * self._current_width
if scrollbar_pixel_length < self.minimum_pixel_length and -scrollbar_pixel_length + self._current_width != 0:
# calculate how much to increase the float interval values so that the scrollbar width is self.minimum_pixel_length
interval_extend_factor = (-scrollbar_pixel_length + self.minimum_pixel_length) / (-scrollbar_pixel_length + self._current_width)
corrected_end_value = self.end_value + (1 - self.end_value) * interval_extend_factor
corrected_start_value = self.start_value - self.start_value * interval_extend_factor
return corrected_start_value, corrected_end_value
else:
return self.start_value, self.end_value
def draw(self, no_color_updates=False):
corrected_start_value, corrected_end_value = self.get_scrollbar_values_for_minimum_pixel_size()
requires_recoloring = self.draw_engine.draw_rounded_scrollbar(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_spacing),
corrected_start_value,
corrected_end_value,
self.orientation)
if no_color_updates is False or requires_recoloring:
if self.hover_state is True:
self.canvas.itemconfig("scrollbar_parts",
fill=ThemeManager.single_color(self.scrollbar_hover_color, self._appearance_mode),
outline=ThemeManager.single_color(self.scrollbar_hover_color, self._appearance_mode))
else:
self.canvas.itemconfig("scrollbar_parts",
fill=ThemeManager.single_color(self.scrollbar_color, self._appearance_mode),
outline=ThemeManager.single_color(self.scrollbar_color, self._appearance_mode))
if self.fg_color is None:
self.canvas.configure(bg=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))
else:
self.canvas.configure(bg=ThemeManager.single_color(self.fg_color, self._appearance_mode))
self.canvas.itemconfig("border_parts",
fill=ThemeManager.single_color(self.fg_color, self._appearance_mode),
outline=ThemeManager.single_color(self.fg_color, self._appearance_mode))
self.canvas.update_idletasks()
def set(self, start_value: float, end_value: float):
self.start_value = float(start_value)
self.end_value = float(end_value)
self.draw()
def get(self):
return self.start_value, self.end_value
def configure(self, require_redraw=False, **kwargs):
if "fg_color" in kwargs:
self.fg_color = kwargs["fg_color"]
require_redraw = True
del kwargs["fg_color"]
if "scrollbar_color" in kwargs:
self.scrollbar_color = kwargs["scrollbar_color"]
require_redraw = True
del kwargs["scrollbar_color"]
if "scrollbar_hover_color" in kwargs:
self.scrollbar_hover_color = kwargs["scrollbar_hover_color"]
require_redraw = True
del kwargs["scrollbar_hover_color"]
if "command" in kwargs:
self.command = kwargs["command"]
del kwargs["command"]
if "corner_radius" in kwargs:
self.corner_radius = kwargs["corner_radius"]
require_redraw = True
del kwargs["corner_radius"]
if "border_spacing" in kwargs:
self.border_spacing = kwargs["border_spacing"]
require_redraw = True
del kwargs["border_spacing"]
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(require_redraw=require_redraw, **kwargs)
def on_enter(self, event=0):
if self.hover is True:
self.hover_state = True
self.canvas.itemconfig("scrollbar_parts",
outline=ThemeManager.single_color(self.scrollbar_hover_color, self._appearance_mode),
fill=ThemeManager.single_color(self.scrollbar_hover_color, self._appearance_mode))
def on_leave(self, event=0):
self.hover_state = False
self.canvas.itemconfig("scrollbar_parts",
outline=ThemeManager.single_color(self.scrollbar_color, self._appearance_mode),
fill=ThemeManager.single_color(self.scrollbar_color, self._appearance_mode))
def clicked(self, event):
if self.orientation == "vertical":
value = ((event.y - self.border_spacing) / (self._current_height - 2 * self.border_spacing)) / self._widget_scaling
else:
value = ((event.x - self.border_spacing) / (self._current_width - 2 * self.border_spacing)) / self._widget_scaling
current_scrollbar_length = self.end_value - self.start_value
value = max(current_scrollbar_length / 2, min(value, 1 - (current_scrollbar_length / 2)))
self.start_value = value - (current_scrollbar_length / 2)
self.end_value = value + (current_scrollbar_length / 2)
self.draw()
if self.command is not None:
self.command('moveto', self.start_value)
def mouse_scroll_event(self, event=None):
if self.command is not None:
if sys.platform.startswith("win"):
self.command('scroll', -int(event.delta/40), 'units')
else:
self.command('scroll', -event.delta, 'units')

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
@ -60,7 +61,7 @@ class CTkSlider(CTkBaseClass):
self.border_width = ThemeManager.theme["shape"]["slider_border_width"] if border_width == "default_theme" else border_width self.border_width = ThemeManager.theme["shape"]["slider_border_width"] if border_width == "default_theme" else border_width
self.button_length = ThemeManager.theme["shape"]["slider_button_length"] if button_length == "default_theme" else button_length self.button_length = ThemeManager.theme["shape"]["slider_button_length"] if button_length == "default_theme" else button_length
self.value = 0.5 # initial value of slider in percent self.value = 0.5 # initial value of slider in percent
self.orient = orient self.orientation = orient
self.hover_state = False self.hover_state = False
self.from_ = from_ self.from_ = from_
self.to = to self.to = to
@ -71,18 +72,19 @@ class CTkSlider(CTkBaseClass):
self.corner_radius = self.button_corner_radius self.corner_radius = self.button_corner_radius
# callback and control variables # callback and control variables
self.callback_function = command self.command = command
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,14 +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() self.draw()
def set_dimensions(self, width=None, height=None): def set_dimensions(self, width=None, height=None):
super().set_dimensions(width, height) super().set_dimensions(width, height)
self.canvas.configure(width=self.apply_widget_scaling(self.desired_width), self.canvas.configure(width=self.apply_widget_scaling(self._desired_width),
height=self.apply_widget_scaling(self.desired_height)) height=self.apply_widget_scaling(self._desired_height))
self.draw() self.draw()
def destroy(self): def destroy(self):
@ -124,22 +126,28 @@ 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.orientation.lower() == "horizontal":
orientation = "w" orientation = "w"
elif self.orient.lower() == "vertical": elif self.orientation.lower() == "vertical":
orientation = "s" orientation = "s"
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),
@ -147,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.orientation.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.command is not None:
self.variable_callback_blocked = True self.command(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:
@ -231,9 +249,6 @@ class CTkSlider(CTkBaseClass):
self.draw(no_color_updates=False) self.draw(no_color_updates=False)
if self.callback_function is not None:
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
self.variable.set(round(self.output_value) if isinstance(self.variable, tkinter.IntVar) else self.output_value) self.variable.set(round(self.output_value) if isinstance(self.variable, tkinter.IntVar) else self.output_value)
@ -243,22 +258,18 @@ class CTkSlider(CTkBaseClass):
if not self.variable_callback_blocked: if not self.variable_callback_blocked:
self.set(self.variable.get(), from_variable_callback=True) self.set(self.variable.get(), from_variable_callback=True)
def configure(self, *args, **kwargs): def configure(self, require_redraw=False, **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.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
del kwargs["fg_color"] 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 "progress_color" in kwargs: if "progress_color" in kwargs:
if kwargs["progress_color"] is None: if kwargs["progress_color"] is None:
self.progress_color = self.fg_color self.progress_color = self.fg_color
@ -300,7 +311,7 @@ class CTkSlider(CTkBaseClass):
del kwargs["number_of_steps"] del kwargs["number_of_steps"]
if "command" in kwargs: if "command" in kwargs:
self.callback_function = kwargs["command"] self.command = kwargs["command"]
del kwargs["command"] del kwargs["command"]
if "variable" in kwargs: if "variable" in kwargs:
@ -325,7 +336,4 @@ class CTkSlider(CTkBaseClass):
self.set_dimensions(height=kwargs["height"]) self.set_dimensions(height=kwargs["height"])
del kwargs["height"] del kwargs["height"]
super().configure(*args, **kwargs) super().configure(require_redraw=require_redraw, **kwargs)
if require_redraw:
self.draw()

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
@ -44,7 +44,7 @@ class CTkSwitch(CTkBaseClass):
self.button_color = ThemeManager.theme["color"]["switch_button"] if button_color == "default_theme" else button_color self.button_color = ThemeManager.theme["color"]["switch_button"] if button_color == "default_theme" else button_color
self.button_hover_color = ThemeManager.theme["color"]["switch_button_hover"] if button_hover_color == "default_theme" else button_hover_color self.button_hover_color = ThemeManager.theme["color"]["switch_button_hover"] if button_hover_color == "default_theme" else button_hover_color
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_button_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
# text # text
self.text = text self.text = text
@ -62,11 +62,8 @@ class CTkSwitch(CTkBaseClass):
self.onvalue = onvalue self.onvalue = onvalue
self.offvalue = offvalue self.offvalue = offvalue
# if self.corner_radius < self.button_corner_radius:
# self.corner_radius = self.button_corner_radius
# callback and control variables # callback and control variables
self.callback_function = command self.command = command
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
@ -79,14 +76,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,24 +91,34 @@ 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.text_label = tkinter.Label(master=self,
bd=0,
text=self.text,
justify=tkinter.LEFT,
font=self.apply_font_scaling(self.text_font),
textvariable=self.textvariable)
self.text_label.grid(row=0, column=2, padx=0, pady=0, sticky="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.variable is not None and self.variable != "":
self.variable_callback_name = self.variable.trace_add("write", self.variable_callback)
self.check_state = True if self.variable.get() == self.onvalue else False
self.draw() # initial draw self.draw() # initial draw
self.set_cursor() self.set_cursor()
if self.variable is not None:
self.variable_callback_name = self.variable.trace_add("write", self.variable_callback)
if self.variable.get() == self.onvalue:
self.select(from_variable_callback=True)
elif self.variable.get() == self.offvalue:
self.deselect(from_variable_callback=True)
def set_scaling(self, *args, **kwargs): def set_scaling(self, *args, **kwargs):
super().set_scaling(*args, **kwargs) super().set_scaling(*args, **kwargs)
self.grid_columnconfigure(1, weight=0, minsize=self.apply_widget_scaling(6)) self.grid_columnconfigure(1, weight=0, minsize=self.apply_widget_scaling(6))
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):
@ -146,16 +153,16 @@ class CTkSwitch(CTkBaseClass):
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),
@ -163,58 +170,35 @@ 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:
self.text_label = tkinter.Label(master=self,
bd=0,
text=self.text,
justify=tkinter.LEFT,
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["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:
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)
def set_text(self, text):
self.text = text
if self.text_label is not None:
self.text_label.configure(text=self.text)
def toggle(self, event=None): def toggle(self, event=None):
if self.state is not tkinter.DISABLED: if self.state is not tkinter.DISABLED:
@ -225,42 +209,42 @@ 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: if self.variable is not None:
self.variable_callback_blocked = True self.variable_callback_blocked = True
self.variable.set(self.onvalue if self.check_state is True else self.offvalue) self.variable.set(self.onvalue if self.check_state is True else self.offvalue)
self.variable_callback_blocked = False self.variable_callback_blocked = False
if self.command is not None:
self.command()
def select(self, from_variable_callback=False): def select(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 = True self.check_state = True
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.command is not None:
self.command()
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.command is not None:
self.command()
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
@ -268,13 +252,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:
@ -283,82 +267,64 @@ class CTkSwitch(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)
def configure(self, *args, **kwargs): def configure(self, require_redraw=False, **kwargs):
require_redraw = False # some attribute changes require a call of self.draw() at the end if "text" in kwargs:
self.text = kwargs.pop("text")
self.text_label.configure(text=self.text)
if "text_font" in kwargs:
self.text_font = kwargs.pop("text_font")
self.text_label.configure(font=self.apply_font_scaling(self.text_font))
if "state" in kwargs: if "state" in kwargs:
self.state = kwargs["state"] self.state = kwargs.pop("state")
self.set_cursor() self.set_cursor()
require_redraw = True 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.pop("fg_color")
require_redraw = True 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 "progress_color" in kwargs: if "progress_color" in kwargs:
if kwargs["progress_color"] is None: new_progress_color = kwargs.pop("progress_color")
if new_progress_color is None:
self.progress_color = self.fg_color self.progress_color = self.fg_color
else: else:
self.progress_color = kwargs["progress_color"] self.progress_color = new_progress_color
require_redraw = True require_redraw = True
del kwargs["progress_color"]
if "button_color" in kwargs: if "button_color" in kwargs:
self.button_color = kwargs["button_color"] self.button_color = kwargs.pop("button_color")
require_redraw = True require_redraw = True
del kwargs["button_color"]
if "button_hover_color" in kwargs: if "button_hover_color" in kwargs:
self.button_hover_color = kwargs["button_hover_color"] self.button_hover_color = kwargs.pop("button_hover_color")
require_redraw = True require_redraw = True
del kwargs["button_hover_color"]
if "border_color" in kwargs: if "border_color" in kwargs:
self.border_color = kwargs["border_color"] self.border_color = kwargs.pop("border_color")
require_redraw = True require_redraw = True
del kwargs["border_color"]
if "border_width" in kwargs: if "border_width" in kwargs:
self.border_width = kwargs["border_width"] self.border_width = kwargs.pop("border_width")
require_redraw = True require_redraw = True
del kwargs["border_width"]
if "command" in kwargs: if "command" in kwargs:
self.callback_function = kwargs["command"] self.command = kwargs.pop("command")
del kwargs["command"]
if "textvariable" in kwargs: if "textvariable" in kwargs:
self.text_label.configure(textvariable=kwargs["textvariable"]) self.textvariable = kwargs.pop("textvariable")
del kwargs["textvariable"] self.text_label.configure(textvariable=self.textvariable)
if "variable" in kwargs: if "variable" in kwargs:
if self.variable is not None: if self.variable is not None and self.variable != "":
self.variable.trace_remove("write", self.variable_callback_name) self.variable.trace_remove("write", self.variable_callback_name)
self.variable = kwargs["variable"] self.variable = kwargs.pop("variable")
if self.variable is not None and self.variable != "": if self.variable is not None and self.variable != "":
self.variable_callback_name = self.variable.trace_add("write", self.variable_callback) self.variable_callback_name = self.variable.trace_add("write", self.variable_callback)
if self.variable.get() == self.onvalue: self.check_state = True if self.variable.get() == self.onvalue else False
self.select(from_variable_callback=True) require_redraw = True
elif self.variable.get() == self.offvalue:
self.deselect(from_variable_callback=True)
else:
self.variable = None
del kwargs["variable"] super().configure(require_redraw=require_redraw, **kwargs)
super().configure(*args, **kwargs)
if require_redraw:
self.draw()

View File

@ -0,0 +1,176 @@
import tkinter
from .ctk_canvas import CTkCanvas
from ..theme_manager import ThemeManager
from ..draw_engine import DrawEngine
from .widget_base_class import CTkBaseClass
class CTkTextbox(CTkBaseClass):
def __init__(self, *args,
bg_color=None,
fg_color="default_theme",
border_color="default_theme",
border_width="default_theme",
corner_radius="default_theme",
text_font="default_theme",
text_color="default_theme",
width=200,
height=200,
**kwargs):
# transfer basic functionality (bg_color, size, _appearance_mode, scaling) to CTkBaseClass
if "master" in kwargs:
super().__init__(*args, bg_color=bg_color, width=width, height=height, master=kwargs.pop("master"))
else:
super().__init__(*args, bg_color=bg_color, width=width, height=height)
# color
self.fg_color = ThemeManager.theme["color"]["entry"] if fg_color == "default_theme" else fg_color
self.border_color = ThemeManager.theme["color"]["frame_border"] if border_color == "default_theme" else border_color
self.text_color = ThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color
# shape
self.corner_radius = ThemeManager.theme["shape"]["frame_corner_radius"] if corner_radius == "default_theme" else corner_radius
self.border_width = ThemeManager.theme["shape"]["frame_border_width"] if border_width == "default_theme" else border_width
# text
self.text_font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font
# configure 1x1 grid
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._current_width),
height=self.apply_widget_scaling(self._current_height))
self.canvas.grid(row=0, column=0, padx=0, pady=0, rowspan=1, columnspan=1, sticky="nsew")
self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
self.draw_engine = DrawEngine(self.canvas)
for arg in ["highlightthickness", "fg", "bg", "font", "width", "height"]:
kwargs.pop(arg, None)
self.textbox = tkinter.Text(self,
fg=ThemeManager.single_color(self.text_color, self._appearance_mode),
width=0,
height=0,
font=self.text_font,
highlightthickness=0,
relief="flat",
insertbackground=ThemeManager.single_color(("black", "white"), self._appearance_mode),
bg=ThemeManager.single_color(self.fg_color, self._appearance_mode),
**kwargs)
self.textbox.grid(row=0, column=0, padx=self.corner_radius, pady=self.corner_radius, rowspan=1, columnspan=1, sticky="nsew")
self.bind('<Configure>', self.update_dimensions_event)
self.draw()
def set_scaling(self, *args, **kwargs):
super().set_scaling(*args, **kwargs)
self.textbox.configure(font=self.apply_font_scaling(self.text_font))
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()
def draw(self, no_color_updates=False):
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.corner_radius),
self.apply_widget_scaling(self.border_width))
if no_color_updates is False or requires_recoloring:
if self.fg_color is None:
self.canvas.itemconfig("inner_parts",
fill=ThemeManager.single_color(self.bg_color, self._appearance_mode),
outline=ThemeManager.single_color(self.bg_color, self._appearance_mode))
else:
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))
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))
self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
self.textbox.configure(fg=ThemeManager.single_color(self.text_color, self._appearance_mode),
bg=ThemeManager.single_color(self.fg_color, self._appearance_mode),
insertbackground=ThemeManager.single_color(("black", "white"), self._appearance_mode))
self.canvas.tag_lower("inner_parts")
self.canvas.tag_lower("border_parts")
def yview(self, *args):
return self.textbox.yview(*args)
def xview(self, *args):
return self.textbox.xview(*args)
def insert(self, *args, **kwargs):
return self.textbox.insert(*args, **kwargs)
def focus(self):
return self.textbox.focus()
def tag_add(self, *args, **kwargs):
return self.textbox.tag_add(*args, **kwargs)
def tag_config(self, *args, **kwargs):
return self.textbox.tag_config(*args, **kwargs)
def tag_configure(self, *args, **kwargs):
return self.textbox.tag_configure(*args, **kwargs)
def tag_remove(self, *args, **kwargs):
return self.textbox.tag_remove(*args, **kwargs)
def configure(self, require_redraw=False, **kwargs):
if "fg_color" in kwargs:
self.fg_color = kwargs.pop("fg_color")
require_redraw = True
# check if CTk widgets are children of the frame and change their bg_color to new frame fg_color
for child in self.winfo_children():
if isinstance(child, CTkBaseClass):
child.configure(bg_color=self.fg_color)
if "border_color" in kwargs:
self.border_color = kwargs.pop("border_color")
require_redraw = True
if "corner_radius" in kwargs:
self.corner_radius = kwargs.pop("corner_radius")
require_redraw = True
if "border_width" in kwargs:
self.border_width = kwargs.pop("border_width")
require_redraw = True
if "width" in kwargs:
self.set_dimensions(width=kwargs.pop("width"))
if "height" in kwargs:
self.set_dimensions(height=kwargs.pop("height"))
if "text_font" in kwargs:
self.text_font = kwargs.pop("text_font")
self.textbox.configure(font=self.apply_font_scaling(self.text_font))
if "font" in kwargs:
raise ValueError("No attribute named font. Use text_font instead of font for CTk widgets")
if "bg_color" in kwargs:
super().configure(bg_color=kwargs.pop("bg_color"), require_redraw=require_redraw)
else:
super().configure(require_redraw=require_redraw)
self.textbox.configure(**kwargs)

View File

@ -1,128 +1,170 @@
import customtkinter
import tkinter import tkinter
import sys import sys
import copy
import re
from typing import Union
from ..theme_manager import ThemeManager from ..theme_manager import ThemeManager
from ..appearance_mode_tracker import AppearanceModeTracker from ..appearance_mode_tracker import AppearanceModeTracker
from ..scaling_tracker import ScalingTracker from ..scaling_tracker import ScalingTracker
class DropdownMenu(tkinter.Toplevel): class DropdownMenu(tkinter.Menu):
def __init__(self, *args, def __init__(self, *args,
fg_color="#555555", min_character_width=18,
button_color="gray50", fg_color="default_theme",
button_hover_color="gray35", hover_color="default_theme",
text_color="black", text_color="default_theme",
corner_radius=6, text_font="default_theme",
button_corner_radius=3,
width=120,
button_height=24,
x_position=0,
y_position=0,
x_spacing=3,
y_spacing=3,
command=None, command=None,
values=None, values=None,
**kwargs): **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
ScalingTracker.add_widget(self.set_scaling, self) ScalingTracker.add_widget(self.set_scaling, self)
self.widget_scaling = ScalingTracker.get_widget_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.values = values
self.command = command self.command = command
# color self.add_menu_commands()
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
self.fg_color = fg_color
self.button_color = button_color
self.button_hover_color = button_hover_color
self.text_color = text_color
# shape def configure_menu_for_platforms(self):
self.width = width """ apply platform specific appearance attributes """
self.corner_radius = corner_radius
self.button_corner_radius = button_corner_radius
self.button_height = button_height
self.geometry(f"{round(self.apply_widget_scaling(self.width))}x" + if sys.platform == "darwin":
f"{round(self.apply_widget_scaling(len(self.values) * (self.button_height + y_spacing) + y_spacing))}+" + self.configure(tearoff=False,
f"{round(x_position)}+{round(y_position)}") font=self.apply_font_scaling(self.text_font))
self.grid_columnconfigure(0, weight=1)
if sys.platform.startswith("darwin"):
self.overrideredirect(True) # remove title-bar
self.overrideredirect(False)
self.wm_attributes("-transparent", True) # turn off window shadow
self.config(bg='systemTransparent') # transparent bg
self.frame = customtkinter.CTkFrame(self,
border_width=0,
width=self.width,
corner_radius=self.corner_radius,
fg_color=ThemeManager.single_color(self.fg_color, self.appearance_mode))
elif sys.platform.startswith("win"): elif sys.platform.startswith("win"):
self.overrideredirect(True) # remove title-bar self.configure(tearoff=False,
self.configure(bg="#010302") relief="flat",
self.wm_attributes("-transparentcolor", "#010302") activebackground=ThemeManager.single_color(self.hover_color, self._appearance_mode),
self.focus() borderwidth=0,
self.frame = customtkinter.CTkFrame(self, activeborderwidth=self.apply_widget_scaling(4),
border_width=0, bg=ThemeManager.single_color(self.fg_color, self._appearance_mode),
width=self.width, fg=ThemeManager.single_color(self.text_color, self._appearance_mode),
corner_radius=self.corner_radius, activeforeground=ThemeManager.single_color(self.text_color, self._appearance_mode),
fg_color=self.fg_color, overwrite_preferred_drawing_method="circle_shapes") font=self.apply_font_scaling(self.text_font),
cursor="hand2")
else: else:
self.overrideredirect(True) # remove title-bar self.configure(tearoff=False,
self.configure(bg="#010302") relief="flat",
self.wm_attributes("-transparentcolor", "#010302") activebackground=ThemeManager.single_color(self.hover_color, self._appearance_mode),
self.frame = customtkinter.CTkFrame(self, borderwidth=0,
border_width=0, activeborderwidth=0,
width=self.width, bg=ThemeManager.single_color(self.fg_color, self._appearance_mode),
corner_radius=self.corner_radius, fg=ThemeManager.single_color(self.text_color, self._appearance_mode),
fg_color=self.fg_color, overwrite_preferred_drawing_method="circle_shapes") activeforeground=ThemeManager.single_color(self.text_color, self._appearance_mode),
font=self.apply_font_scaling(self.text_font))
self.frame.grid(row=0, column=0, sticky="nsew", rowspan=len(self.values) + 1) def add_menu_commands(self):
self.frame.grid_rowconfigure(len(self.values) + 1, minsize=y_spacing) # add spacing at the bottom if sys.platform.startswith("linux"):
self.frame.grid_columnconfigure(0, weight=1) 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")
self.button_list = [] def open(self, x: Union[int, float], y: Union[int, float]):
for index, option in enumerate(self.values): if sys.platform == "darwin":
button = customtkinter.CTkButton(self.frame, y += self.apply_widget_scaling(8)
text=option, else:
height=self.button_height, y += self.apply_widget_scaling(3)
width=self.width - 2 * x_spacing,
fg_color=self.button_color,
text_color=self.text_color,
hover_color=self.button_hover_color,
corner_radius=self.button_corner_radius,
command=lambda i=index: self.button_callback(i))
button.text_label.grid(row=0, column=0, rowspan=2, columnspan=2, sticky="w")
button.grid(row=index, column=0,
padx=x_spacing,
pady=(y_spacing, 0), sticky="ew")
self.button_list.append(button)
self.bind("<FocusOut>", self.focus_loss_event) if sys.platform == "darwin" or sys.platform.startswith("win"):
self.frame.canvas.bind("<Button-1>", self.focus_loss_event) self.post(int(x), int(y))
else: # Linux
self.tk_popup(int(x), int(y))
def apply_widget_scaling(self, value): 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.pop("values")
self.delete(0, "end") # delete all old commands
self.add_menu_commands()
if "fg_color" in kwargs:
self.fg_color = kwargs.pop("fg_color")
self.configure(bg=ThemeManager.single_color(self.fg_color, self._appearance_mode))
if "hover_color" in kwargs:
self.hover_color = kwargs.pop("hover_color")
self.configure(activebackground=ThemeManager.single_color(self.hover_color, self._appearance_mode))
if "text_color" in kwargs:
self.text_color = kwargs.pop("text_color")
self.configure(fg=ThemeManager.single_color(self.text_color, self._appearance_mode))
if "text_font" in kwargs:
self.text_font = kwargs.pop("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)): if isinstance(value, (int, float)):
return value * self.widget_scaling return value * self._widget_scaling
else: else:
return value 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): def set_scaling(self, new_widget_scaling, new_spacing_scaling, new_window_scaling):
return self._widget_scaling = new_widget_scaling
self._spacing_scaling = new_spacing_scaling
def focus_loss_event(self, event): self.configure(font=self.apply_font_scaling(self.text_font))
self.destroy()
if sys.platform.startswith("darwin"):
self.update()
def button_callback(self, index): if sys.platform.startswith("win"):
self.destroy() self.configure(activeborderwidth=self.apply_widget_scaling(4))
if sys.platform.startswith("darwin"):
self.update()
if self.command is not None: def set_appearance_mode(self, mode_string):
self.command(self.values[index]) """ 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

@ -17,38 +17,49 @@ 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
@ -74,15 +85,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:
@ -106,32 +117,27 @@ class CTkBaseClass(tkinter.Frame):
return scaled_kwargs return scaled_kwargs
def config(self, *args, **kwargs): def configure(self, require_redraw=False, **kwargs):
self.configure(*args, **kwargs)
def configure(self, *args, **kwargs):
""" basic configure with bg_color support, to be overridden """ """ basic configure with bg_color support, to be overridden """
require_redraw = False
if "bg_color" in kwargs: if "bg_color" in kwargs:
if kwargs["bg_color"] is None: new_bg_color = kwargs.pop("bg_color")
if new_bg_color is None:
self.bg_color = self.detect_color_of_master() self.bg_color = self.detect_color_of_master()
else: else:
self.bg_color = kwargs["bg_color"] self.bg_color = new_bg_color
require_redraw = True require_redraw = True
del kwargs["bg_color"]
super().configure(*args, **kwargs) super().configure(**kwargs)
if require_redraw: if require_redraw:
self.draw() self.draw()
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), independent of scaling
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
@ -141,15 +147,15 @@ class CTkBaseClass(tkinter.Frame):
if master_widget is None: if master_widget is None:
master_widget = self.master master_widget = self.master
if isinstance(master_widget, CTkBaseClass) and hasattr(master_widget, "fg_color"): # master is CTkFrame if isinstance(master_widget, (CTkBaseClass, CTk, CTkToplevel)) and hasattr(master_widget, "fg_color"):
if master_widget.fg_color is not None: if master_widget.fg_color is not None:
return master_widget.fg_color return master_widget.fg_color
# if fg_color of master is None, try to retrieve fg_color from master of master # if fg_color of master is None, try to retrieve fg_color from master of master
elif hasattr(master_widget.master, "master"): elif hasattr(master_widget.master, "master"):
return self.detect_color_of_master(self.master.master) return self.detect_color_of_master(master_widget.master)
elif isinstance(master_widget, (ttk.Frame, ttk.LabelFrame, ttk.Notebook)): # master is ttk widget elif isinstance(master_widget, (ttk.Frame, ttk.LabelFrame, ttk.Notebook, ttk.Label)): # master is ttk widget
try: try:
ttk_style = ttk.Style() ttk_style = ttk.Style()
return ttk_style.lookup(master_widget.winfo_class(), 'background') return ttk_style.lookup(master_widget.winfo_class(), 'background')
@ -164,45 +170,40 @@ class CTkBaseClass(tkinter.Frame):
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"):
self.bg_color = self.master.fg_color
else:
self.bg_color = self.master.cget("bg")
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 set_dimensions(self, width=None, height=None): def set_dimensions(self, width=None, height=None):
if width is not None: if width is not None:
self.desired_width = width self._desired_width = width
if height is not None: if height is not None:
self.desired_height = height self._desired_height = height
super().configure(width=self.apply_widget_scaling(self.desired_width), super().configure(width=self.apply_widget_scaling(self._desired_width),
height=self.apply_widget_scaling(self.desired_height)) height=self.apply_widget_scaling(self._desired_height))
def apply_widget_scaling(self, value): 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
@ -211,25 +212,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

@ -42,6 +42,8 @@ class CTkInputDialog:
self.top.focus_force() self.top.focus_force()
self.top.grab_set() self.top.grab_set()
self.top.protocol("WM_DELETE_WINDOW", self.on_closing)
self.top.after(10, self.create_widgets) # create widgets with slight delay, to avoid white flickering of background self.top.after(10, self.create_widgets) # create widgets with slight delay, to avoid white flickering of background
def create_widgets(self): def create_widgets(self):
@ -95,6 +97,9 @@ class CTkInputDialog:
self.user_input = self.entry.get() self.user_input = self.entry.get()
self.running = False self.running = False
def on_closing(self):
self.running = False
def cancel_event(self): def cancel_event(self):
self.running = False self.running = False
@ -102,8 +107,12 @@ class CTkInputDialog:
self.running = True self.running = True
while self.running: while self.running:
self.top.update() try:
time.sleep(0.01) self.top.update()
except Exception:
return self.user_input
finally:
time.sleep(0.01)
time.sleep(0.05) time.sleep(0.05)
self.top.destroy() self.top.destroy()

View File

@ -52,7 +52,10 @@ class CTk(tkinter.Tk):
super().title("CTk") super().title("CTk")
self.geometry(f"{self.current_width}x{self.current_height}") self.geometry(f"{self.current_width}x{self.current_height}")
self.window_exists = False # indicates if the window is already shown through .update or .mainloop self.state_before_windows_set_titlebar_color = None
self.window_exists = False # indicates if the window is already shown through update() or mainloop() after init
self.withdraw_called_before_window_exists = False # indicates if withdraw() was called before window is first shown through update() or mainloop()
self.iconify_called_before_window_exists = False # indicates if iconify() was called before window is first shown through update() or mainloop()
if sys.platform.startswith("win"): if sys.platform.startswith("win"):
if self.appearance_mode == 1: if self.appearance_mode == 1:
@ -62,17 +65,23 @@ class CTk(tkinter.Tk):
self.bind('<Configure>', self.update_dimensions_event) self.bind('<Configure>', self.update_dimensions_event)
def update_dimensions_event(self, event=None): self.block_update_dimensions_event = False
detected_width = self.winfo_width() # detect current window size
detected_height = self.winfo_height()
if self.current_width != round(detected_width / self.window_scaling) or self.current_height != round(detected_height / self.window_scaling): def update_dimensions_event(self, event=None):
self.current_width = round(detected_width / self.window_scaling) # adjust current size according to new size given by event if not self.block_update_dimensions_event:
self.current_height = round(detected_height / self.window_scaling) # current_width and current_height are independent of the scale detected_width = self.winfo_width() # detect current window size
detected_height = self.winfo_height()
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_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
# block update_dimensions_event to prevent current_width and current_height to get updated
self.block_update_dimensions_event = True
# force new dimensions on window by using min, max, and geometry # force new dimensions on window by using min, max, and geometry
super().minsize(self.apply_window_scaling(self.current_width), self.apply_window_scaling(self.current_height)) super().minsize(self.apply_window_scaling(self.current_width), self.apply_window_scaling(self.current_height))
super().maxsize(self.apply_window_scaling(self.current_width), self.apply_window_scaling(self.current_height)) super().maxsize(self.apply_window_scaling(self.current_width), self.apply_window_scaling(self.current_height))
@ -81,6 +90,11 @@ class CTk(tkinter.Tk):
# set new scaled min and max with 400ms delay (otherwise it won't work for some reason) # set new scaled min and max with 400ms delay (otherwise it won't work for some reason)
self.after(400, self.set_scaled_min_max) self.after(400, self.set_scaled_min_max)
# release the blocking of update_dimensions_event after a small amount of time (slight delay is necessary)
def set_block_update_dimensions_event_false():
self.block_update_dimensions_event = False
self.after(100, lambda: set_block_update_dimensions_event_false())
def set_scaled_min_max(self): def set_scaled_min_max(self):
if self.min_width is not None or self.min_height is not None: if self.min_width is not None or self.min_height is not None:
super().minsize(self.apply_window_scaling(self.min_width), self.apply_window_scaling(self.min_height)) super().minsize(self.apply_window_scaling(self.min_width), self.apply_window_scaling(self.min_height))
@ -93,16 +107,36 @@ class CTk(tkinter.Tk):
self.disable_macos_dark_title_bar() self.disable_macos_dark_title_bar()
super().destroy() super().destroy()
def withdraw(self):
if self.window_exists is False:
self.withdraw_called_before_window_exists = True
super().withdraw()
def iconify(self):
if self.window_exists is False:
self.iconify_called_before_window_exists = True
super().iconify()
def update(self): def update(self):
if self.window_exists is False: if self.window_exists is False:
self.deiconify()
self.window_exists = True self.window_exists = True
if sys.platform.startswith("win"):
if not self.withdraw_called_before_window_exists and not self.iconify_called_before_window_exists:
# print("window dont exists -> deiconify in update")
self.deiconify()
super().update() super().update()
def mainloop(self, *args, **kwargs): def mainloop(self, *args, **kwargs):
if not self.window_exists: if not self.window_exists:
self.deiconify()
self.window_exists = True self.window_exists = True
if sys.platform.startswith("win"):
if not self.withdraw_called_before_window_exists and not self.iconify_called_before_window_exists:
# print("window dont exists -> deiconify in mainloop")
self.deiconify()
super().mainloop(*args, **kwargs) super().mainloop(*args, **kwargs)
def resizable(self, *args, **kwargs): def resizable(self, *args, **kwargs):
@ -129,16 +163,54 @@ class CTk(tkinter.Tk):
if self.current_height > height: self.current_height = height if self.current_height > height: self.current_height = height
super().maxsize(self.apply_window_scaling(self.max_width), self.apply_window_scaling(self.max_height)) super().maxsize(self.apply_window_scaling(self.max_width), self.apply_window_scaling(self.max_height))
def geometry(self, geometry_string): def geometry(self, geometry_string: str = None):
super().geometry(self.apply_geometry_scaling(geometry_string)) if geometry_string is not None:
super().geometry(self.apply_geometry_scaling(geometry_string))
# update width and height attributes # update width and height attributes
numbers = list(map(int, re.split(r"[x+]", geometry_string))) # split geometry string into list of numbers width, height, x, y = self.parse_geometry_string(geometry_string)
self.current_width = max(self.min_width, min(numbers[0], self.max_width)) # bound value between min and max if width is not None and height is not None:
self.current_height = max(self.min_height, min(numbers[1], self.max_height)) self.current_width = max(self.min_width, min(width, self.max_width)) # bound value between min and max
self.current_height = max(self.min_height, min(height, self.max_height))
else:
return self.reverse_geometry_scaling(super().geometry())
def apply_geometry_scaling(self, geometry_string): @staticmethod
return re.sub(re.compile("\d+"), lambda match_obj: str(round(int(match_obj.group(0)) * self.window_scaling)), geometry_string) def parse_geometry_string(geometry_string: str) -> tuple:
# index: 1 2 3 4 5 6
# regex group structure: ('<width>x<height>', '<width>', '<height>', '+-<x>+-<y>', '-<x>', '-<y>')
result = re.search(r"((\d+)x(\d+)){0,1}(\+{0,1}([+-]{0,1}\d+)\+{0,1}([+-]{0,1}\d+)){0,1}", geometry_string)
width = int(result.group(2)) if result.group(2) is not None else None
height = int(result.group(3)) if result.group(3) is not None else None
x = int(result.group(5)) if result.group(5) is not None else None
y = int(result.group(6)) if result.group(6) is not None else None
return width, height, x, y
def apply_geometry_scaling(self, geometry_string: str) -> str:
width, height, x, y = self.parse_geometry_string(geometry_string)
if x is None and y is None: # no <x> and <y> in geometry_string
return f"{round(width * self.window_scaling)}x{round(height * self.window_scaling)}"
elif width is None and height is None: # no <width> and <height> in geometry_string
return f"+{x}+{y}"
else:
return f"{round(width * self.window_scaling)}x{round(height * self.window_scaling)}+{x}+{y}"
def reverse_geometry_scaling(self, scaled_geometry_string: str) -> str:
width, height, x, y = self.parse_geometry_string(scaled_geometry_string)
if x is None and y is None: # no <x> and <y> in geometry_string
return f"{round(width / self.window_scaling)}x{round(height / self.window_scaling)}"
elif width is None and height is None: # no <width> and <height> in geometry_string
return f"+{x}+{y}"
else:
return f"{round(width / self.window_scaling)}x{round(height / self.window_scaling)}+{x}+{y}"
def apply_window_scaling(self, value): def apply_window_scaling(self, value):
if isinstance(value, (int, float)): if isinstance(value, (int, float)):
@ -214,8 +286,15 @@ class CTk(tkinter.Tk):
if sys.platform.startswith("win") and not Settings.deactivate_windows_window_header_manipulation: if sys.platform.startswith("win") and not Settings.deactivate_windows_window_header_manipulation:
super().withdraw() # hide window so that it can be redrawn after the titlebar change so that the color change is visible if self.window_exists:
if not self.window_exists: self.state_before_windows_set_titlebar_color = self.state()
# print("window_exists -> state_before_windows_set_titlebar_color: ", self.state_before_windows_set_titlebar_color)
if self.state_before_windows_set_titlebar_color != "iconic" or self.state_before_windows_set_titlebar_color != "withdrawn":
super().withdraw() # hide window so that it can be redrawn after the titlebar change so that the color change is visible
else:
# print("window dont exists -> withdraw and update")
super().withdraw()
super().update() super().update()
if color_mode.lower() == "dark": if color_mode.lower() == "dark":
@ -244,7 +323,17 @@ class CTk(tkinter.Tk):
print(err) print(err)
if self.window_exists: if self.window_exists:
self.deiconify() # print("window_exists -> return to original state: ", self.state_before_windows_set_titlebar_color)
if self.state_before_windows_set_titlebar_color == "normal":
self.deiconify()
elif self.state_before_windows_set_titlebar_color == "iconic":
self.iconify()
elif self.state_before_windows_set_titlebar_color == "zoomed":
self.state("zoomed")
else:
self.state(self.state_before_windows_set_titlebar_color) # other states
else:
pass # wait for update or mainloop to be called
def set_appearance_mode(self, mode_string): def set_appearance_mode(self, mode_string):
if mode_string.lower() == "dark": if mode_string.lower() == "dark":

View File

@ -47,7 +47,11 @@ 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.state_before_windows_set_titlebar_color = None
self.windows_set_titlebar_color_called = False # indicates if windows_set_titlebar_color was called, stays True until revert_withdraw_after_windows_set_titlebar_color is called
self.withdraw_called_after_windows_set_titlebar_color = False # indicates if withdraw() was called after windows_set_titlebar_color
self.iconify_called_after_windows_set_titlebar_color = False # indicates if iconify() was called after windows_set_titlebar_color
if sys.platform.startswith("win"): if sys.platform.startswith("win"):
if self.appearance_mode == 1: if self.appearance_mode == 1:
@ -63,7 +67,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
@ -83,19 +87,54 @@ class CTkToplevel(tkinter.Toplevel):
if self.max_width is not None or self.max_height is not None: if self.max_width is not None or self.max_height is not None:
super().maxsize(self.apply_window_scaling(self.max_width), self.apply_window_scaling(self.max_height)) super().maxsize(self.apply_window_scaling(self.max_width), self.apply_window_scaling(self.max_height))
def apply_geometry_scaling(self, geometry_string): def geometry(self, geometry_string: str = None):
numbers = list(map(int, re.split(r"[x+]", geometry_string))) # split geometry string into list of numbers if geometry_string is not None:
super().geometry(self.apply_geometry_scaling(geometry_string))
if len(numbers) == 2: # update width and height attributes
return f"{self.apply_window_scaling(numbers[0]):.0f}x" +\ width, height, x, y = self.parse_geometry_string(geometry_string)
f"{self.apply_window_scaling(numbers[1]):.0f}" if width is not None and height is not None:
elif len(numbers) == 4: self.current_width = max(self.min_width, min(width, self.max_width)) # bound value between min and max
return f"{self.apply_window_scaling(numbers[0]):.0f}x" +\ self.current_height = max(self.min_height, min(height, self.max_height))
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: else:
return geometry_string return self.reverse_geometry_scaling(super().geometry())
@staticmethod
def parse_geometry_string(geometry_string: str) -> tuple:
# index: 1 2 3 4 5 6
# regex group structure: ('<width>x<height>', '<width>', '<height>', '+-<x>+-<y>', '-<x>', '-<y>')
result = re.search(r"((\d+)x(\d+)){0,1}(\+{0,1}([+-]{0,1}\d+)\+{0,1}([+-]{0,1}\d+)){0,1}", geometry_string)
width = int(result.group(2)) if result.group(2) is not None else None
height = int(result.group(3)) if result.group(3) is not None else None
x = int(result.group(5)) if result.group(5) is not None else None
y = int(result.group(6)) if result.group(6) is not None else None
return width, height, x, y
def apply_geometry_scaling(self, geometry_string: str) -> str:
width, height, x, y = self.parse_geometry_string(geometry_string)
if x is None and y is None: # no <x> and <y> in geometry_string
return f"{round(width * self.window_scaling)}x{round(height * self.window_scaling)}"
elif width is None and height is None: # no <width> and <height> in geometry_string
return f"+{x}+{y}"
else:
return f"{round(width * self.window_scaling)}x{round(height * self.window_scaling)}+{x}+{y}"
def reverse_geometry_scaling(self, scaled_geometry_string: str) -> str:
width, height, x, y = self.parse_geometry_string(scaled_geometry_string)
if x is None and y is None: # no <x> and <y> in geometry_string
return f"{round(width / self.window_scaling)}x{round(height / self.window_scaling)}"
elif width is None and height is None: # no <width> and <height> in geometry_string
return f"+{x}+{y}"
else:
return f"{round(width / self.window_scaling)}x{round(height / self.window_scaling)}+{x}+{y}"
def apply_window_scaling(self, value): def apply_window_scaling(self, value):
if isinstance(value, (int, float)): if isinstance(value, (int, float)):
@ -103,20 +142,22 @@ class CTkToplevel(tkinter.Toplevel):
else: else:
return value return value
def geometry(self, geometry_string):
super().geometry(self.apply_geometry_scaling(geometry_string))
# update width and height attributes
numbers = list(map(int, re.split(r"[x+]", geometry_string))) # split geometry string into list of numbers
self.current_width = max(self.min_width, min(numbers[0], self.max_width)) # bound value between min and max
self.current_height = max(self.min_height, min(numbers[1], self.max_height))
def destroy(self): def destroy(self):
AppearanceModeTracker.remove(self.set_appearance_mode) AppearanceModeTracker.remove(self.set_appearance_mode)
ScalingTracker.remove_window(self.set_scaling, self) ScalingTracker.remove_window(self.set_scaling, self)
self.disable_macos_dark_title_bar() self.disable_macos_dark_title_bar()
super().destroy() super().destroy()
def withdraw(self):
if self.windows_set_titlebar_color_called:
self.withdraw_called_after_windows_set_titlebar_color = True
super().withdraw()
def iconify(self):
if self.windows_set_titlebar_color_called:
self.iconify_called_after_windows_set_titlebar_color = True
super().iconify()
def resizable(self, *args, **kwargs): def resizable(self, *args, **kwargs):
super().resizable(*args, **kwargs) super().resizable(*args, **kwargs)
self.last_resizable_args = (args, kwargs) self.last_resizable_args = (args, kwargs)
@ -208,6 +249,7 @@ class CTkToplevel(tkinter.Toplevel):
if sys.platform.startswith("win") and not Settings.deactivate_windows_window_header_manipulation: if sys.platform.startswith("win") and not Settings.deactivate_windows_window_header_manipulation:
self.state_before_windows_set_titlebar_color = self.state()
super().withdraw() # hide window so that it can be redrawn after the titlebar change so that the color change is visible super().withdraw() # hide window so that it can be redrawn after the titlebar change so that the color change is visible
super().update() super().update()
@ -235,7 +277,30 @@ class CTkToplevel(tkinter.Toplevel):
except Exception as err: except Exception as err:
print(err) print(err)
self.deiconify() self.windows_set_titlebar_color_called = True
self.after(5, self.revert_withdraw_after_windows_set_titlebar_color)
def revert_withdraw_after_windows_set_titlebar_color(self):
""" if in a short time (5ms) after """
if self.windows_set_titlebar_color_called:
if self.withdraw_called_after_windows_set_titlebar_color:
pass # leave it withdrawed
elif self.iconify_called_after_windows_set_titlebar_color:
super().iconify()
else:
if self.state_before_windows_set_titlebar_color == "normal":
self.deiconify()
elif self.state_before_windows_set_titlebar_color == "iconic":
self.iconify()
elif self.state_before_windows_set_titlebar_color == "zoomed":
self.state("zoomed")
else:
self.state(self.state_before_windows_set_titlebar_color) # other states
self.windows_set_titlebar_color_called = False
self.withdraw_called_after_windows_set_titlebar_color = False
self.iconify_called_after_windows_set_titlebar_color = False
def set_appearance_mode(self, mode_string): def set_appearance_mode(self, mode_string):
if mode_string.lower() == "dark": if mode_string.lower() == "dark":

View File

@ -46,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 ============
@ -93,6 +90,7 @@ class App(customtkinter.CTk):
"amet consetetur sadipscing elitr,\n" + "amet consetetur sadipscing elitr,\n" +
"sed diam nonumy eirmod tempor" , "sed diam nonumy eirmod tempor" ,
height=100, height=100,
corner_radius=6, # <- custom corner radius
fg_color=("white", "gray38"), # <- custom tuple-color fg_color=("white", "gray38"), # <- custom tuple-color
justify=tkinter.LEFT) justify=tkinter.LEFT)
self.label_info_1.grid(column=0, row=0, sticky="nwe", padx=15, pady=15) self.label_info_1.grid(column=0, row=0, sticky="nwe", padx=15, pady=15)
@ -134,25 +132,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")
@ -169,16 +159,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()
@ -186,11 +180,8 @@ 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()

View File

@ -1,6 +1,5 @@
import tkinter
import customtkinter import customtkinter
from PIL import Image, ImageTk # <- import PIL for the images from PIL import Image, ImageTk
import os import os
PATH = os.path.dirname(os.path.realpath(__file__)) PATH = os.path.dirname(os.path.realpath(__file__))
@ -8,57 +7,61 @@ 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"
app = customtkinter.CTk() # create CTk window like you do with the Tk window (you can also use normal tkinter.Tk window)
app.geometry("450x260") class App(customtkinter.CTk):
app.title("CustomTkinter example_button_images.py") def __init__(self):
super().__init__()
self.geometry("450x260")
self.title("CustomTkinter example_button_images.py")
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1, minsize=200)
self.frame_1 = customtkinter.CTkFrame(master=self, width=250, height=240, corner_radius=15)
self.frame_1.grid(row=0, column=0, padx=20, pady=20, sticky="nsew")
self.frame_1.grid_columnconfigure(0, weight=1)
self.frame_1.grid_columnconfigure(1, weight=1)
self.settings_image = self.load_image("/test_images/settings.png", 20)
self.bell_image = self.load_image("/test_images/bell.png", 20)
self.add_folder_image = self.load_image("/test_images/add-folder.png", 20)
self.add_list_image = self.load_image("/test_images/add-folder.png", 20)
self.add_user_image = self.load_image("/test_images/add-user.png", 20)
self.chat_image = self.load_image("/test_images/chat.png", 20)
self.home_image = self.load_image("/test_images/home.png", 20)
self.button_1 = customtkinter.CTkButton(master=self.frame_1, image=self.add_folder_image, text="Add Folder", height=32,
compound="right", command=self.button_function)
self.button_1.grid(row=1, column=0, columnspan=2, padx=20, pady=(20, 10), sticky="ew")
self.button_2 = customtkinter.CTkButton(master=self.frame_1, image=self.add_list_image, text="Add Item", height=32,
compound="right", fg_color="#D35B58", hover_color="#C77C78",
command=self.button_function)
self.button_2.grid(row=2, column=0, columnspan=2, padx=20, pady=10, sticky="ew")
self.button_3 = customtkinter.CTkButton(master=self.frame_1, image=self.chat_image, text="", width=40, height=40,
corner_radius=10, fg_color="gray40", hover_color="gray25",
command=self.button_function)
self.button_3.grid(row=3, column=0, columnspan=1, padx=20, pady=10, sticky="w")
self.button_4 = customtkinter.CTkButton(master=self.frame_1, image=self.home_image, text="", width=40, height=40,
corner_radius=10, fg_color="gray40", hover_color="gray25",
command=self.button_function)
self.button_4.grid(row=3, column=1, columnspan=1, padx=20, pady=10, sticky="e")
self.button_5 = customtkinter.CTkButton(master=self, image=self.add_user_image, text="Add User", width=130, height=60, border_width=2,
corner_radius=10, compound="bottom", border_color="#D35B58", fg_color=("gray84", "gray25"),
hover_color="#C77C78", command=self.button_function)
self.button_5.grid(row=0, column=1, padx=20, pady=20)
def load_image(self, path, image_size):
""" load rectangular image with path relative to PATH """
return ImageTk.PhotoImage(Image.open(PATH + path).resize((image_size, image_size)))
def button_function(self):
print("button pressed")
def button_function(): if __name__ == "__main__":
print("button pressed") app = App()
app.mainloop()
# load images as PhotoImage
image_size = 20
settings_image = ImageTk.PhotoImage(Image.open(PATH + "/test_images/settings.png").resize((image_size, image_size)))
bell_image = ImageTk.PhotoImage(Image.open(PATH + "/test_images/bell.png").resize((image_size, image_size)))
add_folder_image = ImageTk.PhotoImage(Image.open(PATH + "/test_images/add-folder.png").resize((image_size, image_size), Image.ANTIALIAS))
add_list_image = ImageTk.PhotoImage(Image.open(PATH + "/test_images/add-list.png").resize((image_size, image_size), Image.ANTIALIAS))
add_user_image = ImageTk.PhotoImage(Image.open(PATH + "/test_images/add-user.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))
app.grid_rowconfigure(0, weight=1)
app.grid_columnconfigure(0, weight=1, minsize=200)
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_columnconfigure(0, weight=1)
frame_1.grid_columnconfigure(1, weight=1)
frame_1.grid_rowconfigure(0, minsize=10) # add empty row for spacing
button_1 = customtkinter.CTkButton(master=frame_1, image=add_folder_image, text="Add Folder", width=190, height=40,
compound="right", command=button_function)
button_1.grid(row=1, column=0, columnspan=2, padx=20, pady=10, sticky="ew")
button_2 = customtkinter.CTkButton(master=frame_1, image=add_list_image, text="Add Item", width=190, height=40,
compound="right", fg_color="#D35B58", hover_color="#C77C78",
command=button_function)
button_2.grid(row=2, column=0, columnspan=2, padx=20, pady=10, sticky="ew")
button_3 = customtkinter.CTkButton(master=frame_1, image=chat_image, text="", width=50, height=50,
corner_radius=10, fg_color="gray40", hover_color="gray25", command=button_function)
button_3.grid(row=3, column=0, columnspan=1, padx=20, pady=10, sticky="w")
button_4 = customtkinter.CTkButton(master=frame_1, image=home_image, text="", width=50, height=50,
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_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",
command=button_function)
button_5.grid(row=0, column=1, padx=20, pady=20)
app.mainloop()

View File

@ -4,56 +4,58 @@ 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"
app = customtkinter.CTk() # create CTk window like you do with the Tk window app = customtkinter.CTk()
app.geometry("400x540") app.geometry("400x580")
app.title("CustomTkinter simple_example.py") 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():
print("checkbox_1:", checkbox_1.get())
y_padding = 13
frame_1 = customtkinter.CTkFrame(master=app) 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=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, 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)
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)
app.mainloop() app.mainloop()

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.2.0" current = "4.5.11"
# 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.2.0 version = 4.5.11
description = Create modern looking GUIs with Python description = Create modern looking GUIs with Python
long_description = '# CustomTkinter UI-Library\nhttps://github.com/TomSchimansky/CustomTkinter/blob/master/documentation_images/Windows_dark.png\n\nMore Information: https://github.com/TomSchimansky/CustomTkinter' 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

View File

@ -0,0 +1,148 @@
import tkinter
import tkinter.messagebox
import customtkinter
customtkinter.set_appearance_mode("System") # Modes: "System" (standard), "Dark", "Light"
customtkinter.set_default_color_theme("blue") # Themes: "blue" (standard), "green", "dark-blue"
class App(customtkinter.CTk):
def __init__(self):
super().__init__()
self.title("CustomTkinter complex_example.py")
self.geometry(f"{920}x{500}")
self.protocol("WM_DELETE_WINDOW", self.on_closing) # call .on_closing() when app gets closed
# configure grid layout (4x4)
self.grid_columnconfigure(1, weight=1)
self.grid_columnconfigure((2, 3), weight=0, minsize=200)
self.grid_rowconfigure((0, 1, 2), weight=1)
# create sidebar frame with widgets
self.sidebar_frame = customtkinter.CTkFrame(self, width=140)
self.sidebar_frame.grid(row=0, column=0, rowspan=4, sticky="nsew")
self.sidebar_frame.grid_rowconfigure(4, weight=1)
self.logo_label = customtkinter.CTkLabel(self.sidebar_frame, text="CustomTkinter", text_font=("Roboto", -16))
self.logo_label.grid(row=0, column=0, padx=20, pady=(20, 10))
self.sidebar_button_1 = customtkinter.CTkButton(self.sidebar_frame, command=self.sidebar_button_callback)
self.sidebar_button_1.grid(row=1, column=0, padx=20, pady=10)
self.sidebar_button_2 = customtkinter.CTkButton(self.sidebar_frame, command=self.sidebar_button_callback)
self.sidebar_button_2.grid(row=2, column=0, padx=20, pady=10)
self.sidebar_button_3 = customtkinter.CTkButton(self.sidebar_frame, command=self.sidebar_button_callback)
self.sidebar_button_3.grid(row=3, column=0, padx=20, pady=10)
self.appearance_mode_label = customtkinter.CTkLabel(self.sidebar_frame, text="Appearance Mode:", anchor="w")
self.appearance_mode_label.grid(row=5, column=0, padx=20, pady=(10, 0))
self.appearance_mode_optionemenu = customtkinter.CTkOptionMenu(self.sidebar_frame, values=["Light", "Dark", "System"],
command=self.change_appearance_mode)
self.appearance_mode_optionemenu.grid(row=6, column=0, padx=20, pady=(10, 10))
self.scaling_label = customtkinter.CTkLabel(self.sidebar_frame, text="UI Scaling:", anchor="w")
self.scaling_label.grid(row=7, column=0, padx=20, pady=(10, 0))
self.scaling_optionemenu = customtkinter.CTkOptionMenu(self.sidebar_frame, values=["80%", "90%", "100%", "110%", "120%"],
command=self.change_scaling)
self.scaling_optionemenu.grid(row=8, column=0, padx=20, pady=(10, 20))
# create main entry and button
self.entry = customtkinter.CTkEntry(self, placeholder_text="CTkEntry")
self.entry.grid(row=3, column=1, columnspan=2, padx=(20, 10), pady=(10, 20), sticky="nsew")
self.main_button_1 = customtkinter.CTkButton(self, fg_color=None, border_width=2)
self.main_button_1.grid(row=3, column=3, padx=(10, 20), pady=(10, 20), sticky="nsew")
self.textbox = customtkinter.CTkTextbox(self)
self.textbox.grid(row=0, column=1, padx=(20, 10), pady=(20, 10), sticky="nsew")
# create radiobutton frame
self.radiobutton_frame = customtkinter.CTkFrame(self)
self.radiobutton_frame.grid(row=0, column=3, padx=(10, 20), pady=(20, 10), sticky="nsew")
self.radio_var = tkinter.IntVar(value=0)
self.label_radio_group = customtkinter.CTkLabel(master=self.radiobutton_frame, text="CTkRadioButton Group:")
self.label_radio_group.grid(row=0, column=2, columnspan=1, padx=10, pady=10, sticky="")
self.radio_button_1 = customtkinter.CTkRadioButton(master=self.radiobutton_frame, variable=self.radio_var, value=0)
self.radio_button_1.grid(row=1, column=2, pady=10, padx=20, sticky="n")
self.radio_button_2 = customtkinter.CTkRadioButton(master=self.radiobutton_frame, variable=self.radio_var, value=1)
self.radio_button_2.grid(row=2, column=2, pady=10, padx=20, sticky="n")
self.radio_button_3 = customtkinter.CTkRadioButton(master=self.radiobutton_frame, variable=self.radio_var, value=2)
self.radio_button_3.grid(row=3, column=2, pady=10, padx=20, sticky="n")
# create optionemnu and combobox frame
self.optionemnu_combobox_frame = customtkinter.CTkFrame(self)
self.optionemnu_combobox_frame.grid(row=0, column=2, padx=(10, 10), pady=(20, 10), sticky="nsew")
self.optionmenu_1 = customtkinter.CTkOptionMenu(self.optionemnu_combobox_frame,
dynamic_resizing=False,
values=["Value 1", "Value 2", "Value Long Long Long"])
self.optionmenu_1.grid(row=0, column=0, padx=20, pady=(20, 10), sticky="ew")
self.combobox_1 = customtkinter.CTkComboBox(self.optionemnu_combobox_frame,
values=["Value 1", "Value 2", "Value Long....."])
self.combobox_1.grid(row=1, column=0, padx=20, pady=(10, 10), sticky="ew")
self.string_input_button = customtkinter.CTkButton(self.optionemnu_combobox_frame, text="Open CTkInputDialog",
command=self.open_input_dialog)
self.string_input_button.grid(row=2, column=0, padx=20, pady=(10, 10), sticky="ew")
# create checkbox and switch frame
self.checkbox_slider_frame = customtkinter.CTkFrame(self)
self.checkbox_slider_frame.grid(row=1, column=3, padx=(10, 20), pady=(10, 10), sticky="nsew")
self.checkbox_1 = customtkinter.CTkCheckBox(master=self.checkbox_slider_frame)
self.checkbox_1.grid(row=1, column=0, pady=(20, 10), padx=20, sticky="n")
self.checkbox_2 = customtkinter.CTkCheckBox(master=self.checkbox_slider_frame)
self.checkbox_2.grid(row=2, column=0, pady=10, padx=20, sticky="n")
self.switch_1 = customtkinter.CTkSwitch(master=self.checkbox_slider_frame)
self.switch_1.grid(row=3, column=0, pady=10, padx=20, sticky="n")
self.switch_2 = customtkinter.CTkSwitch(master=self.checkbox_slider_frame)
self.switch_2.grid(row=4, column=0, pady=(10, 20), padx=20, sticky="n")
# create slider and progressbar frame
self.slider_progressbar_frame = customtkinter.CTkFrame(self, fg_color=None)
self.slider_progressbar_frame.grid(row=1, column=1, columnspan=2, padx=(20, 10), pady=(10, 10), sticky="nsew")
self.slider_progressbar_frame.grid_columnconfigure(0, weight=1)
self.slider_progressbar_frame.grid_rowconfigure(3, weight=1)
self.progressbar_1 = customtkinter.CTkProgressBar(self.slider_progressbar_frame)
self.progressbar_1.grid(row=0, column=0, padx=(20, 10), pady=(10, 10), sticky="ew")
self.slider_1 = customtkinter.CTkSlider(self.slider_progressbar_frame)
self.slider_1.grid(row=1, column=0, padx=(20, 10), pady=(10, 10), sticky="ew")
self.slider_2 = customtkinter.CTkSlider(self.slider_progressbar_frame, from_=0, to=4, number_of_steps=4)
self.slider_2.grid(row=2, column=0, padx=(20, 10), pady=(10, 10), sticky="ew")
self.slider_3 = customtkinter.CTkSlider(self.slider_progressbar_frame, orient="vertical")
self.slider_3.grid(row=0, column=1, rowspan=4, padx=(10, 10), pady=(10, 10), sticky="ns")
self.progressbar_2 = customtkinter.CTkProgressBar(self.slider_progressbar_frame, orient="vertical")
self.progressbar_2.grid(row=0, column=2, rowspan=4, padx=(10, 20), pady=(10, 10), sticky="ns")
# set default values
self.sidebar_button_3.configure(state="disabled", text="Disabled CTkButton")
self.checkbox_2.configure(state="disabled")
self.switch_2.configure(state="disabled")
self.checkbox_1.select()
self.switch_1.select()
self.radio_button_3.configure(state="disabled")
self.appearance_mode_optionemenu.set("Dark")
self.scaling_optionemenu.set("100%")
self.optionmenu_1.set("CTkOptionmenu")
self.combobox_1.set("CTkComboBox")
self.textbox.insert("1.0",
"CTkTextbox\n\nLorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.", )
#self.textbox.tag_add("headline", "1.0", "1.end")
#self.textbox.tag_config("headline", foreground="red")
def open_input_dialog(self):
dialog = customtkinter.CTkInputDialog(master=None, text="Type in a number:", title="CTkInputDialog")
print("CTkInputDialog:", dialog.get_input())
def change_appearance_mode(self, new_appearance_mode: str):
customtkinter.set_appearance_mode(new_appearance_mode)
def change_scaling(self, new_scaling: str):
new_scaling_float = int(new_scaling.replace("%", "")) / 100
customtkinter.set_spacing_scaling(new_scaling_float)
customtkinter.set_widget_scaling(new_scaling_float)
def sidebar_button_callback(self):
print("sidebar_button click")
def on_closing(self, event=0):
self.destroy()
if __name__ == "__main__":
app = App()
app.mainloop()

View File

@ -27,21 +27,21 @@ f4 = customtkinter.CTkFrame(app, fg_color="gray90", corner_radius=0)
f4.grid(row=0, column=3, rowspan=1, columnspan=1, sticky="nsew") f4.grid(row=0, column=3, rowspan=1, columnspan=1, sticky="nsew")
f4.grid_columnconfigure(0, weight=1) f4.grid_columnconfigure(0, weight=1)
for i in range(0, 21, 1): for i in range(0, 16, 1):
b = customtkinter.CTkButton(f1, corner_radius=i, height=34, border_width=2, text=f"{i} {i-2}", b = customtkinter.CTkButton(f1, corner_radius=i, height=30, border_width=1, text=f"{i} {i-1}",
border_color="white", fg_color=None, text_color="white") border_color="white", fg_color=None, text_color="white")
# b = tkinter.Button(f1, text=f"{i} {i-2}", width=20) # b = tkinter.Button(f1, text=f"{i} {i-2}", width=20)
b.grid(row=i, column=0, pady=5, padx=15, sticky="nsew") b.grid(row=i, column=0, pady=5, padx=15, sticky="nsew")
b = customtkinter.CTkButton(f2, corner_radius=i, height=34, border_width=0, text=f"{i}", b = customtkinter.CTkButton(f2, corner_radius=i, height=30, border_width=0, text=f"{i}",
fg_color="#228da8") fg_color="#228da8")
b.grid(row=i, column=0, pady=5, padx=15, sticky="nsew") b.grid(row=i, column=0, pady=5, padx=15, sticky="nsew")
b = customtkinter.CTkButton(f3, corner_radius=i, height=34, border_width=2, text=f"{i} {i-2}", b = customtkinter.CTkButton(f3, corner_radius=i, height=30, border_width=1, text=f"{i} {i-1}",
fg_color=None, border_color="gray20", text_color="black") fg_color=None, border_color="gray20", text_color="black")
b.grid(row=i, column=0, pady=5, padx=15, sticky="nsew") b.grid(row=i, column=0, pady=5, padx=15, sticky="nsew")
b = customtkinter.CTkButton(f4, corner_radius=i, height=34, border_width=0, text=f"{i}", b = customtkinter.CTkButton(f4, corner_radius=i, height=30, border_width=0, text=f"{i}",
border_color="gray10", fg_color="#228da8") border_color="gray10", fg_color="#228da8")
b.grid(row=i, column=0, pady=5, padx=15, sticky="nsew") b.grid(row=i, column=0, pady=5, padx=15, sticky="nsew")

View File

@ -0,0 +1,32 @@
import customtkinter
import sys
customtkinter.set_appearance_mode("dark")
app = customtkinter.CTk()
app.geometry("400x240")
def change_appearance_mode():
# test appearance mode change while withdrawn
app.after(500, app.withdraw)
app.after(1500, lambda: customtkinter.set_appearance_mode("light"))
app.after(2500, app.deiconify)
# test appearance mode change while iconified
app.after(3500, app.iconify)
app.after(4500, lambda: customtkinter.set_appearance_mode("dark"))
app.after(5500, app.deiconify)
if sys.platform.startswith("win"):
# test appearance mode change while zoomed
app.after(6500, lambda: app.state("zoomed"))
app.after(7500, lambda: customtkinter.set_appearance_mode("light"))
app.after(8500, lambda: app.state("normal"))
button_1 = customtkinter.CTkButton(app, text="start test", command=change_appearance_mode)
button_1.pack(pady=20, padx=20)
app.mainloop()

View File

@ -0,0 +1,9 @@
import customtkinter
app = customtkinter.CTk()
app.geometry("400x240")
app.iconify()
app.after(2000, app.deiconify)
app.mainloop()

View File

@ -0,0 +1,9 @@
import customtkinter
app = customtkinter.CTk()
app.geometry("400x240")
app.withdraw()
app.after(2000, app.deiconify)
app.mainloop()

View File

@ -0,0 +1,27 @@
import customtkinter
customtkinter.set_appearance_mode("dark")
app = customtkinter.CTk()
app.geometry("400x240")
def change_appearance_mode():
# test zoom with withdraw
app.after(1000, lambda: app.state("zoomed"))
app.after(2000, app.withdraw)
app.after(3000, app.deiconify)
app.after(4000, lambda: app.state("normal"))
# test zoom with iconify
app.after(5000, lambda: app.state("zoomed"))
app.after(6000, app.iconify)
app.after(7000, app.deiconify)
app.after(8000, lambda: app.state("normal"))
button_1 = customtkinter.CTkButton(app, text="start test", command=change_appearance_mode)
button_1.pack(pady=20, padx=20)
app.mainloop()

View File

@ -1,22 +1,49 @@
import customtkinter import customtkinter
customtkinter.set_appearance_mode("dark")
class ExampleApp(customtkinter.CTk):
def __init__(self, *args, **kwargs): class ToplevelWindow(customtkinter.CTkToplevel):
def __init__(self, *args, closing_event=None, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.protocol("WM_DELETE_WINDOW", self.closing)
self.geometry("500x300")
self.closing_event = closing_event
self.geometry("500x400") self.label = customtkinter.CTkLabel(self, text="ToplevelWindow")
self.label.pack(padx=20, pady=20)
self.button_1 = customtkinter.CTkButton(self, text="Create CTkToplevel", command=self.create_toplevel) self.button_1 = customtkinter.CTkButton(self, text="set dark", command=lambda: customtkinter.set_appearance_mode("dark"))
self.button_1.pack(side="top", padx=40, pady=40) self.button_1.pack(side="top", padx=40, pady=40)
def create_toplevel(self): def closing(self):
window = customtkinter.CTkToplevel(self) self.destroy()
window.geometry("400x200") if self.closing_event is not None:
self.closing_event()
label = customtkinter.CTkLabel(window, text="CTkToplevel window")
label.pack(side="top", fill="both", expand=True, padx=40, pady=40)
app = ExampleApp() class App(customtkinter.CTk):
app.mainloop() def __init__(self):
super().__init__()
self.geometry("500x400")
self.button_1 = customtkinter.CTkButton(self, text="Open CTkToplevel", command=self.open_toplevel)
self.button_1.pack(side="top", padx=40, pady=40)
self.button_2 = customtkinter.CTkButton(self, text="iconify toplevel", command=lambda: self.toplevel_window.iconify())
self.button_2.pack(side="top", padx=40, pady=40)
self.button_3 = customtkinter.CTkButton(self, text="set light", command=lambda: customtkinter.set_appearance_mode("light"))
self.button_3.pack(side="top", padx=40, pady=40)
self.toplevel_window = None
def open_toplevel(self):
if self.toplevel_window is None: # create toplevel window only if not already open
self.toplevel_window = ToplevelWindow(self, closing_event=self.toplevel_close_event)
def toplevel_close_event(self):
self.toplevel_window = None
if __name__ == "__main__":
app = App()
app.mainloop()

View File

@ -0,0 +1,35 @@
import customtkinter
import sys
customtkinter.set_appearance_mode("dark")
app = customtkinter.CTk()
app.geometry("400x400+300+300")
toplevel = customtkinter.CTkToplevel(app)
toplevel.geometry("350x240+800+300")
def change_appearance_mode():
# test appearance mode change while withdrawn
app.after(500, toplevel.withdraw)
app.after(1500, lambda: customtkinter.set_appearance_mode("light"))
app.after(2500, toplevel.deiconify)
# test appearance mode change while iconified
app.after(3500, toplevel.iconify)
app.after(4500, lambda: customtkinter.set_appearance_mode("dark"))
app.after(5500, toplevel.deiconify)
if sys.platform.startswith("win"):
# test appearance mode change while zoomed
app.after(6500, lambda: toplevel.state("zoomed"))
app.after(7500, lambda: customtkinter.set_appearance_mode("light"))
app.after(8500, lambda: toplevel.state("normal"))
button_1 = customtkinter.CTkButton(app, text="start test", command=change_appearance_mode)
button_1.pack(pady=20, padx=20)
app.mainloop()

View File

@ -0,0 +1,12 @@
import customtkinter
app = customtkinter.CTk()
app.geometry("400x400+300+300")
toplevel = customtkinter.CTkToplevel(app)
toplevel.geometry("350x240+800+300")
toplevel.iconify()
toplevel.after(2000, toplevel.deiconify)
app.mainloop()

View File

@ -0,0 +1,12 @@
import customtkinter
app = customtkinter.CTk()
app.geometry("400x400+300+300")
toplevel = customtkinter.CTkToplevel(app)
toplevel.geometry("350x240+800+300")
toplevel.withdraw()
toplevel.after(2000, toplevel.deiconify)
app.mainloop()

View File

@ -0,0 +1,29 @@
import customtkinter
customtkinter.set_appearance_mode("dark")
app = customtkinter.CTk()
app.geometry("400x400+300+300")
toplevel = customtkinter.CTkToplevel(app)
toplevel.geometry("350x240+800+300")
def change_appearance_mode():
# test zoom with withdraw
app.after(1000, lambda: toplevel.state("zoomed"))
app.after(2000, toplevel.withdraw)
app.after(3000, toplevel.deiconify)
app.after(4000, lambda: toplevel.state("normal"))
# test zoom with iconify
app.after(5000, lambda: toplevel.state("zoomed"))
app.after(6000, toplevel.iconify)
app.after(7000, toplevel.deiconify)
app.after(8000, lambda: toplevel.state("normal"))
button_1 = customtkinter.CTkButton(app, text="start test", command=change_appearance_mode)
button_1.pack(pady=20, padx=20)
app.mainloop()

View File

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

View File

@ -1,71 +0,0 @@
import customtkinter
import tkinter
import sys
class CTkMenu(tkinter.Toplevel):
def __init__(self, master, x, y, options):
super().__init__()
self.overrideredirect(True)
self.geometry(f"120x{len(options) * (25 + 3) + 3}+{x}+{y}")
if sys.platform.startswith("darwin"):
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.buttons = []
for index, option in enumerate(options):
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.grid(row=index, column=0, padx=(3, 3), pady=(3, 0), columnspan=1, rowspan=1, sticky="ew")
self.buttons.append(button)
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():
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=30, corner_radius=6)
button.pack(pady=20)
button_2 = customtkinter.CTkButton(command=open_menu, height=30, corner_radius=6)
button_2.pack(pady=60)
app.mainloop()

View File

@ -1,26 +0,0 @@
import tkinter
import customtkinter
app = customtkinter.CTk()
app.title('test_optionmenu.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=10, padx=10)
optionmenu_1.set("te")
app.mainloop()

View File

@ -0,0 +1,44 @@
import tkinter
import tkinter.ttk as ttk
import customtkinter
app = customtkinter.CTk()
app.title('Test OptionMenu ComboBox.py')
app.geometry('400x500')
def select_callback(choice):
choice = variable.get()
print("display_selected", choice)
countries = ['Bahamas', 'Canada', 'Cuba', 'United States', "long sdhfhjgdshjafghdgshfhjdsfj"]
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)
optionmenu_2 = customtkinter.CTkOptionMenu(app, variable=variable, values=countries, command=select_callback,
dynamic_resizing=False)
optionmenu_2.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=None, values=countries, command=select_callback, width=300)
combobox_1.pack(pady=20, padx=10)
def set_new_scaling(scaling):
customtkinter.set_spacing_scaling(scaling)
customtkinter.set_window_scaling(scaling)
customtkinter.set_widget_scaling(scaling)
scaling_slider = customtkinter.CTkSlider(app, command=set_new_scaling, from_=0, to=2)
scaling_slider.pack(pady=20, padx=10)
app.mainloop()

View File

@ -0,0 +1,54 @@
import tkinter
import customtkinter
# test with scaling
# customtkinter.set_widget_scaling(2)
# customtkinter.set_window_scaling(2)
# customtkinter.set_spacing_scaling(2)
customtkinter.set_appearance_mode("dark")
app = customtkinter.CTk()
app.title("test_scrollbar.py")
app.grid_rowconfigure(0, weight=1)
app.grid_columnconfigure((0, 2), weight=1)
tk_textbox = tkinter.Text(app, highlightthickness=0, padx=5, pady=5)
tk_textbox.grid(row=0, column=0, sticky="nsew")
ctk_textbox_scrollbar = customtkinter.CTkScrollbar(app, command=tk_textbox.yview)
ctk_textbox_scrollbar.grid(row=0, column=1, padx=0, sticky="ns")
tk_textbox.configure(yscrollcommand=ctk_textbox_scrollbar.set)
frame_1 = customtkinter.CTkFrame(app)
frame_1.grid(row=0, column=2, padx=10, pady=10, sticky="nsew")
frame_1.grid_rowconfigure((0, 1), weight=1)
frame_1.grid_columnconfigure((0, ), weight=1)
tk_textbox_1 = tkinter.Text(frame_1, highlightthickness=0, padx=5, pady=5)
tk_textbox_1.grid(row=0, column=0, sticky="nsew", padx=(5, 0), pady=5)
ctk_textbox_scrollbar_1 = customtkinter.CTkScrollbar(frame_1, command=tk_textbox_1.yview)
ctk_textbox_scrollbar_1.grid(row=0, column=1, sticky="ns", padx=(0, 5), pady=5)
tk_textbox_1.configure(yscrollcommand=ctk_textbox_scrollbar_1.set)
ctk_textbox_scrollbar_1.configure(scrollbar_color="red", scrollbar_hover_color="darkred",
border_spacing=0, width=12, fg_color="green", corner_radius=4)
frame_2 = customtkinter.CTkFrame(frame_1)
frame_2.grid(row=1, column=0, columnspan=2, padx=20, pady=20, sticky="nsew")
frame_2.grid_rowconfigure((0, ), weight=1)
frame_2.grid_columnconfigure((0, ), weight=1)
tk_textbox_2 = tkinter.Text(frame_2, highlightthickness=0, padx=5, pady=5, wrap="none")
tk_textbox_2.grid(row=0, column=0, sticky="nsew", padx=(5, 0), pady=5)
ctk_textbox_scrollbar_2 = customtkinter.CTkScrollbar(frame_2, command=tk_textbox_2.yview)
ctk_textbox_scrollbar_2.grid(row=0, column=1, sticky="ns", padx=(0, 5), pady=5)
ctk_textbox_scrollbar_2_horizontal = customtkinter.CTkScrollbar(frame_2, command=tk_textbox_2.xview, orientation="horizontal")
ctk_textbox_scrollbar_2_horizontal.grid(row=1, column=0, sticky="ew", padx=(5, 0), pady=(0, 5))
tk_textbox_2.configure(yscrollcommand=ctk_textbox_scrollbar_2.set, xscrollcommand=ctk_textbox_scrollbar_2_horizontal.set)
tk_textbox.configure(font=(customtkinter.ThemeManager.theme["text"]["font"], customtkinter.ThemeManager.theme["text"]["size"]))
tk_textbox_1.configure(font=(customtkinter.ThemeManager.theme["text"]["font"], customtkinter.ThemeManager.theme["text"]["size"]))
tk_textbox_2.configure(font=(customtkinter.ThemeManager.theme["text"]["font"], customtkinter.ThemeManager.theme["text"]["size"]))
tk_textbox.insert("insert", "\n".join([str(i) for i in range(100)]))
tk_textbox_1.insert("insert", "\n".join([str(i) for i in range(1000)]))
tk_textbox_2.insert("insert", "\n".join([str(i) + " - "*30 for i in range(10000)]))
app.mainloop()

View File

@ -3,7 +3,7 @@ import customtkinter
app = customtkinter.CTk() app = customtkinter.CTk()
app.geometry("400x800") app.geometry("400x900")
app.title("CustomTkinter Test") app.title("CustomTkinter Test")
@ -40,8 +40,23 @@ button_4.pack(padx=20, pady=(10, 20))
radiobutton_1 = customtkinter.CTkRadioButton(master=app, text="radiobutton_1") radiobutton_1 = customtkinter.CTkRadioButton(master=app, text="radiobutton_1")
radiobutton_1.pack(padx=20, pady=(20, 10)) radiobutton_1.pack(padx=20, pady=(20, 10))
button_5 = customtkinter.CTkButton(master=app, text="Disable/Enable entry_1", command=lambda: change_state(radiobutton_1)) 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)) 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() app.mainloop()

View File

@ -0,0 +1,54 @@
import tkinter
import customtkinter
# test with scaling
# customtkinter.set_widget_scaling(2)
# customtkinter.set_window_scaling(2)
# customtkinter.set_spacing_scaling(2)
customtkinter.set_appearance_mode("dark")
app = customtkinter.CTk()
app.title("test_scrollbar.py")
app.grid_rowconfigure(0, weight=1)
app.grid_columnconfigure((0, 2), weight=1)
tk_textbox = customtkinter.CTkTextbox(app, highlightthickness=0, padx=5, pady=5)
tk_textbox.grid(row=0, column=0, sticky="nsew")
ctk_textbox_scrollbar = customtkinter.CTkScrollbar(app, command=tk_textbox.yview)
ctk_textbox_scrollbar.grid(row=0, column=1, padx=0, sticky="ns")
tk_textbox.configure(yscrollcommand=ctk_textbox_scrollbar.set)
frame_1 = customtkinter.CTkFrame(app)
frame_1.grid(row=0, column=2, padx=10, pady=10, sticky="nsew")
frame_1.grid_rowconfigure((0, 1), weight=1)
frame_1.grid_columnconfigure((0, ), weight=1)
tk_textbox_1 = customtkinter.CTkTextbox(frame_1, highlightthickness=0, padx=5, pady=5)
tk_textbox_1.grid(row=0, column=0, sticky="nsew", padx=(5, 0), pady=5)
ctk_textbox_scrollbar_1 = customtkinter.CTkScrollbar(frame_1, command=tk_textbox_1.yview)
ctk_textbox_scrollbar_1.grid(row=0, column=1, sticky="ns", padx=(0, 5), pady=5)
tk_textbox_1.configure(yscrollcommand=ctk_textbox_scrollbar_1.set)
ctk_textbox_scrollbar_1.configure(scrollbar_color="red", scrollbar_hover_color="darkred",
border_spacing=0, width=12, fg_color="green", corner_radius=4)
frame_2 = customtkinter.CTkFrame(frame_1)
frame_2.grid(row=1, column=0, columnspan=2, padx=20, pady=20, sticky="nsew")
frame_2.grid_rowconfigure((0, ), weight=1)
frame_2.grid_columnconfigure((0, ), weight=1)
tk_textbox_2 = customtkinter.CTkTextbox(frame_2, highlightthickness=0, padx=5, pady=5, wrap="none")
tk_textbox_2.grid(row=0, column=0, sticky="nsew", padx=(5, 0), pady=5)
ctk_textbox_scrollbar_2 = customtkinter.CTkScrollbar(frame_2, command=tk_textbox_2.yview)
ctk_textbox_scrollbar_2.grid(row=0, column=1, sticky="ns", padx=(0, 5), pady=5)
ctk_textbox_scrollbar_2_horizontal = customtkinter.CTkScrollbar(frame_2, command=tk_textbox_2.xview, orientation="horizontal")
ctk_textbox_scrollbar_2_horizontal.grid(row=1, column=0, sticky="ew", padx=(5, 0), pady=(0, 5))
tk_textbox_2.configure(yscrollcommand=ctk_textbox_scrollbar_2.set, xscrollcommand=ctk_textbox_scrollbar_2_horizontal.set)
tk_textbox.configure(font=(customtkinter.ThemeManager.theme["text"]["font"], customtkinter.ThemeManager.theme["text"]["size"]))
tk_textbox_1.configure(font=(customtkinter.ThemeManager.theme["text"]["font"], customtkinter.ThemeManager.theme["text"]["size"]))
tk_textbox_2.configure(font=(customtkinter.ThemeManager.theme["text"]["font"], customtkinter.ThemeManager.theme["text"]["size"]))
tk_textbox.insert("insert", "\n".join([str(i) for i in range(100)]))
tk_textbox_1.insert("insert", "\n".join([str(i) for i in range(1000)]))
tk_textbox_2.insert("insert", "\n".join([str(i) + " - "*30 for i in range(10000)]))
app.mainloop()

View File

@ -5,15 +5,20 @@ TEST_CONFIGURE = True
TEST_REMOVING = False 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 = customtkinter.CTk() # create CTk window like you do with the Tk window (you can also use normal tkinter.Tk window)
app.geometry("400x800") app.geometry("400x900")
app.title("Tkinter Variable Test") app.title("Tkinter Variable Test")
def checkbox_event():
print("checkbox_event")
txt_var = tkinter.StringVar(value="") txt_var = tkinter.StringVar(value="")
entry_1 = customtkinter.CTkEntry(app, width=200, textvariable=txt_var) entry_1 = customtkinter.CTkEntry(app, width=200, textvariable=txt_var, placeholder_text="placeholder")
entry_1.pack(pady=15) entry_1.pack(pady=15)
txt_var.set("new text wjkfjdshkjfb") txt_var.set("new text test")
if TEST_CONFIGURE: entry_1.configure(textvariable=txt_var) if TEST_CONFIGURE: entry_1.configure(textvariable=txt_var)
if TEST_REMOVING: entry_1.configure(textvariable="") if TEST_REMOVING: entry_1.configure(textvariable="")
#entry_1.delete(0, "end")
#entry_1.insert(0, "sadsad")
label_1 = customtkinter.CTkLabel(app, width=200, textvariable=txt_var) label_1 = customtkinter.CTkLabel(app, width=200, textvariable=txt_var)
label_1.pack(pady=15) label_1.pack(pady=15)
@ -46,11 +51,13 @@ if TEST_CONFIGURE: progress_1.configure(variable=int_var)
if TEST_REMOVING: progress_1.configure(variable="") if TEST_REMOVING: progress_1.configure(variable="")
check_var = tkinter.StringVar(value="on") check_var = tkinter.StringVar(value="on")
check_1 = customtkinter.CTkCheckBox(app, text="check 1", variable=check_var, onvalue="on", offvalue="off") check_1 = customtkinter.CTkCheckBox(app, text="check 1", variable=check_var, onvalue="on", offvalue="off", textvariable=txt_var,
command=checkbox_event)
check_1.pack(pady=15) check_1.pack(pady=15)
if TEST_CONFIGURE: check_1.configure(variable=check_var) if TEST_CONFIGURE: check_1.configure(variable=check_var)
if TEST_REMOVING: check_1.configure(variable="") if TEST_REMOVING: check_1.configure(variable="")
print("check_1", check_1.get())
print("check 1 created")
check_2 = customtkinter.CTkCheckBox(app, text="check 2", variable=check_var, onvalue="on", offvalue="off") check_2 = customtkinter.CTkCheckBox(app, text="check 2", variable=check_var, onvalue="on", offvalue="off")
check_2.pack(pady=15) check_2.pack(pady=15)
@ -67,14 +74,17 @@ def switch_event():
s_var = tkinter.StringVar(value="on") 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 = 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.pack(pady=20, padx=10)
switch_1 = customtkinter.CTkSwitch(master=app, variable=s_var, textvariable=s_var, onvalue="on", offvalue="off") switch_2 = customtkinter.CTkSwitch(master=app, variable=s_var, textvariable=s_var, onvalue="on", offvalue="off")
switch_1.pack(pady=20, padx=10) switch_2.pack(pady=20, padx=10)
optionmenu_var = tkinter.StringVar(value="test") optionmenu_var = tkinter.StringVar(value="test")
optionmenu_1 = customtkinter.CTkOptionMenu(master=app, variable=optionmenu_var, values=["Option 1", "Option 2", "Option 3"]) optionmenu_1 = customtkinter.CTkOptionMenu(master=app, variable=optionmenu_var, values=["Option 1", "Option 2", "Option 3"])
optionmenu_1.pack(pady=20, padx=10) optionmenu_1.pack(pady=20, padx=10)
optionmenu_2 = customtkinter.CTkOptionMenu(master=app, values=["Option 1", "Option 2", "Option 3"]) combobox_1 = customtkinter.CTkComboBox(master=app, values=["Option 1", "Option 2", "Option 3"])
optionmenu_2.pack(pady=20, padx=10) combobox_1.pack(pady=20, padx=10)
optionmenu_2.configure(variable=optionmenu_var) combobox_1.configure(variable=optionmenu_var)
radio_1 = customtkinter.CTkRadioButton(app, textvariable=txt_var)
radio_1.pack(pady=20, padx=10)
app.mainloop() app.mainloop()

View File

@ -0,0 +1,37 @@
import customtkinter
customtkinter.set_window_scaling(1.3)
app = customtkinter.CTk()
toplevel = customtkinter.CTkToplevel(app)
app.after(1000, lambda: app.geometry("300x300"))
app.after(2000, lambda: app.geometry("-100-100"))
app.after(3000, lambda: app.geometry("+-100+-100"))
app.after(4000, lambda: app.geometry("+100+100"))
app.after(5000, lambda: app.geometry("300x300-100-100"))
app.after(6000, lambda: app.geometry("300x300+-100+-100"))
app.after(7000, lambda: app.geometry("300x300+100+100"))
app.after(8000, lambda: app.geometry("400x400"))
app.after(9000, lambda: app.geometry("+400+400"))
app.after(10000, lambda: toplevel.geometry("300x300"))
app.after(11000, lambda: toplevel.geometry("-100-100"))
app.after(12000, lambda: toplevel.geometry("+-100+-100"))
app.after(13000, lambda: toplevel.geometry("+100+100"))
app.after(14000, lambda: toplevel.geometry("300x300-100-100"))
app.after(15000, lambda: toplevel.geometry("300x300+-100+-100"))
app.after(16000, lambda: toplevel.geometry("300x300+100+100"))
app.after(17000, lambda: toplevel.geometry("300x300"))
app.after(18000, lambda: toplevel.geometry("+500+500"))
app.after(19000, lambda: print("app:", app.geometry()))
app.after(19000, lambda: print("toplevel:", toplevel.geometry()))
app.mainloop()

View File

@ -0,0 +1,27 @@
import customtkinter
app = customtkinter.CTk() # create CTk window like you do with the Tk window (you can also use normal tkinter.Tk window)
app.geometry("400x400")
app.title("test_entry_placeholder.py")
str_var = customtkinter.StringVar(value="test")
entry_1 = customtkinter.CTkEntry(app, placeholder_text="placeholder", textvariable=str_var)
entry_1.pack(pady=20)
entry_2 = customtkinter.CTkEntry(app, placeholder_text="placeholder", textvariable=str_var)
entry_2.pack(pady=20)
entry_2.insert(0, "sdfjk ")
entry_2.delete(0, 2)
entry_3 = customtkinter.CTkEntry(app, placeholder_text="placeholder")
entry_3.pack(pady=(40, 20))
entry_3.insert(0, "sdfjk")
entry_3.delete(0, "end")
entry_4 = customtkinter.CTkEntry(app, placeholder_text="password", show="*")
entry_4.pack(pady=(20, 20))
entry_4.insert(0, "sdfjk")
entry_4.delete(0, 2)
app.mainloop()