added orientation for CTkSLider, CTkProgressBar

This commit is contained in:
Tom Schimansky
2022-05-16 16:51:19 +02:00
parent a688d07b2a
commit b21c3fa19a
45 changed files with 458 additions and 63 deletions

View File

@ -1,5 +1,6 @@
__version__ = "3.12"
# import widgets
from .widgets.ctk_button import CTkButton
from .widgets.ctk_checkbox import CTkCheckBox
from .widgets.ctk_entry import CTkEntry
@ -11,47 +12,22 @@ from .widgets.ctk_radiobutton import CTkRadioButton
from .widgets.ctk_canvas import CTkCanvas
from .widgets.ctk_switch import CTkSwitch
# import windows
from .windows.ctk_tk import CTk
from .windows.ctk_toplevel import CTkToplevel
from .windows.ctk_input_dialog import CTkInputDialog
# import other classes
from .ctk_settings import CTkSettings
from .appearance_mode_tracker import AppearanceModeTracker
from .theme_manager import CTkThemeManager
from .ctk_theme_manager import CTkThemeManager
from .scaling_tracker import ScalingTracker
from distutils.version import StrictVersion as Version
import tkinter
import os
import sys
import shutil
def enable_macos_darkmode():
if sys.platform == "darwin": # macOS
if Version(tkinter.Tcl().call("info", "patchlevel")) >= Version("8.6.9"): # Tcl/Tk >= 8.6.9
os.system("defaults write -g NSRequiresAquaSystemAppearance -bool No")
sys.stderr.write("WARNING (customtkinter.enable_macos_darkmode): " +
"This command forces macOS dark-mode on all programs. " +
"This can cause bugs on some other programs.\n" +
"Disable it by calling customtkinter.disable_macos_darkmode() at the end of the program.\n")
else:
sys.stderr.write("WARNING (customtkinter.enable_macos_darkmode): " +
"Currently this works only with anaconda python version (Tcl/Tk >= 8.6.9).\n" +
"(python.org Tcl/Tk version is only 8.6.8)\n")
else:
sys.stderr.write("WARNING (customtkinter.enable_macos_darkmode): " +
"System is not macOS, but the following: {}\n".format(sys.platform))
def disable_macos_darkmode():
if sys.platform == "darwin": # macOS
if Version(tkinter.Tcl().call("info", "patchlevel")) >= Version("8.6.9"): # Tcl/Tk >= 8.6.9
os.system("defaults delete -g NSRequiresAquaSystemAppearance")
# This command reverts the dark-mode setting for all programs.
def set_appearance_mode(mode_string):
AppearanceModeTracker.set_appearance_mode(mode_string)

View File

@ -16,7 +16,7 @@
"text": ["gray12", "gray90"],
"text_disabled": ["gray60", "gray50"],
"text_button_disabled": ["gray40", "gray74"],
"progressbar": ["#6B6B6B", "gray6"],
"progressbar": ["#6B6B6B", "gray0"],
"progressbar_progress": ["#608BD5", "#395E9C"],
"progressbar_border": ["gray", "gray"],
"slider": ["#6B6B6B", "gray6"],
@ -66,4 +66,4 @@
"switch_button_corner_radius": 1000,
"switch_button_length": 0
}
}
}

View File

@ -568,6 +568,15 @@ class CTkDrawEngine:
slider_x_position - (button_length / 2), height - button_corner_radius)
self._canvas.itemconfig("slider_line_1",
width=button_corner_radius * 2)
elif orientation == "s":
slider_y_position = corner_radius + (button_length / 2) + (height - 2 * corner_radius - button_length) * (1 - slider_value)
self._canvas.coords("slider_line_1",
button_corner_radius, slider_y_position - (button_length / 2),
button_corner_radius, slider_y_position + (button_length / 2),
width - button_corner_radius, slider_y_position + (button_length / 2),
width - button_corner_radius, slider_y_position - (button_length / 2))
self._canvas.itemconfig("slider_line_1",
width=button_corner_radius * 2)
return requires_recoloring

