44 Commits

Author SHA1 Message Date
3f156f5648 Bump to 4.6.0 2022-09-17 00:55:31 +02:00
8fba7b4481 update CHANGELOG.md 2022-09-17 00:54:32 +02:00
ee85b27271 fixed CTkProgressBar intermediate mode rendering on Windows #115 2022-09-17 00:52:20 +02:00
5204683df4 updated command calls of CTkSwitch, now commmand is called for .toggle() only 2022-09-17 00:39:27 +02:00
98c4c669a6 added indeterminate mode to CTkProgressBar 2022-09-17 00:18:31 +02:00
078918e77b changed methods for command callback for optionmenu and combobox 2022-09-15 23:55:55 +02:00
c16c891115 changed combobox and optionemnu command to only get triggered by manual selection #440 2022-09-15 18:46:24 +02:00
66f9fa2386 updated Readme.md 2022-09-15 16:20:20 +02:00
e52b5fb799 updated Readme.md 2022-09-15 16:20:05 +02:00
6aac63d851 updated Readme.md 2022-09-15 16:18:56 +02:00
d7dda9cb39 updated Readme.md 2022-09-15 16:17:49 +02:00
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
43 changed files with 847 additions and 397 deletions

View File

@ -4,6 +4,10 @@ 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.6.0] - 2022-06-23
### Added
- CTkProgressBar indeterminate mode, automatic progress loop with .start() and .stop()
## [4.5.0] - 2022-06-23 ## [4.5.0] - 2022-06-23
### Added ### Added
- CTkScrollbar (vertical, horizontal) - CTkScrollbar (vertical, horizontal)

View File

