mirror of
https://github.com/TomSchimansky/CustomTkinter.git
synced 2023-08-10 21:13:13 +03:00
add font rendering to button, not finished yet
This commit is contained in:
parent
5007d16df3
commit
5bbcb1c617
@ -61,14 +61,12 @@ def set_default_color_theme(color_string):
|
||||
CTkColorManager.initialize_color_theme(color_string)
|
||||
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
def load_font_windows(fontpath: str, private=True, enumerable=False):
|
||||
from ctypes import windll, byref, create_string_buffer
|
||||
|
||||
FR_PRIVATE = 0x10
|
||||
FR_NOT_ENUM = 0x20
|
||||
|
||||
|
||||
def loadfont(fontpath: str, private=True, enumerable=False):
|
||||
pathbuf = create_string_buffer(bytes(fontpath, "utf-8"))
|
||||
add_font_resource_ex = windll.gdi32.AddFontResourceExA
|
||||
flags = (FR_PRIVATE if private else 0) | (FR_NOT_ENUM if not enumerable else 0)
|
||||
@ -76,6 +74,7 @@ if sys.platform.startswith("win"):
|
||||
return bool(num_fonts_added)
|
||||
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
# load custom font for rendering circles on the tkinter.Canvas with antialiasing
|
||||
script_directory = os.path.dirname(os.path.abspath(__file__))
|
||||
loadfont(os.path.join(script_directory, "assets", "canvas_shapes_font.otf"), private=True)
|
||||
print("load_font_windows:", load_font_windows(os.path.join(script_directory, "assets", "CustomTkinter_shapes_font-Regular.otf"), private=True))
|
||||
|
@ -10,7 +10,7 @@ if Version(darkdetect.__version__) < Version("0.3.1"):
|
||||
exit()
|
||||
|
||||
|
||||
class AppearanceModeTracker():
|
||||
class AppearanceModeTracker:
|
||||
|
||||
callback_list = []
|
||||
root_tk_list = []
|
||||
|
@ -1,172 +0,0 @@
|
||||
from threading import Thread
|
||||
import time
|
||||
import sys
|
||||
from distutils.version import StrictVersion as Version
|
||||
import darkdetect
|
||||
|
||||
if Version(darkdetect.__version__) < Version("0.3.1"):
|
||||
sys.stderr.write("WARNING: You have to update the darkdetect library: pip3 install --upgrade darkdetect\n")
|
||||
if sys.platform != "darwin":
|
||||
exit()
|
||||
|
||||
|
||||
class SystemAppearanceModeListener(Thread):
|
||||
""" This class checks for a system appearance change
|
||||
in a loop, and if a change is detected, than the
|
||||
callback function gets called. Either 'Light' or
|
||||
'Dark' is passed in the callback function. """
|
||||
|
||||
def __init__(self, callback, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.setDaemon(True)
|
||||
|
||||
self.appearance_mode = self.detect_appearance_mode()
|
||||
self.callback_function = callback
|
||||
|
||||
self.activated = True
|
||||
|
||||
def activate(self):
|
||||
self.activated = True
|
||||
|
||||
def deactivate(self):
|
||||
self.activated = False
|
||||
|
||||
def get_mode(self):
|
||||
return self.appearance_mode
|
||||
|
||||
def set_mode(self, appearance_mode):
|
||||
self.appearance_mode = appearance_mode
|
||||
|
||||
@staticmethod
|
||||
def detect_appearance_mode():
|
||||
try:
|
||||
if darkdetect.theme() == "Dark":
|
||||
return 1 # Dark
|
||||
else:
|
||||
return 0 # Light
|
||||
except NameError:
|
||||
return 0 # Light
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
if self.activated:
|
||||
detected_mode = self.detect_appearance_mode()
|
||||
if detected_mode != self.appearance_mode:
|
||||
self.appearance_mode = detected_mode
|
||||
|
||||
if self.appearance_mode == 0:
|
||||
self.callback_function("Light", from_listener=True)
|
||||
else:
|
||||
self.callback_function("Dark", from_listener=True)
|
||||
time.sleep(0.5)
|
||||
else:
|
||||
while self.activated is False:
|
||||
time.sleep(0.05)
|
||||
|
||||
|
||||
class SystemAppearanceModeListenerNoThread:
|
||||
def __init__(self, callback):
|
||||
self.appearance_mode = self.detect_appearance_mode()
|
||||
self.callback_function = callback
|
||||
|
||||
self.activated = True
|
||||
|
||||
def get_mode(self):
|
||||
return self.appearance_mode
|
||||
|
||||
@staticmethod
|
||||
def detect_appearance_mode():
|
||||
try:
|
||||
if darkdetect.theme() == "Dark":
|
||||
return 1 # Dark
|
||||
else:
|
||||
return 0 # Light
|
||||
except NameError:
|
||||
return 0 # Light
|
||||
|
||||
def update(self):
|
||||
detected_mode = self.detect_appearance_mode()
|
||||
if detected_mode != self.appearance_mode:
|
||||
self.appearance_mode = detected_mode
|
||||
|
||||
if self.appearance_mode == 0:
|
||||
self.callback_function("Light", from_listener=True)
|
||||
else:
|
||||
self.callback_function("Dark", from_listener=True)
|
||||
|
||||
|
||||
class AppearanceModeTracker():
|
||||
""" This class holds a list with callback functions
|
||||
of every customtkinter object that gets created.
|
||||
And when either the SystemAppearanceModeListener
|
||||
or the user changes the appearance_mode, all
|
||||
callbacks in the list get called and the
|
||||
new appearance_mode is passed over to the
|
||||
customtkinter objects """
|
||||
|
||||
callback_list = []
|
||||
appearance_mode = 0 # Light (standard)
|
||||
system_mode_listener = None
|
||||
|
||||
@classmethod
|
||||
def init_listener_function(cls, no_thread=False):
|
||||
if isinstance(cls.system_mode_listener, SystemAppearanceModeListener):
|
||||
cls.system_mode_listener.deactivate()
|
||||
|
||||
if no_thread is True:
|
||||
cls.system_mode_listener = SystemAppearanceModeListenerNoThread(cls.set_appearance_mode)
|
||||
cls.appearance_mode = cls.system_mode_listener.get_mode()
|
||||
else:
|
||||
cls.system_mode_listener = SystemAppearanceModeListener(cls.set_appearance_mode)
|
||||
cls.system_mode_listener.start()
|
||||
cls.appearance_mode = cls.system_mode_listener.get_mode()
|
||||
|
||||
@classmethod
|
||||
def add(cls, callback):
|
||||
cls.callback_list.append(callback)
|
||||
|
||||
@classmethod
|
||||
def remove(cls, callback):
|
||||
cls.callback_list.remove(callback)
|
||||
|
||||
@classmethod
|
||||
def get_mode(cls):
|
||||
return cls.appearance_mode
|
||||
|
||||
@classmethod
|
||||
def set_appearance_mode(cls, mode_string, from_listener=False):
|
||||
if mode_string.lower() == "dark":
|
||||
cls.appearance_mode = 1
|
||||
cls.system_mode_listener.set_mode(1)
|
||||
|
||||
if not from_listener:
|
||||
cls.system_mode_listener.deactivate()
|
||||
|
||||
elif mode_string.lower() == "light":
|
||||
cls.appearance_mode = 0
|
||||
cls.system_mode_listener.set_mode(0)
|
||||
|
||||
if not from_listener:
|
||||
cls.system_mode_listener.deactivate()
|
||||
|
||||
elif mode_string.lower() == "system":
|
||||
cls.system_mode_listener.activate()
|
||||
|
||||
if cls.appearance_mode == 0:
|
||||
for callback in cls.callback_list:
|
||||
try:
|
||||
callback("Light")
|
||||
except Exception:
|
||||
print("error callback")
|
||||
continue
|
||||
|
||||
elif cls.appearance_mode == 1:
|
||||
for callback in cls.callback_list:
|
||||
try:
|
||||
callback("Dark")
|
||||
except Exception:
|
||||
print("error callback")
|
||||
continue
|
||||
|
||||
|
||||
AppearanceModeTracker.init_listener_function()
|
Binary file not shown.
@ -5,6 +5,7 @@ from .customtkinter_tk import CTk
|
||||
from .customtkinter_frame import CTkFrame
|
||||
from .appearance_mode_tracker import AppearanceModeTracker
|
||||
from .customtkinter_color_manager import CTkColorManager
|
||||
from .customtkinter_canvas import CTkCanvas
|
||||
|
||||
|
||||
class CTkButton(tkinter.Frame):
|
||||
@ -107,8 +108,8 @@ class CTkButton(tkinter.Frame):
|
||||
if sys.platform == "darwin" and self.function is not None:
|
||||
self.configure(cursor="pointinghand") # other cursor when hovering over button with command
|
||||
|
||||
self.canvas = tkinter.Canvas(master=self,
|
||||
highlightthicknes=0,
|
||||
self.canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self.width,
|
||||
height=self.height)
|
||||
self.canvas.grid(row=0, column=0, rowspan=2, columnspan=2, sticky="nsew")
|
||||
@ -157,6 +158,8 @@ class CTkButton(tkinter.Frame):
|
||||
if sys.platform == "darwin":
|
||||
return user_corner_radius # on macOS just use given value (canvas has Antialiasing)
|
||||
else:
|
||||
draw_method = "font"
|
||||
if draw_method == "shapes":
|
||||
user_corner_radius = 0.5 * round(user_corner_radius / 0.5) # round to 0.5 steps
|
||||
|
||||
# make sure the value is always with .5 at the end for smoother corners
|
||||
@ -166,6 +169,8 @@ class CTkButton(tkinter.Frame):
|
||||
return user_corner_radius + 0.5
|
||||
else:
|
||||
return user_corner_radius
|
||||
elif draw_method == "font":
|
||||
return round(user_corner_radius)
|
||||
|
||||
def draw(self, no_color_updates=False):
|
||||
self.canvas.configure(bg=CTkColorManager.single_color(self.bg_color, self.appearance_mode))
|
||||
@ -174,6 +179,9 @@ class CTkButton(tkinter.Frame):
|
||||
if sys.platform == "darwin":
|
||||
# on macOS draw button with polygons (positions are more accurate, macOS has Antialiasing)
|
||||
self.draw_with_polygon_shapes()
|
||||
elif sys.platform.startswith("win"):
|
||||
#self.draw_with_ovals_and_rects()
|
||||
self.draw_with_font_shapes_and_rects()
|
||||
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()
|
||||
@ -438,6 +446,92 @@ class CTkButton(tkinter.Frame):
|
||||
self.width - self.border_width + rect_bottom_right_shift,
|
||||
self.height - self.inner_corner_radius - self.border_width + rect_bottom_right_shift))
|
||||
|
||||
def draw_with_font_shapes_and_rects(self):
|
||||
|
||||
# create border button parts
|
||||
if self.border_width > 0:
|
||||
if self.corner_radius > 0:
|
||||
# create canvas border corner parts if not already created
|
||||
if not self.canvas.find_withtag("border_oval_1_a"):
|
||||
self.canvas.create_aa_circle(0, 0, 0, tags=("border_oval_1_a", "border_corner_part", "border_parts"), anchor=tkinter.CENTER)
|
||||
self.canvas.create_aa_circle(0, 0, 0, tags=("border_oval_1_b", "border_corner_part", "border_parts"), anchor=tkinter.CENTER, angle=180)
|
||||
self.canvas.create_aa_circle(0, 0, 0, tags=("border_oval_2_a", "border_corner_part", "border_parts"), anchor=tkinter.CENTER)
|
||||
self.canvas.create_aa_circle(0, 0, 0, tags=("border_oval_2_b", "border_corner_part", "border_parts"), anchor=tkinter.CENTER, angle=180)
|
||||
self.canvas.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)
|
||||
|
||||
# change position of border corner parts
|
||||
self.canvas.coords("border_oval_1_a", self.corner_radius, self.corner_radius, self.corner_radius)
|
||||
self.canvas.coords("border_oval_1_b", self.corner_radius, self.corner_radius, self.corner_radius)
|
||||
self.canvas.coords("border_oval_2_a", self.width - self.corner_radius, self.corner_radius, self.corner_radius)
|
||||
self.canvas.coords("border_oval_2_b", self.width - self.corner_radius, self.corner_radius, self.corner_radius)
|
||||
self.canvas.coords("border_oval_3_a", self.width - self.corner_radius, self.height - self.corner_radius, self.corner_radius)
|
||||
self.canvas.coords("border_oval_3_b", self.width - self.corner_radius, self.height - self.corner_radius, self.corner_radius)
|
||||
self.canvas.coords("border_oval_4_a", self.corner_radius, self.height - self.corner_radius, self.corner_radius)
|
||||
self.canvas.coords("border_oval_4_b", self.corner_radius, self.height - self.corner_radius, self.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"))
|
||||
self.canvas.create_rectangle(0, 0, 0, 0, tags=("border_rectangle_2", "border_rectangle_part", "border_parts"))
|
||||
|
||||
# change position of border rectangle parts
|
||||
self.canvas.coords("border_rectangle_1", (0,
|
||||
self.corner_radius,
|
||||
self.width -1,
|
||||
self.height - self.corner_radius -1))
|
||||
self.canvas.coords("border_rectangle_2", (self.corner_radius,
|
||||
0,
|
||||
self.width - self.corner_radius -1,
|
||||
self.height -1))
|
||||
|
||||
# create inner button parts
|
||||
if self.inner_corner_radius > 0:
|
||||
|
||||
# create canvas border corner parts if not already created
|
||||
if not self.canvas.find_withtag("inner_corner_part"):
|
||||
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.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)
|
||||
|
||||
#print(self.corner_radius, self.border_width, self.inner_corner_radius)
|
||||
# change position of border corner parts
|
||||
self.canvas.coords("inner_oval_1_a", self.border_width + self.inner_corner_radius, self.border_width + self.inner_corner_radius, self.inner_corner_radius)
|
||||
self.canvas.coords("inner_oval_1_b", self.border_width + self.inner_corner_radius, self.border_width + self.inner_corner_radius, self.inner_corner_radius)
|
||||
self.canvas.coords("inner_oval_2_a", self.width - self.border_width - self.inner_corner_radius, self.border_width + self.inner_corner_radius, self.inner_corner_radius)
|
||||
self.canvas.coords("inner_oval_2_b", self.width - self.border_width - self.inner_corner_radius, self.border_width + self.inner_corner_radius, self.inner_corner_radius)
|
||||
self.canvas.coords("inner_oval_3_a", self.width - self.border_width - self.inner_corner_radius, self.height - self.border_width - self.inner_corner_radius, self.inner_corner_radius)
|
||||
self.canvas.coords("inner_oval_3_b", self.width - self.border_width - self.inner_corner_radius, self.height - self.border_width - self.inner_corner_radius, self.inner_corner_radius)
|
||||
self.canvas.coords("inner_oval_4_a", self.border_width + self.inner_corner_radius, self.height - self.border_width - self.inner_corner_radius, self.inner_corner_radius)
|
||||
self.canvas.coords("inner_oval_4_b", self.border_width + self.inner_corner_radius, self.height - self.border_width - self.inner_corner_radius, self.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_part"):
|
||||
self.canvas.create_rectangle(0, 0, 0, 0, tags=("inner_rectangle_1", "inner_rectangle_part", "inner_parts"))
|
||||
self.canvas.create_rectangle(0, 0, 0, 0, tags=("inner_rectangle_2", "inner_rectangle_part", "inner_parts"))
|
||||
|
||||
# change position of inner rectangle parts
|
||||
self.canvas.coords("inner_rectangle_1", (self.border_width + self.inner_corner_radius,
|
||||
self.border_width,
|
||||
self.width - self.border_width - self.inner_corner_radius -1,
|
||||
self.height - self.border_width -1))
|
||||
self.canvas.coords("inner_rectangle_2", (self.border_width,
|
||||
self.border_width + self.inner_corner_radius,
|
||||
self.width - self.border_width -1,
|
||||
self.height - self.inner_corner_radius - self.border_width -1))
|
||||
|
||||
def config(self, *args, **kwargs):
|
||||
self.configure(*args, **kwargs)
|
||||
|
||||
|
@ -7,11 +7,61 @@ class CTkCanvas(tkinter.Canvas):
|
||||
|
||||
self.aa_circle_canvas_ids = []
|
||||
|
||||
def create_aa_circle(self, x_pos, y_pos, radius, angle=0, fill="white", tags="") -> str:
|
||||
circle_chars = ["A", "B", "C", "D", "E", "F"]
|
||||
def get_char_from_radius(self, radius):
|
||||
if radius >= 10:
|
||||
char = "B"
|
||||
elif radius >= 6:
|
||||
char = "D"
|
||||
elif radius >= 3:
|
||||
char = "H"
|
||||
else:
|
||||
char = "H"
|
||||
|
||||
return char
|
||||
|
||||
def create_aa_circle(self, x_pos, y_pos, radius, angle=0, fill="white", tags="", anchor=tkinter.CENTER) -> str:
|
||||
# create a circle with a font element
|
||||
circle_1 = self.create_text(x_pos, y_pos, text=circle_chars[0], anchor=tkinter.CENTER, fill=fill,
|
||||
font=("TheCircle", -radius * 2), tags=tags, angle=angle)
|
||||
circle_1 = self.create_text(x_pos, y_pos, text=self.get_char_from_radius(radius), anchor=anchor, fill=fill,
|
||||
font=("CustomTkinter_shapes_font", -radius * 2), tags=tags, angle=angle)
|
||||
self.addtag_withtag("ctk_aa_circle_font_element", circle_1)
|
||||
self.aa_circle_canvas_ids.append(circle_1)
|
||||
|
||||
return circle_1
|
||||
|
||||
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:
|
||||
super().itemconfigure(coords_id, font=("CustomTkinter_shapes_font", -int(args[2]) * 2), text=self.get_char_from_radius(args[2]))
|
||||
|
||||
elif type(tag_or_id) == int and tag_or_id in self.aa_circle_canvas_ids:
|
||||
super().coords(tag_or_id, *args[:2])
|
||||
|
||||
if len(args) == 3:
|
||||
super().itemconfigure(tag_or_id, font=("CustomTkinter_shapes_font", -args[2] * 2), text=self.get_char_from_radius(args[2]))
|
||||
|
||||
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:
|
||||
if tag_or_id in self.aa_circle_canvas_ids:
|
||||
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:
|
||||
if configure_id in self.aa_circle_canvas_ids:
|
||||
super().itemconfigure(configure_id, *args, **kwargs_except_outline)
|
||||
else:
|
||||
super().itemconfigure(configure_id, *args, **kwargs)
|
||||
|
||||
|
||||
|
@ -23,8 +23,8 @@ class CTkColorManager:
|
||||
|
||||
if theme_name.lower() == "blue":
|
||||
cls.WINDOW_BG = ("#ECECEC", "#323232") # macOS standard light and dark window bg colors
|
||||
cls.MAIN = ("#1C94CF", "#1C94CF")
|
||||
cls.MAIN_HOVER = ("#5FB4DD", "#5FB4DD")
|
||||
cls.MAIN = ("#64A1D2", "#1C94CF")
|
||||
cls.MAIN_HOVER = ("#A7C2E0", "#5FB4DD")
|
||||
cls.ENTRY = ("white", "#222222")
|
||||
cls.TEXT = ("black", "white")
|
||||
cls.PLACEHOLDER_TEXT = ("gray52", "gray62")
|
||||
|
@ -96,6 +96,7 @@ class CTkDialog:
|
||||
if __name__ == "__main__":
|
||||
import customtkinter
|
||||
customtkinter.set_appearance_mode("System")
|
||||
customtkinter.set_default_color_theme("dark-blue")
|
||||
|
||||
app = customtkinter.CTk()
|
||||
app.geometry("400x300")
|
||||
|
@ -4,7 +4,7 @@ import customtkinter
|
||||
import sys
|
||||
|
||||
customtkinter.set_appearance_mode("Light") # Modes: "System" (standard), "Dark", "Light"
|
||||
customtkinter.set_default_color_theme("dark-blue") # Themes: "blue" (standard), "green", "dark-blue"
|
||||
customtkinter.set_default_color_theme("blue") # Themes: "blue" (standard), "green", "dark-blue"
|
||||
|
||||
|
||||
class App(customtkinter.CTk):
|
||||
|
Loading…
Reference in New Issue
Block a user