View File

@ -81,9 +81,24 @@ class ScalingTracker:
if window_root not in cls.window_dpi_scaling_dict:
cls.window_dpi_scaling_dict[window_root] = cls.get_window_dpi_scaling(window_root)
if not cls.update_loop_running:
window_root.after(100, cls.check_dpi_scaling)
cls.update_loop_running = True
#if not cls.update_loop_running:
# window_root.after(100, cls.check_dpi_scaling)
# cls.update_loop_running = True
@classmethod
def remove_widget(cls, widget_callback, widget):
window_root = cls.get_window_root_of_widget(widget)
try:
cls.window_widgets_dict[window_root].remove(widget_callback)
except:
pass
@classmethod
def remove_window(cls, window_callback, window):
try:
del cls.window_widgets_dict[window]
except:
pass
@classmethod
def add_window(cls, window_callback, window):
@ -133,6 +148,9 @@ class ScalingTracker:
@classmethod
def check_dpi_scaling(cls):
# check for every window if scaling value changed
# (not implemented yet)
# find an existing tkinter object for the next call of .after()
for root_tk in cls.window_widgets_dict.keys():
try:

View File

@ -3,7 +3,7 @@ import sys
import math
from .ctk_canvas import CTkCanvas
from ..theme_manager import CTkThemeManager
from ..ctk_theme_manager import CTkThemeManager
from ..ctk_settings import CTkSettings
from ..ctk_draw_engine import CTkDrawEngine
from .widget_base_class import CTkBaseClass

View File

@ -2,7 +2,7 @@ import tkinter
import sys
from .ctk_canvas import CTkCanvas
from ..theme_manager import CTkThemeManager
from ..ctk_theme_manager import CTkThemeManager
from ..ctk_settings import CTkSettings
from ..ctk_draw_engine import CTkDrawEngine
from .widget_base_class import CTkBaseClass
@ -325,7 +325,10 @@ class CTkCheckBox(CTkBaseClass):
self.variable_callback_blocked = False
if self.function is not None:
self.function()
try:
self.function()
except:
pass
def deselect(self, from_variable_callback=False):
self.check_state = False
@ -337,7 +340,10 @@ class CTkCheckBox(CTkBaseClass):
self.variable_callback_blocked = False
if self.function is not None:
self.function()
try:
self.function()
except:
pass
def get(self):
return self.onvalue if self.check_state is True else self.offvalue

View File

@ -1,7 +1,7 @@
import tkinter
from .ctk_canvas import CTkCanvas
from ..theme_manager import CTkThemeManager
from ..ctk_theme_manager import CTkThemeManager
from ..ctk_settings import CTkSettings
from ..ctk_draw_engine import CTkDrawEngine
from .widget_base_class import CTkBaseClass

View File

@ -1,7 +1,7 @@
import tkinter
from .ctk_canvas import CTkCanvas
from ..theme_manager import CTkThemeManager
from ..ctk_theme_manager import CTkThemeManager
from ..ctk_settings import CTkSettings
from ..ctk_draw_engine import CTkDrawEngine
from .widget_base_class import CTkBaseClass

View File

@ -1,7 +1,7 @@
import tkinter
from .ctk_canvas import CTkCanvas
from ..theme_manager import CTkThemeManager
from ..ctk_theme_manager import CTkThemeManager
from ..ctk_settings import CTkSettings
from ..ctk_draw_engine import CTkDrawEngine
from .widget_base_class import CTkBaseClass

View File