@ -31,7 +31,9 @@ pip3 install customtkinter
## Documentation ## Documentation
A detailed documentation can be found in the Wiki Tab here: **[Documentation](https://github.com/TomSchimansky/CustomTkinter/wiki)**. The **official** documentation can be found in the Wiki Tab here:
**--> [Documentation](https://github.com/TomSchimansky/CustomTkinter/wiki)**.
## Example Program ## Example Program
To test customtkinter you can try this simple example with only a single button: To test customtkinter you can try this simple example with only a single button:

View File

@ -1,4 +1,4 @@
__version__ = "4.5.7" __version__ = "4.6.0"
import os import os
import sys import sys

View File

@ -45,7 +45,7 @@ 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

View File

@ -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"],

View File

@ -1,41 +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"],
"optionmenu_button": ["#36719F", "#144870"], "optionmenu_button": ["#525983", "#080b12"],
"optionmenu_button_hover": ["#27577D", "#203A4F"], "optionmenu_button_hover": ["#fa00d0", "#080b12"],
"combobox_border": ["#979DA2", "#565B5E"], "combobox_border": ["#525983", "#080b12"],
"combobox_button_hover": ["#6E7174", "#7A848D"], "combobox_button_hover": ["#fa00d0", "#fa00d0"],
"dropdown_color": ["gray90", "gray20"], "dropdown_color": ["#dee2e7", "#212435"],
"dropdown_hover": ["gray75", "gray28"], "dropdown_hover": ["#fa00d0", "#fa00d0"],
"dropdown_text": ["gray10", "#DCE4EE"], "dropdown_text": ["#0c0e14", "#cdc8ce"],
"scrollbar_button": ["gray55", "gray41"], "scrollbar_button": ["#fa00d0", "#fa00d0"],
"scrollbar_button_hover": ["gray40", "gray53"] "scrollbar_button_hover": ["#9b45ff", "#9b45ff"]
}, },
"text": { "text": {
"macOS": { "macOS": {
@ -53,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": 0, "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,

View File

@ -645,8 +645,8 @@ class DrawEngine:
return requires_recoloring return requires_recoloring
def draw_rounded_progress_bar_with_border(self, width: Union[float, int], height: Union[float, int], corner_radius: Union[float, int], def draw_rounded_progress_bar_with_border(self, width: Union[float, int], height: Union[float, int], corner_radius: Union[float, int],
border_width: Union[float, int], progress_value: float, orientation: str) -> bool: border_width: Union[float, int], progress_value_1: float, progress_value_2: 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, and onntop sits a progress bar from value 1 to value 2 (range 0-1, left to right, bottom to top).
The border elements get the 'border_parts' tag", the main elements get the 'inner_parts' tag and The border elements get the 'border_parts' tag", the main elements get the 'inner_parts' tag and
the progress elements get the 'progress_parts' tag. The 'orientation' argument defines from which direction the progress starts (n, w, s, e). the progress elements get the 'progress_parts' tag. The 'orientation' argument defines from which direction the progress starts (n, w, s, e).
@ -668,13 +668,13 @@ class DrawEngine:
if self.preferred_drawing_method == "polygon_shapes" or self.preferred_drawing_method == "circle_shapes": if self.preferred_drawing_method == "polygon_shapes" or self.preferred_drawing_method == "circle_shapes":
return self.__draw_rounded_progress_bar_with_border_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius, return self.__draw_rounded_progress_bar_with_border_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius,
progress_value, orientation) progress_value_1, progress_value_2, orientation)
elif self.preferred_drawing_method == "font_shapes": elif self.preferred_drawing_method == "font_shapes":
return self.__draw_rounded_progress_bar_with_border_font_shapes(width, height, corner_radius, border_width, inner_corner_radius, return self.__draw_rounded_progress_bar_with_border_font_shapes(width, height, corner_radius, border_width, inner_corner_radius,
progress_value, orientation) progress_value_1, progress_value_2, orientation)
def __draw_rounded_progress_bar_with_border_polygon_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int, def __draw_rounded_progress_bar_with_border_polygon_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int,
progress_value: float, orientation: str) -> bool: progress_value_1: float, progress_value_2: float, orientation: str) -> bool:
requires_recoloring = self.__draw_rounded_rect_with_border_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius) requires_recoloring = self.__draw_rounded_rect_with_border_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius)
@ -691,32 +691,32 @@ class DrawEngine:
if orientation == "w": if orientation == "w":
self._canvas.coords("progress_line_1", self._canvas.coords("progress_line_1",
border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_1,
border_width + inner_corner_radius, border_width + inner_corner_radius,
border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_2,
border_width + inner_corner_radius, border_width + inner_corner_radius,
border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value, border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_2,
border_width + inner_corner_radius,
border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value,
height - (border_width + inner_corner_radius) + bottom_right_shift, height - (border_width + inner_corner_radius) + bottom_right_shift,
border_width + inner_corner_radius, border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_1,
height - (border_width + inner_corner_radius) + bottom_right_shift) height - (border_width + inner_corner_radius) + bottom_right_shift)
elif orientation == "s": elif orientation == "s":
self._canvas.coords("progress_line_1", self._canvas.coords("progress_line_1",
border_width + inner_corner_radius, border_width + inner_corner_radius,
border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value), border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_2),
width - (border_width + inner_corner_radius), width - (border_width + inner_corner_radius),
border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value), border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_2),
width - (border_width + inner_corner_radius), width - (border_width + inner_corner_radius),
height - (border_width + inner_corner_radius) + bottom_right_shift, border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_1),
border_width + inner_corner_radius, border_width + inner_corner_radius,
height - (border_width + inner_corner_radius) + bottom_right_shift) border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_1))
self._canvas.itemconfig("progress_line_1", width=inner_corner_radius * 2) self._canvas.itemconfig("progress_line_1", width=inner_corner_radius * 2)
return requires_recoloring return requires_recoloring
def __draw_rounded_progress_bar_with_border_font_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int, def __draw_rounded_progress_bar_with_border_font_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int,
progress_value: float, orientation: str) -> bool: progress_value_1: float, progress_value_2: float, orientation: str) -> bool:
requires_recoloring, requires_recoloring_2 = False, False requires_recoloring, requires_recoloring_2 = False, False
@ -751,64 +751,72 @@ class DrawEngine:
# horizontal orientation from the bottom # horizontal orientation from the bottom
if orientation == "w": if orientation == "w":
requires_recoloring_2 = self.__draw_rounded_rect_with_border_font_shapes(width, height, corner_radius, border_width, inner_corner_radius, requires_recoloring_2 = self.__draw_rounded_rect_with_border_font_shapes(width, height, corner_radius, border_width, inner_corner_radius,
("inner_oval_1", "inner_oval_4")) ())
# set positions of progress corner parts # set positions of progress corner parts
self._canvas.coords("progress_oval_1_a", border_width + inner_corner_radius, border_width + inner_corner_radius, inner_corner_radius) self._canvas.coords("progress_oval_1_a", border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_1,
self._canvas.coords("progress_oval_1_b", border_width + inner_corner_radius, border_width + inner_corner_radius, inner_corner_radius)
self._canvas.coords("progress_oval_2_a", border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value,
border_width + inner_corner_radius, inner_corner_radius) border_width + inner_corner_radius, inner_corner_radius)
self._canvas.coords("progress_oval_2_b", border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value, self._canvas.coords("progress_oval_1_b", border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_1,
border_width + inner_corner_radius, inner_corner_radius) border_width + inner_corner_radius, inner_corner_radius)
self._canvas.coords("progress_oval_3_a", border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value, self._canvas.coords("progress_oval_2_a", border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_2,
border_width + inner_corner_radius, inner_corner_radius)
self._canvas.coords("progress_oval_2_b", border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_2,
border_width + inner_corner_radius, inner_corner_radius)
self._canvas.coords("progress_oval_3_a", border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_2,
height - border_width - inner_corner_radius, inner_corner_radius) height - border_width - inner_corner_radius, inner_corner_radius)
self._canvas.coords("progress_oval_3_b", border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value, self._canvas.coords("progress_oval_3_b", border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_2,
height - border_width - inner_corner_radius, inner_corner_radius)
self._canvas.coords("progress_oval_4_a", border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_1,
height - border_width - inner_corner_radius, inner_corner_radius)
self._canvas.coords("progress_oval_4_b", border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_1,
height - border_width - inner_corner_radius, inner_corner_radius) height - border_width - inner_corner_radius, inner_corner_radius)
self._canvas.coords("progress_oval_4_a", border_width + inner_corner_radius, height - border_width - inner_corner_radius, inner_corner_radius)
self._canvas.coords("progress_oval_4_b", border_width + inner_corner_radius, height - border_width - inner_corner_radius, inner_corner_radius)
# set positions of progress rect parts # set positions of progress rect parts
self._canvas.coords("progress_rectangle_1", self._canvas.coords("progress_rectangle_1",
border_width + inner_corner_radius, border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_1,
border_width, border_width,
border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value, border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_2,
height - border_width) height - border_width)
self._canvas.coords("progress_rectangle_2", self._canvas.coords("progress_rectangle_2",
border_width, border_width + 2 * inner_corner_radius + (width - 2 * inner_corner_radius - 2 * border_width) * progress_value_1,
border_width + inner_corner_radius, border_width + inner_corner_radius,
border_width + 2 * inner_corner_radius + (width - 2 * inner_corner_radius - 2 * border_width) * progress_value, border_width + 2 * inner_corner_radius + (width - 2 * inner_corner_radius - 2 * border_width) * progress_value_2,
height - inner_corner_radius - border_width) height - inner_corner_radius - border_width)
# vertical orientation from the bottom # vertical orientation from the bottom
if orientation == "s": if orientation == "s":
requires_recoloring_2 = self.__draw_rounded_rect_with_border_font_shapes(width, height, corner_radius, border_width, inner_corner_radius, requires_recoloring_2 = self.__draw_rounded_rect_with_border_font_shapes(width, height, corner_radius, border_width, inner_corner_radius,
("inner_oval_3", "inner_oval_4")) ())
# set positions of progress corner parts # set positions of progress corner parts
self._canvas.coords("progress_oval_1_a", border_width + inner_corner_radius, self._canvas.coords("progress_oval_1_a", border_width + inner_corner_radius,
border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value), inner_corner_radius) border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_2), inner_corner_radius)
self._canvas.coords("progress_oval_1_b", border_width + inner_corner_radius, self._canvas.coords("progress_oval_1_b", border_width + inner_corner_radius,
border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value), inner_corner_radius) border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_2), inner_corner_radius)
self._canvas.coords("progress_oval_2_a", width - border_width - inner_corner_radius, self._canvas.coords("progress_oval_2_a", width - border_width - inner_corner_radius,
border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value), inner_corner_radius) border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_2), inner_corner_radius)
self._canvas.coords("progress_oval_2_b", width - border_width - inner_corner_radius, self._canvas.coords("progress_oval_2_b", width - border_width - inner_corner_radius,
border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value), inner_corner_radius) border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_2), inner_corner_radius)
self._canvas.coords("progress_oval_3_a", width - border_width - inner_corner_radius, height - border_width - inner_corner_radius, inner_corner_radius) self._canvas.coords("progress_oval_3_a", width - border_width - inner_corner_radius,
self._canvas.coords("progress_oval_3_b", width - border_width - inner_corner_radius, height - border_width - inner_corner_radius, inner_corner_radius) border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_1), inner_corner_radius)
self._canvas.coords("progress_oval_4_a", border_width + inner_corner_radius, height - border_width - inner_corner_radius, inner_corner_radius) self._canvas.coords("progress_oval_3_b", width - border_width - inner_corner_radius,
self._canvas.coords("progress_oval_4_b", border_width + inner_corner_radius, height - border_width - inner_corner_radius, inner_corner_radius) border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_1), inner_corner_radius)
self._canvas.coords("progress_oval_4_a", border_width + inner_corner_radius,
border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_1), inner_corner_radius)
self._canvas.coords("progress_oval_4_b", border_width + inner_corner_radius,
border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_1), inner_corner_radius)
# set positions of progress rect parts # set positions of progress rect parts
self._canvas.coords("progress_rectangle_1", self._canvas.coords("progress_rectangle_1",
border_width + inner_corner_radius, border_width + inner_corner_radius,
border_width + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value), border_width + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_2),
width - border_width - inner_corner_radius, width - border_width - inner_corner_radius,
height - border_width) border_width + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_1))
self._canvas.coords("progress_rectangle_2", self._canvas.coords("progress_rectangle_2",
border_width, border_width,
border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value), border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_2),
width - border_width, width - border_width,
height - inner_corner_radius - border_width) border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_1))
return requires_recoloring or requires_recoloring_2 return requires_recoloring or requires_recoloring_2
@ -847,7 +855,7 @@ class DrawEngine:
# draw normal progressbar # draw normal progressbar
requires_recoloring = self.__draw_rounded_progress_bar_with_border_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius, requires_recoloring = self.__draw_rounded_progress_bar_with_border_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius,
slider_value, orientation) 0, slider_value, orientation)
# create slider button part # create slider button part
if not self._canvas.find_withtag("slider_parts"): if not self._canvas.find_withtag("slider_parts"):
@ -886,7 +894,7 @@ class DrawEngine:
# draw normal progressbar # draw normal progressbar
requires_recoloring = self.__draw_rounded_progress_bar_with_border_font_shapes(width, height, corner_radius, border_width, inner_corner_radius, requires_recoloring = self.__draw_rounded_progress_bar_with_border_font_shapes(width, height, corner_radius, border_width, inner_corner_radius,
slider_value, orientation) 0, slider_value, orientation)
# create 4 circles (if not needed, then less) # create 4 circles (if not needed, then less)
if not self._canvas.find_withtag("slider_oval_1_a"): if not self._canvas.find_withtag("slider_oval_1_a"):

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

