draw_engine.py 89 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235
  1. from __future__ import annotations
  2. import sys
  3. import math
  4. import tkinter
  5. from typing import Union, TYPE_CHECKING
  6. if TYPE_CHECKING:
  7. from ..core_rendering import CTkCanvas
  8. class DrawEngine:
  9. """
  10. This is the core of the CustomTkinter library where all the drawing on the tkinter.Canvas happens.
  11. A year of experimenting and trying out different drawing methods have led to the current state of this
  12. class, and I don't think there's much I can do to make the rendering look better than this with the
  13. limited capabilities the tkinter.Canvas offers.
  14. Functions:
  15. - draw_rounded_rect_with_border()
  16. - draw_rounded_rect_with_border_vertical_split()
  17. - draw_rounded_progress_bar_with_border()
  18. - draw_rounded_slider_with_border_and_button()
  19. - draw_rounded_scrollbar()
  20. - draw_checkmark()
  21. - draw_dropdown_arrow()
  22. """
  23. preferred_drawing_method: str = None # 'polygon_shapes', 'font_shapes', 'circle_shapes'
  24. def __init__(self, canvas: CTkCanvas):
  25. self._canvas = canvas
  26. self._round_width_to_even_numbers: bool = True
  27. self._round_height_to_even_numbers: bool = True
  28. def set_round_to_even_numbers(self, round_width_to_even_numbers: bool = True, round_height_to_even_numbers: bool = True):
  29. self._round_width_to_even_numbers: bool = round_width_to_even_numbers
  30. self._round_height_to_even_numbers: bool = round_height_to_even_numbers
  31. def __calc_optimal_corner_radius(self, user_corner_radius: Union[float, int]) -> Union[float, int]:
  32. # optimize for drawing with polygon shapes
  33. if self.preferred_drawing_method == "polygon_shapes":
  34. if sys.platform == "darwin":
  35. return user_corner_radius
  36. else:
  37. return round(user_corner_radius)
  38. # optimize for drawing with antialiased font shapes
  39. elif self.preferred_drawing_method == "font_shapes":
  40. return round(user_corner_radius)
  41. # optimize for drawing with circles and rects
  42. elif self.preferred_drawing_method == "circle_shapes":
  43. user_corner_radius = 0.5 * round(user_corner_radius / 0.5) # round to 0.5 steps
  44. # make sure the value is always with .5 at the end for smoother corners
  45. if user_corner_radius == 0:
  46. return 0
  47. elif user_corner_radius % 1 == 0:
  48. return user_corner_radius + 0.5
  49. else:
  50. return user_corner_radius
  51. def draw_background_corners(self, width: Union[float, int], height: Union[float, int], ):
  52. if self._round_width_to_even_numbers:
  53. width = math.floor(width / 2) * 2 # round (floor) _current_width and _current_height and restrict them to even values only
  54. if self._round_height_to_even_numbers:
  55. height = math.floor(height / 2) * 2
  56. requires_recoloring = False
  57. if not self._canvas.find_withtag("background_corner_top_left"):
  58. self._canvas.create_rectangle((0, 0, 0, 0), tags=("background_parts", "background_corner_top_left"), width=0)
  59. requires_recoloring = True
  60. if not self._canvas.find_withtag("background_corner_top_right"):
  61. self._canvas.create_rectangle((0, 0, 0, 0), tags=("background_parts", "background_corner_top_right"), width=0)
  62. requires_recoloring = True
  63. if not self._canvas.find_withtag("background_corner_bottom_right"):
  64. self._canvas.create_rectangle((0, 0, 0, 0), tags=("background_parts", "background_corner_bottom_right"), width=0)
  65. requires_recoloring = True
  66. if not self._canvas.find_withtag("background_corner_bottom_left"):
  67. self._canvas.create_rectangle((0, 0, 0, 0), tags=("background_parts", "background_corner_bottom_left"), width=0)
  68. requires_recoloring = True
  69. mid_width, mid_height = round(width / 2), round(height / 2)
  70. self._canvas.coords("background_corner_top_left", (0, 0, mid_width, mid_height))
  71. self._canvas.coords("background_corner_top_right", (mid_width, 0, width, mid_height))
  72. self._canvas.coords("background_corner_bottom_right", (mid_width, mid_height, width, height))
  73. self._canvas.coords("background_corner_bottom_left", (0, mid_height, mid_width, height))
  74. if requires_recoloring: # new parts were added -> manage z-order
  75. self._canvas.tag_lower("background_parts")
  76. return requires_recoloring
  77. def draw_rounded_rect_with_border(self, width: Union[float, int], height: Union[float, int], corner_radius: Union[float, int],
  78. border_width: Union[float, int], overwrite_preferred_drawing_method: str = None) -> bool:
  79. """ Draws a rounded rectangle with a corner_radius and border_width on the canvas. The border elements have a 'border_parts' tag,
  80. the main foreground elements have an 'inner_parts' tag to color the elements accordingly.
  81. returns bool if recoloring is necessary """
  82. if self._round_width_to_even_numbers:
  83. width = math.floor(width / 2) * 2 # round (floor) _current_width and _current_height and restrict them to even values only
  84. if self._round_height_to_even_numbers:
  85. height = math.floor(height / 2) * 2
  86. corner_radius = round(corner_radius)
  87. if corner_radius > width / 2 or corner_radius > height / 2: # restrict corner_radius if it's too large
  88. corner_radius = min(width / 2, height / 2)
  89. border_width = round(border_width)
  90. corner_radius = self.__calc_optimal_corner_radius(corner_radius) # optimize corner_radius for different drawing methods (different rounding)
  91. if corner_radius >= border_width:
  92. inner_corner_radius = corner_radius - border_width
  93. else:
  94. inner_corner_radius = 0
  95. if overwrite_preferred_drawing_method is not None:
  96. preferred_drawing_method = overwrite_preferred_drawing_method
  97. else:
  98. preferred_drawing_method = self.preferred_drawing_method
  99. if preferred_drawing_method == "polygon_shapes":
  100. return self.__draw_rounded_rect_with_border_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius)
  101. elif preferred_drawing_method == "font_shapes":
  102. return self.__draw_rounded_rect_with_border_font_shapes(width, height, corner_radius, border_width, inner_corner_radius, ())
  103. elif preferred_drawing_method == "circle_shapes":
  104. return self.__draw_rounded_rect_with_border_circle_shapes(width, height, corner_radius, border_width, inner_corner_radius)
  105. def __draw_rounded_rect_with_border_polygon_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int) -> bool:
  106. requires_recoloring = False
  107. # create border button parts (only if border exists)
  108. if border_width > 0:
  109. if not self._canvas.find_withtag("border_parts"):
  110. self._canvas.create_polygon((0, 0, 0, 0), tags=("border_line_1", "border_parts"))
  111. requires_recoloring = True
  112. self._canvas.coords("border_line_1",
  113. (corner_radius,
  114. corner_radius,
  115. width - corner_radius,
  116. corner_radius,
  117. width - corner_radius,
  118. height - corner_radius,
  119. corner_radius,
  120. height - corner_radius))
  121. self._canvas.itemconfig("border_line_1",
  122. joinstyle=tkinter.ROUND,
  123. width=corner_radius * 2)
  124. else:
  125. self._canvas.delete("border_parts")
  126. # create inner button parts
  127. if not self._canvas.find_withtag("inner_parts"):
  128. self._canvas.create_polygon((0, 0, 0, 0), tags=("inner_line_1", "inner_parts"), joinstyle=tkinter.ROUND)
  129. requires_recoloring = True
  130. if corner_radius <= border_width:
  131. bottom_right_shift = -1 # weird canvas rendering inaccuracy that has to be corrected in some cases
  132. else:
  133. bottom_right_shift = 0
  134. self._canvas.coords("inner_line_1",
  135. border_width + inner_corner_radius,
  136. border_width + inner_corner_radius,
  137. width - (border_width + inner_corner_radius) + bottom_right_shift,
  138. border_width + inner_corner_radius,
  139. width - (border_width + inner_corner_radius) + bottom_right_shift,
  140. height - (border_width + inner_corner_radius) + bottom_right_shift,
  141. border_width + inner_corner_radius,
  142. height - (border_width + inner_corner_radius) + bottom_right_shift)
  143. self._canvas.itemconfig("inner_line_1",
  144. width=inner_corner_radius * 2)
  145. if requires_recoloring: # new parts were added -> manage z-order
  146. self._canvas.tag_lower("inner_parts")
  147. self._canvas.tag_lower("border_parts")
  148. self._canvas.tag_lower("background_parts")
  149. return requires_recoloring
  150. def __draw_rounded_rect_with_border_font_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int,
  151. exclude_parts: tuple) -> bool:
  152. requires_recoloring = False
  153. # create border button parts
  154. if border_width > 0:
  155. if corner_radius > 0:
  156. # create canvas border corner parts if not already created, but only if needed, and delete if not needed
  157. if not self._canvas.find_withtag("border_oval_1_a") and "border_oval_1" not in exclude_parts:
  158. self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_1_a", "border_corner_part", "border_parts"), anchor=tkinter.CENTER)
  159. self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_1_b", "border_corner_part", "border_parts"), anchor=tkinter.CENTER, angle=180)
  160. requires_recoloring = True
  161. elif self._canvas.find_withtag("border_oval_1_a") and "border_oval_1" in exclude_parts:
  162. self._canvas.delete("border_oval_1_a", "border_oval_1_b")
  163. if not self._canvas.find_withtag("border_oval_2_a") and width > 2 * corner_radius and "border_oval_2" not in exclude_parts:
  164. self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_2_a", "border_corner_part", "border_parts"), anchor=tkinter.CENTER)
  165. self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_2_b", "border_corner_part", "border_parts"), anchor=tkinter.CENTER, angle=180)
  166. requires_recoloring = True
  167. elif self._canvas.find_withtag("border_oval_2_a") and (not width > 2 * corner_radius or "border_oval_2" in exclude_parts):
  168. self._canvas.delete("border_oval_2_a", "border_oval_2_b")
  169. if not self._canvas.find_withtag("border_oval_3_a") and height > 2 * corner_radius \
  170. and width > 2 * corner_radius and "border_oval_3" not in exclude_parts:
  171. self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_3_a", "border_corner_part", "border_parts"), anchor=tkinter.CENTER)
  172. self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_3_b", "border_corner_part", "border_parts"), anchor=tkinter.CENTER, angle=180)
  173. requires_recoloring = True
  174. elif self._canvas.find_withtag("border_oval_3_a") and (not (height > 2 * corner_radius
  175. and width > 2 * corner_radius) or "border_oval_3" in exclude_parts):
  176. self._canvas.delete("border_oval_3_a", "border_oval_3_b")
  177. if not self._canvas.find_withtag("border_oval_4_a") and height > 2 * corner_radius and "border_oval_4" not in exclude_parts:
  178. self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_4_a", "border_corner_part", "border_parts"), anchor=tkinter.CENTER)
  179. self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_4_b", "border_corner_part", "border_parts"), anchor=tkinter.CENTER, angle=180)
  180. requires_recoloring = True
  181. elif self._canvas.find_withtag("border_oval_4_a") and (not height > 2 * corner_radius or "border_oval_4" in exclude_parts):
  182. self._canvas.delete("border_oval_4_a", "border_oval_4_b")
  183. # change position of border corner parts
  184. self._canvas.coords("border_oval_1_a", corner_radius, corner_radius, corner_radius)
  185. self._canvas.coords("border_oval_1_b", corner_radius, corner_radius, corner_radius)
  186. self._canvas.coords("border_oval_2_a", width - corner_radius, corner_radius, corner_radius)
  187. self._canvas.coords("border_oval_2_b", width - corner_radius, corner_radius, corner_radius)
  188. self._canvas.coords("border_oval_3_a", width - corner_radius, height - corner_radius, corner_radius)
  189. self._canvas.coords("border_oval_3_b", width - corner_radius, height - corner_radius, corner_radius)
  190. self._canvas.coords("border_oval_4_a", corner_radius, height - corner_radius, corner_radius)
  191. self._canvas.coords("border_oval_4_b", corner_radius, height - corner_radius, corner_radius)
  192. else:
  193. self._canvas.delete("border_corner_part") # delete border corner parts if not needed
  194. # create canvas border rectangle parts if not already created
  195. if not self._canvas.find_withtag("border_rectangle_1"):
  196. self._canvas.create_rectangle(0, 0, 0, 0, tags=("border_rectangle_1", "border_rectangle_part", "border_parts"), width=0)
  197. self._canvas.create_rectangle(0, 0, 0, 0, tags=("border_rectangle_2", "border_rectangle_part", "border_parts"), width=0)
  198. requires_recoloring = True
  199. # change position of border rectangle parts
  200. self._canvas.coords("border_rectangle_1", (0, corner_radius, width, height - corner_radius))
  201. self._canvas.coords("border_rectangle_2", (corner_radius, 0, width - corner_radius, height))
  202. else:
  203. self._canvas.delete("border_parts")
  204. # create inner button parts
  205. if inner_corner_radius > 0:
  206. # create canvas border corner parts if not already created, but only if they're needed and delete if not needed
  207. if not self._canvas.find_withtag("inner_oval_1_a") and "inner_oval_1" not in exclude_parts:
  208. self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_1_a", "inner_corner_part", "inner_parts"), anchor=tkinter.CENTER)
  209. self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_1_b", "inner_corner_part", "inner_parts"), anchor=tkinter.CENTER, angle=180)
  210. requires_recoloring = True
  211. elif self._canvas.find_withtag("inner_oval_1_a") and "inner_oval_1" in exclude_parts:
  212. self._canvas.delete("inner_oval_1_a", "inner_oval_1_b")
  213. if not self._canvas.find_withtag("inner_oval_2_a") and width - (2 * border_width) > 2 * inner_corner_radius and "inner_oval_2" not in exclude_parts:
  214. self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_2_a", "inner_corner_part", "inner_parts"), anchor=tkinter.CENTER)
  215. self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_2_b", "inner_corner_part", "inner_parts"), anchor=tkinter.CENTER, angle=180)
  216. requires_recoloring = True
  217. elif self._canvas.find_withtag("inner_oval_2_a") and (not width - (2 * border_width) > 2 * inner_corner_radius or "inner_oval_2" in exclude_parts):
  218. self._canvas.delete("inner_oval_2_a", "inner_oval_2_b")
  219. if not self._canvas.find_withtag("inner_oval_3_a") and height - (2 * border_width) > 2 * inner_corner_radius \
  220. and width - (2 * border_width) > 2 * inner_corner_radius and "inner_oval_3" not in exclude_parts:
  221. self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_3_a", "inner_corner_part", "inner_parts"), anchor=tkinter.CENTER)
  222. self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_3_b", "inner_corner_part", "inner_parts"), anchor=tkinter.CENTER, angle=180)
  223. requires_recoloring = True
  224. elif self._canvas.find_withtag("inner_oval_3_a") and (not (height - (2 * border_width) > 2 * inner_corner_radius
  225. and width - (2 * border_width) > 2 * inner_corner_radius) or "inner_oval_3" in exclude_parts):
  226. self._canvas.delete("inner_oval_3_a", "inner_oval_3_b")
  227. if not self._canvas.find_withtag("inner_oval_4_a") and height - (2 * border_width) > 2 * inner_corner_radius and "inner_oval_4" not in exclude_parts:
  228. self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_4_a", "inner_corner_part", "inner_parts"), anchor=tkinter.CENTER)
  229. self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_4_b", "inner_corner_part", "inner_parts"), anchor=tkinter.CENTER, angle=180)
  230. requires_recoloring = True
  231. elif self._canvas.find_withtag("inner_oval_4_a") and (not height - (2 * border_width) > 2 * inner_corner_radius or "inner_oval_4" in exclude_parts):
  232. self._canvas.delete("inner_oval_4_a", "inner_oval_4_b")
  233. # change position of border corner parts
  234. self._canvas.coords("inner_oval_1_a", border_width + inner_corner_radius, border_width + inner_corner_radius, inner_corner_radius)
  235. self._canvas.coords("inner_oval_1_b", border_width + inner_corner_radius, border_width + inner_corner_radius, inner_corner_radius)
  236. self._canvas.coords("inner_oval_2_a", width - border_width - inner_corner_radius, border_width + inner_corner_radius, inner_corner_radius)
  237. self._canvas.coords("inner_oval_2_b", width - border_width - inner_corner_radius, border_width + inner_corner_radius, inner_corner_radius)
  238. self._canvas.coords("inner_oval_3_a", width - border_width - inner_corner_radius, height - border_width - inner_corner_radius, inner_corner_radius)
  239. self._canvas.coords("inner_oval_3_b", width - border_width - inner_corner_radius, height - border_width - inner_corner_radius, inner_corner_radius)
  240. self._canvas.coords("inner_oval_4_a", border_width + inner_corner_radius, height - border_width - inner_corner_radius, inner_corner_radius)
  241. self._canvas.coords("inner_oval_4_b", border_width + inner_corner_radius, height - border_width - inner_corner_radius, inner_corner_radius)
  242. else:
  243. self._canvas.delete("inner_corner_part") # delete inner corner parts if not needed
  244. # create canvas inner rectangle parts if not already created
  245. if not self._canvas.find_withtag("inner_rectangle_1"):
  246. self._canvas.create_rectangle(0, 0, 0, 0, tags=("inner_rectangle_1", "inner_rectangle_part", "inner_parts"), width=0)
  247. requires_recoloring = True
  248. if not self._canvas.find_withtag("inner_rectangle_2") and inner_corner_radius * 2 < height - (border_width * 2):
  249. self._canvas.create_rectangle(0, 0, 0, 0, tags=("inner_rectangle_2", "inner_rectangle_part", "inner_parts"), width=0)
  250. requires_recoloring = True
  251. elif self._canvas.find_withtag("inner_rectangle_2") and not inner_corner_radius * 2 < height - (border_width * 2):
  252. self._canvas.delete("inner_rectangle_2")
  253. # change position of inner rectangle parts
  254. self._canvas.coords("inner_rectangle_1", (border_width + inner_corner_radius,
  255. border_width,
  256. width - border_width - inner_corner_radius,
  257. height - border_width))
  258. self._canvas.coords("inner_rectangle_2", (border_width,
  259. border_width + inner_corner_radius,
  260. width - border_width,
  261. height - inner_corner_radius - border_width))
  262. if requires_recoloring: # new parts were added -> manage z-order
  263. self._canvas.tag_lower("inner_parts")
  264. self._canvas.tag_lower("border_parts")
  265. self._canvas.tag_lower("background_parts")
  266. return requires_recoloring
  267. def __draw_rounded_rect_with_border_circle_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int) -> bool:
  268. requires_recoloring = False
  269. # border button parts
  270. if border_width > 0:
  271. if corner_radius > 0:
  272. if not self._canvas.find_withtag("border_oval_1"):
  273. self._canvas.create_oval(0, 0, 0, 0, tags=("border_oval_1", "border_corner_part", "border_parts"), width=0)
  274. self._canvas.create_oval(0, 0, 0, 0, tags=("border_oval_2", "border_corner_part", "border_parts"), width=0)
  275. self._canvas.create_oval(0, 0, 0, 0, tags=("border_oval_3", "border_corner_part", "border_parts"), width=0)
  276. self._canvas.create_oval(0, 0, 0, 0, tags=("border_oval_4", "border_corner_part", "border_parts"), width=0)
  277. self._canvas.tag_lower("border_parts")
  278. requires_recoloring = True
  279. self._canvas.coords("border_oval_1", 0, 0, corner_radius * 2 - 1, corner_radius * 2 - 1)
  280. self._canvas.coords("border_oval_2", width - corner_radius * 2, 0, width - 1, corner_radius * 2 - 1)
  281. self._canvas.coords("border_oval_3", 0, height - corner_radius * 2, corner_radius * 2 - 1, height - 1)
  282. self._canvas.coords("border_oval_4", width - corner_radius * 2, height - corner_radius * 2, width - 1, height - 1)
  283. else:
  284. self._canvas.delete("border_corner_part")
  285. if not self._canvas.find_withtag("border_rectangle_1"):
  286. self._canvas.create_rectangle(0, 0, 0, 0, tags=("border_rectangle_1", "border_rectangle_part", "border_parts"), width=0)
  287. self._canvas.create_rectangle(0, 0, 0, 0, tags=("border_rectangle_2", "border_rectangle_part", "border_parts"), width=0)
  288. self._canvas.tag_lower("border_parts")
  289. requires_recoloring = True
  290. self._canvas.coords("border_rectangle_1", (0, corner_radius, width, height - corner_radius))
  291. self._canvas.coords("border_rectangle_2", (corner_radius, 0, width - corner_radius, height))
  292. else:
  293. self._canvas.delete("border_parts")
  294. # inner button parts
  295. if inner_corner_radius > 0:
  296. if not self._canvas.find_withtag("inner_oval_1"):
  297. self._canvas.create_oval(0, 0, 0, 0, tags=("inner_oval_1", "inner_corner_part", "inner_parts"), width=0)
  298. self._canvas.create_oval(0, 0, 0, 0, tags=("inner_oval_2", "inner_corner_part", "inner_parts"), width=0)
  299. self._canvas.create_oval(0, 0, 0, 0, tags=("inner_oval_3", "inner_corner_part", "inner_parts"), width=0)
  300. self._canvas.create_oval(0, 0, 0, 0, tags=("inner_oval_4", "inner_corner_part", "inner_parts"), width=0)
  301. self._canvas.tag_raise("inner_parts")
  302. requires_recoloring = True
  303. self._canvas.coords("inner_oval_1", (border_width, border_width,
  304. border_width + inner_corner_radius * 2 - 1, border_width + inner_corner_radius * 2 - 1))
  305. self._canvas.coords("inner_oval_2", (width - border_width - inner_corner_radius * 2, border_width,
  306. width - border_width - 1, border_width + inner_corner_radius * 2 - 1))
  307. self._canvas.coords("inner_oval_3", (border_width, height - border_width - inner_corner_radius * 2,
  308. border_width + inner_corner_radius * 2 - 1, height - border_width - 1))
  309. self._canvas.coords("inner_oval_4", (width - border_width - inner_corner_radius * 2, height - border_width - inner_corner_radius * 2,
  310. width - border_width - 1, height - border_width - 1))
  311. else:
  312. self._canvas.delete("inner_corner_part") # delete inner corner parts if not needed
  313. if not self._canvas.find_withtag("inner_rectangle_1"):
  314. self._canvas.create_rectangle(0, 0, 0, 0, tags=("inner_rectangle_1", "inner_rectangle_part", "inner_parts"), width=0)
  315. self._canvas.create_rectangle(0, 0, 0, 0, tags=("inner_rectangle_2", "inner_rectangle_part", "inner_parts"), width=0)
  316. self._canvas.tag_raise("inner_parts")
  317. requires_recoloring = True
  318. self._canvas.coords("inner_rectangle_1", (border_width + inner_corner_radius,
  319. border_width,
  320. width - border_width - inner_corner_radius,
  321. height - border_width))
  322. self._canvas.coords("inner_rectangle_2", (border_width,
  323. border_width + inner_corner_radius,
  324. width - border_width,
  325. height - inner_corner_radius - border_width))
  326. return requires_recoloring
  327. def draw_rounded_rect_with_border_vertical_split(self, width: Union[float, int], height: Union[float, int], corner_radius: Union[float, int],
  328. border_width: Union[float, int], left_section_width: Union[float, int]) -> bool:
  329. """ Draws a rounded rectangle with a corner_radius and border_width on the canvas which is split at left_section_width.
  330. The border elements have the tags 'border_parts_left', 'border_parts_lright',
  331. the main foreground elements have an 'inner_parts_left' and inner_parts_right' tag,
  332. to color the elements accordingly.
  333. returns bool if recoloring is necessary """
  334. left_section_width = round(left_section_width)
  335. if self._round_width_to_even_numbers:
  336. width = math.floor(width / 2) * 2 # round (floor) _current_width and _current_height and restrict them to even values only
  337. if self._round_height_to_even_numbers:
  338. height = math.floor(height / 2) * 2
  339. corner_radius = round(corner_radius)
  340. if corner_radius > width / 2 or corner_radius > height / 2: # restrict corner_radius if it's too larger
  341. corner_radius = min(width / 2, height / 2)
  342. border_width = round(border_width)
  343. corner_radius = self.__calc_optimal_corner_radius(corner_radius) # optimize corner_radius for different drawing methods (different rounding)
  344. if corner_radius >= border_width:
  345. inner_corner_radius = corner_radius - border_width
  346. else:
  347. inner_corner_radius = 0
  348. if left_section_width > width - corner_radius * 2:
  349. left_section_width = width - corner_radius * 2
  350. elif left_section_width < corner_radius * 2:
  351. left_section_width = corner_radius * 2
  352. if self.preferred_drawing_method == "polygon_shapes" or self.preferred_drawing_method == "circle_shapes":
  353. return self.__draw_rounded_rect_with_border_vertical_split_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius, left_section_width)
  354. elif self.preferred_drawing_method == "font_shapes":
  355. return self.__draw_rounded_rect_with_border_vertical_split_font_shapes(width, height, corner_radius, border_width, inner_corner_radius, left_section_width, ())
  356. def __draw_rounded_rect_with_border_vertical_split_polygon_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int,
  357. left_section_width: int) -> bool:
  358. requires_recoloring = False
  359. # create border button parts (only if border exists)
  360. if border_width > 0:
  361. if not self._canvas.find_withtag("border_parts"):
  362. self._canvas.create_polygon((0, 0, 0, 0), tags=("border_line_left_1", "border_parts_left", "border_parts", "left_parts"))
  363. self._canvas.create_polygon((0, 0, 0, 0), tags=("border_line_right_1", "border_parts_right", "border_parts", "right_parts"))
  364. self._canvas.create_rectangle((0, 0, 0, 0), tags=("border_rect_left_1", "border_parts_left", "border_parts", "left_parts"), width=0)
  365. self._canvas.create_rectangle((0, 0, 0, 0), tags=("border_rect_right_1", "border_parts_right", "border_parts", "right_parts"), width=0)
  366. requires_recoloring = True
  367. self._canvas.coords("border_line_left_1",
  368. (corner_radius,
  369. corner_radius,
  370. left_section_width - corner_radius,
  371. corner_radius,
  372. left_section_width - corner_radius,
  373. height - corner_radius,
  374. corner_radius,
  375. height - corner_radius))
  376. self._canvas.coords("border_line_right_1",
  377. (left_section_width + corner_radius,
  378. corner_radius,
  379. width - corner_radius,
  380. corner_radius,
  381. width - corner_radius,
  382. height - corner_radius,
  383. left_section_width + corner_radius,
  384. height - corner_radius))
  385. self._canvas.coords("border_rect_left_1",
  386. (left_section_width - corner_radius,
  387. 0,
  388. left_section_width,
  389. height))
  390. self._canvas.coords("border_rect_right_1",
  391. (left_section_width,
  392. 0,
  393. left_section_width + corner_radius,
  394. height))
  395. self._canvas.itemconfig("border_line_left_1", joinstyle=tkinter.ROUND, width=corner_radius * 2)
  396. self._canvas.itemconfig("border_line_right_1", joinstyle=tkinter.ROUND, width=corner_radius * 2)
  397. else:
  398. self._canvas.delete("border_parts")
  399. # create inner button parts
  400. if not self._canvas.find_withtag("inner_parts"):
  401. self._canvas.create_polygon((0, 0, 0, 0), tags=("inner_line_left_1", "inner_parts_left", "inner_parts", "left_parts"), joinstyle=tkinter.ROUND)
  402. self._canvas.create_polygon((0, 0, 0, 0), tags=("inner_line_right_1", "inner_parts_right", "inner_parts", "right_parts"), joinstyle=tkinter.ROUND)
  403. self._canvas.create_rectangle((0, 0, 0, 0), tags=("inner_rect_left_1", "inner_parts_left", "inner_parts", "left_parts"), width=0)
  404. self._canvas.create_rectangle((0, 0, 0, 0), tags=("inner_rect_right_1", "inner_parts_right", "inner_parts", "right_parts"), width=0)
  405. requires_recoloring = True
  406. self._canvas.coords("inner_line_left_1",
  407. corner_radius,
  408. corner_radius,
  409. left_section_width - inner_corner_radius,
  410. corner_radius,
  411. left_section_width - inner_corner_radius,
  412. height - corner_radius,
  413. corner_radius,
  414. height - corner_radius)
  415. self._canvas.coords("inner_line_right_1",
  416. left_section_width + inner_corner_radius,
  417. corner_radius,
  418. width - corner_radius,
  419. corner_radius,
  420. width - corner_radius,
  421. height - corner_radius,
  422. left_section_width + inner_corner_radius,
  423. height - corner_radius)
  424. self._canvas.coords("inner_rect_left_1",
  425. (left_section_width - inner_corner_radius,
  426. border_width,
  427. left_section_width,
  428. height - border_width))
  429. self._canvas.coords("inner_rect_right_1",
  430. (left_section_width,
  431. border_width,
  432. left_section_width + inner_corner_radius,
  433. height - border_width))
  434. self._canvas.itemconfig("inner_line_left_1", width=inner_corner_radius * 2)
  435. self._canvas.itemconfig("inner_line_right_1", width=inner_corner_radius * 2)
  436. if requires_recoloring: # new parts were added -> manage z-order
  437. self._canvas.tag_lower("inner_parts")
  438. self._canvas.tag_lower("border_parts")
  439. self._canvas.tag_lower("background_parts")
  440. return requires_recoloring
  441. def __draw_rounded_rect_with_border_vertical_split_font_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int,
  442. left_section_width: int, exclude_parts: tuple) -> bool:
  443. requires_recoloring = False
  444. # create border button parts
  445. if border_width > 0:
  446. if corner_radius > 0:
  447. # create canvas border corner parts if not already created, but only if needed, and delete if not needed
  448. if not self._canvas.find_withtag("border_oval_1_a") and "border_oval_1" not in exclude_parts:
  449. self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_1_a", "border_corner_part", "border_parts_left", "border_parts", "left_parts"), anchor=tkinter.CENTER)
  450. self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_1_b", "border_corner_part", "border_parts_left", "border_parts", "left_parts"), anchor=tkinter.CENTER,
  451. angle=180)
  452. requires_recoloring = True
  453. elif self._canvas.find_withtag("border_oval_1_a") and "border_oval_1" in exclude_parts:
  454. self._canvas.delete("border_oval_1_a", "border_oval_1_b")
  455. if not self._canvas.find_withtag("border_oval_2_a") and width > 2 * corner_radius and "border_oval_2" not in exclude_parts:
  456. self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_2_a", "border_corner_part", "border_parts_right", "border_parts", "right_parts"),
  457. anchor=tkinter.CENTER)
  458. self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_2_b", "border_corner_part", "border_parts_right", "border_parts", "right_parts"),
  459. anchor=tkinter.CENTER, angle=180)
  460. requires_recoloring = True
  461. elif self._canvas.find_withtag("border_oval_2_a") and (not width > 2 * corner_radius or "border_oval_2" in exclude_parts):
  462. self._canvas.delete("border_oval_2_a", "border_oval_2_b")
  463. if not self._canvas.find_withtag("border_oval_3_a") and height > 2 * corner_radius \
  464. and width > 2 * corner_radius and "border_oval_3" not in exclude_parts:
  465. self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_3_a", "border_corner_part", "border_parts_right", "border_parts", "right_parts"),
  466. anchor=tkinter.CENTER)
  467. self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_3_b", "border_corner_part", "border_parts_right", "border_parts", "right_parts"),
  468. anchor=tkinter.CENTER, angle=180)
  469. requires_recoloring = True
  470. elif self._canvas.find_withtag("border_oval_3_a") and (not (height > 2 * corner_radius
  471. and width > 2 * corner_radius) or "border_oval_3" in exclude_parts):
  472. self._canvas.delete("border_oval_3_a", "border_oval_3_b")
  473. if not self._canvas.find_withtag("border_oval_4_a") and height > 2 * corner_radius and "border_oval_4" not in exclude_parts:
  474. self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_4_a", "border_corner_part", "border_parts_left", "border_parts", "left_parts"), anchor=tkinter.CENTER)
  475. self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_4_b", "border_corner_part", "border_parts_left", "border_parts", "left_parts"), anchor=tkinter.CENTER,
  476. angle=180)
  477. requires_recoloring = True
  478. elif self._canvas.find_withtag("border_oval_4_a") and (not height > 2 * corner_radius or "border_oval_4" in exclude_parts):
  479. self._canvas.delete("border_oval_4_a", "border_oval_4_b")
  480. # change position of border corner parts
  481. self._canvas.coords("border_oval_1_a", corner_radius, corner_radius, corner_radius)
  482. self._canvas.coords("border_oval_1_b", corner_radius, corner_radius, corner_radius)
  483. self._canvas.coords("border_oval_2_a", width - corner_radius, corner_radius, corner_radius)
  484. self._canvas.coords("border_oval_2_b", width - corner_radius, corner_radius, corner_radius)
  485. self._canvas.coords("border_oval_3_a", width - corner_radius, height - corner_radius, corner_radius)
  486. self._canvas.coords("border_oval_3_b", width - corner_radius, height - corner_radius, corner_radius)
  487. self._canvas.coords("border_oval_4_a", corner_radius, height - corner_radius, corner_radius)
  488. self._canvas.coords("border_oval_4_b", corner_radius, height - corner_radius, corner_radius)
  489. else:
  490. self._canvas.delete("border_corner_part") # delete border corner parts if not needed
  491. # create canvas border rectangle parts if not already created
  492. if not self._canvas.find_withtag("border_rectangle_1"):
  493. self._canvas.create_rectangle(0, 0, 0, 0, tags=("border_rectangle_left_1", "border_rectangle_part", "border_parts_left", "border_parts", "left_parts"), width=0)
  494. self._canvas.create_rectangle(0, 0, 0, 0, tags=("border_rectangle_left_2", "border_rectangle_part", "border_parts_left", "border_parts", "left_parts"), width=0)
  495. self._canvas.create_rectangle(0, 0, 0, 0, tags=("border_rectangle_right_1", "border_rectangle_part", "border_parts_right", "border_parts", "right_parts"), width=0)
  496. self._canvas.create_rectangle(0, 0, 0, 0, tags=("border_rectangle_right_2", "border_rectangle_part", "border_parts_right", "border_parts", "right_parts"), width=0)
  497. requires_recoloring = True
  498. # change position of border rectangle parts
  499. self._canvas.coords("border_rectangle_left_1", (0, corner_radius, left_section_width, height - corner_radius))
  500. self._canvas.coords("border_rectangle_left_2", (corner_radius, 0, left_section_width, height))
  501. self._canvas.coords("border_rectangle_right_1", (left_section_width, corner_radius, width, height - corner_radius))
  502. self._canvas.coords("border_rectangle_right_2", (left_section_width, 0, width - corner_radius, height))
  503. else:
  504. self._canvas.delete("border_parts")
  505. # create inner button parts
  506. if inner_corner_radius > 0:
  507. # create canvas border corner parts if not already created, but only if they're needed and delete if not needed
  508. if not self._canvas.find_withtag("inner_oval_1_a") and "inner_oval_1" not in exclude_parts:
  509. self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_1_a", "inner_corner_part", "inner_parts_left", "inner_parts", "left_parts"), anchor=tkinter.CENTER)
  510. self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_1_b", "inner_corner_part", "inner_parts_left", "inner_parts", "left_parts"), anchor=tkinter.CENTER,
  511. angle=180)
  512. requires_recoloring = True
  513. elif self._canvas.find_withtag("inner_oval_1_a") and "inner_oval_1" in exclude_parts:
  514. self._canvas.delete("inner_oval_1_a", "inner_oval_1_b")
  515. if not self._canvas.find_withtag("inner_oval_2_a") and width - (2 * border_width) > 2 * inner_corner_radius and "inner_oval_2" not in exclude_parts:
  516. self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_2_a", "inner_corner_part", "inner_parts_right", "inner_parts", "right_parts"), anchor=tkinter.CENTER)
  517. self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_2_b", "inner_corner_part", "inner_parts_right", "inner_parts", "right_parts"), anchor=tkinter.CENTER,
  518. angle=180)
  519. requires_recoloring = True
  520. elif self._canvas.find_withtag("inner_oval_2_a") and (not width - (2 * border_width) > 2 * inner_corner_radius or "inner_oval_2" in exclude_parts):
  521. self._canvas.delete("inner_oval_2_a", "inner_oval_2_b")
  522. if not self._canvas.find_withtag("inner_oval_3_a") and height - (2 * border_width) > 2 * inner_corner_radius \
  523. and width - (2 * border_width) > 2 * inner_corner_radius and "inner_oval_3" not in exclude_parts:
  524. self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_3_a", "inner_corner_part", "inner_parts_right", "inner_parts", "right_parts"), anchor=tkinter.CENTER)
  525. self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_3_b", "inner_corner_part", "inner_parts_right", "inner_parts", "right_parts"), anchor=tkinter.CENTER,
  526. angle=180)
  527. requires_recoloring = True
  528. elif self._canvas.find_withtag("inner_oval_3_a") and (not (height - (2 * border_width) > 2 * inner_corner_radius
  529. and width - (2 * border_width) > 2 * inner_corner_radius) or "inner_oval_3" in exclude_parts):
  530. self._canvas.delete("inner_oval_3_a", "inner_oval_3_b")
  531. if not self._canvas.find_withtag("inner_oval_4_a") and height - (2 * border_width) > 2 * inner_corner_radius and "inner_oval_4" not in exclude_parts:
  532. self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_4_a", "inner_corner_part", "inner_parts_left", "inner_parts", "left_parts"), anchor=tkinter.CENTER)
  533. self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_4_b", "inner_corner_part", "inner_parts_left", "inner_parts", "left_parts"), anchor=tkinter.CENTER,
  534. angle=180)
  535. requires_recoloring = True
  536. elif self._canvas.find_withtag("inner_oval_4_a") and (not height - (2 * border_width) > 2 * inner_corner_radius or "inner_oval_4" in exclude_parts):
  537. self._canvas.delete("inner_oval_4_a", "inner_oval_4_b")
  538. # change position of border corner parts
  539. self._canvas.coords("inner_oval_1_a", border_width + inner_corner_radius, border_width + inner_corner_radius, inner_corner_radius)
  540. self._canvas.coords("inner_oval_1_b", border_width + inner_corner_radius, border_width + inner_corner_radius, inner_corner_radius)
  541. self._canvas.coords("inner_oval_2_a", width - border_width - inner_corner_radius, border_width + inner_corner_radius, inner_corner_radius)
  542. self._canvas.coords("inner_oval_2_b", width - border_width - inner_corner_radius, border_width + inner_corner_radius, inner_corner_radius)
  543. self._canvas.coords("inner_oval_3_a", width - border_width - inner_corner_radius, height - border_width - inner_corner_radius, inner_corner_radius)
  544. self._canvas.coords("inner_oval_3_b", width - border_width - inner_corner_radius, height - border_width - inner_corner_radius, inner_corner_radius)
  545. self._canvas.coords("inner_oval_4_a", border_width + inner_corner_radius, height - border_width - inner_corner_radius, inner_corner_radius)
  546. self._canvas.coords("inner_oval_4_b", border_width + inner_corner_radius, height - border_width - inner_corner_radius, inner_corner_radius)
  547. else:
  548. self._canvas.delete("inner_corner_part") # delete inner corner parts if not needed
  549. # create canvas inner rectangle parts if not already created
  550. if not self._canvas.find_withtag("inner_rectangle_1"):
  551. self._canvas.create_rectangle(0, 0, 0, 0, tags=("inner_rectangle_left_1", "inner_rectangle_part", "inner_parts_left", "inner_parts", "left_parts"), width=0)
  552. self._canvas.create_rectangle(0, 0, 0, 0, tags=("inner_rectangle_right_1", "inner_rectangle_part", "inner_parts_right", "inner_parts", "right_parts"), width=0)
  553. requires_recoloring = True
  554. if not self._canvas.find_withtag("inner_rectangle_2") and inner_corner_radius * 2 < height - (border_width * 2):
  555. self._canvas.create_rectangle(0, 0, 0, 0, tags=("inner_rectangle_left_2", "inner_rectangle_part", "inner_parts_left", "inner_parts", "left_parts"), width=0)
  556. self._canvas.create_rectangle(0, 0, 0, 0, tags=("inner_rectangle_right_2", "inner_rectangle_part", "inner_parts_right", "inner_parts", "right_parts"), width=0)
  557. requires_recoloring = True
  558. elif self._canvas.find_withtag("inner_rectangle_2") and not inner_corner_radius * 2 < height - (border_width * 2):
  559. self._canvas.delete("inner_rectangle_left_2")
  560. self._canvas.delete("inner_rectangle_right_2")
  561. # change position of inner rectangle parts
  562. self._canvas.coords("inner_rectangle_left_1", (border_width + inner_corner_radius,
  563. border_width,
  564. left_section_width,
  565. height - border_width))
  566. self._canvas.coords("inner_rectangle_left_2", (border_width,
  567. border_width + inner_corner_radius,
  568. left_section_width,
  569. height - inner_corner_radius - border_width))
  570. self._canvas.coords("inner_rectangle_right_1", (left_section_width,
  571. border_width,
  572. width - border_width - inner_corner_radius,
  573. height - border_width))
  574. self._canvas.coords("inner_rectangle_right_2", (left_section_width,
  575. border_width + inner_corner_radius,
  576. width - border_width,
  577. height - inner_corner_radius - border_width))
  578. if requires_recoloring: # new parts were added -> manage z-order
  579. self._canvas.tag_lower("inner_parts")
  580. self._canvas.tag_lower("border_parts")
  581. self._canvas.tag_lower("background_parts")
  582. return requires_recoloring
  583. def draw_rounded_progress_bar_with_border(self, width: Union[float, int], height: Union[float, int], corner_radius: Union[float, int],
  584. border_width: Union[float, int], progress_value_1: float, progress_value_2: float, orientation: str) -> bool:
  585. """ Draws a rounded bar on the canvas, and onntop sits a progress bar from value 1 to value 2 (range 0-1, left to right, bottom to top).
  586. The border elements get the 'border_parts' tag", the main elements get the 'inner_parts' tag and
  587. the progress elements get the 'progress_parts' tag. The 'orientation' argument defines from which direction the progress starts (n, w, s, e).
  588. returns bool if recoloring is necessary """
  589. if self._round_width_to_even_numbers:
  590. width = math.floor(width / 2) * 2 # round _current_width and _current_height and restrict them to even values only
  591. if self._round_height_to_even_numbers:
  592. height = math.floor(height / 2) * 2
  593. if corner_radius > width / 2 or corner_radius > height / 2: # restrict corner_radius if it's too larger
  594. corner_radius = min(width / 2, height / 2)
  595. border_width = round(border_width)
  596. corner_radius = self.__calc_optimal_corner_radius(corner_radius) # optimize corner_radius for different drawing methods (different rounding)
  597. if corner_radius >= border_width:
  598. inner_corner_radius = corner_radius - border_width
  599. else:
  600. inner_corner_radius = 0
  601. if self.preferred_drawing_method == "polygon_shapes" or self.preferred_drawing_method == "circle_shapes":
  602. return self.__draw_rounded_progress_bar_with_border_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius,
  603. progress_value_1, progress_value_2, orientation)
  604. elif self.preferred_drawing_method == "font_shapes":
  605. return self.__draw_rounded_progress_bar_with_border_font_shapes(width, height, corner_radius, border_width, inner_corner_radius,
  606. progress_value_1, progress_value_2, orientation)
  607. def __draw_rounded_progress_bar_with_border_polygon_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int,
  608. progress_value_1: float, progress_value_2: float, orientation: str) -> bool:
  609. requires_recoloring = self.__draw_rounded_rect_with_border_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius)
  610. if corner_radius <= border_width:
  611. bottom_right_shift = 0 # weird canvas rendering inaccuracy that has to be corrected in some cases
  612. else:
  613. bottom_right_shift = 0
  614. # create progress parts
  615. if not self._canvas.find_withtag("progress_parts"):
  616. self._canvas.create_polygon((0, 0, 0, 0), tags=("progress_line_1", "progress_parts"), joinstyle=tkinter.ROUND)
  617. self._canvas.tag_raise("progress_parts", "inner_parts")
  618. requires_recoloring = True
  619. if orientation == "w":
  620. self._canvas.coords("progress_line_1",
  621. border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_1,
  622. border_width + inner_corner_radius,
  623. border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_2,
  624. border_width + inner_corner_radius,
  625. border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_2,
  626. height - (border_width + inner_corner_radius) + bottom_right_shift,
  627. border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_1,
  628. height - (border_width + inner_corner_radius) + bottom_right_shift)
  629. elif orientation == "s":
  630. self._canvas.coords("progress_line_1",
  631. border_width + inner_corner_radius,
  632. border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_2),
  633. width - (border_width + inner_corner_radius),
  634. border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_2),
  635. width - (border_width + inner_corner_radius),
  636. border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_1),
  637. border_width + inner_corner_radius,
  638. border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_1))
  639. self._canvas.itemconfig("progress_line_1", width=inner_corner_radius * 2)
  640. return requires_recoloring
  641. def __draw_rounded_progress_bar_with_border_font_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int,
  642. progress_value_1: float, progress_value_2: float, orientation: str) -> bool:
  643. requires_recoloring, requires_recoloring_2 = False, False
  644. if inner_corner_radius > 0:
  645. # create canvas border corner parts if not already created
  646. if not self._canvas.find_withtag("progress_oval_1_a"):
  647. self._canvas.create_aa_circle(0, 0, 0, tags=("progress_oval_1_a", "progress_corner_part", "progress_parts"), anchor=tkinter.CENTER)
  648. self._canvas.create_aa_circle(0, 0, 0, tags=("progress_oval_1_b", "progress_corner_part", "progress_parts"), anchor=tkinter.CENTER, angle=180)
  649. self._canvas.create_aa_circle(0, 0, 0, tags=("progress_oval_2_a", "progress_corner_part", "progress_parts"), anchor=tkinter.CENTER)
  650. self._canvas.create_aa_circle(0, 0, 0, tags=("progress_oval_2_b", "progress_corner_part", "progress_parts"), anchor=tkinter.CENTER, angle=180)
  651. requires_recoloring = True
  652. if not self._canvas.find_withtag("progress_oval_3_a") and round(inner_corner_radius) * 2 < height - 2 * border_width:
  653. self._canvas.create_aa_circle(0, 0, 0, tags=("progress_oval_3_a", "progress_corner_part", "progress_parts"), anchor=tkinter.CENTER)
  654. self._canvas.create_aa_circle(0, 0, 0, tags=("progress_oval_3_b", "progress_corner_part", "progress_parts"), anchor=tkinter.CENTER, angle=180)
  655. self._canvas.create_aa_circle(0, 0, 0, tags=("progress_oval_4_a", "progress_corner_part", "progress_parts"), anchor=tkinter.CENTER)
  656. self._canvas.create_aa_circle(0, 0, 0, tags=("progress_oval_4_b", "progress_corner_part", "progress_parts"), anchor=tkinter.CENTER, angle=180)
  657. requires_recoloring = True
  658. elif self._canvas.find_withtag("progress_oval_3_a") and not round(inner_corner_radius) * 2 < height - 2 * border_width:
  659. self._canvas.delete("progress_oval_3_a", "progress_oval_3_b", "progress_oval_4_a", "progress_oval_4_b")
  660. if not self._canvas.find_withtag("progress_rectangle_1"):
  661. self._canvas.create_rectangle(0, 0, 0, 0, tags=("progress_rectangle_1", "progress_rectangle_part", "progress_parts"), width=0)
  662. requires_recoloring = True
  663. if not self._canvas.find_withtag("progress_rectangle_2") and inner_corner_radius * 2 < height - (border_width * 2):
  664. self._canvas.create_rectangle(0, 0, 0, 0, tags=("progress_rectangle_2", "progress_rectangle_part", "progress_parts"), width=0)
  665. requires_recoloring = True
  666. elif self._canvas.find_withtag("progress_rectangle_2") and not inner_corner_radius * 2 < height - (border_width * 2):
  667. self._canvas.delete("progress_rectangle_2")
  668. # horizontal orientation from the bottom
  669. if orientation == "w":
  670. requires_recoloring_2 = self.__draw_rounded_rect_with_border_font_shapes(width, height, corner_radius, border_width, inner_corner_radius,
  671. ())
  672. # set positions of progress corner parts
  673. self._canvas.coords("progress_oval_1_a", border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_1,
  674. border_width + inner_corner_radius, inner_corner_radius)
  675. self._canvas.coords("progress_oval_1_b", border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_1,
  676. border_width + inner_corner_radius, inner_corner_radius)
  677. self._canvas.coords("progress_oval_2_a", border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_2,
  678. border_width + inner_corner_radius, inner_corner_radius)
  679. self._canvas.coords("progress_oval_2_b", border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_2,
  680. border_width + inner_corner_radius, inner_corner_radius)
  681. self._canvas.coords("progress_oval_3_a", border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_2,
  682. height - border_width - inner_corner_radius, inner_corner_radius)
  683. self._canvas.coords("progress_oval_3_b", border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_2,
  684. height - border_width - inner_corner_radius, inner_corner_radius)
  685. self._canvas.coords("progress_oval_4_a", border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_1,
  686. height - border_width - inner_corner_radius, inner_corner_radius)
  687. self._canvas.coords("progress_oval_4_b", border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_1,
  688. height - border_width - inner_corner_radius, inner_corner_radius)
  689. # set positions of progress rect parts
  690. self._canvas.coords("progress_rectangle_1",
  691. border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_1,
  692. border_width,
  693. border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_2,
  694. height - border_width)
  695. self._canvas.coords("progress_rectangle_2",
  696. border_width + 2 * inner_corner_radius + (width - 2 * inner_corner_radius - 2 * border_width) * progress_value_1,
  697. border_width + inner_corner_radius,
  698. border_width + 2 * inner_corner_radius + (width - 2 * inner_corner_radius - 2 * border_width) * progress_value_2,
  699. height - inner_corner_radius - border_width)
  700. # vertical orientation from the bottom
  701. if orientation == "s":
  702. requires_recoloring_2 = self.__draw_rounded_rect_with_border_font_shapes(width, height, corner_radius, border_width, inner_corner_radius,
  703. ())
  704. # set positions of progress corner parts
  705. self._canvas.coords("progress_oval_1_a", border_width + inner_corner_radius,
  706. border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_2), inner_corner_radius)
  707. self._canvas.coords("progress_oval_1_b", border_width + inner_corner_radius,
  708. border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_2), inner_corner_radius)
  709. self._canvas.coords("progress_oval_2_a", width - border_width - inner_corner_radius,
  710. border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_2), inner_corner_radius)
  711. self._canvas.coords("progress_oval_2_b", width - border_width - inner_corner_radius,
  712. border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_2), inner_corner_radius)
  713. self._canvas.coords("progress_oval_3_a", width - border_width - inner_corner_radius,
  714. border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_1), inner_corner_radius)
  715. self._canvas.coords("progress_oval_3_b", width - border_width - inner_corner_radius,
  716. border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_1), inner_corner_radius)
  717. self._canvas.coords("progress_oval_4_a", border_width + inner_corner_radius,
  718. border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_1), inner_corner_radius)
  719. self._canvas.coords("progress_oval_4_b", border_width + inner_corner_radius,
  720. border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_1), inner_corner_radius)
  721. # set positions of progress rect parts
  722. self._canvas.coords("progress_rectangle_1",
  723. border_width + inner_corner_radius,
  724. border_width + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_2),
  725. width - border_width - inner_corner_radius,
  726. border_width + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_1))
  727. self._canvas.coords("progress_rectangle_2",
  728. border_width,
  729. border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_2),
  730. width - border_width,
  731. border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_1))
  732. return requires_recoloring or requires_recoloring_2
  733. def draw_rounded_slider_with_border_and_button(self, width: Union[float, int], height: Union[float, int], corner_radius: Union[float, int],
  734. border_width: Union[float, int], button_length: Union[float, int], button_corner_radius: Union[float, int],
  735. slider_value: float, orientation: str) -> bool:
  736. if self._round_width_to_even_numbers:
  737. width = math.floor(width / 2) * 2 # round _current_width and _current_height and restrict them to even values only
  738. if self._round_height_to_even_numbers:
  739. height = math.floor(height / 2) * 2
  740. if corner_radius > width / 2 or corner_radius > height / 2: # restrict corner_radius if it's too larger
  741. corner_radius = min(width / 2, height / 2)
  742. if button_corner_radius > width / 2 or button_corner_radius > height / 2: # restrict button_corner_radius if it's too larger
  743. button_corner_radius = min(width / 2, height / 2)
  744. button_length = round(button_length)
  745. border_width = round(border_width)
  746. button_corner_radius = round(button_corner_radius)
  747. corner_radius = self.__calc_optimal_corner_radius(corner_radius) # optimize corner_radius for different drawing methods (different rounding)
  748. if corner_radius >= border_width:
  749. inner_corner_radius = corner_radius - border_width
  750. else:
  751. inner_corner_radius = 0
  752. if self.preferred_drawing_method == "polygon_shapes" or self.preferred_drawing_method == "circle_shapes":
  753. return self.__draw_rounded_slider_with_border_and_button_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius,
  754. button_length, button_corner_radius, slider_value, orientation)
  755. elif self.preferred_drawing_method == "font_shapes":
  756. return self.__draw_rounded_slider_with_border_and_button_font_shapes(width, height, corner_radius, border_width, inner_corner_radius,
  757. button_length, button_corner_radius, slider_value, orientation)
  758. 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,
  759. button_length: int, button_corner_radius: int, slider_value: float, orientation: str) -> bool:
  760. # draw normal progressbar
  761. requires_recoloring = self.__draw_rounded_progress_bar_with_border_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius,
  762. 0, slider_value, orientation)
  763. # create slider button part
  764. if not self._canvas.find_withtag("slider_parts"):
  765. self._canvas.create_polygon((0, 0, 0, 0), tags=("slider_line_1", "slider_parts"), joinstyle=tkinter.ROUND)
  766. self._canvas.tag_raise("slider_parts") # manage z-order
  767. requires_recoloring = True
  768. if corner_radius <= border_width:
  769. bottom_right_shift = -1 # weird canvas rendering inaccuracy that has to be corrected in some cases
  770. else:
  771. bottom_right_shift = 0
  772. if orientation == "w":
  773. slider_x_position = corner_radius + (button_length / 2) + (width - 2 * corner_radius - button_length) * slider_value
  774. self._canvas.coords("slider_line_1",
  775. slider_x_position - (button_length / 2), button_corner_radius,
  776. slider_x_position + (button_length / 2), button_corner_radius,
  777. slider_x_position + (button_length / 2), height - button_corner_radius,
  778. slider_x_position - (button_length / 2), height - button_corner_radius)
  779. self._canvas.itemconfig("slider_line_1",
  780. width=button_corner_radius * 2)
  781. elif orientation == "s":
  782. slider_y_position = corner_radius + (button_length / 2) + (height - 2 * corner_radius - button_length) * (1 - slider_value)
  783. self._canvas.coords("slider_line_1",
  784. button_corner_radius, slider_y_position - (button_length / 2),
  785. button_corner_radius, slider_y_position + (button_length / 2),
  786. width - button_corner_radius, slider_y_position + (button_length / 2),
  787. width - button_corner_radius, slider_y_position - (button_length / 2))
  788. self._canvas.itemconfig("slider_line_1",
  789. width=button_corner_radius * 2)
  790. return requires_recoloring
  791. 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,
  792. button_length: int, button_corner_radius: int, slider_value: float, orientation: str) -> bool:
  793. # draw normal progressbar
  794. requires_recoloring = self.__draw_rounded_progress_bar_with_border_font_shapes(width, height, corner_radius, border_width, inner_corner_radius,
  795. 0, slider_value, orientation)
  796. # create 4 circles (if not needed, then less)
  797. if not self._canvas.find_withtag("slider_oval_1_a"):
  798. self._canvas.create_aa_circle(0, 0, 0, tags=("slider_oval_1_a", "slider_corner_part", "slider_parts"), anchor=tkinter.CENTER)
  799. self._canvas.create_aa_circle(0, 0, 0, tags=("slider_oval_1_b", "slider_corner_part", "slider_parts"), anchor=tkinter.CENTER, angle=180)
  800. requires_recoloring = True
  801. if not self._canvas.find_withtag("slider_oval_2_a") and button_length > 0:
  802. self._canvas.create_aa_circle(0, 0, 0, tags=("slider_oval_2_a", "slider_corner_part", "slider_parts"), anchor=tkinter.CENTER)
  803. self._canvas.create_aa_circle(0, 0, 0, tags=("slider_oval_2_b", "slider_corner_part", "slider_parts"), anchor=tkinter.CENTER, angle=180)
  804. requires_recoloring = True
  805. elif self._canvas.find_withtag("slider_oval_2_a") and not button_length > 0:
  806. self._canvas.delete("slider_oval_2_a", "slider_oval_2_b")
  807. if not self._canvas.find_withtag("slider_oval_4_a") and height > 2 * button_corner_radius:
  808. self._canvas.create_aa_circle(0, 0, 0, tags=("slider_oval_4_a", "slider_corner_part", "slider_parts"), anchor=tkinter.CENTER)
  809. self._canvas.create_aa_circle(0, 0, 0, tags=("slider_oval_4_b", "slider_corner_part", "slider_parts"), anchor=tkinter.CENTER, angle=180)
  810. requires_recoloring = True
  811. elif self._canvas.find_withtag("slider_oval_4_a") and not height > 2 * button_corner_radius:
  812. self._canvas.delete("slider_oval_4_a", "slider_oval_4_b")
  813. if not self._canvas.find_withtag("slider_oval_3_a") and button_length > 0 and height > 2 * button_corner_radius:
  814. self._canvas.create_aa_circle(0, 0, 0, tags=("slider_oval_3_a", "slider_corner_part", "slider_parts"), anchor=tkinter.CENTER)
  815. self._canvas.create_aa_circle(0, 0, 0, tags=("slider_oval_3_b", "slider_corner_part", "slider_parts"), anchor=tkinter.CENTER, angle=180)
  816. requires_recoloring = True
  817. elif self._canvas.find_withtag("border_oval_3_a") and not (button_length > 0 and height > 2 * button_corner_radius):
  818. self._canvas.delete("slider_oval_3_a", "slider_oval_3_b")
  819. # create the 2 rectangles (if needed)
  820. if not self._canvas.find_withtag("slider_rectangle_1") and button_length > 0:
  821. self._canvas.create_rectangle(0, 0, 0, 0, tags=("slider_rectangle_1", "slider_rectangle_part", "slider_parts"), width=0)
  822. requires_recoloring = True
  823. elif self._canvas.find_withtag("slider_rectangle_1") and not button_length > 0:
  824. self._canvas.delete("slider_rectangle_1")
  825. if not self._canvas.find_withtag("slider_rectangle_2") and height > 2 * button_corner_radius:
  826. self._canvas.create_rectangle(0, 0, 0, 0, tags=("slider_rectangle_2", "slider_rectangle_part", "slider_parts"), width=0)
  827. requires_recoloring = True
  828. elif self._canvas.find_withtag("slider_rectangle_2") and not height > 2 * button_corner_radius:
  829. self._canvas.delete("slider_rectangle_2")
  830. # set positions of circles and rectangles
  831. if orientation == "w":
  832. slider_x_position = corner_radius + (button_length / 2) + (width - 2 * corner_radius - button_length) * slider_value
  833. self._canvas.coords("slider_oval_1_a", slider_x_position - (button_length / 2), button_corner_radius, button_corner_radius)
  834. self._canvas.coords("slider_oval_1_b", slider_x_position - (button_length / 2), button_corner_radius, button_corner_radius)
  835. self._canvas.coords("slider_oval_2_a", slider_x_position + (button_length / 2), button_corner_radius, button_corner_radius)
  836. self._canvas.coords("slider_oval_2_b", slider_x_position + (button_length / 2), button_corner_radius, button_corner_radius)
  837. self._canvas.coords("slider_oval_3_a", slider_x_position + (button_length / 2), height - button_corner_radius, button_corner_radius)
  838. self._canvas.coords("slider_oval_3_b", slider_x_position + (button_length / 2), height - button_corner_radius, button_corner_radius)
  839. self._canvas.coords("slider_oval_4_a", slider_x_position - (button_length / 2), height - button_corner_radius, button_corner_radius)
  840. self._canvas.coords("slider_oval_4_b", slider_x_position - (button_length / 2), height - button_corner_radius, button_corner_radius)
  841. self._canvas.coords("slider_rectangle_1",
  842. slider_x_position - (button_length / 2), 0,
  843. slider_x_position + (button_length / 2), height)
  844. self._canvas.coords("slider_rectangle_2",
  845. slider_x_position - (button_length / 2) - button_corner_radius, button_corner_radius,
  846. slider_x_position + (button_length / 2) + button_corner_radius, height - button_corner_radius)
  847. elif orientation == "s":
  848. slider_y_position = corner_radius + (button_length / 2) + (height - 2 * corner_radius - button_length) * (1 - slider_value)
  849. self._canvas.coords("slider_oval_1_a", button_corner_radius, slider_y_position - (button_length / 2), button_corner_radius)
  850. self._canvas.coords("slider_oval_1_b", button_corner_radius, slider_y_position - (button_length / 2), button_corner_radius)
  851. self._canvas.coords("slider_oval_2_a", button_corner_radius, slider_y_position + (button_length / 2), button_corner_radius)
  852. self._canvas.coords("slider_oval_2_b", button_corner_radius, slider_y_position + (button_length / 2), button_corner_radius)
  853. self._canvas.coords("slider_oval_3_a", width - button_corner_radius, slider_y_position + (button_length / 2), button_corner_radius)
  854. self._canvas.coords("slider_oval_3_b", width - button_corner_radius, slider_y_position + (button_length / 2), button_corner_radius)
  855. self._canvas.coords("slider_oval_4_a", width - button_corner_radius, slider_y_position - (button_length / 2), button_corner_radius)
  856. self._canvas.coords("slider_oval_4_b", width - button_corner_radius, slider_y_position - (button_length / 2), button_corner_radius)
  857. self._canvas.coords("slider_rectangle_1",
  858. 0, slider_y_position - (button_length / 2),
  859. width, slider_y_position + (button_length / 2))
  860. self._canvas.coords("slider_rectangle_2",
  861. button_corner_radius, slider_y_position - (button_length / 2) - button_corner_radius,
  862. width - button_corner_radius, slider_y_position + (button_length / 2) + button_corner_radius)
  863. if requires_recoloring: # new parts were added -> manage z-order
  864. self._canvas.tag_raise("slider_parts")
  865. return requires_recoloring
  866. def draw_rounded_scrollbar(self, width: Union[float, int], height: Union[float, int], corner_radius: Union[float, int],
  867. border_spacing: Union[float, int], start_value: float, end_value: float, orientation: str) -> bool:
  868. if self._round_width_to_even_numbers:
  869. width = math.floor(width / 2) * 2 # round _current_width and _current_height and restrict them to even values only
  870. if self._round_height_to_even_numbers:
  871. height = math.floor(height / 2) * 2
  872. if corner_radius > width / 2 or corner_radius > height / 2: # restrict corner_radius if it's too larger
  873. corner_radius = min(width / 2, height / 2)
  874. border_spacing = round(border_spacing)
  875. corner_radius = self.__calc_optimal_corner_radius(corner_radius) # optimize corner_radius for different drawing methods (different rounding)
  876. if corner_radius >= border_spacing:
  877. inner_corner_radius = corner_radius - border_spacing
  878. else:
  879. inner_corner_radius = 0
  880. if self.preferred_drawing_method == "polygon_shapes" or self.preferred_drawing_method == "circle_shapes":
  881. return self.__draw_rounded_scrollbar_polygon_shapes(width, height, corner_radius, inner_corner_radius,
  882. start_value, end_value, orientation)
  883. elif self.preferred_drawing_method == "font_shapes":
  884. return self.__draw_rounded_scrollbar_font_shapes(width, height, corner_radius, inner_corner_radius,
  885. start_value, end_value, orientation)
  886. def __draw_rounded_scrollbar_polygon_shapes(self, width: int, height: int, corner_radius: int, inner_corner_radius: int,
  887. start_value: float, end_value: float, orientation: str) -> bool:
  888. requires_recoloring = False
  889. if not self._canvas.find_withtag("border_parts"):
  890. self._canvas.create_rectangle(0, 0, 0, 0, tags=("border_rectangle_1", "border_parts"), width=0)
  891. requires_recoloring = True
  892. self._canvas.coords("border_rectangle_1", 0, 0, width, height)
  893. if not self._canvas.find_withtag("scrollbar_parts"):
  894. self._canvas.create_polygon((0, 0, 0, 0), tags=("scrollbar_polygon_1", "scrollbar_parts"), joinstyle=tkinter.ROUND)
  895. self._canvas.tag_raise("scrollbar_parts", "border_parts")
  896. requires_recoloring = True
  897. if orientation == "vertical":
  898. self._canvas.coords("scrollbar_polygon_1",
  899. corner_radius, corner_radius + (height - 2 * corner_radius) * start_value,
  900. width - corner_radius, corner_radius + (height - 2 * corner_radius) * start_value,
  901. width - corner_radius, corner_radius + (height - 2 * corner_radius) * end_value,
  902. corner_radius, corner_radius + (height - 2 * corner_radius) * end_value)
  903. elif orientation == "horizontal":
  904. self._canvas.coords("scrollbar_polygon_1",
  905. corner_radius + (width - 2 * corner_radius) * start_value, corner_radius,
  906. corner_radius + (width - 2 * corner_radius) * end_value, corner_radius,
  907. corner_radius + (width - 2 * corner_radius) * end_value, height - corner_radius,
  908. corner_radius + (width - 2 * corner_radius) * start_value, height - corner_radius,)
  909. self._canvas.itemconfig("scrollbar_polygon_1", width=inner_corner_radius * 2)
  910. return requires_recoloring
  911. def __draw_rounded_scrollbar_font_shapes(self, width: int, height: int, corner_radius: int, inner_corner_radius: int,
  912. start_value: float, end_value: float, orientation: str) -> bool:
  913. requires_recoloring = False
  914. if not self._canvas.find_withtag("border_parts"):
  915. self._canvas.create_rectangle(0, 0, 0, 0, tags=("border_rectangle_1", "border_parts"), width=0)
  916. requires_recoloring = True
  917. self._canvas.coords("border_rectangle_1", 0, 0, width, height)
  918. if inner_corner_radius > 0:
  919. if not self._canvas.find_withtag("scrollbar_oval_1_a"):
  920. self._canvas.create_aa_circle(0, 0, 0, tags=("scrollbar_oval_1_a", "scrollbar_corner_part", "scrollbar_parts"), anchor=tkinter.CENTER)
  921. self._canvas.create_aa_circle(0, 0, 0, tags=("scrollbar_oval_1_b", "scrollbar_corner_part", "scrollbar_parts"), anchor=tkinter.CENTER, angle=180)
  922. requires_recoloring = True
  923. if not self._canvas.find_withtag("scrollbar_oval_2_a") and width > 2 * corner_radius:
  924. self._canvas.create_aa_circle(0, 0, 0, tags=("scrollbar_oval_2_a", "scrollbar_corner_part", "scrollbar_parts"), anchor=tkinter.CENTER)
  925. self._canvas.create_aa_circle(0, 0, 0, tags=("scrollbar_oval_2_b", "scrollbar_corner_part", "scrollbar_parts"), anchor=tkinter.CENTER, angle=180)
  926. requires_recoloring = True
  927. elif self._canvas.find_withtag("scrollbar_oval_2_a") and not width > 2 * corner_radius:
  928. self._canvas.delete("scrollbar_oval_2_a", "scrollbar_oval_2_b")
  929. if not self._canvas.find_withtag("scrollbar_oval_3_a") and height > 2 * corner_radius and width > 2 * corner_radius:
  930. self._canvas.create_aa_circle(0, 0, 0, tags=("scrollbar_oval_3_a", "scrollbar_corner_part", "scrollbar_parts"), anchor=tkinter.CENTER)
  931. self._canvas.create_aa_circle(0, 0, 0, tags=("scrollbar_oval_3_b", "scrollbar_corner_part", "scrollbar_parts"), anchor=tkinter.CENTER, angle=180)
  932. requires_recoloring = True
  933. elif self._canvas.find_withtag("scrollbar_oval_3_a") and not (height > 2 * corner_radius and width > 2 * corner_radius):
  934. self._canvas.delete("scrollbar_oval_3_a", "scrollbar_oval_3_b")
  935. if not self._canvas.find_withtag("scrollbar_oval_4_a") and height > 2 * corner_radius:
  936. self._canvas.create_aa_circle(0, 0, 0, tags=("scrollbar_oval_4_a", "scrollbar_corner_part", "scrollbar_parts"), anchor=tkinter.CENTER)
  937. self._canvas.create_aa_circle(0, 0, 0, tags=("scrollbar_oval_4_b", "scrollbar_corner_part", "scrollbar_parts"), anchor=tkinter.CENTER, angle=180)
  938. requires_recoloring = True
  939. elif self._canvas.find_withtag("scrollbar_oval_4_a") and not height > 2 * corner_radius:
  940. self._canvas.delete("scrollbar_oval_4_a", "scrollbar_oval_4_b")
  941. else:
  942. self._canvas.delete("scrollbar_corner_part")
  943. if not self._canvas.find_withtag("scrollbar_rectangle_1") and height > 2 * corner_radius:
  944. self._canvas.create_rectangle(0, 0, 0, 0, tags=("scrollbar_rectangle_1", "scrollbar_rectangle_part", "scrollbar_parts"), width=0)
  945. requires_recoloring = True
  946. elif self._canvas.find_withtag("scrollbar_rectangle_1") and not height > 2 * corner_radius:
  947. self._canvas.delete("scrollbar_rectangle_1")
  948. if not self._canvas.find_withtag("scrollbar_rectangle_2") and width > 2 * corner_radius:
  949. self._canvas.create_rectangle(0, 0, 0, 0, tags=("scrollbar_rectangle_2", "scrollbar_rectangle_part", "scrollbar_parts"), width=0)
  950. requires_recoloring = True
  951. elif self._canvas.find_withtag("scrollbar_rectangle_2") and not width > 2 * corner_radius:
  952. self._canvas.delete("scrollbar_rectangle_2")
  953. if orientation == "vertical":
  954. self._canvas.coords("scrollbar_rectangle_1",
  955. corner_radius - inner_corner_radius, corner_radius + (height - 2 * corner_radius) * start_value,
  956. width - (corner_radius - inner_corner_radius), corner_radius + (height - 2 * corner_radius) * end_value)
  957. self._canvas.coords("scrollbar_rectangle_2",
  958. corner_radius, corner_radius - inner_corner_radius + (height - 2 * corner_radius) * start_value,
  959. width - (corner_radius), corner_radius + inner_corner_radius + (height - 2 * corner_radius) * end_value)
  960. self._canvas.coords("scrollbar_oval_1_a", corner_radius, corner_radius + (height - 2 * corner_radius) * start_value, inner_corner_radius)
  961. self._canvas.coords("scrollbar_oval_1_b", corner_radius, corner_radius + (height - 2 * corner_radius) * start_value, inner_corner_radius)
  962. self._canvas.coords("scrollbar_oval_2_a", width - corner_radius, corner_radius + (height - 2 * corner_radius) * start_value, inner_corner_radius)
  963. self._canvas.coords("scrollbar_oval_2_b", width - corner_radius, corner_radius + (height - 2 * corner_radius) * start_value, inner_corner_radius)
  964. self._canvas.coords("scrollbar_oval_3_a", width - corner_radius, corner_radius + (height - 2 * corner_radius) * end_value, inner_corner_radius)
  965. self._canvas.coords("scrollbar_oval_3_b", width - corner_radius, corner_radius + (height - 2 * corner_radius) * end_value, inner_corner_radius)
  966. self._canvas.coords("scrollbar_oval_4_a", corner_radius, corner_radius + (height - 2 * corner_radius) * end_value, inner_corner_radius)
  967. self._canvas.coords("scrollbar_oval_4_b", corner_radius, corner_radius + (height - 2 * corner_radius) * end_value, inner_corner_radius)
  968. if orientation == "horizontal":
  969. self._canvas.coords("scrollbar_rectangle_1",
  970. corner_radius - inner_corner_radius + (width - 2 * corner_radius) * start_value, corner_radius,
  971. corner_radius + inner_corner_radius + (width - 2 * corner_radius) * end_value, height - corner_radius)
  972. self._canvas.coords("scrollbar_rectangle_2",
  973. corner_radius + (width - 2 * corner_radius) * start_value, corner_radius - inner_corner_radius,
  974. corner_radius + (width - 2 * corner_radius) * end_value, height - (corner_radius - inner_corner_radius))
  975. self._canvas.coords("scrollbar_oval_1_a", corner_radius + (width - 2 * corner_radius) * start_value, corner_radius, inner_corner_radius)
  976. self._canvas.coords("scrollbar_oval_1_b", corner_radius + (width - 2 * corner_radius) * start_value, corner_radius, inner_corner_radius)
  977. self._canvas.coords("scrollbar_oval_2_a", corner_radius + (width - 2 * corner_radius) * end_value, corner_radius, inner_corner_radius)
  978. self._canvas.coords("scrollbar_oval_2_b", corner_radius + (width - 2 * corner_radius) * end_value, corner_radius, inner_corner_radius)
  979. self._canvas.coords("scrollbar_oval_3_a", corner_radius + (width - 2 * corner_radius) * end_value, height - corner_radius, inner_corner_radius)
  980. self._canvas.coords("scrollbar_oval_3_b", corner_radius + (width - 2 * corner_radius) * end_value, height - corner_radius, inner_corner_radius)
  981. self._canvas.coords("scrollbar_oval_4_a", corner_radius + (width - 2 * corner_radius) * start_value, height - corner_radius, inner_corner_radius)
  982. self._canvas.coords("scrollbar_oval_4_b", corner_radius + (width - 2 * corner_radius) * start_value, height - corner_radius, inner_corner_radius)
  983. return requires_recoloring
  984. def draw_checkmark(self, width: Union[float, int], height: Union[float, int], size: Union[int, float]) -> bool:
  985. """ Draws a rounded rectangle with a corner_radius and border_width on the canvas. The border elements have a 'border_parts' tag,
  986. the main foreground elements have an 'inner_parts' tag to color the elements accordingly.
  987. returns bool if recoloring is necessary """
  988. size = round(size)
  989. requires_recoloring = False
  990. if self.preferred_drawing_method == "polygon_shapes" or self.preferred_drawing_method == "circle_shapes":
  991. x, y, radius = width / 2, height / 2, size / 2.8
  992. if not self._canvas.find_withtag("checkmark"):
  993. self._canvas.create_line(0, 0, 0, 0, tags=("checkmark", "create_line"), width=round(height / 8), joinstyle=tkinter.MITER, capstyle=tkinter.ROUND)
  994. self._canvas.tag_raise("checkmark")
  995. requires_recoloring = True
  996. self._canvas.coords("checkmark",
  997. x + radius, y - radius,
  998. x - radius / 4, y + radius * 0.8,
  999. x - radius, y + radius / 6)
  1000. elif self.preferred_drawing_method == "font_shapes":
  1001. if not self._canvas.find_withtag("checkmark"):
  1002. self._canvas.create_text(0, 0, text="Z", font=("CustomTkinter_shapes_font", -size), tags=("checkmark", "create_text"), anchor=tkinter.CENTER)
  1003. self._canvas.tag_raise("checkmark")
  1004. requires_recoloring = True
  1005. self._canvas.coords("checkmark", round(width / 2), round(height / 2))
  1006. return requires_recoloring
  1007. def draw_dropdown_arrow(self, x_position: Union[int, float], y_position: Union[int, float], size: Union[int, float]) -> bool:
  1008. """ Draws a dropdown bottom facing arrow at (x_position, y_position) in a given size
  1009. returns bool if recoloring is necessary """
  1010. x_position, y_position, size = round(x_position), round(y_position), round(size)
  1011. requires_recoloring = False
  1012. if self.preferred_drawing_method == "polygon_shapes" or self.preferred_drawing_method == "circle_shapes":
  1013. if not self._canvas.find_withtag("dropdown_arrow"):
  1014. self._canvas.create_line(0, 0, 0, 0, tags="dropdown_arrow", width=round(size / 3), joinstyle=tkinter.ROUND, capstyle=tkinter.ROUND)
  1015. self._canvas.tag_raise("dropdown_arrow")
  1016. requires_recoloring = True
  1017. self._canvas.coords("dropdown_arrow",
  1018. x_position - (size / 2),
  1019. y_position - (size / 5),
  1020. x_position,
  1021. y_position + (size / 5),
  1022. x_position + (size / 2),
  1023. y_position - (size / 5))
  1024. elif self.preferred_drawing_method == "font_shapes":
  1025. if not self._canvas.find_withtag("dropdown_arrow"):
  1026. self._canvas.create_text(0, 0, text="Y", font=("CustomTkinter_shapes_font", -size), tags="dropdown_arrow", anchor=tkinter.CENTER)
  1027. self._canvas.tag_raise("dropdown_arrow")
  1028. requires_recoloring = True
  1029. self._canvas.itemconfigure("dropdown_arrow", font=("CustomTkinter_shapes_font", -size))
  1030. self._canvas.coords("dropdown_arrow", x_position, y_position)
  1031. return requires_recoloring