finished CTkDrawEngine for all current widgets

This commit is contained in:
TomSchimansky 2022-03-01 16:12:55 +01:00
parent bb1fe25625
commit 965a7ff87d
11 changed files with 498 additions and 574 deletions

View File

@ -4,8 +4,8 @@
"button": ["#64A1D2", "#1C94CF"], "button": ["#64A1D2", "#1C94CF"],
"button_hover": ["#A7C2E0", "#5FB4DD"], "button_hover": ["#A7C2E0", "#5FB4DD"],
"button_border": ["#A7C2E0", "#5FB4DD"], "button_border": ["#A7C2E0", "#5FB4DD"],
"checkbox_border": ["black", "#ededed"], "checkbox_border": ["gray20", "#ededed"],
"entry": ["gray95", "#222222"], "entry": [null, null],
"entry_border": ["gray65", "gray40"], "entry_border": ["gray65", "gray40"],
"entry_placeholder_text": ["gray52", "gray62"], "entry_placeholder_text": ["gray52", "gray62"],
"frame_border": ["#A7C2E0", "#5FB4DD"], "frame_border": ["#A7C2E0", "#5FB4DD"],
@ -14,7 +14,7 @@
"label": ["white", "#626061"], "label": ["white", "#626061"],
"text": ["gray18", "gray90"], "text": ["gray18", "gray90"],
"progressbar": ["#6B6B6B", "#222222"], "progressbar": ["#6B6B6B", "#222222"],
"progressbar_progress": ["red", "red"], "progressbar_progress": ["#64A1D2", "#1C94CF"],
"progressbar_border": ["gray", "gray"], "progressbar_border": ["gray", "gray"],
"slider": ["#6B6B6B", "#222222"], "slider": ["#6B6B6B", "#222222"],
"slider_progress": ["#A5A6A5", "#555555"], "slider_progress": ["#A5A6A5", "#555555"],
@ -47,9 +47,11 @@
"frame_corner_radius": 10, "frame_corner_radius": 10,
"frame_border_width": 0, "frame_border_width": 0,
"label_corner_radius": 8, "label_corner_radius": 8,
"progressbar_border_width": 2, "progressbar_border_width": 0,
"progressbar_corner_radius": 1000, "progressbar_corner_radius": 100,
"slider_border_width": 4, "slider_border_width": 5,
"slider_corner_radius": 3 "slider_corner_radius": 8,
"slider_button_length": 4,
"slider_button_corner_radius": 6
} }
} }

View File

@ -7,7 +7,7 @@ from .appearance_mode_tracker import AppearanceModeTracker
from .customtkinter_theme_manager import CTkThemeManager from .customtkinter_theme_manager import CTkThemeManager
from .customtkinter_canvas import CTkCanvas from .customtkinter_canvas import CTkCanvas
from .customtkinter_settings import CTkSettings from .customtkinter_settings import CTkSettings
from .customtkinter_draw_engine import DrawEngine from .customtkinter_draw_engine import CTkDrawEngine
class CTkButton(tkinter.Frame): class CTkButton(tkinter.Frame):
@ -110,7 +110,7 @@ class CTkButton(tkinter.Frame):
height=self.height) height=self.height)
self.canvas.grid(row=0, column=0, rowspan=2, columnspan=2, sticky="nsew") self.canvas.grid(row=0, column=0, rowspan=2, columnspan=2, sticky="nsew")
self.draw_engine = DrawEngine(self.canvas, CTkSettings.preferred_drawing_method) self.draw_engine = CTkDrawEngine(self.canvas, CTkSettings.preferred_drawing_method)
# event bindings # event bindings
if self.hover is True: if self.hover is True:

View File

@ -12,7 +12,7 @@ class CTkCanvas(tkinter.Canvas):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.aa_circle_canvas_ids = [] self.aa_circle_canvas_ids = set()
def get_char_from_radius(self, radius): def get_char_from_radius(self, radius):
if CTkSettings.scaling_factor == 1: 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, 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) font=("CustomTkinter_shapes_font", -radius * 2), tags=tags, angle=angle)
self.addtag_withtag("ctk_aa_circle_font_element", circle_1) 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 return circle_1

View File