@ -243,6 +243,11 @@ class CTkButton(CTkBaseClass):
else: else:
self.text_label.configure(text=self.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.pop("state") self.state = kwargs.pop("state")
self.set_cursor() self.set_cursor()
@ -362,7 +367,7 @@ class CTkButton(CTkBaseClass):
def clicked(self, event=None): def clicked(self, event=None):
if self.command 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()

View File

@ -20,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',

View File

@ -118,6 +118,7 @@ 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.canvas.delete("checkmark")
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()
@ -176,6 +177,11 @@ class CTkCheckBox(CTkBaseClass):
self.text = kwargs.pop("text") self.text = kwargs.pop("text")
self.text_label.configure(text=self.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.pop("state") self.state = kwargs.pop("state")
self.set_cursor() self.set_cursor()

View File

@ -63,14 +63,9 @@ class CTkComboBox(CTkBaseClass):
else: else:
self.values = values 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, self.dropdown_menu = DropdownMenu(master=self,
values=self.values, values=self.values,
command=self.set, command=self.dropdown_callback,
fg_color=dropdown_color, fg_color=dropdown_color,
hover_color=dropdown_hover_color, hover_color=dropdown_hover_color,
text_color=dropdown_text_color, text_color=dropdown_text_color,
@ -98,8 +93,11 @@ class CTkComboBox(CTkBaseClass):
padx=(max(self.apply_widget_scaling(self.corner_radius), self.apply_widget_scaling(3)), padx=(max(self.apply_widget_scaling(self.corner_radius), self.apply_widget_scaling(3)),
max(self.apply_widget_scaling(self._current_width - left_section_width + 3), self.apply_widget_scaling(3)))) max(self.apply_widget_scaling(self._current_width - left_section_width + 3), self.apply_widget_scaling(3))))
self.entry.delete(0, tkinter.END) # insert default value
self.entry.insert(0, self.current_value) if len(self.values) > 0:
self.entry.insert(0, self.values[0])
else:
self.entry.insert(0, "CTkComboBox")
self.draw() # initial draw self.draw() # initial draw
@ -203,6 +201,10 @@ class CTkComboBox(CTkBaseClass):
self.text_color = kwargs.pop("text_color") self.text_color = kwargs.pop("text_color")
require_redraw = True 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: if "command" in kwargs:
self.command = kwargs.pop("command") self.command = kwargs.pop("command")
@ -264,21 +266,28 @@ class CTkComboBox(CTkBaseClass):
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 set(self, value: str, from_variable_callback: bool = False): def dropdown_callback(self, value: str):
self.current_value = value
if self.state == "readonly": if self.state == "readonly":
self.entry.configure(state="normal") self.entry.configure(state="normal")
self.entry.delete(0, tkinter.END) self.entry.delete(0, tkinter.END)
self.entry.insert(0, self.current_value) self.entry.insert(0, value)
self.entry.configure(state="readonly") self.entry.configure(state="readonly")
else: else:
self.entry.delete(0, tkinter.END) self.entry.delete(0, tkinter.END)
self.entry.insert(0, self.current_value) self.entry.insert(0, value)
if not from_variable_callback: if self.command is not None:
if self.command is not None: self.command(value)
self.command(self.current_value)
def set(self, value: str):
if self.state == "readonly":
self.entry.configure(state="normal")
self.entry.delete(0, tkinter.END)
self.entry.insert(0, value)
self.entry.configure(state="readonly")
else:
self.entry.delete(0, tkinter.END)
self.entry.insert(0, value)
def get(self) -> str: def get(self) -> str:
return self.entry.get() return self.entry.get()

View File

@ -77,7 +77,7 @@ class CTkEntry(CTkBaseClass):
self.entry.bind('<FocusOut>', self.entry_focus_out) self.entry.bind('<FocusOut>', self.entry_focus_out)
self.entry.bind('<FocusIn>', self.entry_focus_in) self.entry.bind('<FocusIn>', self.entry_focus_in)
self.set_placeholder() self.activate_placeholder()
self.draw() self.draw()
def set_scaling(self, *args, **kwargs): def set_scaling(self, *args, **kwargs):
@ -176,6 +176,8 @@ class CTkEntry(CTkBaseClass):
if self.placeholder_text_active: if self.placeholder_text_active:
self.entry.delete(0, tkinter.END) self.entry.delete(0, tkinter.END)
self.entry.insert(0, self.placeholder_text) self.entry.insert(0, self.placeholder_text)
else:
self.activate_placeholder()
if "placeholder_text_color" in kwargs: if "placeholder_text_color" in kwargs:
self.placeholder_text_color = kwargs.pop("placeholder_text_color") self.placeholder_text_color = kwargs.pop("placeholder_text_color")
@ -185,6 +187,10 @@ class CTkEntry(CTkBaseClass):
self.textvariable = kwargs.pop("textvariable") self.textvariable = kwargs.pop("textvariable")
self.entry.configure(textvariable=self.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 "show" in kwargs:
if self.placeholder_text_active: if self.placeholder_text_active:
self.pre_placeholder_arguments["show"] = kwargs.pop("show") self.pre_placeholder_arguments["show"] = kwargs.pop("show")
@ -198,7 +204,7 @@ class CTkEntry(CTkBaseClass):
self.entry.configure(**kwargs) # pass remaining kwargs to entry self.entry.configure(**kwargs) # pass remaining kwargs to entry
def set_placeholder(self): def activate_placeholder(self):
if self.entry.get() == "" and self.placeholder_text is not None and (self.textvariable is None or self.textvariable == ""): 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.placeholder_text_active = True
@ -207,7 +213,7 @@ class CTkEntry(CTkBaseClass):
self.entry.delete(0, tkinter.END) self.entry.delete(0, tkinter.END)
self.entry.insert(0, self.placeholder_text) self.entry.insert(0, self.placeholder_text)
def clear_placeholder(self): def deactivate_placeholder(self):
if self.placeholder_text_active: if self.placeholder_text_active:
self.placeholder_text_active = False self.placeholder_text_active = False
@ -217,19 +223,19 @@ class CTkEntry(CTkBaseClass):
self.entry[argument] = value self.entry[argument] = value
def entry_focus_out(self, event=None): def entry_focus_out(self, event=None):
self.set_placeholder() self.activate_placeholder()
def entry_focus_in(self, event=None): def entry_focus_in(self, event=None):
self.clear_placeholder() self.deactivate_placeholder()
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
self.entry.delete(*args, **kwargs) self.entry.delete(*args, **kwargs)
if self.entry.get() == "": if self.entry.get() == "":
self.set_placeholder() 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)

View File

@ -119,9 +119,14 @@ class CTkLabel(CTkBaseClass):
sticky=text_label_grid_sticky) 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
@ -148,5 +153,7 @@ class CTkLabel(CTkBaseClass):
self.text_label.configure(**kwargs) # pass remaining kwargs to label self.text_label.configure(**kwargs) # pass remaining kwargs to label
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

