diff --git a/customtkinter/__init__.py b/customtkinter/__init__.py index 2eb42e5..35ae416 100644 --- a/customtkinter/__init__.py +++ b/customtkinter/__init__.py @@ -6,8 +6,8 @@ from .customtkinter_frame import CTkFrame from .customtkinter_progressbar import CTkProgressBar from .customtkinter_label import CTkLabel from .customtkinter_entry import CTkEntry -from .customtkinter_dialog import CTkDialog from .customtkinter_checkbox import CTkCheckBox +from .customtkinter_dialog import CTkDialog from .customtkinter_tk import CTk from .customtkinter_canvas import CTkCanvas from .customtkinter_toplevel import CTkToplevel @@ -62,24 +62,24 @@ def set_default_color_theme(color_string): CTkThemeManager.initialize_color_theme(color_string) -import warnings -warnings.simplefilter("ignore", category=UserWarning) +if not sys.platform == "darwin": + import warnings + warnings.simplefilter("ignore", category=UserWarning) -import pyglet.font + import pyglet.font + # load text fonts and custom font with circle shapes for round corner rendering + script_directory = os.path.dirname(os.path.abspath(__file__)) + pyglet.font.add_file(os.path.join(script_directory, "assets", "CustomTkinter_shapes_font-Regular.otf")) + pyglet.font.add_file(os.path.join(script_directory, "assets", "Roboto", "Roboto-Regular.ttf")) + pyglet.font.add_file(os.path.join(script_directory, "assets", "Roboto", "Roboto-Medium.ttf")) + CTkSettings.circle_font_is_ready = pyglet.font.have_font("CustomTkinter_shapes_font") -# load text fonts and custom font with circle shapes for round corner rendering -script_directory = os.path.dirname(os.path.abspath(__file__)) -pyglet.font.add_file(os.path.join(script_directory, "assets", "CustomTkinter_shapes_font-Regular.otf")) -pyglet.font.add_file(os.path.join(script_directory, "assets", "Roboto", "Roboto-Regular.ttf")) -pyglet.font.add_file(os.path.join(script_directory, "assets", "Roboto", "Roboto-Medium.ttf")) -CTkSettings.circle_font_is_ready = pyglet.font.have_font("CustomTkinter_shapes_font") + warnings.simplefilter("default") -warnings.simplefilter("default") - -# correct drawing method if font could not be loaded -if not CTkSettings.circle_font_is_ready: - if CTkSettings.preferred_drawing_method == "font_shapes": - sys.stderr.write("WARNING (customtkinter.CTkSettings): " + - "Preferred drawing method 'font_shapes' can not be used because the font file could not be loaded. Using 'circle_shapes' instead.") - CTkSettings.preferred_drawing_method = "circle_shapes" + # correct drawing method if font could not be loaded + if not CTkSettings.circle_font_is_ready: + if CTkSettings.preferred_drawing_method == "font_shapes": + sys.stderr.write("WARNING (customtkinter.CTkSettings): " + + "Preferred drawing method 'font_shapes' can not be used because the font file could not be loaded. Using 'circle_shapes' instead.") + CTkSettings.preferred_drawing_method = "circle_shapes" diff --git a/customtkinter/assets/themes/blue.json b/customtkinter/assets/themes/blue.json new file mode 100644 index 0000000..44743df --- /dev/null +++ b/customtkinter/assets/themes/blue.json @@ -0,0 +1,55 @@ +{ + "color": { + "window_bg_color": ["#ECECEC", "#323232"], + "button": ["#64A1D2", "#1C94CF"], + "button_hover": ["#A7C2E0", "#5FB4DD"], + "button_border": ["#A7C2E0", "#5FB4DD"], + "checkbox_border": ["black", "#ededed"], + "entry": ["gray95", "#222222"], + "entry_border": ["gray65", "gray40"], + "entry_placeholder_text": ["gray52", "gray62"], + "frame_border": ["#A7C2E0", "#5FB4DD"], + "frame_low": ["#D4D5D6", "#3F3F3F"], + "frame_high": ["#BFBEC1", "#505050"], + "label": ["white", "#626061"], + "text": ["gray18", "gray90"], + "progressbar": ["#6B6B6B", "#222222"], + "progressbar_progress": ["red", "red"], + "progressbar_border": ["gray", "gray"], + "slider": ["#6B6B6B", "#222222"], + "slider_progress": ["#A5A6A5", "#555555"], + "slider_button": ["#64A1D2", "#1C94CF"], + "slider_button_hover": ["#A7C2E0", "#5FB4DD"], + "darken_factor": 0.8 + }, + + "text": { + "macOS": { + "font": "SF Display", + "size": -14 + }, + "Windows": { + "font": "Roboto", + "size": -14 + }, + "Linux": { + "font": "Roboto", + "size": -14 + } + + }, + "shape": { + "button_corner_radius": 8, + "button_border_width": 2, + "checkbox_corner_radius": 6, + "checkbox_border_width": 3, + "entry_border_width": 2, + "frame_corner_radius": 10, + "frame_border_width": 0, + "label_corner_radius": 8, + "progressbar_border_width": 2, + "progressbar_corner_radius": 1000, + "slider_border_width": 4, + "slider_corner_radius": 3 + } +} \ No newline at end of file diff --git a/customtkinter/customtkinter_button.py b/customtkinter/customtkinter_button.py index f7a9954..92b4e09 100644 --- a/customtkinter/customtkinter_button.py +++ b/customtkinter/customtkinter_button.py @@ -18,12 +18,12 @@ class CTkButton(tkinter.Frame): fg_color="default_theme", hover_color="default_theme", border_color="default_theme", - border_width=0, + border_width="default_theme", command=None, textvariable=None, width=120, height=30, - corner_radius=8, + corner_radius="default_theme", text_font="default_theme", text_color="default_theme", text="CTkButton", @@ -62,16 +62,16 @@ class CTkButton(tkinter.Frame): # color variables self.bg_color = self.detect_color_of_master() if bg_color is None else bg_color - self.fg_color = CTkThemeManager.MAIN_COLOR if fg_color == "default_theme" else fg_color - self.hover_color = CTkThemeManager.MAIN_HOVER_COLOR if hover_color == "default_theme" else hover_color - self.border_color = CTkThemeManager.CHECKBOX_LINES_COLOR if border_color == "default_theme" else border_color + self.fg_color = CTkThemeManager.theme["color"]["button"] if fg_color == "default_theme" else fg_color + self.hover_color = CTkThemeManager.theme["color"]["button_hover"] if hover_color == "default_theme" else hover_color + self.border_color = CTkThemeManager.theme["color"]["button_hover"] if border_color == "default_theme" else border_color # shape and size self.width = width self.height = height self.configure(width=self.width, height=self.height) - self.corner_radius = corner_radius - self.border_width = border_width + self.corner_radius = CTkThemeManager.theme["shape"]["button_corner_radius"] if corner_radius == "default_theme" else corner_radius + self.border_width = CTkThemeManager.theme["shape"]["button_border_width"] if border_width == "default_theme" else border_width if self.corner_radius * 2 > self.height: self.corner_radius = self.height / 2 @@ -88,8 +88,8 @@ class CTkButton(tkinter.Frame): self.image_label = None self.text = text self.text_label = None - self.text_color = CTkThemeManager.TEXT_COLOR if text_color == "default_theme" else text_color - self.text_font = (CTkThemeManager.TEXT_FONT_NAME, CTkThemeManager.TEXT_FONT_SIZE) if text_font == "default_theme" else text_font + self.text_color = CTkThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color + self.text_font = (CTkThemeManager.theme["text"]["font"], CTkThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font # callback and hover functionality self.function = command diff --git a/customtkinter/customtkinter_checkbox.py b/customtkinter/customtkinter_checkbox.py index 60ffb04..8464154 100644 --- a/customtkinter/customtkinter_checkbox.py +++ b/customtkinter/customtkinter_checkbox.py @@ -1,13 +1,13 @@ import tkinter import sys -from .customtkinter_tk import CTk -from .customtkinter_frame import CTkFrame -from .appearance_mode_tracker import AppearanceModeTracker -from .customtkinter_theme_manager import CTkThemeManager -from .customtkinter_canvas import CTkCanvas -from .customtkinter_settings import CTkSettings -from .customtkinter_draw_engine import DrawEngine +from customtkinter.customtkinter_tk import CTk +from customtkinter.customtkinter_frame import CTkFrame +from customtkinter.appearance_mode_tracker import AppearanceModeTracker +from customtkinter.customtkinter_theme_manager import CTkThemeManager +from customtkinter.customtkinter_canvas import CTkCanvas +from customtkinter.customtkinter_settings import CTkSettings +from customtkinter.customtkinter_draw_engine import DrawEngine class CTkCheckBox(tkinter.Frame): @@ -18,10 +18,10 @@ class CTkCheckBox(tkinter.Frame): fg_color="default_theme", hover_color="default_theme", border_color="default_theme", - border_width=3, - width=25, - height=25, - corner_radius=6, + border_width="default_theme", + width=24, + height=24, + corner_radius="default_theme", text_font="default_theme", text_color="default_theme", text="CTkCheckBox", @@ -60,29 +60,28 @@ class CTkCheckBox(tkinter.Frame): self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark" self.bg_color = self.detect_color_of_master() if bg_color is None else bg_color - self.fg_color = CTkThemeManager.MAIN_COLOR if fg_color == "default_theme" else fg_color - self.hover_color = CTkThemeManager.MAIN_HOVER_COLOR if hover_color == "default_theme" else hover_color - self.border_color = CTkThemeManager.CHECKBOX_LINES_COLOR if border_color == "default_theme" else border_color + self.fg_color = CTkThemeManager.theme["color"]["button"] if fg_color == "default_theme" else fg_color + self.hover_color = CTkThemeManager.theme["color"]["button"] if hover_color == "default_theme" else hover_color + self.border_color = CTkThemeManager.theme["color"]["checkbox_border"] if border_color == "default_theme" else border_color self.width = width self.height = height - self.corner_radius = corner_radius + self.corner_radius = CTkThemeManager.theme["shape"]["checkbox_corner_radius"] if corner_radius == "default_theme" else corner_radius + self.border_width = CTkThemeManager.theme["shape"]["checkbox_border_width"] if border_width == "default_theme" else border_width if self.corner_radius*2 > self.height: self.corner_radius = self.height/2 elif self.corner_radius*2 > self.width: self.corner_radius = self.width/2 - self.border_width = border_width - if self.corner_radius >= self.border_width: self.inner_corner_radius = self.corner_radius - self.border_width else: self.inner_corner_radius = 0 self.text = text - self.text_color = CTkThemeManager.TEXT_COLOR if text_color == "default_theme" else text_color - self.text_font = (CTkThemeManager.TEXT_FONT_NAME, CTkThemeManager.TEXT_FONT_SIZE) if text_font == "default_theme" else text_font + self.text_color = CTkThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color + self.text_font = (CTkThemeManager.theme["text"]["font"], CTkThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font self.function = command self.state = state diff --git a/customtkinter/customtkinter_dialog.py b/customtkinter/customtkinter_dialog.py index 617b58c..a61e1b7 100644 --- a/customtkinter/customtkinter_dialog.py +++ b/customtkinter/customtkinter_dialog.py @@ -13,7 +13,8 @@ class CTkDialog: title="CTkDialog", text="CTkDialog", fg_color="default_theme", - hover_color="default_theme"): + hover_color="default_theme", + border_color="default_theme"): self.master = master self.user_input = None @@ -21,8 +22,9 @@ class CTkDialog: self.height = len(text.split("\n"))*20 + 150 - self.fg_color = CTkThemeManager.MAIN_COLOR if fg_color == "default_theme" else fg_color - self.hover_color = CTkThemeManager.MAIN_HOVER_COLOR if hover_color == "default_theme" else hover_color + self.fg_color = CTkThemeManager.theme["color"]["button"] if fg_color == "default_theme" else fg_color + self.hover_color = CTkThemeManager.theme["color"]["button_hover"] if hover_color == "default_theme" else hover_color + self.border_color = CTkThemeManager.theme["color"]["button_hover"] if border_color == "default_theme" else border_color self.top = customtkinter.CTkToplevel() self.top.geometry(f"280x{self.height}") @@ -60,7 +62,8 @@ class CTkDialog: width=100, command=self.ok_event, fg_color=self.fg_color, - hover_color=self.hover_color) + hover_color=self.hover_color, + border_color=self.border_color) self.ok_button.place(relx=0.28, rely=0.65, anchor=tkinter.CENTER) self.cancel_button = CTkButton(master=self.button_and_entry_frame, @@ -68,7 +71,8 @@ class CTkDialog: width=100, command=self.cancel_event, fg_color=self.fg_color, - hover_color=self.hover_color) + hover_color=self.hover_color, + border_color=self.border_color) self.cancel_button.place(relx=0.72, rely=0.65, anchor=tkinter.CENTER) self.entry.entry.focus_force() diff --git a/customtkinter/customtkinter_draw_engine.py b/customtkinter/customtkinter_draw_engine.py index 579e6ba..af5d5ca 100644 --- a/customtkinter/customtkinter_draw_engine.py +++ b/customtkinter/customtkinter_draw_engine.py @@ -1,3 +1,4 @@ +import sys import tkinter from typing import Union from .customtkinter_canvas import CTkCanvas @@ -6,12 +7,15 @@ from .customtkinter_canvas import CTkCanvas class DrawEngine: def __init__(self, canvas: CTkCanvas, rendering_method: str): self.canvas = canvas - self.rendering_method = rendering_method + self.rendering_method = rendering_method # "polygon_shapes" (macOS), "font_shapes" (Windows, Linux), "circle_shapes" (backup) def calc_optimal_corner_radius(self, user_corner_radius: Union[float, int]) -> Union[float, int]: # optimize for drawing with polygon shapes if self.rendering_method == "polygon_shapes": - return user_corner_radius + if sys.platform == "darwin": + return user_corner_radius + else: + return round(user_corner_radius) # optimize forx drawing with antialiased font shapes elif self.rendering_method == "font_shapes": @@ -30,7 +34,13 @@ class DrawEngine: return user_corner_radius def draw_rounded_rect_with_border(self, width: int, height: int, corner_radius: Union[float, int], border_width: Union[float, int]) -> bool: - """ returns bool if recoloring is necessary """ + """ Draws a rounded rectangle with a corner_radius and border_width on the canvas. The border elements have a 'border_parts' tag, + the main foreground elements have an 'inner_parts' tag to color the elements accordingly. + + returns bool if recoloring is necessary """ + + if corner_radius > width / 2 or corner_radius > height / 2: # restrict corner_radius if it's too larger + corner_radius = min(width / 2, height / 2) border_width = round(border_width) corner_radius = self.calc_optimal_corner_radius(corner_radius) # optimize corner_radius for different drawing methods (different rounding) @@ -287,8 +297,302 @@ class DrawEngine: return requires_recoloring - def draw_rounded_bar_with_border(self, width: int, height: int, corner_radius: Union[float, int], border_width: Union[float, int]) -> bool: - pass + def draw_rounded_progress_bar_with_border(self, width: int, height: int, corner_radius: Union[float, int], border_width: Union[float, int], + progress_value: float, orientation: str) -> bool: + """ Draws a rounded bar on the canvas, which is splitted in half according to the argument 'progress_value' (0 - 1). + The border elements get the 'border_parts' tag", the main elements get the 'inner_parts' tag and + the progress elements get the 'progress_parts' tag. The 'orientation' argument defines from which direction the progress starts (n, w, s, e). + + returns bool if recoloring is necessary """ + + if corner_radius > width / 2 or corner_radius > height / 2: # restrict corner_radius if it's too larger + corner_radius = min(width / 2, height / 2) + + border_width = round(border_width) + corner_radius = self.calc_optimal_corner_radius(corner_radius) # optimize corner_radius for different drawing methods (different rounding) + + if corner_radius >= border_width: + inner_corner_radius = corner_radius - border_width + else: + inner_corner_radius = 0 + + if self.rendering_method == "polygon_shapes": + return self._draw_rounded_progress_bar_with_border_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius, + progress_value, orientation) + elif self.rendering_method == "font_shapes": + return self._draw_rounded_progress_bar_with_border_font_shapes(width, height, corner_radius, border_width, inner_corner_radius, + progress_value, orientation) + elif self.rendering_method == "circle_shapes": + return self._draw_rounded_progress_bar_with_border_circle_shapes(width, height, corner_radius, border_width, inner_corner_radius) + + 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: + requires_recoloring = False + print(border_width, corner_radius, inner_corner_radius) + + # create border button parts (only if border exists) + if border_width > 0: + if not self.canvas.find_withtag("border_parts"): + self.canvas.create_polygon((0, 0, 0, 0), tags=("border_line_1", "border_parts"), joinstyle=tkinter.ROUND) + print("create border") + self.canvas.tag_lower("border_parts") + requires_recoloring = True + + self.canvas.coords("border_line_1", + (corner_radius, corner_radius, + width - corner_radius, corner_radius, + width - corner_radius, height - corner_radius, + corner_radius, height - corner_radius)) + self.canvas.itemconfig("border_line_1", width=corner_radius * 2) + else: + self.canvas.delete("border_parts") + + # create inner button parts + if not self.canvas.find_withtag("inner_parts"): + self.canvas.create_polygon((0, 0, 0, 0), tags=("inner_line_1", "inner_parts"), joinstyle=tkinter.ROUND) + print("create inner") + if self.canvas.find_withtag("border_parts"): + self.canvas.tag_raise("inner_parts", "border_parts") + requires_recoloring = True + + if corner_radius <= border_width: + bottom_right_shift = -1 # weird canvas rendering inaccuracy that has to be corrected in some cases + else: + bottom_right_shift = 0 + + self.canvas.coords("inner_line_1", + border_width + inner_corner_radius, + border_width + inner_corner_radius, + width - (border_width + inner_corner_radius) + bottom_right_shift, + border_width + inner_corner_radius, + width - (border_width + inner_corner_radius) + bottom_right_shift, + height - (border_width + inner_corner_radius) + bottom_right_shift, + border_width + inner_corner_radius, + height - (border_width + inner_corner_radius) + bottom_right_shift) + self.canvas.itemconfig("inner_line_1", width=inner_corner_radius * 2) + + # create progress parts + if not self.canvas.find_withtag("progress_parts"): + self.canvas.create_polygon((0, 0, 0, 0), tags=("progress_line_1", "progress_parts"), joinstyle=tkinter.ROUND) + self.canvas.tag_raise("progress_parts", "inner_parts") + requires_recoloring = True + + if orientation == "w": + self.canvas.coords("progress_line_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, + 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, + border_width + inner_corner_radius, + height - (border_width + inner_corner_radius) + bottom_right_shift) + + elif orientation == "s": + self.canvas.coords("progress_line_1", + border_width + inner_corner_radius, + border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value), + width - (border_width + inner_corner_radius), + border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value), + width - (border_width + inner_corner_radius), + height - (border_width + inner_corner_radius) + bottom_right_shift, + border_width + inner_corner_radius, + height - (border_width + inner_corner_radius) + bottom_right_shift) + + self.canvas.itemconfig("progress_line_1", width=inner_corner_radius * 2) + + 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, + progress_value: float, orientation: str) -> bool: + + print(width, height, border_width, corner_radius, inner_corner_radius) + requires_recoloring = False + + # create border button parts + if border_width > 0: + if corner_radius > 0: + # create canvas border corner parts if not already created + if not self.canvas.find_withtag("border_oval_1_a"): + self.canvas.create_aa_circle(0, 0, 0, tags=("border_oval_1_a", "border_corner_part", "border_parts"), anchor=tkinter.CENTER) + self.canvas.create_aa_circle(0, 0, 0, tags=("border_oval_1_b", "border_corner_part", "border_parts"), anchor=tkinter.CENTER, angle=180) + self.canvas.create_aa_circle(0, 0, 0, tags=("border_oval_2_a", "border_corner_part", "border_parts"), anchor=tkinter.CENTER) + self.canvas.create_aa_circle(0, 0, 0, tags=("border_oval_2_b", "border_corner_part", "border_parts"), anchor=tkinter.CENTER, angle=180) + self.canvas.tag_lower("border_corner_part") + requires_recoloring = True + + if not self.canvas.find_withtag("border_oval_3_a") and round(corner_radius) * 2 < height: + self.canvas.create_aa_circle(0, 0, 0, tags=("border_oval_3_a", "border_corner_part", "border_parts"), anchor=tkinter.CENTER) + self.canvas.create_aa_circle(0, 0, 0, tags=("border_oval_3_b", "border_corner_part", "border_parts"), anchor=tkinter.CENTER, angle=180) + self.canvas.create_aa_circle(0, 0, 0, tags=("border_oval_4_a", "border_corner_part", "border_parts"), anchor=tkinter.CENTER) + self.canvas.create_aa_circle(0, 0, 0, tags=("border_oval_4_b", "border_corner_part", "border_parts"), anchor=tkinter.CENTER, angle=180) + self.canvas.tag_lower("border_corner_part") + requires_recoloring = True + + elif self.canvas.find_withtag("border_oval_3_a") and not round(corner_radius) * 2 < height: + self.canvas.delete(["border_oval_3_a", "border_oval_3_b", "border_oval_4_a", "border_oval_4_b"]) + + # change position of border corner parts + self.canvas.coords("border_oval_1_a", corner_radius, corner_radius, corner_radius) + self.canvas.coords("border_oval_1_b", corner_radius, corner_radius, corner_radius) + self.canvas.coords("border_oval_2_a", width - corner_radius, corner_radius, corner_radius) + self.canvas.coords("border_oval_2_b", width - corner_radius, corner_radius, corner_radius) + self.canvas.coords("border_oval_3_a", width - corner_radius, height - corner_radius, corner_radius) + self.canvas.coords("border_oval_3_b", width - corner_radius, height - corner_radius, corner_radius) + self.canvas.coords("border_oval_4_a", corner_radius, height - corner_radius, corner_radius) + self.canvas.coords("border_oval_4_b", corner_radius, height - corner_radius, corner_radius) + + else: + self.canvas.delete("border_corner_part") # delete border corner parts if not needed + + # create canvas border rectangle parts if not already created + if not self.canvas.find_withtag("border_rectangle_1"): + self.canvas.create_rectangle(0, 0, 0, 0, tags=("border_rectangle_1", "border_rectangle_part", "border_parts"), width=0) + self.canvas.create_rectangle(0, 0, 0, 0, tags=("border_rectangle_2", "border_rectangle_part", "border_parts"), width=0) + self.canvas.tag_lower("border_rectangle_part") + requires_recoloring = True + + # change position of border rectangle parts + self.canvas.coords("border_rectangle_1", (0, corner_radius, width, height - corner_radius)) + self.canvas.coords("border_rectangle_2", (corner_radius, 0, width - corner_radius, height)) + + else: + self.canvas.delete("border_parts") + + # create inner button parts + if inner_corner_radius > 0: + + # create canvas border corner parts if not already created + if not self.canvas.find_withtag("inner_oval_1_a"): + self.canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_1_a", "inner_corner_part", "inner_parts"), anchor=tkinter.CENTER) + self.canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_1_b", "inner_corner_part", "inner_parts"), anchor=tkinter.CENTER, angle=180) + self.canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_2_a", "inner_corner_part", "inner_parts"), anchor=tkinter.CENTER) + self.canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_2_b", "inner_corner_part", "inner_parts"), anchor=tkinter.CENTER, angle=180) + self.canvas.tag_raise("inner_corner_part") + requires_recoloring = True + + if not self.canvas.find_withtag("inner_oval_3_a") and round(inner_corner_radius) * 2 < height - (2 * border_width): + self.canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_3_a", "inner_corner_part", "inner_parts"), anchor=tkinter.CENTER) + self.canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_3_b", "inner_corner_part", "inner_parts"), anchor=tkinter.CENTER, angle=180) + self.canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_4_a", "inner_corner_part", "inner_parts"), anchor=tkinter.CENTER) + self.canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_4_b", "inner_corner_part", "inner_parts"), anchor=tkinter.CENTER, angle=180) + self.canvas.tag_raise("inner_corner_part") + requires_recoloring = True + + elif self.canvas.find_withtag("inner_oval_3_a") and not round(inner_corner_radius) * 2 < height - (2 * border_width): + self.canvas.delete(["inner_oval_3_a", "inner_oval_3_b", "inner_oval_4_a", "inner_oval_4_b"]) + + # change position of border corner parts + self.canvas.coords("inner_oval_1_a", border_width + inner_corner_radius, border_width + inner_corner_radius, inner_corner_radius) + self.canvas.coords("inner_oval_1_b", border_width + inner_corner_radius, border_width + inner_corner_radius, inner_corner_radius) + self.canvas.coords("inner_oval_2_a", width - border_width - inner_corner_radius, border_width + inner_corner_radius, inner_corner_radius) + self.canvas.coords("inner_oval_2_b", width - border_width - inner_corner_radius, border_width + inner_corner_radius, inner_corner_radius) + self.canvas.coords("inner_oval_3_a", width - border_width - inner_corner_radius, height - border_width - inner_corner_radius, inner_corner_radius) + self.canvas.coords("inner_oval_3_b", width - border_width - inner_corner_radius, height - border_width - inner_corner_radius, inner_corner_radius) + self.canvas.coords("inner_oval_4_a", border_width + inner_corner_radius, height - border_width - inner_corner_radius, inner_corner_radius) + self.canvas.coords("inner_oval_4_b", border_width + inner_corner_radius, height - border_width - inner_corner_radius, inner_corner_radius) + else: + self.canvas.delete("inner_corner_part") # delete inner corner parts if not needed + + # create canvas inner rectangle parts if not already created + if not self.canvas.find_withtag("inner_rectangle_1"): + self.canvas.create_rectangle(0, 0, 0, 0, tags=("inner_rectangle_1", "inner_rectangle_part", "inner_parts"), width=0) + self.canvas.tag_raise("inner_rectangle_part") + requires_recoloring = True + + if not self.canvas.find_withtag("inner_rectangle_2") and inner_corner_radius * 2 < height - (border_width * 2): + self.canvas.create_rectangle(0, 0, 0, 0, tags=("inner_rectangle_2", "inner_rectangle_part", "inner_parts"), width=0) + self.canvas.tag_raise("inner_rectangle_part") + requires_recoloring = True + + elif self.canvas.find_withtag("inner_rectangle_2") and not inner_corner_radius * 2 < height - (border_width * 2): + self.canvas.delete("inner_rectangle_2") + + # change position of inner rectangle parts + self.canvas.coords("inner_rectangle_1", (border_width + inner_corner_radius, + border_width, + width - border_width - inner_corner_radius, + height - border_width)) + self.canvas.coords("inner_rectangle_2", (border_width, + border_width + inner_corner_radius, + width - border_width, + height - inner_corner_radius - border_width)) + + return requires_recoloring + + def _draw_rounded_rect_with_border_circle_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int) -> bool: + requires_recoloring = False + + # border button parts + if border_width > 0: + if corner_radius > 0: + + if not self.canvas.find_withtag("border_oval_1"): + self.canvas.create_oval(0, 0, 0, 0, tags=("border_oval_1", "border_corner_part", "border_parts"), width=0) + self.canvas.create_oval(0, 0, 0, 0, tags=("border_oval_2", "border_corner_part", "border_parts"), width=0) + self.canvas.create_oval(0, 0, 0, 0, tags=("border_oval_3", "border_corner_part", "border_parts"), width=0) + self.canvas.create_oval(0, 0, 0, 0, tags=("border_oval_4", "border_corner_part", "border_parts"), width=0) + self.canvas.tag_lower("border_parts") + requires_recoloring = True + + self.canvas.coords("border_oval_1", 0, 0, corner_radius * 2 - 1, corner_radius * 2 - 1) + self.canvas.coords("border_oval_2", width - corner_radius * 2, 0, width - 1, corner_radius * 2 - 1) + self.canvas.coords("border_oval_3", 0, height - corner_radius * 2, corner_radius * 2 - 1, height - 1) + self.canvas.coords("border_oval_4", width - corner_radius * 2, height - corner_radius * 2, width - 1, height - 1) + + else: + self.canvas.delete("border_corner_part") + + if not self.canvas.find_withtag("border_rectangle_1"): + self.canvas.create_rectangle(0, 0, 0, 0, tags=("border_rectangle_1", "border_rectangle_part", "border_parts"), width=0) + self.canvas.create_rectangle(0, 0, 0, 0, tags=("border_rectangle_2", "border_rectangle_part", "border_parts"), width=0) + self.canvas.tag_lower("border_parts") + requires_recoloring = True + + self.canvas.coords("border_rectangle_1", (0, corner_radius, width, height - corner_radius)) + self.canvas.coords("border_rectangle_2", (corner_radius, 0, width - corner_radius, height)) + + else: + self.canvas.delete("border_parts") + + # inner button parts + if inner_corner_radius > 0: + + if not self.canvas.find_withtag("inner_oval_1"): + self.canvas.create_oval(0, 0, 0, 0, tags=("inner_oval_1", "inner_corner_part", "inner_parts"), width=0) + self.canvas.create_oval(0, 0, 0, 0, tags=("inner_oval_2", "inner_corner_part", "inner_parts"), width=0) + self.canvas.create_oval(0, 0, 0, 0, tags=("inner_oval_3", "inner_corner_part", "inner_parts"), width=0) + self.canvas.create_oval(0, 0, 0, 0, tags=("inner_oval_4", "inner_corner_part", "inner_parts"), width=0) + self.canvas.tag_raise("inner_parts") + requires_recoloring = True + + self.canvas.coords("inner_oval_1", (border_width, border_width, + border_width + inner_corner_radius * 2 - 1, border_width + inner_corner_radius * 2 - 1)) + self.canvas.coords("inner_oval_2", (width - border_width - inner_corner_radius * 2, border_width, + width - border_width - 1, border_width + inner_corner_radius * 2 - 1)) + self.canvas.coords("inner_oval_3", (border_width, height - border_width - inner_corner_radius * 2, + border_width + inner_corner_radius * 2 - 1, height - border_width - 1)) + self.canvas.coords("inner_oval_4", (width - border_width - inner_corner_radius * 2, height - border_width - inner_corner_radius * 2, + width - border_width - 1, height - border_width - 1)) + else: + self.canvas.delete("inner_corner_part") # delete inner corner parts if not needed + + if not self.canvas.find_withtag("inner_rectangle_1"): + self.canvas.create_rectangle(0, 0, 0, 0, tags=("inner_rectangle_1", "inner_rectangle_part", "inner_parts"), width=0) + self.canvas.create_rectangle(0, 0, 0, 0, tags=("inner_rectangle_2", "inner_rectangle_part", "inner_parts"), width=0) + self.canvas.tag_raise("inner_parts") + requires_recoloring = True + + self.canvas.coords("inner_rectangle_1", (border_width + inner_corner_radius, + border_width, + width - border_width - inner_corner_radius, + height - border_width)) + self.canvas.coords("inner_rectangle_2", (border_width, + border_width + inner_corner_radius, + width - border_width, + height - inner_corner_radius - border_width)) + + return requires_recoloring def draw_rounded_button(self, canvas, width, height): pass diff --git a/customtkinter/customtkinter_entry.py b/customtkinter/customtkinter_entry.py index cc6bd5d..d3a46ac 100644 --- a/customtkinter/customtkinter_entry.py +++ b/customtkinter/customtkinter_entry.py @@ -20,6 +20,8 @@ class CTkEntry(tkinter.Frame): text_font="default_theme", placeholder_text=None, corner_radius=8, + border_width=0, + border_color="default_theme", width=120, height=30, **kwargs): @@ -55,10 +57,11 @@ class CTkEntry(tkinter.Frame): self.configure_basic_grid() self.bg_color = self.detect_color_of_master() if bg_color is None else bg_color - self.fg_color = CTkThemeManager.ENTRY_COLOR if fg_color == "default_theme" else fg_color - self.text_color = CTkThemeManager.TEXT_COLOR if text_color == "default_theme" else text_color - self.placeholder_text_color = CTkThemeManager.PLACEHOLDER_TEXT_COLOR if placeholder_text_color == "default_theme" else placeholder_text_color - self.text_font = (CTkThemeManager.TEXT_FONT_NAME, CTkThemeManager.TEXT_FONT_SIZE) if text_font == "default_theme" else text_font + self.fg_color = CTkThemeManager.theme["color"]["entry"] if fg_color == "default_theme" else fg_color + self.text_color = CTkThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color + self.placeholder_text_color = CTkThemeManager.theme["color"]["entry_placeholder_text"] if placeholder_text_color == "default_theme" else placeholder_text_color + self.text_font = (CTkThemeManager.theme["text"]["font"], CTkThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font + self.border_color = CTkThemeManager.theme["color"]["entry_border"] if border_color == "default_theme" else border_color self.placeholder_text = placeholder_text self.placeholder_text_active = False @@ -66,7 +69,8 @@ class CTkEntry(tkinter.Frame): self.width = width self.height = height - self.corner_radius = corner_radius + self.corner_radius = CTkThemeManager.theme["shape"]["button_corner_radius"] if corner_radius == "default_theme" else corner_radius + self.border_width = CTkThemeManager.theme["shape"]["entry_border_width"] if border_width == "default_theme" else border_width if self.corner_radius*2 > self.height: self.corner_radius = self.height/2 @@ -150,12 +154,16 @@ class CTkEntry(tkinter.Frame): fg=CTkThemeManager.single_color(self.text_color, self.appearance_mode), insertbackground=CTkThemeManager.single_color(self.text_color, self.appearance_mode)) - requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.width, self.height, self.corner_radius, 0) + requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.width, self.height, self.corner_radius, self.border_width) self.canvas.itemconfig("inner_parts", fill=CTkThemeManager.single_color(self.fg_color, self.appearance_mode), outline=CTkThemeManager.single_color(self.fg_color, self.appearance_mode)) + self.canvas.itemconfig("border_parts", + fill=CTkThemeManager.single_color(self.border_color, self.appearance_mode), + outline=CTkThemeManager.single_color(self.border_color, self.appearance_mode)) + def bind(self, *args, **kwargs): self.entry.bind(*args, **kwargs) diff --git a/customtkinter/customtkinter_frame.py b/customtkinter/customtkinter_frame.py index 327fb80..fa6b07a 100644 --- a/customtkinter/customtkinter_frame.py +++ b/customtkinter/customtkinter_frame.py @@ -1,5 +1,4 @@ import tkinter -import sys from .customtkinter_tk import CTk from .appearance_mode_tracker import AppearanceModeTracker @@ -14,8 +13,8 @@ class CTkFrame(tkinter.Frame): bg_color=None, fg_color="default_theme", border_color="default_theme", - border_width=0, - corner_radius=10, + border_width="default_theme", + corner_radius="default_theme", width=200, height=200, **kwargs): @@ -46,16 +45,16 @@ class CTkFrame(tkinter.Frame): self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark" self.bg_color = self.detect_color_of_master() if bg_color is None else bg_color - self.border_color = CTkThemeManager.CHECKBOX_LINES_COLOR if border_color == "default_theme" else border_color + self.border_color = CTkThemeManager.theme["color"]["frame_border"] if border_color == "default_theme" else border_color if fg_color == "default_theme": if isinstance(self.master, CTkFrame): - if self.master.fg_color == CTkThemeManager.FRAME_COLOR: - self.fg_color = CTkThemeManager.FRAME_2_COLOR + if self.master.fg_color == CTkThemeManager.theme["color"]["frame_low"]: + self.fg_color = CTkThemeManager.theme["color"]["frame_high"] else: - self.fg_color = CTkThemeManager.FRAME_COLOR + self.fg_color = CTkThemeManager.theme["color"]["frame_low"] else: - self.fg_color = CTkThemeManager.FRAME_COLOR + self.fg_color = CTkThemeManager.theme["color"]["frame_low"] else: self.fg_color = fg_color @@ -63,8 +62,8 @@ class CTkFrame(tkinter.Frame): self.height = height self.configure(width=self.width, height=self.height) - self.corner_radius = corner_radius - self.border_width = border_width + self.corner_radius = CTkThemeManager.theme["shape"]["frame_corner_radius"] if corner_radius == "default_theme" else corner_radius + self.border_width = CTkThemeManager.theme["shape"]["frame_border_width"] if border_width == "default_theme" else border_width if self.corner_radius * 2 > self.height: self.corner_radius = self.height / 2 @@ -138,7 +137,7 @@ class CTkFrame(tkinter.Frame): from .customtkinter_progressbar import CTkProgressBar from .customtkinter_label import CTkLabel from .customtkinter_entry import CTkEntry - from .customtkinter_checkbox import CTkCheckBox + from customtkinter.customtkinter_checkbox import CTkCheckBox from .customtkinter_button import CTkButton for child in self.winfo_children(): diff --git a/customtkinter/customtkinter_label.py b/customtkinter/customtkinter_label.py index c262c56..b0ebe3a 100644 --- a/customtkinter/customtkinter_label.py +++ b/customtkinter/customtkinter_label.py @@ -16,7 +16,7 @@ class CTkLabel(tkinter.Frame): bg_color=None, fg_color="default_theme", text_color="default_theme", - corner_radius=8, + corner_radius="default_theme", width=120, height=25, text="CTkLabel", @@ -52,14 +52,14 @@ class CTkLabel(tkinter.Frame): self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark" self.bg_color = self.detect_color_of_master() if bg_color is None else bg_color - self.fg_color = CTkThemeManager.LABEL_BG_COLOR if fg_color == "default_theme" else fg_color + self.fg_color = CTkThemeManager.theme["color"]["label"] if fg_color == "default_theme" else fg_color if self.fg_color is None: self.fg_color = self.bg_color - self.text_color = CTkThemeManager.TEXT_COLOR if text_color == "default_theme" else text_color + self.text_color = CTkThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color self.width = width self.height = height - self.corner_radius = corner_radius + self.corner_radius = CTkThemeManager.theme["shape"]["label_corner_radius"] if corner_radius == "default_theme" else corner_radius if self.corner_radius * 2 > self.height: self.corner_radius = self.height / 2 @@ -67,7 +67,7 @@ class CTkLabel(tkinter.Frame): self.corner_radius = self.width / 2 self.text = text - self.text_font = (CTkThemeManager.TEXT_FONT_NAME, CTkThemeManager.TEXT_FONT_SIZE) if text_font == "default_theme" else text_font + self.text_font = (CTkThemeManager.theme["text"]["font"], CTkThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font self.canvas = CTkCanvas(master=self, highlightthickness=0, diff --git a/customtkinter/customtkinter_progressbar.py b/customtkinter/customtkinter_progressbar.py index 834ed34..648fe90 100644 --- a/customtkinter/customtkinter_progressbar.py +++ b/customtkinter/customtkinter_progressbar.py @@ -5,6 +5,9 @@ from .customtkinter_tk import CTk from .customtkinter_frame import CTkFrame from .appearance_mode_tracker import AppearanceModeTracker from .customtkinter_theme_manager import CTkThemeManager +from .customtkinter_canvas import CTkCanvas +from .customtkinter_draw_engine import DrawEngine +from .customtkinter_settings import CTkSettings class CTkProgressBar(tkinter.Frame): @@ -16,9 +19,10 @@ class CTkProgressBar(tkinter.Frame): border_color="default_theme", fg_color="default_theme", progress_color="default_theme", - width=160, - height=10, - border_width=0, + corner_radius="default_theme", + width=200, + height=16, + border_width="default_theme", **kwargs): super().__init__(*args, **kwargs) @@ -47,26 +51,29 @@ class CTkProgressBar(tkinter.Frame): self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark" self.bg_color = self.detect_color_of_master() if bg_color is None else bg_color - self.border_color = CTkThemeManager.PROGRESS_BG_COLOR if border_color == "default_theme" else border_color - self.fg_color = CTkThemeManager.PROGRESS_BG_COLOR if fg_color == "default_theme" else fg_color - self.progress_color = CTkThemeManager.MAIN_COLOR if progress_color == "default_theme" else progress_color + self.border_color = CTkThemeManager.theme["color"]["progressbar_border"] if border_color == "default_theme" else border_color + self.fg_color = CTkThemeManager.theme["color"]["progressbar"] if fg_color == "default_theme" else fg_color + self.progress_color = CTkThemeManager.theme["color"]["progressbar_progress"] if progress_color == "default_theme" else progress_color self.variable = variable self.variable_callback_blocked = False - self.variabel_callback_name = None + self.variable_callback_name = None self.width = width - self.height = self.calc_optimal_height(height) - self.border_width = round(border_width) + self.height = height + 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.configure(width=self.width, height=self.height) - self.canvas = tkinter.Canvas(master=self, - highlightthickness=0, - width=self.width, - height=self.height) - self.canvas.place(x=0, y=0) + self.canvas = CTkCanvas(master=self, + highlightthickness=0, + width=self.width, + height=self.height) + self.canvas.place(x=0, y=0, relwidth=1, relheight=1) + + self.draw_engine = DrawEngine(self.canvas, CTkSettings.preferred_drawing_method) # Each time an item is resized due to pack position mode, the binding Configure is called on the widget self.bind('', self.update_dimensions) @@ -74,7 +81,7 @@ class CTkProgressBar(tkinter.Frame): self.draw() # initial draw if self.variable is not None: - self.variabel_callback_name = self.variable.trace_add("write", self.variable_callback) + self.variable_callback_name = self.variable.trace_add("write", self.variable_callback) self.variable_callback_blocked = True self.set(self.variable.get(), from_variable_callback=True) self.variable_callback_blocked = False @@ -83,7 +90,7 @@ class CTkProgressBar(tkinter.Frame): AppearanceModeTracker.remove(self.change_appearance_mode) if self.variable is not None: - self.variable.trace_remove("write", self.variabel_callback_name) + self.variable.trace_remove("write", self.variable_callback_name) super().destroy() @@ -117,18 +124,26 @@ class CTkProgressBar(tkinter.Frame): def draw(self, no_color_updates=False): # decide the drawing method - if sys.platform == "darwin": - # on macOS draw button with polygons (positions are more accurate, macOS has Antialiasing) - self.draw_with_polygon_shapes() - else: - # on Windows and other draw with ovals (corner_radius can be optimised to look better than with polygons) - self.draw_with_ovals_and_rects() + #if sys.platform == "darwin": + # # on macOS draw button with polygons (positions are more accurate, macOS has Antialiasing) + # self.draw_with_polygon_shapes() + #else: + # # on Windows and other draw with ovals (corner_radius can be optimised to look better than with polygons) + #self.draw_with_ovals_and_rects() - if no_color_updates is False: + requires_recoloring = self.draw_engine.draw_rounded_progress_bar_with_border(self.width, self.height, self.corner_radius, self.border_width, self.value, "w") + + if no_color_updates is False or requires_recoloring: self.canvas.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode)) - self.canvas.itemconfig("border_parts", fill=CTkThemeManager.single_color(self.border_color, self.appearance_mode)) - self.canvas.itemconfig("inner_parts", fill=CTkThemeManager.single_color(self.fg_color, self.appearance_mode)) - self.canvas.itemconfig("progress_parts", fill=CTkThemeManager.single_color(self.progress_color, self.appearance_mode)) + self.canvas.itemconfig("border_parts", + fill=CTkThemeManager.single_color(self.border_color, self.appearance_mode), + outline=CTkThemeManager.single_color(self.border_color, self.appearance_mode)) + self.canvas.itemconfig("inner_parts", + fill=CTkThemeManager.single_color(self.fg_color, self.appearance_mode), + outline=CTkThemeManager.single_color(self.fg_color, self.appearance_mode)) + self.canvas.itemconfig("progress_parts", + fill=CTkThemeManager.single_color(self.progress_color, self.appearance_mode), + outline=CTkThemeManager.single_color(self.progress_color, self.appearance_mode)) def draw_with_polygon_shapes(self): """ draw the progress bar parts with just three polygons that have a rounded border """ @@ -277,12 +292,12 @@ class CTkProgressBar(tkinter.Frame): if "variable" in kwargs: if self.variable is not None: - self.variable.trace_remove("write", self.variabel_callback_name) + self.variable.trace_remove("write", self.variable_callback_name) self.variable = kwargs["variable"] if self.variable is not None and self.variable != "": - self.variabel_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) else: self.variable = None diff --git a/customtkinter/customtkinter_slider.py b/customtkinter/customtkinter_slider.py index 03b0141..79961a7 100644 --- a/customtkinter/customtkinter_slider.py +++ b/customtkinter/customtkinter_slider.py @@ -22,7 +22,8 @@ class CTkSlider(tkinter.Frame): number_of_steps=None, width=160, height=16, - border_width=5, + corner_radius="default_theme", + border_width="default_theme", command=None, variable=None, **kwargs): @@ -56,14 +57,15 @@ class CTkSlider(tkinter.Frame): self.bg_color = self.detect_color_of_master() if bg_color is None else bg_color self.border_color = border_color - self.fg_color = CTkThemeManager.SLIDER_BG_COLOR if fg_color == "default_theme" else fg_color - self.progress_color = CTkThemeManager.SLIDER_PROGRESS_COLOR if progress_color == "default_theme" else progress_color - self.button_color = CTkThemeManager.MAIN_COLOR if button_color == "default_theme" else button_color - self.button_hover_color = CTkThemeManager.MAIN_HOVER_COLOR if button_hover_color == "default_theme" else button_hover_color + self.fg_color = CTkThemeManager.theme["color"]["slider"] if fg_color == "default_theme" else fg_color + self.progress_color = CTkThemeManager.theme["color"]["slider_progress"] if progress_color == "default_theme" else progress_color + self.button_color = CTkThemeManager.theme["color"]["slider_button"] if button_color == "default_theme" else button_color + self.button_hover_color = CTkThemeManager.theme["color"]["slider_button_hover"] if button_hover_color == "default_theme" else button_hover_color self.width = width self.height = self.calc_optimal_height(height) - self.border_width = round(border_width) + self.corner_radius = CTkThemeManager.theme["shape"]["button_corner_radius"] if corner_radius == "default_theme" else corner_radius + self.border_width = CTkThemeManager.theme["shape"]["slider_border_width"] if border_width == "default_theme" else border_width self.value = 0.5 # initial value of slider in percent self.hover_state = False self.from_ = from_ @@ -74,7 +76,7 @@ class CTkSlider(tkinter.Frame): self.callback_function = command self.variable: tkinter.Variable = variable self.variable_callback_blocked = False - self.variabel_callback_name = None + self.variable_callback_name = None self.configure(width=self.width, height=self.height) if sys.platform == "darwin": @@ -97,7 +99,7 @@ class CTkSlider(tkinter.Frame): self.draw() # initial draw if self.variable is not None: - self.variabel_callback_name = self.variable.trace_add("write", self.variable_callback) + self.variable_callback_name = self.variable.trace_add("write", self.variable_callback) self.variable_callback_blocked = True self.set(self.variable.get(), from_variable_callback=True) self.variable_callback_blocked = False @@ -106,9 +108,9 @@ class CTkSlider(tkinter.Frame): # remove change_appearance_mode function from callback list of AppearanceModeTracker AppearanceModeTracker.remove(self.change_appearance_mode) - # remove variabel_callback from variable callbacks if variable exists + # remove variable_callback from variable callbacks if variable exists if self.variable is not None: - self.variable.trace_remove("write", self.variabel_callback_name) + self.variable.trace_remove("write", self.variable_callback_name) super().destroy() @@ -444,12 +446,12 @@ class CTkSlider(tkinter.Frame): if "variable" in kwargs: if self.variable is not None: - self.variable.trace_remove("write", self.variabel_callback_name) + self.variable.trace_remove("write", self.variable_callback_name) self.variable = kwargs["variable"] if self.variable is not None and self.variable != "": - self.variabel_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) else: self.variable = None diff --git a/customtkinter/customtkinter_theme_manager.py b/customtkinter/customtkinter_theme_manager.py index 40e0c3a..08be887 100644 --- a/customtkinter/customtkinter_theme_manager.py +++ b/customtkinter/customtkinter_theme_manager.py @@ -1,25 +1,30 @@ import sys +import os +import json class CTkThemeManager: - TEXT_FONT_NAME = None - TEXT_FONT_SIZE = None + theme = {} # contains all the theme data + built_in_themes = ["blue", "green", "dark_blue"] - WINDOW_BG_COLOR = None - MAIN_COLOR = None - MAIN_HOVER_COLOR = None - ENTRY_COLOR = None - TEXT_COLOR = None - PLACEHOLDER_TEXT_COLOR = None - LABEL_BG_COLOR = None - SLIDER_BG_COLOR = None - SLIDER_PROGRESS_COLOR = None - PROGRESS_BG_COLOR = None - FRAME_COLOR = None - FRAME_2_COLOR = None - CHECKBOX_LINES_COLOR = None - DARKEN_COLOR_FACTOR = None + @classmethod + def load_theme(cls, theme_name_or_path: str): + script_directory = os.path.dirname(os.path.abspath(__file__)) + + if theme_name_or_path in cls.built_in_themes: + with open(os.path.join(script_directory, "assets", "themes", f"{theme_name_or_path}.json"), "r") as f: + cls.theme = json.load(f) + else: + with open(theme_name_or_path, "r") as f: + cls.theme = json.load(f) + + if sys.platform == "darwin": + cls.theme["text"] = cls.theme["text"]["macOS"] + elif sys.platform.startswith("win"): + cls.theme["text"] = cls.theme["text"]["Windows"] + else: + cls.theme["text"] = cls.theme["text"]["Linux"] @classmethod def initialize_color_theme(cls, theme_name): @@ -37,7 +42,8 @@ class CTkThemeManager: cls.WINDOW_BG_COLOR = ("#ECECEC", "#323232") # macOS standard light and dark window bg colors cls.MAIN_COLOR = ("#64A1D2", "#1C94CF") cls.MAIN_HOVER_COLOR = ("#A7C2E0", "#5FB4DD") - cls.ENTRY_COLOR = ("white", "#222222") + cls.ENTRY_COLOR = ("gray95", "#222222") + cls.ENTRY_BORDER_COLOR = ("gray65", "gray40") cls.TEXT_COLOR = ("black", "white") cls.PLACEHOLDER_TEXT_COLOR = ("gray52", "gray62") cls.LABEL_BG_COLOR = ("white", "#626061") @@ -87,7 +93,7 @@ class CTkThemeManager: tuple color with (light_color, dark_color). The functions then returns always a single color string """ - if type(color) == tuple: + if type(color) == tuple or type(color) == list: return color[appearance_mode] else: return color @@ -138,4 +144,4 @@ class CTkThemeManager: cls.MAIN_HOVER_COLOR = main_color_hover -CTkThemeManager.initialize_color_theme("blue") +CTkThemeManager.load_theme("blue") diff --git a/customtkinter/customtkinter_tk.py b/customtkinter/customtkinter_tk.py index 71cc2c8..5235b2c 100644 --- a/customtkinter/customtkinter_tk.py +++ b/customtkinter/customtkinter_tk.py @@ -101,7 +101,7 @@ class CTk(tkinter.Tk): from .customtkinter_label import CTkLabel from .customtkinter_frame import CTkFrame from .customtkinter_entry import CTkEntry - from .customtkinter_checkbox import CTkCheckBox + from customtkinter.customtkinter_checkbox import CTkCheckBox from .customtkinter_button import CTkButton for child in self.winfo_children(): diff --git a/customtkinter/customtkinter_toplevel.py b/customtkinter/customtkinter_toplevel.py index b3de789..d84cebd 100644 --- a/customtkinter/customtkinter_toplevel.py +++ b/customtkinter/customtkinter_toplevel.py @@ -87,7 +87,7 @@ class CTkToplevel(tkinter.Toplevel): from .customtkinter_label import CTkLabel from .customtkinter_frame import CTkFrame from .customtkinter_entry import CTkEntry - from .customtkinter_checkbox import CTkCheckBox + from customtkinter.customtkinter_checkbox import CTkCheckBox from .customtkinter_button import CTkButton for child in self.winfo_children(): diff --git a/examples/complex_example.py b/examples/complex_example.py index 7b7328a..29988d7 100644 --- a/examples/complex_example.py +++ b/examples/complex_example.py @@ -4,7 +4,7 @@ import customtkinter import sys customtkinter.set_appearance_mode("Light") # Modes: "System" (standard), "Dark", "Light" -customtkinter.set_default_color_theme("dark-blue") # Themes: "blue" (standard), "green", "dark-blue" +customtkinter.set_default_color_theme("blue") # Themes: "blue" (standard), "green", "dark-blue" customtkinter.CTkSettings.preferred_drawing_method = "font_shapes" @@ -38,8 +38,6 @@ class App(customtkinter.CTk): self.frame_right = customtkinter.CTkFrame(master=self, width=420, height=App.HEIGHT-40, - border_width=0, - border_color=customtkinter.CTkThemeManager.MAIN_COLOR, corner_radius=12) self.frame_right.grid(row=0, column=1, sticky="nswe", padx=20, pady=20) @@ -95,8 +93,7 @@ class App(customtkinter.CTk): self.frame_info = customtkinter.CTkFrame(master=self.frame_right, width=380, - height=200, - corner_radius=10) + height=200) self.frame_info.grid(row=0, column=0, columnspan=3, pady=20, padx=20, sticky="wens") # ============ frame_right -> frame_info ============ @@ -108,22 +105,18 @@ class App(customtkinter.CTk): "invidunt ut labore", width=250, height=100, - corner_radius=8, fg_color=("white", "gray38"), # <- custom tuple-color justify=tkinter.LEFT) self.label_info_1.place(relx=0.5, rely=0.15, anchor=tkinter.N) - self.progressbar = customtkinter.CTkProgressBar(master=self.frame_info, - width=250, - height=12) + self.progressbar = customtkinter.CTkProgressBar(master=self.frame_info) self.progressbar.place(relx=0.5, rely=0.85, anchor=tkinter.S) - self.progressbar.set(0.65) # ============ frame_right <- ============ self.slider_1 = customtkinter.CTkSlider(master=self.frame_right, height=16, - border_width=5, + border_width=0, from_=1, to=0, number_of_steps=3, @@ -134,7 +127,6 @@ class App(customtkinter.CTk): self.slider_2 = customtkinter.CTkSlider(master=self.frame_right, width=160, height=16, - border_width=5, command=self.progressbar.set) self.slider_2.grid(row=2, column=0, columnspan=2, pady=10, padx=20, sticky="we") self.slider_2.set(0.7) @@ -157,8 +149,9 @@ class App(customtkinter.CTk): self.entry = customtkinter.CTkEntry(master=self.frame_right, width=120, - height=25, - corner_radius=8, + height=30, + corner_radius=10, + border_width=2, placeholder_text="CTkEntry") self.entry.grid(row=4, column=0, columnspan=2, pady=20, padx=20, sticky="we") @@ -172,6 +165,8 @@ class App(customtkinter.CTk): corner_radius=13) self.button_5.grid(row=4, column=2, columnspan=1, pady=20, padx=20, sticky="we") + self.progressbar.set(0.2) + def button_event(self): print("Button pressed")