From 965a7ff87de0e0e3fbabcffdea31d48184a1fe87 Mon Sep 17 00:00:00 2001 From: TomSchimansky Date: Tue, 1 Mar 2022 16:12:55 +0100 Subject: [PATCH] finished CTkDrawEngine for all current widgets --- customtkinter/assets/themes/blue.json | 16 +- customtkinter/customtkinter_button.py | 4 +- customtkinter/customtkinter_canvas.py | 4 +- customtkinter/customtkinter_checkbox.py | 4 +- customtkinter/customtkinter_draw_engine.py | 806 +++++++++++---------- customtkinter/customtkinter_entry.py | 27 +- customtkinter/customtkinter_frame.py | 4 +- customtkinter/customtkinter_label.py | 4 +- customtkinter/customtkinter_progressbar.py | 131 +--- customtkinter/customtkinter_slider.py | 66 +- examples/complex_example.py | 6 +- 11 files changed, 498 insertions(+), 574 deletions(-) diff --git a/customtkinter/assets/themes/blue.json b/customtkinter/assets/themes/blue.json index 44743df..1166012 100644 --- a/customtkinter/assets/themes/blue.json +++ b/customtkinter/assets/themes/blue.json @@ -4,8 +4,8 @@ "button": ["#64A1D2", "#1C94CF"], "button_hover": ["#A7C2E0", "#5FB4DD"], "button_border": ["#A7C2E0", "#5FB4DD"], - "checkbox_border": ["black", "#ededed"], - "entry": ["gray95", "#222222"], + "checkbox_border": ["gray20", "#ededed"], + "entry": [null, null], "entry_border": ["gray65", "gray40"], "entry_placeholder_text": ["gray52", "gray62"], "frame_border": ["#A7C2E0", "#5FB4DD"], @@ -14,7 +14,7 @@ "label": ["white", "#626061"], "text": ["gray18", "gray90"], "progressbar": ["#6B6B6B", "#222222"], - "progressbar_progress": ["red", "red"], + "progressbar_progress": ["#64A1D2", "#1C94CF"], "progressbar_border": ["gray", "gray"], "slider": ["#6B6B6B", "#222222"], "slider_progress": ["#A5A6A5", "#555555"], @@ -47,9 +47,11 @@ "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 + "progressbar_border_width": 0, + "progressbar_corner_radius": 100, + "slider_border_width": 5, + "slider_corner_radius": 8, + "slider_button_length": 4, + "slider_button_corner_radius": 6 } } \ No newline at end of file diff --git a/customtkinter/customtkinter_button.py b/customtkinter/customtkinter_button.py index 92b4e09..fc1564d 100644 --- a/customtkinter/customtkinter_button.py +++ b/customtkinter/customtkinter_button.py @@ -7,7 +7,7 @@ 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_draw_engine import CTkDrawEngine class CTkButton(tkinter.Frame): @@ -110,7 +110,7 @@ class CTkButton(tkinter.Frame): height=self.height) self.canvas.grid(row=0, column=0, rowspan=2, columnspan=2, sticky="nsew") - self.draw_engine = DrawEngine(self.canvas, CTkSettings.preferred_drawing_method) + self.draw_engine = CTkDrawEngine(self.canvas, CTkSettings.preferred_drawing_method) # event bindings if self.hover is True: diff --git a/customtkinter/customtkinter_canvas.py b/customtkinter/customtkinter_canvas.py index daa4c3e..b3fabdd 100644 --- a/customtkinter/customtkinter_canvas.py +++ b/customtkinter/customtkinter_canvas.py @@ -12,7 +12,7 @@ class CTkCanvas(tkinter.Canvas): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.aa_circle_canvas_ids = [] + self.aa_circle_canvas_ids = set() def get_char_from_radius(self, radius): if CTkSettings.scaling_factor == 1: @@ -26,7 +26,7 @@ class CTkCanvas(tkinter.Canvas): circle_1 = self.create_text(x_pos, y_pos, text=self.get_char_from_radius(radius), anchor=anchor, fill=fill, font=("CustomTkinter_shapes_font", -radius * 2), tags=tags, angle=angle) self.addtag_withtag("ctk_aa_circle_font_element", circle_1) - self.aa_circle_canvas_ids.append(circle_1) + self.aa_circle_canvas_ids.add(circle_1) return circle_1 diff --git a/customtkinter/customtkinter_checkbox.py b/customtkinter/customtkinter_checkbox.py index 8464154..9651a7d 100644 --- a/customtkinter/customtkinter_checkbox.py +++ b/customtkinter/customtkinter_checkbox.py @@ -7,7 +7,7 @@ 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 +from customtkinter.customtkinter_draw_engine import CTkDrawEngine class CTkCheckBox(tkinter.Frame): @@ -100,7 +100,7 @@ class CTkCheckBox(tkinter.Frame): height=self.height) self.canvas.pack(side='left') - self.draw_engine = DrawEngine(self.canvas, CTkSettings.preferred_drawing_method) + self.draw_engine = CTkDrawEngine(self.canvas, CTkSettings.preferred_drawing_method) if sys.platform == "darwin" and self.state == tkinter.NORMAL and CTkSettings.hand_cursor_enabled: self.canvas.configure(cursor="pointinghand") diff --git a/customtkinter/customtkinter_draw_engine.py b/customtkinter/customtkinter_draw_engine.py index af5d5ca..3e4c7d7 100644 --- a/customtkinter/customtkinter_draw_engine.py +++ b/customtkinter/customtkinter_draw_engine.py @@ -4,25 +4,39 @@ from typing import Union from .customtkinter_canvas import CTkCanvas -class DrawEngine: - def __init__(self, canvas: CTkCanvas, rendering_method: str): - self.canvas = canvas - self.rendering_method = rendering_method # "polygon_shapes" (macOS), "font_shapes" (Windows, Linux), "circle_shapes" (backup) +class CTkDrawEngine: + """ + This is the core of the CustomTkinter library where all the drawing on the tkinter.Canvas happens. + A year of experimenting and trying out different drawing methods have led to the current state of this + class, and I don't think there's much I can do to make the rendering look better than this with the + limited capabilities the tkinter.Canvas offers. - def calc_optimal_corner_radius(self, user_corner_radius: Union[float, int]) -> Union[float, int]: + Functions: + - draw_rounded_rect_with_border() + - draw_rounded_progress_bar_with_border() + - draw_rounded_slider_with_border_and_button() + + """ + + def __init__(self, canvas: CTkCanvas, rendering_method: str): + self._canvas = canvas + self._rendering_method = rendering_method # "polygon_shapes" (macOS), "font_shapes" (Windows, Linux), "circle_shapes" (backup without fonts) + self._existing_tags = set() + + 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": + if self._rendering_method == "polygon_shapes": 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": + elif self._rendering_method == "font_shapes": return round(user_corner_radius) # optimize for drawing with circles and rects - elif self.rendering_method == "circle_shapes": + elif self._rendering_method == "circle_shapes": user_corner_radius = 0.5 * round(user_corner_radius / 0.5) # round to 0.5 steps # make sure the value is always with .5 at the end for smoother corners @@ -43,18 +57,18 @@ class DrawEngine: 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) + 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": + if self._rendering_method == "polygon_shapes": return self._draw_rounded_rect_with_border_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius) - elif self.rendering_method == "font_shapes": - return self._draw_rounded_rect_with_border_font_shapes(width, height, corner_radius, border_width, inner_corner_radius) - elif self.rendering_method == "circle_shapes": + elif self._rendering_method == "font_shapes": + return self._draw_rounded_rect_with_border_font_shapes(width, height, corner_radius, border_width, inner_corner_radius, ()) + elif self._rendering_method == "circle_shapes": return self._draw_rounded_rect_with_border_circle_shapes(width, height, corner_radius, border_width, inner_corner_radius) def _draw_rounded_rect_with_border_polygon_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int) -> bool: @@ -62,13 +76,12 @@ class DrawEngine: # 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")) - self.canvas.tag_lower("border_parts") + if not self._canvas.find_withtag("border_parts"): + self._canvas.create_polygon((0, 0, 0, 0), tags=("border_line_1", "border_parts")) requires_recoloring = True - self.canvas.coords("border_line_1", - (corner_radius, + self._canvas.coords("border_line_1", + (corner_radius, corner_radius, width - corner_radius, corner_radius, @@ -76,17 +89,16 @@ class DrawEngine: height - corner_radius, corner_radius, height - corner_radius)) - self.canvas.itemconfig("border_line_1", - joinstyle=tkinter.ROUND, - width=corner_radius * 2) + self._canvas.itemconfig("border_line_1", + joinstyle=tkinter.ROUND, + width=corner_radius * 2) else: - self.canvas.delete("border_parts") + 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")) - self.canvas.tag_raise("inner_parts") + if not self._canvas.find_withtag("inner_parts"): + self._canvas.create_polygon((0, 0, 0, 0), tags=("inner_line_1", "inner_parts")) requires_recoloring = True if corner_radius <= border_width: @@ -94,8 +106,8 @@ class DrawEngine: else: bottom_right_shift = 0 - self.canvas.coords("inner_line_1", - (border_width + inner_corner_radius, + 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, @@ -103,123 +115,132 @@ class DrawEngine: 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", - joinstyle=tkinter.ROUND, - width=inner_corner_radius * 2) + self._canvas.itemconfig("inner_line_1", + joinstyle=tkinter.ROUND, + width=inner_corner_radius * 2) + + if requires_recoloring: + self._canvas.tag_lower("inner_parts") + self._canvas.tag_lower("border_parts") return requires_recoloring - def _draw_rounded_rect_with_border_font_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int) -> bool: + def _draw_rounded_rect_with_border_font_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int, + exclude_parts: tuple) -> bool: 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") + 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) 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") + 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) 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"]) + 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) + 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 + 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") + 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) 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)) + 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") + 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") + if not self._canvas.find_withtag("inner_oval_1_a") and "inner_oval_1" not in exclude_parts: + self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_1_a", "inner_corner_part", "inner_parts"), 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) 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") + if not self._canvas.find_withtag("inner_oval_2_a") and "inner_oval_2" not in exclude_parts: + self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_2_a", "inner_corner_part", "inner_parts"), 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) 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"]) + if not self._canvas.find_withtag("inner_oval_3_a") and round(inner_corner_radius) * 2 < height - (2 * border_width) and "inner_oval_3" not in exclude_parts: + self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_3_a", "inner_corner_part", "inner_parts"), 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) + 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") + + if not self._canvas.find_withtag("inner_oval_4_a") and round(inner_corner_radius) * 2 < height - (2 * border_width) and "inner_oval_4" not in exclude_parts: + self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_4_a", "inner_corner_part", "inner_parts"), 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) + requires_recoloring = True + elif self._canvas.find_withtag("inner_oval_4_a") and not round(inner_corner_radius) * 2 < height - (2 * border_width): + self._canvas.delete("inner_oval_4_a", "inner_oval_4_b") # change position of border corner parts - self.canvas.coords("inner_oval_1_a", border_width + inner_corner_radius, border_width + inner_corner_radius, inner_corner_radius) - self.canvas.coords("inner_oval_1_b", border_width + inner_corner_radius, border_width + inner_corner_radius, inner_corner_radius) - self.canvas.coords("inner_oval_2_a", width - border_width - inner_corner_radius, border_width + inner_corner_radius, inner_corner_radius) - self.canvas.coords("inner_oval_2_b", width - border_width - inner_corner_radius, border_width + inner_corner_radius, inner_corner_radius) - self.canvas.coords("inner_oval_3_a", width - border_width - inner_corner_radius, height - border_width - inner_corner_radius, inner_corner_radius) - self.canvas.coords("inner_oval_3_b", width - border_width - inner_corner_radius, height - border_width - inner_corner_radius, inner_corner_radius) - self.canvas.coords("inner_oval_4_a", border_width + inner_corner_radius, height - border_width - inner_corner_radius, inner_corner_radius) - self.canvas.coords("inner_oval_4_b", border_width + inner_corner_radius, height - border_width - inner_corner_radius, inner_corner_radius) + 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 + 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") + 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) 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") + 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) 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") + 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)) + 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)) + + if requires_recoloring: + self._canvas.tag_lower("inner_parts") + self._canvas.tag_lower("border_parts") return requires_recoloring @@ -230,76 +251,76 @@ class DrawEngine: 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") + 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) + 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") + 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") + 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 )) + 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") + 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") + 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)) + 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 + 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") + 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)) + 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_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). + """ Draws a rounded bar on the canvas, which is split in half according to the argument 'progress_value' (0 - 1). The border elements get the 'border_parts' tag", the main elements get the 'inner_parts' tag and the progress elements get the 'progress_parts' tag. The 'orientation' argument defines from which direction the progress starts (n, w, s, e). @@ -309,50 +330,204 @@ class DrawEngine: 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) + 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": + if self._rendering_method == "polygon_shapes" or self._rendering_method == "circle_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": + 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 = self._draw_rounded_rect_with_border_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius) + + 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 + + # 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: + + requires_recoloring, requires_recoloring_2 = False, False + + if inner_corner_radius > 0: + # create canvas border corner parts if not already created + if not self._canvas.find_withtag("progress_oval_1_a"): + self._canvas.create_aa_circle(0, 0, 0, tags=("progress_oval_1_a", "progress_corner_part", "progress_parts"), anchor=tkinter.CENTER) + self._canvas.create_aa_circle(0, 0, 0, tags=("progress_oval_1_b", "progress_corner_part", "progress_parts"), anchor=tkinter.CENTER, angle=180) + self._canvas.create_aa_circle(0, 0, 0, tags=("progress_oval_2_a", "progress_corner_part", "progress_parts"), anchor=tkinter.CENTER) + self._canvas.create_aa_circle(0, 0, 0, tags=("progress_oval_2_b", "progress_corner_part", "progress_parts"), anchor=tkinter.CENTER, angle=180) 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") + if not self._canvas.find_withtag("progress_oval_3_a") and round(inner_corner_radius) * 2 < height - 2 * border_width: + self._canvas.create_aa_circle(0, 0, 0, tags=("progress_oval_3_a", "progress_corner_part", "progress_parts"), anchor=tkinter.CENTER) + self._canvas.create_aa_circle(0, 0, 0, tags=("progress_oval_3_b", "progress_corner_part", "progress_parts"), anchor=tkinter.CENTER, angle=180) + self._canvas.create_aa_circle(0, 0, 0, tags=("progress_oval_4_a", "progress_corner_part", "progress_parts"), anchor=tkinter.CENTER) + self._canvas.create_aa_circle(0, 0, 0, tags=("progress_oval_4_b", "progress_corner_part", "progress_parts"), anchor=tkinter.CENTER, angle=180) + requires_recoloring = True + elif self._canvas.find_withtag("progress_oval_3_a") and not round(inner_corner_radius) * 2 < height - 2 * border_width: + self._canvas.delete("progress_oval_3_a", "progress_oval_3_b", "progress_oval_4_a", "progress_oval_4_b") - # 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") + if not self._canvas.find_withtag("progress_rectangle_1"): + self._canvas.create_rectangle(0, 0, 0, 0, tags=("progress_rectangle_1", "progress_rectangle_part", "progress_parts"), width=0) + requires_recoloring = True + + if not self._canvas.find_withtag("progress_rectangle_2") and inner_corner_radius * 2 < height - (border_width * 2): + self._canvas.create_rectangle(0, 0, 0, 0, tags=("progress_rectangle_2", "progress_rectangle_part", "progress_parts"), width=0) + requires_recoloring = True + elif self._canvas.find_withtag("progress_rectangle_2") and not inner_corner_radius * 2 < height - (border_width * 2): + self._canvas.delete("progress_rectangle_2") + + # horizontal orientation from the bottom + if orientation == "w": + 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 + 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_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) + self._canvas.coords("progress_oval_2_b", border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value, + 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, + 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, + 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 + self._canvas.coords("progress_rectangle_1", + border_width + inner_corner_radius, + border_width, + border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value, + height - border_width) + self._canvas.coords("progress_rectangle_2", + border_width, + border_width + inner_corner_radius, + border_width + 2 * inner_corner_radius + (width - 2 * inner_corner_radius - 2 * border_width) * progress_value, + height - inner_corner_radius - border_width) + + # vertical orientation from the bottom + if orientation == "s": + 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 + 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) + 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) + 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) + 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) + 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_b", width - border_width - 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 + self._canvas.coords("progress_rectangle_1", + border_width + inner_corner_radius, + border_width + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value), + width - border_width - inner_corner_radius, + height - border_width) + self._canvas.coords("progress_rectangle_2", + border_width, + border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value), + width - border_width, + height - inner_corner_radius - border_width) + + return requires_recoloring or requires_recoloring_2 + + def draw_rounded_slider_with_border_and_button(self, width: int, height: int, corner_radius: Union[float, int], border_width: Union[float, int], + button_length: Union[float, int], button_corner_radius: Union[float, int], slider_value: float, + orientation: str) -> bool: + + 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) + + if button_corner_radius > width / 2 or button_corner_radius > height / 2: # restrict button_corner_radius if it's too larger + button_corner_radius = min(width / 2, height / 2) + + button_length = round(button_length) + border_width = round(border_width) + button_corner_radius = round(button_corner_radius) + corner_radius = self._calc_optimal_corner_radius(corner_radius) # optimize corner_radius for different drawing methods (different rounding) + + if corner_radius >= border_width: + inner_corner_radius = corner_radius - border_width + else: + inner_corner_radius = 0 + + # scale slider_value to a smaller range to be the progress_value of the underlaying progressbar + if orientation == "w" or orientation == "e": + x = (button_length / width) / 2 + else: + x = (button_length / height) / 2 + print(x) + new_slider_value = slider_value * (1 - 2 * x) + x + + if self._rendering_method == "polygon_shapes" or self._rendering_method == "circle_shapes": + return self._draw_rounded_slider_with_border_and_button_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius, + button_length, button_corner_radius, slider_value, orientation) + elif self._rendering_method == "font_shapes": + return self._draw_rounded_slider_with_border_and_button_font_shapes(width, height, corner_radius, border_width, inner_corner_radius, + button_length, button_corner_radius, slider_value, orientation) + + def _draw_rounded_slider_with_border_and_button_polygon_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int, + button_length: int, button_corner_radius: int, slider_value: float, orientation: str) -> bool: + + requires_recoloring = self._draw_rounded_progress_bar_with_border_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius, + slider_value, orientation) + + # create button parts + if not self._canvas.find_withtag("slider_parts"): + self._canvas.create_polygon((0, 0, 0, 0), tags=("slider_line_1", "slider_parts"), joinstyle=tkinter.ROUND) + self._canvas.tag_raise("slider_parts") requires_recoloring = True if corner_radius <= border_width: @@ -360,239 +535,80 @@ class DrawEngine: 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) + slider_x_position = corner_radius + (button_length / 2) + (width - 2 * corner_radius - button_length) * slider_value + self._canvas.coords("slider_line_1", + slider_x_position - (button_length / 2), button_corner_radius, + slider_x_position + (button_length / 2), button_corner_radius, + slider_x_position + (button_length / 2), height - button_corner_radius, + slider_x_position - (button_length / 2), height - button_corner_radius) + self._canvas.itemconfig("slider_line_1", + width=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: + def _draw_rounded_slider_with_border_and_button_font_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int, + button_length: int, button_corner_radius: int, slider_value: float, orientation: str) -> bool: - print(width, height, border_width, corner_radius, inner_corner_radius) - requires_recoloring = False + requires_recoloring = self._draw_rounded_progress_bar_with_border_font_shapes(width, height, corner_radius, border_width, inner_corner_radius, + slider_value, orientation) - # 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") + if not self._canvas.find_withtag("slider_oval_1_a"): + self._canvas.create_aa_circle(0, 0, 0, tags=("slider_oval_1_a", "slider_corner_part", "slider_parts"), anchor=tkinter.CENTER) + self._canvas.create_aa_circle(0, 0, 0, tags=("slider_oval_1_b", "slider_corner_part", "slider_parts"), anchor=tkinter.CENTER, angle=180) 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") + if not self._canvas.find_withtag("slider_oval_2_a") and button_length > 0: + self._canvas.create_aa_circle(0, 0, 0, tags=("slider_oval_2_a", "slider_corner_part", "slider_parts"), anchor=tkinter.CENTER) + self._canvas.create_aa_circle(0, 0, 0, tags=("slider_oval_2_b", "slider_corner_part", "slider_parts"), anchor=tkinter.CENTER, angle=180) requires_recoloring = True + elif self._canvas.find_withtag("slider_oval_2_a") and not button_length > 0: + self._canvas.delete("slider_oval_2_a", "slider_oval_2_b") - 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") + if not self._canvas.find_withtag("slider_oval_4_a") and height > 2 * button_corner_radius: + self._canvas.create_aa_circle(0, 0, 0, tags=("slider_oval_4_a", "slider_corner_part", "slider_parts"), anchor=tkinter.CENTER) + self._canvas.create_aa_circle(0, 0, 0, tags=("slider_oval_4_b", "slider_corner_part", "slider_parts"), anchor=tkinter.CENTER, angle=180) requires_recoloring = True + elif self._canvas.find_withtag("slider_oval_4_a") and not height > 2 * button_corner_radius: + self._canvas.delete("slider_oval_4_a", "slider_oval_4_b") - 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)) + if not self._canvas.find_withtag("slider_oval_3_a") and button_length > 0 and height > 2 * button_corner_radius: + self._canvas.create_aa_circle(0, 0, 0, tags=("slider_oval_3_a", "slider_corner_part", "slider_parts"), anchor=tkinter.CENTER) + self._canvas.create_aa_circle(0, 0, 0, tags=("slider_oval_3_b", "slider_corner_part", "slider_parts"), anchor=tkinter.CENTER, angle=180) + requires_recoloring = True + elif self._canvas.find_withtag("border_oval_3_a") and not (button_length > 0 and height > 2 * button_corner_radius): + self._canvas.delete("slider_oval_3_a", "slider_oval_3_b") - return requires_recoloring + if not self._canvas.find_withtag("slider_rectangle_1") and button_length > 0: + self._canvas.create_rectangle(0, 0, 0, 0, tags=("slider_rectangle_1", "slider_rectangle_part", "slider_parts"), width=0) + requires_recoloring = True + elif self._canvas.find_withtag("slider_rectangle_1") and not button_length > 0: + self._canvas.delete("slider_rectangle_1") - def draw_rounded_button(self, canvas, width, height): - pass + if not self._canvas.find_withtag("slider_rectangle_2") and height > 2 * button_corner_radius: + self._canvas.create_rectangle(0, 0, 0, 0, tags=("slider_rectangle_2", "slider_rectangle_part", "slider_parts"), width=0) + requires_recoloring = True + elif self._canvas.find_withtag("slider_rectangle_2") and not height > 2 * button_corner_radius: + self._canvas.delete("slider_rectangle_2") + + slider_x_position = corner_radius + (button_length / 2) + (width - 2 * corner_radius - button_length) * slider_value + self._canvas.coords("slider_oval_1_a", slider_x_position - (button_length / 2), button_corner_radius, button_corner_radius) + self._canvas.coords("slider_oval_1_b", slider_x_position - (button_length / 2), button_corner_radius, button_corner_radius) + self._canvas.coords("slider_oval_2_a", slider_x_position + (button_length / 2), button_corner_radius, button_corner_radius) + self._canvas.coords("slider_oval_2_b", slider_x_position + (button_length / 2), button_corner_radius, button_corner_radius) + self._canvas.coords("slider_oval_3_a", slider_x_position + (button_length / 2), height - button_corner_radius, button_corner_radius) + self._canvas.coords("slider_oval_3_b", slider_x_position + (button_length / 2), height - button_corner_radius, button_corner_radius) + self._canvas.coords("slider_oval_4_a", slider_x_position - (button_length / 2), height - button_corner_radius, button_corner_radius) + self._canvas.coords("slider_oval_4_b", slider_x_position - (button_length / 2), height - button_corner_radius, button_corner_radius) + + self._canvas.coords("slider_rectangle_1", + slider_x_position - (button_length / 2), 0, + slider_x_position + (button_length / 2), height) + self._canvas.coords("slider_rectangle_2", + slider_x_position - (button_length / 2) - button_corner_radius, button_corner_radius, + slider_x_position + (button_length / 2) + button_corner_radius, height - button_corner_radius) + + if requires_recoloring: + self._canvas.tag_raise("slider_parts") + + return requires_recoloring \ No newline at end of file diff --git a/customtkinter/customtkinter_entry.py b/customtkinter/customtkinter_entry.py index d3a46ac..3781249 100644 --- a/customtkinter/customtkinter_entry.py +++ b/customtkinter/customtkinter_entry.py @@ -7,7 +7,7 @@ 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_draw_engine import CTkDrawEngine class CTkEntry(tkinter.Frame): @@ -93,7 +93,7 @@ class CTkEntry(tkinter.Frame): **kwargs) self.entry.grid(column=0, row=0, sticky="we", padx=self.corner_radius if self.corner_radius >= 6 else 6) - self.draw_engine = DrawEngine(self.canvas, CTkSettings.preferred_drawing_method) + self.draw_engine = CTkDrawEngine(self.canvas, CTkSettings.preferred_drawing_method) super().bind('', self.update_dimensions) self.entry.bind('', self.set_placeholder) @@ -149,16 +149,25 @@ class CTkEntry(tkinter.Frame): def draw(self): self.canvas.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode)) - self.entry.configure(bg=CTkThemeManager.single_color(self.fg_color, self.appearance_mode), - highlightcolor=CTkThemeManager.single_color(self.fg_color, self.appearance_mode), - 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, 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)) + if CTkThemeManager.single_color(self.fg_color, self.appearance_mode) is not None: + 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.entry.configure(bg=CTkThemeManager.single_color(self.fg_color, self.appearance_mode), + highlightcolor=CTkThemeManager.single_color(self.fg_color, self.appearance_mode), + fg=CTkThemeManager.single_color(self.text_color, self.appearance_mode), + insertbackground=CTkThemeManager.single_color(self.text_color, self.appearance_mode)) + else: + self.canvas.itemconfig("inner_parts", + fill=CTkThemeManager.single_color(self.bg_color, self.appearance_mode), + outline=CTkThemeManager.single_color(self.bg_color, self.appearance_mode)) + self.entry.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode), + highlightcolor=CTkThemeManager.single_color(self.bg_color, self.appearance_mode), + fg=CTkThemeManager.single_color(self.text_color, self.appearance_mode), + insertbackground=CTkThemeManager.single_color(self.text_color, self.appearance_mode)) self.canvas.itemconfig("border_parts", fill=CTkThemeManager.single_color(self.border_color, self.appearance_mode), diff --git a/customtkinter/customtkinter_frame.py b/customtkinter/customtkinter_frame.py index fa6b07a..b8a7d6a 100644 --- a/customtkinter/customtkinter_frame.py +++ b/customtkinter/customtkinter_frame.py @@ -5,7 +5,7 @@ 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_draw_engine import CTkDrawEngine class CTkFrame(tkinter.Frame): @@ -82,7 +82,7 @@ class CTkFrame(tkinter.Frame): self.canvas.place(x=0, y=0, relwidth=1, relheight=1) self.canvas.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode)) - self.draw_engine = DrawEngine(self.canvas, CTkSettings.preferred_drawing_method) + self.draw_engine = CTkDrawEngine(self.canvas, CTkSettings.preferred_drawing_method) self.bind('', self.update_dimensions) diff --git a/customtkinter/customtkinter_label.py b/customtkinter/customtkinter_label.py index b0ebe3a..1eb07e2 100644 --- a/customtkinter/customtkinter_label.py +++ b/customtkinter/customtkinter_label.py @@ -7,7 +7,7 @@ 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_draw_engine import CTkDrawEngine class CTkLabel(tkinter.Frame): @@ -83,7 +83,7 @@ class CTkLabel(tkinter.Frame): **kwargs) self.text_label.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER) - self.draw_engine = DrawEngine(self.canvas, CTkSettings.preferred_drawing_method) + self.draw_engine = CTkDrawEngine(self.canvas, CTkSettings.preferred_drawing_method) super().configure(width=self.width, height=self.height) diff --git a/customtkinter/customtkinter_progressbar.py b/customtkinter/customtkinter_progressbar.py index 648fe90..2fc8ea9 100644 --- a/customtkinter/customtkinter_progressbar.py +++ b/customtkinter/customtkinter_progressbar.py @@ -6,7 +6,7 @@ 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_draw_engine import CTkDrawEngine from .customtkinter_settings import CTkSettings @@ -21,7 +21,7 @@ class CTkProgressBar(tkinter.Frame): progress_color="default_theme", corner_radius="default_theme", width=200, - height=16, + height=10, border_width="default_theme", **kwargs): super().__init__(*args, **kwargs) @@ -73,7 +73,7 @@ class CTkProgressBar(tkinter.Frame): height=self.height) self.canvas.place(x=0, y=0, relwidth=1, relheight=1) - self.draw_engine = DrawEngine(self.canvas, CTkSettings.preferred_drawing_method) + self.draw_engine = CTkDrawEngine(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) @@ -123,14 +123,6 @@ 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() - 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: @@ -145,123 +137,6 @@ class CTkProgressBar(tkinter.Frame): 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 """ - - coordinate_shift = -1 - width_reduced = -1 - - # create border button parts (only if border exists) - if self.border_width > 0: - if not self.canvas.find_withtag("border_parts"): - self.canvas.create_line((0, 0, 0, 0), tags=("border_line_1", "border_parts")) - - self.canvas.coords("border_line_1", - (self.height / 2, - self.height / 2, - self.width - self.height / 2 + coordinate_shift, - self.height / 2)) - self.canvas.itemconfig("border_line_1", - capstyle=tkinter.ROUND, - width=self.height + width_reduced) - self.canvas.lower("border_parts") - - # create inner button parts - if not self.canvas.find_withtag("inner_parts"): - self.canvas.create_line((0, 0, 0, 0), tags=("inner_line_1", "inner_parts")) - - self.canvas.coords("inner_line_1", - (self.height / 2, - self.height / 2, - self.width - self.height / 2 + coordinate_shift, - self.height / 2)) - self.canvas.itemconfig("inner_line_1", - capstyle=tkinter.ROUND, - width=self.height - self.border_width * 2 + width_reduced) - - # progress parts - if not self.canvas.find_withtag("progress_parts"): - self.canvas.create_line((0, 0, 0, 0), tags=("progress_line_1", "progress_parts")) - - self.canvas.coords("progress_line_1", - (self.height / 2, - self.height / 2, - self.height / 2 + (self.width + coordinate_shift - self.height) * self.value, - self.height / 2)) - self.canvas.itemconfig("progress_line_1", - capstyle=tkinter.ROUND, - width=self.height - self.border_width * 2 + width_reduced) - - def draw_with_ovals_and_rects(self): - """ draw the progress bar parts with ovals and rectangles """ - - if sys.platform == "darwin": - oval_bottom_right_shift = 0 - rect_bottom_right_shift = 0 - else: - # ovals and rects are always rendered too large on Windows and need to be made smaller by -1 - oval_bottom_right_shift = -1 - rect_bottom_right_shift = -0 - - # frame_border - if self.border_width > 0: - if not self.canvas.find_withtag("border_parts"): - self.canvas.create_oval((0, 0, 0, 0), tags=("border_oval_1", "border_parts"), width=0) - self.canvas.create_rectangle((0, 0, 0, 0), tags=("border_rect_1", "border_parts"), width=0) - self.canvas.create_oval((0, 0, 0, 0), tags=("border_oval_2", "border_parts"), width=0) - - self.canvas.coords("border_oval_1", (0, - 0, - self.height + oval_bottom_right_shift, - self.height + oval_bottom_right_shift)) - self.canvas.coords("border_rect_1", (self.height/2, - 0, - self.width-(self.height/2) + rect_bottom_right_shift, - self.height + rect_bottom_right_shift)) - self.canvas.coords("border_oval_2", (self.width-self.height, - 0, - self.width + oval_bottom_right_shift, - self.height + oval_bottom_right_shift)) - - # foreground - if not self.canvas.find_withtag("inner_parts"): - self.canvas.create_oval((0, 0, 0, 0), tags=("inner_oval_1", "inner_parts"), width=0) - self.canvas.create_rectangle((0, 0, 0, 0), tags=("inner_rect_1", "inner_parts"), width=0) - self.canvas.create_oval((0, 0, 0, 0), tags=("inner_oval_2", "inner_parts"), width=0) - - self.canvas.coords("inner_oval_1", (self.border_width, - self.border_width, - self.height-self.border_width + oval_bottom_right_shift, - self.height-self.border_width + oval_bottom_right_shift)) - self.canvas.coords("inner_rect_1", (self.height/2, - self.border_width, - self.width-(self.height/2 + rect_bottom_right_shift), - self.height-self.border_width + rect_bottom_right_shift)) - self.canvas.coords("inner_oval_2", (self.width-self.height+self.border_width, - self.border_width, - self.width-self.border_width + oval_bottom_right_shift, - self.height-self.border_width + oval_bottom_right_shift)) - - # progress parts - if not self.canvas.find_withtag("progress_parts"): - self.canvas.create_oval((0, 0, 0, 0), tags=("progress_oval_1", "progress_parts"), width=0) - self.canvas.create_rectangle((0, 0, 0, 0), tags=("progress_rect_1", "progress_parts"), width=0) - self.canvas.create_oval((0, 0, 0, 0), tags=("progress_oval_2", "progress_parts"), width=0) - - self.canvas.coords("progress_oval_1", (self.border_width, - self.border_width, - self.height - self.border_width + oval_bottom_right_shift, - self.height - self.border_width + oval_bottom_right_shift)) - self.canvas.coords("progress_rect_1", (self.height / 2, - self.border_width, - self.height / 2 + (self.width - self.height) * self.value + rect_bottom_right_shift, - self.height - self.border_width + rect_bottom_right_shift)) - self.canvas.coords("progress_oval_2", - (self.height / 2 + (self.width - self.height) * self.value - self.height / 2 + self.border_width, - self.border_width, - self.height / 2 + (self.width - self.height) * self.value + self.height / 2 - self.border_width + oval_bottom_right_shift, - self.height - self.border_width + oval_bottom_right_shift)) - def configure(self, *args, **kwargs): require_redraw = False # some attribute changes require a call of self.draw() at the end diff --git a/customtkinter/customtkinter_slider.py b/customtkinter/customtkinter_slider.py index 79961a7..5789016 100644 --- a/customtkinter/customtkinter_slider.py +++ b/customtkinter/customtkinter_slider.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_settings import CTkSettings +from .customtkinter_draw_engine import CTkDrawEngine +from .customtkinter_canvas import CTkCanvas class CTkSlider(tkinter.Frame): @@ -23,7 +26,9 @@ class CTkSlider(tkinter.Frame): width=160, height=16, corner_radius="default_theme", + button_corner_radius="default_theme", border_width="default_theme", + button_length="default_theme", command=None, variable=None, **kwargs): @@ -63,9 +68,11 @@ class CTkSlider(tkinter.Frame): 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.corner_radius = CTkThemeManager.theme["shape"]["button_corner_radius"] if corner_radius == "default_theme" else corner_radius + self.height = height + self.corner_radius = CTkThemeManager.theme["shape"]["slider_corner_radius"] if corner_radius == "default_theme" else corner_radius + self.button_corner_radius = CTkThemeManager.theme["shape"]["slider_button_corner_radius"] if button_corner_radius == "default_theme" else button_corner_radius 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.hover_state = False self.from_ = from_ @@ -81,13 +88,17 @@ class CTkSlider(tkinter.Frame): self.configure(width=self.width, height=self.height) if sys.platform == "darwin": self.configure(cursor="pointinghand") + elif sys.platform.startswith("win"): + self.configure(cursor="hand2") - self.canvas = tkinter.Canvas(master=self, - highlightthickness=0, - width=self.width, - height=self.height) + self.canvas = CTkCanvas(master=self, + highlightthickness=0, + width=self.width, + height=self.height) self.canvas.grid(column=0, row=0, sticky="nswe") + self.draw_engine = CTkDrawEngine(self.canvas, CTkSettings.preferred_drawing_method) + self.canvas.bind("", self.on_enter) self.canvas.bind("", self.on_leave) self.canvas.bind("", self.clicked) @@ -147,30 +158,39 @@ class CTkSlider(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() + # # 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 no_color_updates is False: + requires_recoloring = self.draw_engine.draw_rounded_slider_with_border_and_button(self.width, self.height, self.corner_radius, self.border_width, + self.button_length, self.button_corner_radius, 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)) if self.border_color is None: - self.canvas.itemconfig("border_parts", fill=CTkThemeManager.single_color(self.bg_color, self.appearance_mode)) + self.canvas.itemconfig("border_parts", fill=CTkThemeManager.single_color(self.bg_color, self.appearance_mode), + outline=CTkThemeManager.single_color(self.bg_color, self.appearance_mode)) else: - self.canvas.itemconfig("border_parts", fill=CTkThemeManager.single_color(self.border_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)) + 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)) if self.progress_color is None: - self.canvas.itemconfig("progress_parts", fill=CTkThemeManager.single_color(self.fg_color, self.appearance_mode)) + self.canvas.itemconfig("progress_parts", fill=CTkThemeManager.single_color(self.fg_color, self.appearance_mode), + outline=CTkThemeManager.single_color(self.fg_color, self.appearance_mode)) else: - self.canvas.itemconfig("progress_parts", fill=CTkThemeManager.single_color(self.progress_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)) - self.canvas.itemconfig("button_parts", fill=CTkThemeManager.single_color(self.button_color, self.appearance_mode)) + self.canvas.itemconfig("slider_parts", fill=CTkThemeManager.single_color(self.button_color, self.appearance_mode), + outline=CTkThemeManager.single_color(self.button_color, self.appearance_mode)) def draw_with_polygon_shapes(self): """ draw the slider parts with just three polygons that have a rounded border """ @@ -335,11 +355,13 @@ class CTkSlider(tkinter.Frame): def on_enter(self, event=0): self.hover_state = True - self.canvas.itemconfig("button_parts", fill=CTkThemeManager.single_color(self.button_hover_color, self.appearance_mode)) + self.canvas.itemconfig("slider_parts", fill=CTkThemeManager.single_color(self.button_hover_color, self.appearance_mode), + outline=CTkThemeManager.single_color(self.button_hover_color, self.appearance_mode)) def on_leave(self, event=0): self.hover_state = False - self.canvas.itemconfig("button_parts", fill=CTkThemeManager.single_color(self.button_color, self.appearance_mode)) + self.canvas.itemconfig("slider_parts", fill=CTkThemeManager.single_color(self.button_color, self.appearance_mode), + outline=CTkThemeManager.single_color(self.button_color, self.appearance_mode)) def round_to_step_size(self, value): if self.number_of_steps is not None: diff --git a/examples/complex_example.py b/examples/complex_example.py index 29988d7..d6d9f9a 100644 --- a/examples/complex_example.py +++ b/examples/complex_example.py @@ -6,7 +6,7 @@ import sys customtkinter.set_appearance_mode("Light") # Modes: "System" (standard), "Dark", "Light" customtkinter.set_default_color_theme("blue") # Themes: "blue" (standard), "green", "dark-blue" -customtkinter.CTkSettings.preferred_drawing_method = "font_shapes" +customtkinter.CTkSettings.preferred_drawing_method = "polygon_shapes" class App(customtkinter.CTk): @@ -116,7 +116,6 @@ class App(customtkinter.CTk): self.slider_1 = customtkinter.CTkSlider(master=self.frame_right, height=16, - border_width=0, from_=1, to=0, number_of_steps=3, @@ -127,6 +126,7 @@ class App(customtkinter.CTk): self.slider_2 = customtkinter.CTkSlider(master=self.frame_right, width=160, height=16, + button_length=0, 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) @@ -165,7 +165,7 @@ 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) + self.progressbar.set(0.5) def button_event(self): print("Button pressed")