@ -72,7 +72,7 @@ class CTkOptionMenu(CTkBaseClass):
self.dropdown_menu = DropdownMenu(master=self, self.dropdown_menu = DropdownMenu(master=self,
values=self.values, values=self.values,
command=self.set, command=self.dropdown_callback,
fg_color=dropdown_color, fg_color=dropdown_color,
hover_color=dropdown_hover_color, hover_color=dropdown_hover_color,
text_color=dropdown_text_color, text_color=dropdown_text_color,
@ -124,7 +124,8 @@ class CTkOptionMenu(CTkBaseClass):
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)
self.set(self.variable.get(), from_variable_callback=True) self.current_value = self.variable.get()
self.text_label.configure(text=self.current_value)
def set_scaling(self, *args, **kwargs): def set_scaling(self, *args, **kwargs):
super().set_scaling(*args, **kwargs) super().set_scaling(*args, **kwargs)
@ -210,6 +211,10 @@ class CTkOptionMenu(CTkBaseClass):
self.text_color = kwargs.pop("text_color") self.text_color = kwargs.pop("text_color")
require_redraw = True require_redraw = True
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.command = kwargs.pop("command") self.command = kwargs.pop("command")
@ -221,7 +226,7 @@ class CTkOptionMenu(CTkBaseClass):
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)
self.set(self.variable.get(), from_variable_callback=True) self.set(self.variable.get(), block_set_variable=True)
else: else:
self.variable = None self.variable = None
@ -245,7 +250,8 @@ class CTkOptionMenu(CTkBaseClass):
self.dropdown_menu.configure(text_color=kwargs.pop("dropdown_text_color")) self.dropdown_menu.configure(text_color=kwargs.pop("dropdown_text_color"))
if "dropdown_text_font" in kwargs: if "dropdown_text_font" in kwargs:
self.dropdown_menu.configure(text_font=kwargs.pop("dropdown_text_font")) self.dropdown_text_font = kwargs.pop("dropdown_text_font")
self.dropdown_menu.configure(text_font=self.dropdown_text_font)
if "dynamic_resizing" in kwargs: if "dynamic_resizing" in kwargs:
self.dynamic_resizing = kwargs.pop("dynamic_resizing") self.dynamic_resizing = kwargs.pop("dynamic_resizing")
@ -272,21 +278,29 @@ class CTkOptionMenu(CTkBaseClass):
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:
self.set(self.variable.get(), from_variable_callback=True) self.current_value = self.variable.get()
self.text_label.configure(text=self.current_value)
def set(self, value: str, from_variable_callback: bool = False): def dropdown_callback(self, value: str):
self.current_value = value self.current_value = value
self.text_label.configure(text=self.current_value) self.text_label.configure(text=self.current_value)
if self.variable is not None and not from_variable_callback: if self.variable is not None:
self.variable_callback_blocked = True self.variable_callback_blocked = True
self.variable.set(self.current_value) self.variable.set(self.current_value)
self.variable_callback_blocked = False self.variable_callback_blocked = False
if not from_variable_callback: if self.command is not None:
if self.command is not None: self.command(self.current_value)
self.command(self.current_value)
def set(self, value: str):
self.current_value = value
self.text_label.configure(text=self.current_value)
if self.variable is not None:
self.variable_callback_blocked = True
self.variable.set(self.current_value)
self.variable_callback_blocked = False
def get(self) -> str: def get(self) -> str:
return self.current_value return self.current_value