@ -1,7 +1,7 @@
import tkinter
from .ctk_canvas import CTkCanvas
from ..theme_manager import CTkThemeManager
from ..ctk_theme_manager import CTkThemeManager
from ..ctk_draw_engine import CTkDrawEngine
from ..ctk_settings import CTkSettings
from .widget_base_class import CTkBaseClass
@ -17,11 +17,24 @@ class CTkProgressBar(CTkBaseClass):
fg_color="default_theme",
progress_color="default_theme",
corner_radius="default_theme",
width=200,
height=8,
width=None,
height=None,
border_width="default_theme",
orient="horizontal",
**kwargs):
# set default dimensions according to orientation
if width is None:
if orient.lower() == "vertical":
width = 8
else:
width = 200
if height is None:
if orient.lower() == "vertical":
height = 200
else:
height = 8
# transfer basic functionality (bg_color, size, appearance_mode, scaling) to CTkBaseClass
super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs)
@ -39,6 +52,7 @@ class CTkProgressBar(CTkBaseClass):
self.corner_radius = CTkThemeManager.theme["shape"]["progressbar_corner_radius"] if corner_radius == "default_theme" else corner_radius
self.border_width = CTkThemeManager.theme["shape"]["progressbar_border_width"] if border_width == "default_theme" else border_width
self.value = 0.5
self.orient = orient
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
@ -74,11 +88,19 @@ class CTkProgressBar(CTkBaseClass):
super().destroy()
def draw(self, no_color_updates=False):
if self.orient.lower() == "horizontal":
orientation = "w"
elif self.orient.lower() == "vertical":
orientation = "s"
else:
orientation = "w"
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),
self.value, "w")
self.value,
orientation)
if no_color_updates is False or requires_recoloring:
self.canvas.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))

View File

@ -2,7 +2,7 @@ import tkinter
import sys
from .ctk_canvas import CTkCanvas
from ..theme_manager import CTkThemeManager
from ..ctk_theme_manager import CTkThemeManager
from ..ctk_settings import CTkSettings
from ..ctk_draw_engine import CTkDrawEngine
from .widget_base_class import CTkBaseClass

View File

@ -2,7 +2,7 @@ import tkinter
import sys
from .ctk_canvas import CTkCanvas
from ..theme_manager import CTkThemeManager
from ..ctk_theme_manager import CTkThemeManager
from ..ctk_settings import CTkSettings
from ..ctk_draw_engine import CTkDrawEngine
from .widget_base_class import CTkBaseClass
@ -21,16 +21,29 @@ class CTkSlider(CTkBaseClass):
from_=0,
to=1,
number_of_steps=None,
width=160,
height=16,
width=None,
height=None,
corner_radius="default_theme",
button_corner_radius="default_theme",
border_width="default_theme",
button_length="default_theme",
command=None,
variable=None,
orient="horizontal",
**kwargs):
# set default dimensions according to orientation
if width is None:
if orient.lower() == "vertical":
width = 16
else:
width = 200
if height is None:
if orient.lower() == "vertical":
height = 200
else:
height = 16
# transfer basic functionality (bg_color, size, appearance_mode, scaling) to CTkBaseClass
super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs)
@ -47,6 +60,7 @@ class CTkSlider(CTkBaseClass):
self.border_width = CTkThemeManager.theme["shape"]["slider_border_width"] if border_width == "default_theme" else border_width
self.button_length = CTkThemeManager.theme["shape"]["slider_button_length"] if button_length == "default_theme" else button_length
self.value = 0.5 # initial value of slider in percent
self.orient = orient
self.hover_state = False
self.from_ = from_
self.to = to
@ -110,13 +124,20 @@ class CTkSlider(CTkBaseClass):
self.configure(cursor="hand2")
def draw(self, no_color_updates=False):
if self.orient.lower() == "horizontal":
orientation = "w"
elif self.orient.lower() == "vertical":
orientation = "s"
else:
orientation = "w"
requires_recoloring = self.draw_engine.draw_rounded_slider_with_border_and_button(self.apply_widget_scaling(self.current_width),
self.apply_widget_scaling(self.current_height),
self.apply_widget_scaling(self.corner_radius),
self.apply_widget_scaling(self.border_width),
self.apply_widget_scaling(self.button_length),
self.apply_widget_scaling(self.button_corner_radius),
self.value, "w")
self.value, orientation)
if no_color_updates is False or requires_recoloring:
self.canvas.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
@ -142,7 +163,10 @@ class CTkSlider(CTkBaseClass):
outline=CTkThemeManager.single_color(self.button_color, self.appearance_mode))
def clicked(self, event=None):
self.value = (event.x / self.current_width) / self.widget_scaling
if self.orient.lower() == "horizontal":
self.value = (event.x / self.current_width) / self.widget_scaling
else:
self.value = 1 - (event.y / self.current_height) / self.widget_scaling
if self.value > 1:
self.value = 1

