2022-02-21 17:35:08 +03:00
|
|
|
import tkinter
|
2022-05-22 01:02:45 +03:00
|
|
|
import sys
|
2022-05-22 03:12:13 +03:00
|
|
|
from typing import Union, Tuple
|
2022-02-21 17:35:08 +03:00
|
|
|
|
|
|
|
|
|
|
|
class CTkCanvas(tkinter.Canvas):
|
2022-10-02 04:23:10 +03:00
|
|
|
"""
|
|
|
|
Canvas with additional functionality to draw antialiased circles on Windows/Linux.
|
|
|
|
|
|
|
|
Call .init_font_character_mapping() at program start to load the correct character
|
|
|
|
dictionary according to the operating system. Characters (circle sizes) are optimised
|
|
|
|
to look best for rendering CustomTkinter shapes on the different operating systems.
|
|
|
|
|
|
|
|
- .create_aa_circle() creates antialiased circle and returns int identifier.
|
|
|
|
- .coords() is modified to support the aa-circle shapes correctly like you would expect.
|
|
|
|
- .itemconfig() is also modified to support aa-cricle shapes.
|
|
|
|
|
|
|
|
The aa-circles are created by choosing a character from the custom created and loaded
|
|
|
|
font 'CustomTkinter_shapes_font'. It contains circle shapes with different sizes filling
|
|
|
|
either the whole character space or just pert of it (characters A to R). Circles with a smaller
|
|
|
|
radius need a smaller circle character to look correct when rendered on the canvas.
|
|
|
|
|
|
|
|
For an optimal result, the draw-engine creates two aa-circles on top of each other, while
|
|
|
|
one is rotated by 90 degrees. This helps to make the circle look more symetric, which is
|
|
|
|
not can be a problem when using only a single circle character.
|
|
|
|
"""
|
|
|
|
|
2022-05-22 01:02:45 +03:00
|
|
|
radius_to_char_fine: dict = None # dict to map radius to font circle character
|
2022-02-23 00:38:40 +03:00
|
|
|
|
2022-02-21 17:35:08 +03:00
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
super().__init__(*args, **kwargs)
|
2022-10-02 04:23:10 +03:00
|
|
|
self._aa_circle_canvas_ids = set()
|
2022-02-21 17:35:08 +03:00
|
|
|
|
2022-05-22 01:02:45 +03:00
|
|
|
@classmethod
|
|
|
|
def init_font_character_mapping(cls):
|
|
|
|
""" optimizations made for Windows 10, 11 only """
|
|
|
|
|
|
|
|
radius_to_char_warped = {19: 'B', 18: 'B', 17: 'B', 16: 'B', 15: 'B', 14: 'B', 13: 'B', 12: 'B', 11: 'B',
|
|
|
|
10: 'B',
|
|
|
|
9: 'C', 8: 'D', 7: 'C', 6: 'E', 5: 'F', 4: 'G', 3: 'H', 2: 'H', 1: 'H', 0: 'A'}
|
|
|
|
|
|
|
|
radius_to_char_fine_windows_10 = {19: 'A', 18: 'A', 17: 'B', 16: 'B', 15: 'B', 14: 'B', 13: 'C', 12: 'C',
|
|
|
|
11: 'C', 10: 'C',
|
2022-08-05 16:37:23 +03:00
|
|
|
9: 'D', 8: 'D', 7: 'D', 6: 'C', 5: 'D', 4: 'G', 3: 'G', 2: 'H', 1: 'H',
|
2022-05-22 01:02:45 +03:00
|
|
|
0: 'A'}
|
|
|
|
|
|
|
|
radius_to_char_fine_windows_11 = {19: 'A', 18: 'A', 17: 'B', 16: 'B', 15: 'B', 14: 'B', 13: 'C', 12: 'C',
|
|
|
|
11: 'D', 10: 'D',
|
|
|
|
9: 'E', 8: 'F', 7: 'C', 6: 'I', 5: 'E', 4: 'G', 3: 'P', 2: 'R', 1: 'R',
|
|
|
|
0: 'A'}
|
|
|
|
|
2022-06-16 01:15:24 +03:00
|
|
|
radius_to_char_fine_linux = {19: 'A', 18: 'A', 17: 'B', 16: 'B', 15: 'B', 14: 'B', 13: 'F', 12: 'C',
|
|
|
|
11: 'F', 10: 'C',
|
|
|
|
9: 'D', 8: 'G', 7: 'D', 6: 'F', 5: 'D', 4: 'G', 3: 'M', 2: 'H', 1: 'H',
|
|
|
|
0: 'A'}
|
|
|
|
|
2022-05-22 01:02:45 +03:00
|
|
|
if sys.platform.startswith("win"):
|
|
|
|
if sys.getwindowsversion().build > 20000: # Windows 11
|
|
|
|
cls.radius_to_char_fine = radius_to_char_fine_windows_11
|
|
|
|
else: # < Windows 11
|
|
|
|
cls.radius_to_char_fine = radius_to_char_fine_windows_10
|
2022-06-16 01:15:24 +03:00
|
|
|
elif sys.platform.startswith("linux"): # Optimized on Kali Linux
|
|
|
|
cls.radius_to_char_fine = radius_to_char_fine_linux
|
|
|
|
else:
|
2022-05-22 01:02:45 +03:00
|
|
|
cls.radius_to_char_fine = radius_to_char_fine_windows_10
|
|
|
|
|
2022-10-02 04:23:10 +03:00
|
|
|
def _get_char_from_radius(self, radius: int) -> str:
|
2022-04-21 19:45:51 +03:00
|
|
|
if radius >= 20:
|
|
|
|
return "A"
|
|
|
|
else:
|
|
|
|
return self.radius_to_char_fine[radius]
|
2022-02-22 16:36:36 +03:00
|
|
|
|
2022-05-22 01:02:45 +03:00
|
|
|
def create_aa_circle(self, x_pos: int, y_pos: int, radius: int, angle: int = 0, fill: str = "white",
|
2022-05-22 03:12:13 +03:00
|
|
|
tags: Union[str, Tuple[str, ...]] = "", anchor: str = tkinter.CENTER) -> int:
|
2022-02-21 17:35:08 +03:00
|
|
|
# create a circle with a font element
|
2022-10-02 04:23:10 +03:00
|
|
|
circle_1 = self.create_text(x_pos, y_pos, text=self._get_char_from_radius(radius), anchor=anchor, fill=fill,
|
2022-02-22 16:36:36 +03:00
|
|
|
font=("CustomTkinter_shapes_font", -radius * 2), tags=tags, angle=angle)
|
|
|
|
self.addtag_withtag("ctk_aa_circle_font_element", circle_1)
|
2022-10-02 04:23:10 +03:00
|
|
|
self._aa_circle_canvas_ids.add(circle_1)
|
2022-02-21 17:35:08 +03:00
|
|
|
|
|
|
|
return circle_1
|
2022-02-22 16:36:36 +03:00
|
|
|
|
|
|
|
def coords(self, tag_or_id, *args):
|
|
|
|
|
|
|
|
if type(tag_or_id) == str and "ctk_aa_circle_font_element" in self.gettags(tag_or_id):
|
|
|
|
coords_id = self.find_withtag(tag_or_id)[0] # take the lowest id for the given tag
|
|
|
|
super().coords(coords_id, *args[:2])
|
|
|
|
|
|
|
|
if len(args) == 3:
|
2022-10-02 04:23:10 +03:00
|
|
|
super().itemconfigure(coords_id, font=("CustomTkinter_shapes_font", -int(args[2]) * 2), text=self._get_char_from_radius(args[2]))
|
2022-02-22 16:36:36 +03:00
|
|
|
|
2022-10-02 04:23:10 +03:00
|
|
|
elif type(tag_or_id) == int and tag_or_id in self._aa_circle_canvas_ids:
|
2022-02-22 16:36:36 +03:00
|
|
|
super().coords(tag_or_id, *args[:2])
|
|
|
|
|
|
|
|
if len(args) == 3:
|
2022-10-02 04:23:10 +03:00
|
|
|
super().itemconfigure(tag_or_id, font=("CustomTkinter_shapes_font", -args[2] * 2), text=self._get_char_from_radius(args[2]))
|
2022-02-22 16:36:36 +03:00
|
|
|
|
|
|
|
else:
|
|
|
|
super().coords(tag_or_id, *args)
|
|
|
|
|
|
|
|
def itemconfig(self, tag_or_id, *args, **kwargs):
|
|
|
|
kwargs_except_outline = kwargs.copy()
|
|
|
|
if "outline" in kwargs_except_outline:
|
|
|
|
del kwargs_except_outline["outline"]
|
|
|
|
|
|
|
|
if type(tag_or_id) == int:
|
2022-10-02 04:23:10 +03:00
|
|
|
if tag_or_id in self._aa_circle_canvas_ids:
|
2022-02-22 16:36:36 +03:00
|
|
|
super().itemconfigure(tag_or_id, *args, **kwargs_except_outline)
|
|
|
|
else:
|
|
|
|
super().itemconfigure(tag_or_id, *args, **kwargs)
|
|
|
|
else:
|
|
|
|
configure_ids = self.find_withtag(tag_or_id)
|
|
|
|
for configure_id in configure_ids:
|
2022-10-02 04:23:10 +03:00
|
|
|
if configure_id in self._aa_circle_canvas_ids:
|
2022-02-22 16:36:36 +03:00
|
|
|
super().itemconfigure(configure_id, *args, **kwargs_except_outline)
|
|
|
|
else:
|
|
|
|
super().itemconfigure(configure_id, *args, **kwargs)
|