View File

@ -1,4 +1,5 @@
import tkinter import tkinter
import math
from .ctk_canvas import CTkCanvas from .ctk_canvas import CTkCanvas
from ..theme_manager import ThemeManager from ..theme_manager import ThemeManager
@ -20,6 +21,9 @@ class CTkProgressBar(CTkBaseClass):
height=None, height=None,
border_width="default_theme", border_width="default_theme",
orient="horizontal", orient="horizontal",
mode="determinate",
determinate_speed=1,
indeterminate_speed=1,
**kwargs): **kwargs):
# set default dimensions according to orientation # set default dimensions according to orientation
@ -50,8 +54,14 @@ class CTkProgressBar(CTkBaseClass):
# shape # shape
self.corner_radius = ThemeManager.theme["shape"]["progressbar_corner_radius"] if corner_radius == "default_theme" else corner_radius self.corner_radius = ThemeManager.theme["shape"]["progressbar_corner_radius"] if corner_radius == "default_theme" else corner_radius
self.border_width = ThemeManager.theme["shape"]["progressbar_border_width"] if border_width == "default_theme" else border_width self.border_width = ThemeManager.theme["shape"]["progressbar_border_width"] if border_width == "default_theme" else border_width
self.value = 0.5 self.determinate_value = 0.5 # range 0-1
self.determinate_speed = determinate_speed # range 0-1
self.indeterminate_value = 0 # range 0-inf
self.indeterminate_width = 0.4 # range 0-1
self.indeterminate_speed = indeterminate_speed # range 0-1 to travel in 50ms
self.loop_running = False
self.orient = orient self.orient = orient
self.mode = mode # "determinate" or "indeterminate"
self.grid_rowconfigure(0, weight=1) self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1) self.grid_columnconfigure(0, weight=1)
@ -101,12 +111,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), if self.mode == "determinate":
self.apply_widget_scaling(self._current_height), requires_recoloring = self.draw_engine.draw_rounded_progress_bar_with_border(self.apply_widget_scaling(self._current_width),
self.apply_widget_scaling(self.corner_radius), self.apply_widget_scaling(self._current_height),
self.apply_widget_scaling(self.border_width), self.apply_widget_scaling(self.corner_radius),
self.value, self.apply_widget_scaling(self.border_width),
orientation) 0,
self.determinate_value,
orientation)
else: # indeterminate mode
progress_value = (math.sin(self.indeterminate_value * math.pi / 40) + 1) / 2
progress_value_1 = min(1.0, progress_value + (self.indeterminate_width / 2))
progress_value_2 = max(0.0, progress_value - (self.indeterminate_width / 2))
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.corner_radius),
self.apply_widget_scaling(self.border_width),
progress_value_1,
progress_value_2,
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))
@ -155,6 +179,16 @@ class CTkProgressBar(CTkBaseClass):
del kwargs["variable"] del kwargs["variable"]
if "mode" in kwargs:
self.mode = kwargs.pop("mode")
require_redraw = True
if "determinate_speed" in kwargs:
self.determinate_speed = kwargs.pop("determinate_speed")
if "indeterminate_speed" in kwargs:
self.indeterminate_speed = kwargs.pop("indeterminate_speed")
if "width" in kwargs: if "width" in kwargs:
self.set_dimensions(width=kwargs["width"]) self.set_dimensions(width=kwargs["width"])
del kwargs["width"] del kwargs["width"]
@ -170,16 +204,54 @@ class CTkProgressBar(CTkBaseClass):
self.set(self.variable.get(), from_variable_callback=True) self.set(self.variable.get(), from_variable_callback=True)
def set(self, value, from_variable_callback=False): def set(self, value, from_variable_callback=False):
self.value = value """ set determinate value """
self.determinate_value = value
if self.value > 1: if self.determinate_value > 1:
self.value = 1 self.determinate_value = 1
elif self.value < 0: elif self.determinate_value < 0:
self.value = 0 self.determinate_value = 0
self.draw(no_color_updates=True) self.draw(no_color_updates=True)
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.value) if isinstance(self.variable, tkinter.IntVar) else self.value) self.variable.set(round(self.determinate_value) if isinstance(self.variable, tkinter.IntVar) else self.determinate_value)
self.variable_callback_blocked = False self.variable_callback_blocked = False
def get(self):
""" get determinate value """
return self.determinate_value
def start(self):
""" start indeterminate mode """
if not self.loop_running:
self.loop_running = True
self.internal_loop()
def stop(self):
""" stop indeterminate mode """
self.loop_running = False
def internal_loop(self):
if self.loop_running:
if self.mode == "determinate":
self.determinate_value += self.determinate_speed / 50
if self.determinate_value > 1:
self.determinate_value -= 1
self.draw()
self.after(20, self.internal_loop)
else:
self.indeterminate_value += self.indeterminate_speed
self.draw()
self.after(20, self.internal_loop)
def step(self):
if self.mode == "determinate":
self.determinate_value += self.determinate_speed / 50
if self.determinate_value > 1:
self.determinate_value -= 1
self.draw()
else:
self.indeterminate_value += self.indeterminate_speed
self.draw()