@ -7,7 +7,7 @@ from customtkinter.appearance_mode_tracker import AppearanceModeTracker
from customtkinter.customtkinter_theme_manager import CTkThemeManager from customtkinter.customtkinter_theme_manager import CTkThemeManager
from customtkinter.customtkinter_canvas import CTkCanvas from customtkinter.customtkinter_canvas import CTkCanvas
from customtkinter.customtkinter_settings import CTkSettings from customtkinter.customtkinter_settings import CTkSettings
from customtkinter.customtkinter_draw_engine import DrawEngine from customtkinter.customtkinter_draw_engine import CTkDrawEngine
class CTkCheckBox(tkinter.Frame): class CTkCheckBox(tkinter.Frame):
@ -100,7 +100,7 @@ class CTkCheckBox(tkinter.Frame):
height=self.height) height=self.height)
self.canvas.pack(side='left') 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: if sys.platform == "darwin" and self.state == tkinter.NORMAL and CTkSettings.hand_cursor_enabled:
self.canvas.configure(cursor="pointinghand") self.canvas.configure(cursor="pointinghand")

View File

@ -4,25 +4,39 @@ from typing import Union
from .customtkinter_canvas import CTkCanvas from .customtkinter_canvas import CTkCanvas
class DrawEngine: class CTkDrawEngine:
def __init__(self, canvas: CTkCanvas, rendering_method: str): """
self.canvas = canvas This is the core of the CustomTkinter library where all the drawing on the tkinter.Canvas happens.
self.rendering_method = rendering_method # "polygon_shapes" (macOS), "font_shapes" (Windows, Linux), "circle_shapes" (backup) 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 # optimize for drawing with polygon shapes
if self.rendering_method == "polygon_shapes": if self._rendering_method == "polygon_shapes":
if sys.platform == "darwin": if sys.platform == "darwin":
return user_corner_radius return user_corner_radius
else: else:
return round(user_corner_radius) return round(user_corner_radius)
# optimize forx drawing with antialiased font shapes # optimize forx drawing with antialiased font shapes
elif self.rendering_method == "font_shapes": elif self._rendering_method == "font_shapes":
return round(user_corner_radius) return round(user_corner_radius)
# optimize for drawing with circles and rects # 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 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 # 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) corner_radius = min(width / 2, height / 2)
border_width = round(border_width) 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: if corner_radius >= border_width:
inner_corner_radius = corner_radius - border_width inner_corner_radius = corner_radius - border_width
else: else:
inner_corner_radius = 0 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) return self._draw_rounded_rect_with_border_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius)
elif self.rendering_method == "font_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) 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 == "circle_shapes":
return self._draw_rounded_rect_with_border_circle_shapes(width, height, corner_radius, border_width, inner_corner_radius) 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: 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) # create border button parts (only if border exists)
if border_width > 0: if border_width > 0:
if not self.canvas.find_withtag("border_parts"): if not self._canvas.find_withtag("border_parts"):
self.canvas.create_polygon((0, 0, 0, 0), tags=("border_line_1", "border_parts")) self._canvas.create_polygon((0, 0, 0, 0), tags=("border_line_1", "border_parts"))
self.canvas.tag_lower("border_parts")
requires_recoloring = True requires_recoloring = True
self.canvas.coords("border_line_1", self._canvas.coords("border_line_1",
(corner_radius, (corner_radius,
corner_radius, corner_radius,
width - corner_radius, width - corner_radius,
corner_radius, corner_radius,
@ -76,17 +89,16 @@ class DrawEngine:
height - corner_radius, height - corner_radius,
corner_radius, corner_radius,
height - corner_radius)) height - corner_radius))
self.canvas.itemconfig("border_line_1", self._canvas.itemconfig("border_line_1",
joinstyle=tkinter.ROUND, joinstyle=tkinter.ROUND,
width=corner_radius * 2) width=corner_radius * 2)
else: else:
self.canvas.delete("border_parts") self._canvas.delete("border_parts")
# create inner button parts # create inner button parts
if not self.canvas.find_withtag("inner_parts"): if not self._canvas.find_withtag("inner_parts"):
self.canvas.create_polygon((0, 0, 0, 0), tags=("inner_line_1", "inner_parts")) self._canvas.create_polygon((0, 0, 0, 0), tags=("inner_line_1", "inner_parts"))
self.canvas.tag_raise("inner_parts")
requires_recoloring = True requires_recoloring = True
if corner_radius <= border_width: if corner_radius <= border_width:
@ -94,8 +106,8 @@ class DrawEngine:
else: else:
bottom_right_shift = 0 bottom_right_shift = 0
self.canvas.coords("inner_line_1", self._canvas.coords("inner_line_1",
(border_width + inner_corner_radius, (border_width + inner_corner_radius,
border_width + inner_corner_radius, border_width + inner_corner_radius,
width - (border_width + inner_corner_radius) + bottom_right_shift, width - (border_width + inner_corner_radius) + bottom_right_shift,
border_width + inner_corner_radius, border_width + inner_corner_radius,
@ -103,123 +115,132 @@ class DrawEngine:
height - (border_width + inner_corner_radius) + bottom_right_shift, height - (border_width + inner_corner_radius) + bottom_right_shift,
border_width + inner_corner_radius, border_width + inner_corner_radius,
height - (border_width + inner_corner_radius) + bottom_right_shift)) height - (border_width + inner_corner_radius) + bottom_right_shift))
self.canvas.itemconfig("inner_line_1", self._canvas.itemconfig("inner_line_1",
joinstyle=tkinter.ROUND, joinstyle=tkinter.ROUND,
width=inner_corner_radius * 2) width=inner_corner_radius * 2)
if requires_recoloring:
self._canvas.tag_lower("inner_parts")
self._canvas.tag_lower("border_parts")
return requires_recoloring 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 requires_recoloring = False
# create border button parts # create border button parts
if border_width > 0: if border_width > 0:
if corner_radius > 0: if corner_radius > 0:
# create canvas border corner parts if not already created # create canvas border corner parts if not already created
if not self.canvas.find_withtag("border_oval_1_a"): 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_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_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_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.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 requires_recoloring = True
if not self.canvas.find_withtag("border_oval_3_a") and round(corner_radius) * 2 < height: 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_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_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_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.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 requires_recoloring = True
elif self.canvas.find_withtag("border_oval_3_a") and not round(corner_radius) * 2 < height: 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"]) 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 # 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_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_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_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_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_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_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_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_4_b", corner_radius, height - corner_radius, corner_radius)
else: 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 # create canvas border rectangle parts if not already created
if not self.canvas.find_withtag("border_rectangle_1"): if not self._canvas.find_withtag("border_rectangle_1"):
self.canvas.create_rectangle(0, 0, 0, 0, tags=("border_rectangle_1", "border_rectangle_part", "border_parts"), width=0) 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.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 requires_recoloring = True
# change position of border rectangle parts # change position of border rectangle parts
self.canvas.coords("border_rectangle_1", (0, corner_radius, width, height - corner_radius)) 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_2", (corner_radius, 0, width - corner_radius, height))
else: else:
self.canvas.delete("border_parts") self._canvas.delete("border_parts")
# create inner button parts # create inner button parts
if inner_corner_radius > 0: if inner_corner_radius > 0:
# create canvas border corner parts if not already created # create canvas border corner parts if not already created
if not self.canvas.find_withtag("inner_oval_1_a"): 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_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_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 requires_recoloring = True
if not self.canvas.find_withtag("inner_oval_3_a") and round(inner_corner_radius) * 2 < height - (2 * border_width): 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_3_a", "inner_corner_part", "inner_parts"), anchor=tkinter.CENTER) 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_3_b", "inner_corner_part", "inner_parts"), anchor=tkinter.CENTER, angle=180) self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_2_b", "inner_corner_part", "inner_parts"), 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 requires_recoloring = True
elif self.canvas.find_withtag("inner_oval_3_a") and not round(inner_corner_radius) * 2 < height - (2 * border_width): 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.delete(["inner_oval_3_a", "inner_oval_3_b", "inner_oval_4_a", "inner_oval_4_b"]) 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 # 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_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_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_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_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_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_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_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_4_b", border_width + inner_corner_radius, height - border_width - inner_corner_radius, inner_corner_radius)
else: 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 # create canvas inner rectangle parts if not already created
if not self.canvas.find_withtag("inner_rectangle_1"): if not self._canvas.find_withtag("inner_rectangle_1"):
self.canvas.create_rectangle(0, 0, 0, 0, tags=("inner_rectangle_1", "inner_rectangle_part", "inner_parts"), width=0) 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 requires_recoloring = True
if not self.canvas.find_withtag("inner_rectangle_2") and inner_corner_radius * 2 < height - (border_width * 2): if not self._canvas.find_withtag("inner_rectangle_2") and inner_corner_radius * 2 < height - (border_width * 2):
self.canvas.create_rectangle(0, 0, 0, 0, tags=("inner_rectangle_2", "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_rectangle_part")
requires_recoloring = True requires_recoloring = True
elif self.canvas.find_withtag("inner_rectangle_2") and not inner_corner_radius * 2 < height - (border_width * 2): elif self._canvas.find_withtag("inner_rectangle_2") and not inner_corner_radius * 2 < height - (border_width * 2):
self.canvas.delete("inner_rectangle_2") self._canvas.delete("inner_rectangle_2")
# change position of inner rectangle parts # change position of inner rectangle parts
self.canvas.coords("inner_rectangle_1", (border_width + inner_corner_radius, self._canvas.coords("inner_rectangle_1", (border_width + inner_corner_radius,
border_width, border_width,
width - border_width - inner_corner_radius, width - border_width - inner_corner_radius,
height - border_width)) height - border_width))
self.canvas.coords("inner_rectangle_2", (border_width, self._canvas.coords("inner_rectangle_2", (border_width,
border_width + inner_corner_radius, border_width + inner_corner_radius,
width - border_width, width - border_width,
height - inner_corner_radius - 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 return requires_recoloring
@ -230,76 +251,76 @@ class DrawEngine:
if border_width > 0: if border_width > 0:
if corner_radius > 0: if corner_radius > 0:
if not self.canvas.find_withtag("border_oval_1"): 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_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_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_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.create_oval(0, 0, 0, 0, tags=("border_oval_4", "border_corner_part", "border_parts"), width=0)
self.canvas.tag_lower("border_parts") self._canvas.tag_lower("border_parts")
requires_recoloring = True requires_recoloring = True
self.canvas.coords("border_oval_1", 0, 0, corner_radius * 2 - 1, corner_radius * 2 - 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_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_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_4", width - corner_radius * 2, height - corner_radius * 2, width - 1, height - 1)
else: else:
self.canvas.delete("border_corner_part") self._canvas.delete("border_corner_part")
if not self.canvas.find_withtag("border_rectangle_1"): if not self._canvas.find_withtag("border_rectangle_1"):
self.canvas.create_rectangle(0, 0, 0, 0, tags=("border_rectangle_1", "border_rectangle_part", "border_parts"), width=0) 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.create_rectangle(0, 0, 0, 0, tags=("border_rectangle_2", "border_rectangle_part", "border_parts"), width=0)
self.canvas.tag_lower("border_parts") self._canvas.tag_lower("border_parts")
requires_recoloring = True requires_recoloring = True
self.canvas.coords("border_rectangle_1", (0, corner_radius, width, height - corner_radius )) 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_2", (corner_radius, 0, width - corner_radius, height))
else: else:
self.canvas.delete("border_parts") self._canvas.delete("border_parts")
# inner button parts # inner button parts
if inner_corner_radius > 0: if inner_corner_radius > 0:
if not self.canvas.find_withtag("inner_oval_1"): 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_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_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_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.create_oval(0, 0, 0, 0, tags=("inner_oval_4", "inner_corner_part", "inner_parts"), width=0)
self.canvas.tag_raise("inner_parts") self._canvas.tag_raise("inner_parts")
requires_recoloring = True requires_recoloring = True
self.canvas.coords("inner_oval_1", (border_width, border_width, self._canvas.coords("inner_oval_1", (border_width, border_width,
border_width + inner_corner_radius * 2 - 1, border_width + inner_corner_radius * 2 - 1)) 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, 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)) 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, 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)) 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, 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)) width - border_width - 1, height - border_width - 1))
else: 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"): 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_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.create_rectangle(0, 0, 0, 0, tags=("inner_rectangle_2", "inner_rectangle_part", "inner_parts"), width=0)
self.canvas.tag_raise("inner_parts") self._canvas.tag_raise("inner_parts")
requires_recoloring = True requires_recoloring = True
self.canvas.coords("inner_rectangle_1", (border_width + inner_corner_radius, self._canvas.coords("inner_rectangle_1", (border_width + inner_corner_radius,
border_width, border_width,
width - border_width - inner_corner_radius, width - border_width - inner_corner_radius,
height - border_width)) height - border_width))
self.canvas.coords("inner_rectangle_2", (border_width, self._canvas.coords("inner_rectangle_2", (border_width,
border_width + inner_corner_radius, border_width + inner_corner_radius,
width - border_width , width - border_width ,
height - inner_corner_radius - border_width)) height - inner_corner_radius - border_width))
return requires_recoloring return requires_recoloring
def draw_rounded_progress_bar_with_border(self, width: int, height: int, corner_radius: Union[float, int], border_width: Union[float, int], def draw_rounded_progress_bar_with_border(self, width: int, height: int, corner_radius: Union[float, int], border_width: Union[float, int],
progress_value: float, orientation: str) -> bool: 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 border elements get the 'border_parts' tag", the main elements get the 'inner_parts' tag and
the progress elements get the 'progress_parts' tag. The 'orientation' argument defines from which direction the progress starts (n, w, s, e). the progress elements get the 'progress_parts' tag. The 'orientation' argument defines from which direction the progress starts (n, w, s, e).
@ -309,50 +330,204 @@ class DrawEngine:
corner_radius = min(width / 2, height / 2) corner_radius = min(width / 2, height / 2)
border_width = round(border_width) 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: if corner_radius >= border_width:
inner_corner_radius = corner_radius - border_width inner_corner_radius = corner_radius - border_width
else: else:
inner_corner_radius = 0 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, return self._draw_rounded_progress_bar_with_border_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius,
progress_value, orientation) 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, return self._draw_rounded_progress_bar_with_border_font_shapes(width, height, corner_radius, border_width, inner_corner_radius,
progress_value, orientation) 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, def _draw_rounded_progress_bar_with_border_polygon_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int,
progress_value: float, orientation: str) -> bool: progress_value: float, orientation: str) -> bool:
requires_recoloring = False
print(border_width, corner_radius, inner_corner_radius)
# create border button parts (only if border exists) requires_recoloring = self._draw_rounded_rect_with_border_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius)
if border_width > 0:
if not self.canvas.find_withtag("border_parts"): if corner_radius <= border_width:
self.canvas.create_polygon((0, 0, 0, 0), tags=("border_line_1", "border_parts"), joinstyle=tkinter.ROUND) bottom_right_shift = -1 # weird canvas rendering inaccuracy that has to be corrected in some cases
print("create border") else:
self.canvas.tag_lower("border_parts") 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 requires_recoloring = True
self.canvas.coords("border_line_1", if not self._canvas.find_withtag("progress_oval_3_a") and round(inner_corner_radius) * 2 < height - 2 * border_width:
(corner_radius, corner_radius, self._canvas.create_aa_circle(0, 0, 0, tags=("progress_oval_3_a", "progress_corner_part", "progress_parts"), anchor=tkinter.CENTER)
width - corner_radius, corner_radius, self._canvas.create_aa_circle(0, 0, 0, tags=("progress_oval_3_b", "progress_corner_part", "progress_parts"), anchor=tkinter.CENTER, angle=180)
width - corner_radius, height - corner_radius, self._canvas.create_aa_circle(0, 0, 0, tags=("progress_oval_4_a", "progress_corner_part", "progress_parts"), anchor=tkinter.CENTER)
corner_radius, height - corner_radius)) self._canvas.create_aa_circle(0, 0, 0, tags=("progress_oval_4_b", "progress_corner_part", "progress_parts"), anchor=tkinter.CENTER, angle=180)
self.canvas.itemconfig("border_line_1", width=corner_radius * 2) requires_recoloring = True
else: elif self._canvas.find_withtag("progress_oval_3_a") and not round(inner_corner_radius) * 2 < height - 2 * border_width:
self.canvas.delete("border_parts") 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("progress_rectangle_1"):
if not self.canvas.find_withtag("inner_parts"): self._canvas.create_rectangle(0, 0, 0, 0, tags=("progress_rectangle_1", "progress_rectangle_part", "progress_parts"), width=0)
self.canvas.create_polygon((0, 0, 0, 0), tags=("inner_line_1", "inner_parts"), joinstyle=tkinter.ROUND) requires_recoloring = True
print("create inner")
if self.canvas.find_withtag("border_parts"): if not self._canvas.find_withtag("progress_rectangle_2") and inner_corner_radius * 2 < height - (border_width * 2):
self.canvas.tag_raise("inner_parts", "border_parts") 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 requires_recoloring = True
if corner_radius <= border_width: if corner_radius <= border_width:
@ -360,239 +535,80 @@ class DrawEngine:
else: else:
bottom_right_shift = 0 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": if orientation == "w":
self.canvas.coords("progress_line_1", slider_x_position = corner_radius + (button_length / 2) + (width - 2 * corner_radius - button_length) * slider_value
border_width + inner_corner_radius, self._canvas.coords("slider_line_1",
border_width + inner_corner_radius, slider_x_position - (button_length / 2), button_corner_radius,
border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value, slider_x_position + (button_length / 2), button_corner_radius,
border_width + inner_corner_radius, slider_x_position + (button_length / 2), height - button_corner_radius,
border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value, slider_x_position - (button_length / 2), height - button_corner_radius)
height - (border_width + inner_corner_radius) + bottom_right_shift, self._canvas.itemconfig("slider_line_1",
border_width + inner_corner_radius, width=corner_radius * 2)
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 return requires_recoloring
def _draw_rounded_progress_bar_with_border_font_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int, def _draw_rounded_slider_with_border_and_button_font_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int,
progress_value: float, orientation: str) -> bool: 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 = self._draw_rounded_progress_bar_with_border_font_shapes(width, height, corner_radius, border_width, inner_corner_radius,
requires_recoloring = False slider_value, orientation)
# create border button parts if not self._canvas.find_withtag("slider_oval_1_a"):
if border_width > 0: self._canvas.create_aa_circle(0, 0, 0, tags=("slider_oval_1_a", "slider_corner_part", "slider_parts"), anchor=tkinter.CENTER)
if corner_radius > 0: self._canvas.create_aa_circle(0, 0, 0, tags=("slider_oval_1_b", "slider_corner_part", "slider_parts"), anchor=tkinter.CENTER, angle=180)
# 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 requires_recoloring = True
if not self.canvas.find_withtag("inner_rectangle_2") and inner_corner_radius * 2 < height - (border_width * 2): if not self._canvas.find_withtag("slider_oval_2_a") and button_length > 0:
self.canvas.create_rectangle(0, 0, 0, 0, tags=("inner_rectangle_2", "inner_rectangle_part", "inner_parts"), width=0) self._canvas.create_aa_circle(0, 0, 0, tags=("slider_oval_2_a", "slider_corner_part", "slider_parts"), anchor=tkinter.CENTER)
self.canvas.tag_raise("inner_rectangle_part") 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 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): if not self._canvas.find_withtag("slider_oval_4_a") and height > 2 * button_corner_radius:
self.canvas.delete("inner_rectangle_2") 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)
# 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 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, if not self._canvas.find_withtag("slider_oval_3_a") and button_length > 0 and height > 2 * button_corner_radius:
border_width, self._canvas.create_aa_circle(0, 0, 0, tags=("slider_oval_3_a", "slider_corner_part", "slider_parts"), anchor=tkinter.CENTER)
width - border_width - inner_corner_radius, self._canvas.create_aa_circle(0, 0, 0, tags=("slider_oval_3_b", "slider_corner_part", "slider_parts"), anchor=tkinter.CENTER, angle=180)
height - border_width)) requires_recoloring = True
self.canvas.coords("inner_rectangle_2", (border_width, elif self._canvas.find_withtag("border_oval_3_a") and not (button_length > 0 and height > 2 * button_corner_radius):
border_width + inner_corner_radius, self._canvas.delete("slider_oval_3_a", "slider_oval_3_b")
width - border_width,
height - inner_corner_radius - border_width))
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): if not self._canvas.find_withtag("slider_rectangle_2") and height > 2 * button_corner_radius:
pass 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

View File

@ -7,7 +7,7 @@ from .appearance_mode_tracker import AppearanceModeTracker
from .customtkinter_theme_manager import CTkThemeManager from .customtkinter_theme_manager import CTkThemeManager
from .customtkinter_canvas import CTkCanvas from .customtkinter_canvas import CTkCanvas
from .customtkinter_settings import CTkSettings from .customtkinter_settings import CTkSettings
from .customtkinter_draw_engine import DrawEngine from .customtkinter_draw_engine import CTkDrawEngine
class CTkEntry(tkinter.Frame): class CTkEntry(tkinter.Frame):
@ -93,7 +93,7 @@ class CTkEntry(tkinter.Frame):
**kwargs) **kwargs)
self.entry.grid(column=0, row=0, sticky="we", padx=self.corner_radius if self.corner_radius >= 6 else 6) 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('<Configure>', self.update_dimensions) super().bind('<Configure>', self.update_dimensions)
self.entry.bind('<FocusOut>', self.set_placeholder) self.entry.bind('<FocusOut>', self.set_placeholder)
@ -149,16 +149,25 @@ class CTkEntry(tkinter.Frame):
def draw(self): def draw(self):
self.canvas.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode)) 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) 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", if CTkThemeManager.single_color(self.fg_color, self.appearance_mode) is not None:
fill=CTkThemeManager.single_color(self.fg_color, self.appearance_mode), self.canvas.itemconfig("inner_parts",
outline=CTkThemeManager.single_color(self.fg_color, self.appearance_mode)) 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", self.canvas.itemconfig("border_parts",
fill=CTkThemeManager.single_color(self.border_color, self.appearance_mode), fill=CTkThemeManager.single_color(self.border_color, self.appearance_mode),

View File

@ -5,7 +5,7 @@ from .appearance_mode_tracker import AppearanceModeTracker
from .customtkinter_theme_manager import CTkThemeManager from .customtkinter_theme_manager import CTkThemeManager
from .customtkinter_canvas import CTkCanvas from .customtkinter_canvas import CTkCanvas
from .customtkinter_settings import CTkSettings from .customtkinter_settings import CTkSettings
from .customtkinter_draw_engine import DrawEngine from .customtkinter_draw_engine import CTkDrawEngine
class CTkFrame(tkinter.Frame): 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.place(x=0, y=0, relwidth=1, relheight=1)
self.canvas.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode)) 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('<Configure>', self.update_dimensions) self.bind('<Configure>', self.update_dimensions)

View File

@ -7,7 +7,7 @@ from .appearance_mode_tracker import AppearanceModeTracker
from .customtkinter_theme_manager import CTkThemeManager from .customtkinter_theme_manager import CTkThemeManager
from .customtkinter_canvas import CTkCanvas from .customtkinter_canvas import CTkCanvas
from .customtkinter_settings import CTkSettings from .customtkinter_settings import CTkSettings
from .customtkinter_draw_engine import DrawEngine from .customtkinter_draw_engine import CTkDrawEngine
class CTkLabel(tkinter.Frame): class CTkLabel(tkinter.Frame):
@ -83,7 +83,7 @@ class CTkLabel(tkinter.Frame):
**kwargs) **kwargs)
self.text_label.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER) 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) super().configure(width=self.width, height=self.height)

View File

@ -6,7 +6,7 @@ from .customtkinter_frame import CTkFrame
from .appearance_mode_tracker import AppearanceModeTracker from .appearance_mode_tracker import AppearanceModeTracker
from .customtkinter_theme_manager import CTkThemeManager from .customtkinter_theme_manager import CTkThemeManager
from .customtkinter_canvas import CTkCanvas from .customtkinter_canvas import CTkCanvas
from .customtkinter_draw_engine import DrawEngine from .customtkinter_draw_engine import CTkDrawEngine
from .customtkinter_settings import CTkSettings from .customtkinter_settings import CTkSettings
@ -21,7 +21,7 @@ class CTkProgressBar(tkinter.Frame):
progress_color="default_theme", progress_color="default_theme",
corner_radius="default_theme", corner_radius="default_theme",
width=200, width=200,
height=16, height=10,
border_width="default_theme", border_width="default_theme",
**kwargs): **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -73,7 +73,7 @@ class CTkProgressBar(tkinter.Frame):
height=self.height) height=self.height)
self.canvas.place(x=0, y=0, relwidth=1, relheight=1) 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 # Each time an item is resized due to pack position mode, the binding Configure is called on the widget
self.bind('<Configure>', self.update_dimensions) self.bind('<Configure>', self.update_dimensions)
@ -123,14 +123,6 @@ class CTkProgressBar(tkinter.Frame):
def draw(self, no_color_updates=False): 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") 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: 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), fill=CTkThemeManager.single_color(self.progress_color, self.appearance_mode),
outline=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): def configure(self, *args, **kwargs):
require_redraw = False # some attribute changes require a call of self.draw() at the end require_redraw = False # some attribute changes require a call of self.draw() at the end

View File

@ -5,6 +5,9 @@ from .customtkinter_tk import CTk
from .customtkinter_frame import CTkFrame from .customtkinter_frame import CTkFrame
from .appearance_mode_tracker import AppearanceModeTracker from .appearance_mode_tracker import AppearanceModeTracker
from .customtkinter_theme_manager import CTkThemeManager 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): class CTkSlider(tkinter.Frame):
@ -23,7 +26,9 @@ class CTkSlider(tkinter.Frame):
width=160, width=160,
height=16, height=16,
corner_radius="default_theme", corner_radius="default_theme",
button_corner_radius="default_theme",
border_width="default_theme", border_width="default_theme",
button_length="default_theme",
command=None, command=None,
variable=None, variable=None,
**kwargs): **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.button_hover_color = CTkThemeManager.theme["color"]["slider_button_hover"] if button_hover_color == "default_theme" else button_hover_color
self.width = width self.width = width
self.height = self.calc_optimal_height(height) self.height = height
self.corner_radius = CTkThemeManager.theme["shape"]["button_corner_radius"] if corner_radius == "default_theme" else corner_radius 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.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.value = 0.5 # initial value of slider in percent
self.hover_state = False self.hover_state = False
self.from_ = from_ self.from_ = from_
@ -81,13 +88,17 @@ class CTkSlider(tkinter.Frame):
self.configure(width=self.width, height=self.height) self.configure(width=self.width, height=self.height)
if sys.platform == "darwin": if sys.platform == "darwin":
self.configure(cursor="pointinghand") self.configure(cursor="pointinghand")
elif sys.platform.startswith("win"):
self.configure(cursor="hand2")
self.canvas = tkinter.Canvas(master=self, self.canvas = CTkCanvas(master=self,
highlightthickness=0, highlightthickness=0,
width=self.width, width=self.width,
height=self.height) height=self.height)
self.canvas.grid(column=0, row=0, sticky="nswe") self.canvas.grid(column=0, row=0, sticky="nswe")
self.draw_engine = CTkDrawEngine(self.canvas, CTkSettings.preferred_drawing_method)
self.canvas.bind("<Enter>", self.on_enter) self.canvas.bind("<Enter>", self.on_enter)
self.canvas.bind("<Leave>", self.on_leave) self.canvas.bind("<Leave>", self.on_leave)
self.canvas.bind("<Button-1>", self.clicked) self.canvas.bind("<Button-1>", self.clicked)
@ -147,30 +158,39 @@ class CTkSlider(tkinter.Frame):
def draw(self, no_color_updates=False): def draw(self, no_color_updates=False):
# decide the drawing method # # decide the drawing method
if sys.platform == "darwin": # if sys.platform == "darwin":
# on macOS draw button with polygons (positions are more accurate, macOS has Antialiasing) # # on macOS draw button with polygons (positions are more accurate, macOS has Antialiasing)
self.draw_with_polygon_shapes() # self.draw_with_polygon_shapes()
else: # else:
# on Windows and other draw with ovals (corner_radius can be optimised to look better than with polygons) # # on Windows and other draw with ovals (corner_radius can be optimised to look better than with polygons)
self.draw_with_ovals_and_rects() # 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)) self.canvas.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
if self.border_color is None: if self.border_color is None:
self.canvas.itemconfig("border_parts", fill=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: 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: 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: 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): def draw_with_polygon_shapes(self):
""" draw the slider parts with just three polygons that have a rounded border """ """ 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): def on_enter(self, event=0):
self.hover_state = True 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): def on_leave(self, event=0):
self.hover_state = False 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): def round_to_step_size(self, value):
if self.number_of_steps is not None: if self.number_of_steps is not None:

View File

@ -6,7 +6,7 @@ import sys
customtkinter.set_appearance_mode("Light") # Modes: "System" (standard), "Dark", "Light" customtkinter.set_appearance_mode("Light") # Modes: "System" (standard), "Dark", "Light"
customtkinter.set_default_color_theme("blue") # Themes: "blue" (standard), "green", "dark-blue" customtkinter.set_default_color_theme("blue") # Themes: "blue" (standard), "green", "dark-blue"
customtkinter.CTkSettings.preferred_drawing_method = "font_shapes" customtkinter.CTkSettings.preferred_drawing_method = "polygon_shapes"
class App(customtkinter.CTk): class App(customtkinter.CTk):
@ -116,7 +116,6 @@ class App(customtkinter.CTk):
self.slider_1 = customtkinter.CTkSlider(master=self.frame_right, self.slider_1 = customtkinter.CTkSlider(master=self.frame_right,
height=16, height=16,
border_width=0,
from_=1, from_=1,
to=0, to=0,
number_of_steps=3, number_of_steps=3,
@ -127,6 +126,7 @@ class App(customtkinter.CTk):
self.slider_2 = customtkinter.CTkSlider(master=self.frame_right, self.slider_2 = customtkinter.CTkSlider(master=self.frame_right,
width=160, width=160,
height=16, height=16,
button_length=0,
command=self.progressbar.set) command=self.progressbar.set)
self.slider_2.grid(row=2, column=0, columnspan=2, pady=10, padx=20, sticky="we") self.slider_2.grid(row=2, column=0, columnspan=2, pady=10, padx=20, sticky="we")
self.slider_2.set(0.7) self.slider_2.set(0.7)
@ -165,7 +165,7 @@ class App(customtkinter.CTk):
corner_radius=13) corner_radius=13)
self.button_5.grid(row=4, column=2, columnspan=1, pady=20, padx=20, sticky="we") 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): def button_event(self):
print("Button pressed") print("Button pressed")