View File

@ -2,7 +2,7 @@ import tkinter
import sys
from .ctk_canvas import CTkCanvas
from ..theme_manager import CTkThemeManager
from ..ctk_theme_manager import CTkThemeManager
from ..ctk_settings import CTkSettings
from ..ctk_draw_engine import CTkDrawEngine
from .widget_base_class import CTkBaseClass

View File

@ -9,7 +9,7 @@ from ..windows.ctk_tk import CTk
from ..windows.ctk_toplevel import CTkToplevel
from ..appearance_mode_tracker import AppearanceModeTracker
from ..scaling_tracker import ScalingTracker
from ..theme_manager import CTkThemeManager
from ..ctk_theme_manager import CTkThemeManager
class CTkBaseClass(tkinter.Frame):

View File

@ -7,7 +7,7 @@ from ..widgets.ctk_frame import CTkFrame
from ..windows.ctk_toplevel import CTkToplevel
from ..widgets.ctk_button import CTkButton
from ..appearance_mode_tracker import AppearanceModeTracker
from ..theme_manager import CTkThemeManager
from ..ctk_theme_manager import CTkThemeManager
class CTkInputDialog:

View File

@ -8,7 +8,7 @@ import re
from typing import Union, Tuple
from ..appearance_mode_tracker import AppearanceModeTracker
from ..theme_manager import CTkThemeManager
from ..ctk_theme_manager import CTkThemeManager
from ..scaling_tracker import ScalingTracker
from ..ctk_settings import CTkSettings
@ -33,10 +33,10 @@ class CTk(tkinter.Tk):
self.current_width = 600 # initial window size, always without scaling
self.current_height = 500
self.min_width: Union[int, None] = None
self.min_height: Union[int, None] = None
self.max_width: Union[int, None] = None
self.max_height: Union[int, None] = None
self.min_width: int = 0
self.min_height: int = 0
self.max_width: int = 1_000_000
self.max_height: int = 1_000_000
self.last_resizable_args: Union[Tuple[list, dict], None] = None # (args, kwargs)
self.fg_color = CTkThemeManager.theme["color"]["window_bg_color"] if fg_color == "default_theme" else fg_color
@ -86,7 +86,7 @@ class CTk(tkinter.Tk):
# set scaled min, max sizes and reapply resizable
if self.last_resizable_args is not None:
super().resizable(self.last_resizable_args[0], self.last_resizable_args[1]) # args, kwargs
super().resizable(*self.last_resizable_args[0], **self.last_resizable_args[1]) # args, kwargs
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))
if self.max_width is not None or self.max_height is not None:
@ -94,6 +94,7 @@ class CTk(tkinter.Tk):
def destroy(self):
AppearanceModeTracker.remove(self.set_appearance_mode)
ScalingTracker.remove_window(self.set_scaling, self)
self.disable_macos_dark_title_bar()
super().destroy()
@ -122,11 +123,15 @@ class CTk(tkinter.Tk):
def minsize(self, width=None, height=None):
self.min_width = width
self.min_height = height
if self.current_width < width: self.current_width = width
if self.current_height < height: self.current_height = height
super().minsize(self.apply_window_scaling(self.min_width), self.apply_window_scaling(self.min_height))
def maxsize(self, width=None, height=None):
self.max_width = width
self.max_height = height
if self.current_width > width: self.current_width = width
if self.current_height > height: self.current_height = height
super().maxsize(self.apply_window_scaling(self.max_width), self.apply_window_scaling(self.max_height))
def geometry(self, geometry_string):
@ -134,7 +139,8 @@ class CTk(tkinter.Tk):
# 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, self.current_height = numbers[0], numbers[1]
self.current_width = max(self.min_width, min(numbers[0], self.max_width)) # bound value between min and max
self.current_height = max(self.min_height, min(numbers[1], self.max_height))
def apply_geometry_scaling(self, geometry_string):
numbers = list(map(int, re.split(r"[x+]", geometry_string))) # split geometry string into list of numbers