View File

@ -156,6 +156,10 @@ class CTkRadioButton(CTkBaseClass):
self.text = kwargs.pop("text") self.text = kwargs.pop("text")
self.text_label.configure(text=self.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.pop("state") self.state = kwargs.pop("state")
self.set_cursor() self.set_cursor()

View File

@ -228,9 +228,6 @@ class CTkSwitch(CTkBaseClass):
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
@ -242,9 +239,6 @@ class CTkSwitch(CTkBaseClass):
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
@ -272,6 +266,10 @@ class CTkSwitch(CTkBaseClass):
self.text = kwargs.pop("text") self.text = kwargs.pop("text")
self.text_label.configure(text=self.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.pop("state") self.state = kwargs.pop("state")
self.set_cursor() self.set_cursor()

View File

@ -57,6 +57,7 @@ class CTkTextbox(CTkBaseClass):
height=0, height=0,
font=self.text_font, font=self.text_font,
highlightthickness=0, highlightthickness=0,
relief="flat",
insertbackground=ThemeManager.single_color(("black", "white"), self._appearance_mode), insertbackground=ThemeManager.single_color(("black", "white"), self._appearance_mode),
bg=ThemeManager.single_color(self.fg_color, self._appearance_mode), bg=ThemeManager.single_color(self.fg_color, self._appearance_mode),
**kwargs) **kwargs)
@ -160,6 +161,13 @@ class CTkTextbox(CTkBaseClass):
if "height" in kwargs: if "height" in kwargs:
self.set_dimensions(height=kwargs.pop("height")) 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: if "bg_color" in kwargs:
super().configure(bg_color=kwargs.pop("bg_color"), require_redraw=require_redraw) super().configure(bg_color=kwargs.pop("bg_color"), require_redraw=require_redraw)
else: else:

View File

@ -153,7 +153,7 @@ class CTkBaseClass(tkinter.Frame):
# 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, ttk.Label)): # master is ttk widget elif isinstance(master_widget, (ttk.Frame, ttk.LabelFrame, ttk.Notebook, ttk.Label)): # master is ttk widget
try: try:

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

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):
@ -134,37 +168,49 @@ class CTk(tkinter.Tk):
super().geometry(self.apply_geometry_scaling(geometry_string)) super().geometry(self.apply_geometry_scaling(geometry_string))
# update width and height attributes # update width and height attributes
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: else:
return self.reverse_geometry_scaling(super().geometry()) return self.reverse_geometry_scaling(super().geometry())
def apply_geometry_scaling(self, geometry_string): @staticmethod
value_list = re.split(r"[x+-]", geometry_string) def parse_geometry_string(geometry_string: str) -> tuple:
separator_list = re.split(r"\d+", geometry_string) # 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)
if len(value_list) == 2: width = int(result.group(2)) if result.group(2) is not None else None
scaled_width = str(round(int(value_list[0]) * self.window_scaling)) height = int(result.group(3)) if result.group(3) is not None else None
scaled_height = str(round(int(value_list[1]) * self.window_scaling)) x = int(result.group(5)) if result.group(5) is not None else None
return f"{scaled_width}x{scaled_height}" y = int(result.group(6)) if result.group(6) is not None else None
elif len(value_list) == 4:
scaled_width = str(round(int(value_list[0]) * self.window_scaling))
scaled_height = str(round(int(value_list[1]) * self.window_scaling))
return f"{scaled_width}x{scaled_height}{separator_list[1]}{value_list[2]}{separator_list[2]}{value_list[3]}"
def reverse_geometry_scaling(self, scaled_geometry_string): return width, height, x, y
value_list = re.split(r"[x+-]", scaled_geometry_string)
separator_list = re.split(r"\d+", scaled_geometry_string)
if len(value_list) == 2: def apply_geometry_scaling(self, geometry_string: str) -> str:
width = str(round(int(value_list[0]) / self.window_scaling)) width, height, x, y = self.parse_geometry_string(geometry_string)
height = str(round(int(value_list[1]) / self.window_scaling))
return f"{width}x{height}" if x is None and y is None: # no <x> and <y> in geometry_string
elif len(value_list) == 4: return f"{round(width * self.window_scaling)}x{round(height * self.window_scaling)}"
width = str(round(int(value_list[0]) / self.window_scaling))
height = str(round(int(value_list[1]) / self.window_scaling)) elif width is None and height is None: # no <width> and <height> in geometry_string
return f"{width}x{height}{separator_list[1]}{value_list[2]}{separator_list[2]}{value_list[3]}" 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)):
@ -240,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":
@ -270,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:
@ -83,31 +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):
value_list = re.split(r"[x+-]", geometry_string) if geometry_string is not None:
separator_list = re.split(r"\d+", geometry_string) super().geometry(self.apply_geometry_scaling(geometry_string))
if len(value_list) == 2: # update width and height attributes
scaled_width = str(round(int(value_list[0]) * self.window_scaling)) width, height, x, y = self.parse_geometry_string(geometry_string)
scaled_height = str(round(int(value_list[1]) * self.window_scaling)) if width is not None and height is not None:
return f"{scaled_width}x{scaled_height}" self.current_width = max(self.min_width, min(width, self.max_width)) # bound value between min and max
elif len(value_list) == 4: self.current_height = max(self.min_height, min(height, self.max_height))
scaled_width = str(round(int(value_list[0]) * self.window_scaling)) else:
scaled_height = str(round(int(value_list[1]) * self.window_scaling)) return self.reverse_geometry_scaling(super().geometry())
return f"{scaled_width}x{scaled_height}{separator_list[1]}{value_list[2]}{separator_list[2]}{value_list[3]}"
def reverse_geometry_scaling(self, scaled_geometry_string): @staticmethod
value_list = re.split(r"[x+-]", scaled_geometry_string) def parse_geometry_string(geometry_string: str) -> tuple:
separator_list = re.split(r"\d+", scaled_geometry_string) # 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)
if len(value_list) == 2: width = int(result.group(2)) if result.group(2) is not None else None
width = str(round(int(value_list[0]) / self.window_scaling)) height = int(result.group(3)) if result.group(3) is not None else None
height = str(round(int(value_list[1]) / self.window_scaling)) x = int(result.group(5)) if result.group(5) is not None else None
return f"{width}x{height}" y = int(result.group(6)) if result.group(6) is not None else None
elif len(value_list) == 4:
width = str(round(int(value_list[0]) / self.window_scaling)) return width, height, x, y
height = str(round(int(value_list[1]) / self.window_scaling))
return f"{width}x{height}{separator_list[1]}{value_list[2]}{separator_list[2]}{value_list[3]}" 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)):
@ -115,23 +142,22 @@ class CTkToplevel(tkinter.Toplevel):
else: else:
return value return value
def geometry(self, geometry_string: str = None):
if geometry_string is not None:
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))
else:
return self.reverse_geometry_scaling(super().geometry())
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)
@ -223,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()
@ -250,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

@ -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

@ -28,7 +28,6 @@ progressbar_1.pack(pady=12, padx=10)
button_1 = customtkinter.CTkButton(master=frame_1, command=button_callback) button_1 = customtkinter.CTkButton(master=frame_1, command=button_callback)
button_1.pack(pady=12, padx=10) button_1.pack(pady=12, padx=10)
button_1.configure(state='disabled')
slider_1 = customtkinter.CTkSlider(master=frame_1, command=slider_callback, from_=0, to=1) slider_1 = customtkinter.CTkSlider(master=frame_1, command=slider_callback, from_=0, to=1)
slider_1.pack(pady=12, padx=10) slider_1.pack(pady=12, padx=10)

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.5.7" current = "4.6.0"
# 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,6 +1,6 @@
[metadata] [metadata]
name = customtkinter name = customtkinter
version = 4.5.7 version = 4.6.0
description = Create modern looking GUIs with Python description = Create modern looking GUIs with Python
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 = 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

View File