View File

@ -5,9 +5,10 @@ import os
import platform
import ctypes
import re
from typing import Union, Tuple
from ..appearance_mode_tracker import AppearanceModeTracker
from ..theme_manager import CTkThemeManager
from ..ctk_theme_manager import CTkThemeManager
from ..ctk_settings import CTkSettings
from ..scaling_tracker import ScalingTracker
@ -27,6 +28,11 @@ class CTkToplevel(tkinter.Toplevel):
self.current_width = 600 # initial window size, always without scaling
self.current_height = 500
self.min_width: int = 0
self.min_height: int = 0
self.max_width: int = 1_000_000
self.max_height: int = 1_000_000
self.last_resizable_args: Union[Tuple[list, dict], None] = None # (args, kwargs)
self.fg_color = CTkThemeManager.theme["color"]["window_bg_color"] if fg_color == "default_theme" else fg_color
@ -59,9 +65,25 @@ class CTkToplevel(tkinter.Toplevel):
def set_scaling(self, new_widget_scaling, new_spacing_scaling, new_window_scaling):
self.window_scaling = new_window_scaling
# reset min, max and resizable constraints for applying scaling
if self.last_resizable_args is not None:
super().resizable(True, True)
if self.min_width is not None or self.min_height is not None:
super().minsize(0, 0)
if self.max_width is not None or self.max_height is not None:
super().maxsize(1_000_000, 1_000_000)
# set new window size by applying scaling to the current window size
self.geometry(f"{self.current_width}x{self.current_height}")
# set scaled min, max sizes and reapply resizable
if self.last_resizable_args is not None:
super().resizable(*self.last_resizable_args[0], **self.last_resizable_args[1]) # args, kwargs
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))
if self.max_width is not None or self.max_height is not None:
super().maxsize(self.apply_window_scaling(self.max_width), self.apply_window_scaling(self.max_height))
def apply_geometry_scaling(self, geometry_string):
numbers = list(map(int, re.split(r"[x+]", geometry_string))) # split geometry string into list of numbers
@ -78,7 +100,7 @@ class CTkToplevel(tkinter.Toplevel):
def apply_window_scaling(self, value):
if isinstance(value, (int, float)):
return value * self.window_scaling
return int(value * self.window_scaling)
else:
return value
@ -87,15 +109,18 @@ class CTkToplevel(tkinter.Toplevel):
# 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, self.current_height = numbers[0], numbers[1]
self.current_width = max(self.min_width, min(numbers[0], self.max_width)) # bound value between min and max
self.current_height = max(self.min_height, min(numbers[1], self.max_height))
def destroy(self):
AppearanceModeTracker.remove(self.set_appearance_mode)
ScalingTracker.remove_window(self.set_scaling, self)
self.disable_macos_dark_title_bar()
super().destroy()
def resizable(self, *args, **kwargs):
super().resizable(*args, **kwargs)
self.last_resizable_args = (args, kwargs)
if sys.platform.startswith("win"):
if self.appearance_mode == 1:
@ -103,6 +128,20 @@ class CTkToplevel(tkinter.Toplevel):
else:
self.windows_set_titlebar_color("light")
def minsize(self, width=None, height=None):
self.min_width = width
self.min_height = height
if self.current_width < width: self.current_width = width
if self.current_height < height: self.current_height = height
super().minsize(self.apply_window_scaling(self.min_width), self.apply_window_scaling(self.min_height))
def maxsize(self, width=None, height=None):
self.max_width = width
self.max_height = height
if self.current_width > width: self.current_width = width
if self.current_height > height: self.current_height = height
super().maxsize(self.apply_window_scaling(self.max_width), self.apply_window_scaling(self.max_height))
def config(self, *args, **kwargs):
self.configure(*args, **kwargs)