@ -37,9 +37,9 @@ class App(customtkinter.CTk):
self.appearance_mode_optionemenu = customtkinter.CTkOptionMenu(self.sidebar_frame, values=["Light", "Dark", "System"], self.appearance_mode_optionemenu = customtkinter.CTkOptionMenu(self.sidebar_frame, values=["Light", "Dark", "System"],
command=self.change_appearance_mode) command=self.change_appearance_mode)
self.appearance_mode_optionemenu.grid(row=6, column=0, padx=20, pady=(10, 10)) self.appearance_mode_optionemenu.grid(row=6, column=0, padx=20, pady=(10, 10))
self.scaling_label = customtkinter.CTkLabel(self.sidebar_frame, text="Widget Scaling:", anchor="w") 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_label.grid(row=7, column=0, padx=20, pady=(10, 0))
self.scaling_optionemenu = customtkinter.CTkOptionMenu(self.sidebar_frame, values=["90%", "100%", "110%"], self.scaling_optionemenu = customtkinter.CTkOptionMenu(self.sidebar_frame, values=["80%", "90%", "100%", "110%", "120%"],
command=self.change_scaling) command=self.change_scaling)
self.scaling_optionemenu.grid(row=8, column=0, padx=20, pady=(10, 20)) self.scaling_optionemenu.grid(row=8, column=0, padx=20, pady=(10, 20))
@ -87,7 +87,7 @@ class App(customtkinter.CTk):
self.checkbox_1.grid(row=1, column=0, pady=(20, 10), padx=20, sticky="n") 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 = customtkinter.CTkCheckBox(master=self.checkbox_slider_frame)
self.checkbox_2.grid(row=2, column=0, pady=10, padx=20, sticky="n") 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 = customtkinter.CTkSwitch(master=self.checkbox_slider_frame, command=lambda: print("switch 1 toggle"))
self.switch_1.grid(row=3, column=0, pady=10, padx=20, sticky="n") 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 = customtkinter.CTkSwitch(master=self.checkbox_slider_frame)
self.switch_2.grid(row=4, column=0, pady=(10, 20), padx=20, sticky="n") self.switch_2.grid(row=4, column=0, pady=(10, 20), padx=20, sticky="n")
@ -99,14 +99,14 @@ class App(customtkinter.CTk):
self.slider_progressbar_frame.grid_rowconfigure(3, weight=1) self.slider_progressbar_frame.grid_rowconfigure(3, weight=1)
self.progressbar_1 = customtkinter.CTkProgressBar(self.slider_progressbar_frame) 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.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.progressbar_2 = customtkinter.CTkProgressBar(self.slider_progressbar_frame)
self.slider_1.grid(row=1, column=0, padx=(20, 10), pady=(10, 10), sticky="ew") self.progressbar_2.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_1 = customtkinter.CTkSlider(self.slider_progressbar_frame, from_=0, to=1, number_of_steps=4)
self.slider_2.grid(row=2, column=0, padx=(20, 10), pady=(10, 10), sticky="ew") self.slider_1.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_2 = 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.slider_2.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_3 = 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") self.progressbar_3.grid(row=0, column=2, rowspan=4, padx=(10, 20), pady=(10, 10), sticky="ns")
# set default values # set default values
self.sidebar_button_3.configure(state="disabled", text="Disabled CTkButton") self.sidebar_button_3.configure(state="disabled", text="Disabled CTkButton")
@ -119,10 +119,11 @@ class App(customtkinter.CTk):
self.scaling_optionemenu.set("100%") self.scaling_optionemenu.set("100%")
self.optionmenu_1.set("CTkOptionmenu") self.optionmenu_1.set("CTkOptionmenu")
self.combobox_1.set("CTkComboBox") self.combobox_1.set("CTkComboBox")
self.textbox.insert("1.0", 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.")
"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.slider_1.configure(command=self.progressbar_2.set)
#self.textbox.tag_add("headline", "1.0", "1.end") self.slider_2.configure(command=self.progressbar_3.set)
#self.textbox.tag_config("headline", foreground="red") self.progressbar_1.configure(mode="indeterminnate")
self.progressbar_1.start()
def open_input_dialog(self): def open_input_dialog(self):
dialog = customtkinter.CTkInputDialog(master=None, text="Type in a number:", title="CTkInputDialog") dialog = customtkinter.CTkInputDialog(master=None, text="Type in a number:", title="CTkInputDialog")

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

@ -27,10 +27,10 @@ optionmenu_2 = customtkinter.CTkOptionMenu(app, variable=variable, values=countr
dynamic_resizing=False) dynamic_resizing=False)
optionmenu_2.pack(pady=20, padx=10) optionmenu_2.pack(pady=20, padx=10)
combobox_tk = ttk.Combobox(app, values=countries) combobox_tk = ttk.Combobox(app, values=countries, textvariable=variable)
combobox_tk.pack(pady=10, padx=10) combobox_tk.pack(pady=10, padx=10)
combobox_1 = customtkinter.CTkComboBox(app, variable=None, values=countries, command=select_callback, width=300) combobox_1 = customtkinter.CTkComboBox(app, variable=variable, values=countries, command=select_callback, width=300)
combobox_1.pack(pady=20, padx=10) combobox_1.pack(pady=20, padx=10)
def set_new_scaling(scaling): def set_new_scaling(scaling):

View File

@ -0,0 +1,47 @@
import customtkinter
import tkinter.ttk as ttk
app = customtkinter.CTk()
app.geometry("400x600")
p1 = customtkinter.CTkProgressBar(app)
p1.pack(pady=20)
p2 = ttk.Progressbar(app)
p2.pack(pady=20)
s1 = customtkinter.CTkSlider(app, command=p1.set)
s1.pack(pady=20)
def switch_func():
if sw1.get() == 1:
p1.configure(mode="indeterminate")
p2.configure(mode="indeterminate")
else:
p1.configure(mode="determinate")
p2.configure(mode="determinate")
def start():
p1.start()
p2.start()
def stop():
p1.stop()
p2.stop()
def step():
p1.step()
p2.step(10)
sw1 = customtkinter.CTkSwitch(app, text="intermediate mode", command=switch_func)
sw1.pack(pady=20)
b1 = customtkinter.CTkButton(app, text="start", command=start)
b1.pack(pady=20)
b2 = customtkinter.CTkButton(app, text="stop", command=stop)
b2.pack(pady=20)
b3 = customtkinter.CTkButton(app, text="step", command=step)
b3.pack(pady=20)
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()