first main

This commit is contained in:
Tom Schimansky 2021-03-04 18:27:46 +01:00
commit d43a35e670
11 changed files with 1226 additions and 0 deletions

2
Readme.md Normal file
View File

@ -0,0 +1,2 @@
# CustomTkinter

79
customtkinter/__init__.py Normal file
View File

@ -0,0 +1,79 @@
from .customtkinter_button import CTkButton
from .customtkinter_slider import CTkSlider
from .customtkinter_frame import CTkFrame
from .customtkinter_progressbar import CTkProgressBar
from .customtkinter_label import CTkLabel
from .customtkinter_entry import CTkEntry
from .appearance_mode_tracker import AppearanceModeTracker, SystemAppearanceModeListenerNoThread
from .customtkinter_color_manager import CTkColorManager
from distutils.version import StrictVersion as Version
import tkinter
import os
import sys
def enable_macos_darkmode():
if sys.platform == "darwin": # macOS
if Version(tkinter.Tcl().call("info", "patchlevel")) >= Version("8.6.9"): # Tcl/Tk >= 8.6.9
os.system("defaults write -g NSRequiresAquaSystemAppearance -bool No")
sys.stderr.write("WARNING (customtkinter.enable_macos_darkmode): " +
"This command forces macOS dark-mode on all programs." +
"This can cause bugs on some other programs.\n" +
"Disable it by calling customtkinter.disable_macos_darkmode at the end of the program.\n")
else:
sys.stderr.write("WARNING (customtkinter.enable_macos_darkmode): " +
"Currently this works only with anaconda python version (Tcl/Tk >= 8.6.9).\n" +
"(python.org Tcl/Tk version is only 8.6.8)\n")
else:
sys.stderr.write("WARNING (customtkinter.enable_macos_darkmode): " +
"System is not macOS, but the following: {}\n".format(sys.platform))
def disable_macos_darkmode():
if sys.platform == "darwin": # macOS
if Version(tkinter.Tcl().call("info", "patchlevel")) >= Version("8.6.9"): # Tcl/Tk >= 8.6.9
os.system("defaults delete -g NSRequiresAquaSystemAppearance")
# This command reverts the dark-mode setting for all programs.
def set_appearance_mode(mode_string):
AppearanceModeTracker.set_appearance_mode(mode_string)
def get_appearance_mode():
if AppearanceModeTracker.appearance_mode == 0:
return "Light"
elif AppearanceModeTracker.appearance_mode == 1:
return "Dark"
def set_theme(main_color):
CTkColorManager.set_theme(main_color)
def deactivate_threading():
AppearanceModeTracker.init_listener_function(no_thread=True)
sys.stderr.write("WARNING (customtkinter.deactivate_threading): Automatic threaded search for a change of the " +
"system appearance mode is deativated now.\nYou have to update the appearance mode manually " +
"in your mainloop by calling customtkinter.update_theme() every time.\n")
def activate_threading():
AppearanceModeTracker.init_listener_function()
def update_theme():
if isinstance(AppearanceModeTracker.system_mode_listener, SystemAppearanceModeListenerNoThread):
AppearanceModeTracker.system_mode_listener.update()
else:
sys.stderr.write("WARNING (customtkinter.update_theme): no need to call update_theme, because " +
"customtkinter is constantly searching for a mode change in a background thread.\n")

View File

@ -0,0 +1,144 @@
from threading import Thread
from time import sleep
import darkdetect
import sys
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
@staticmethod
def detect_appearance_mode():
if sys.platform == "darwin":
if darkdetect.theme() == "Dark":
return 1 # Dark
else:
return 0 # Light
else:
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)
sleep(0.5)
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():
if sys.platform == "darwin":
if darkdetect.theme() == "Dark":
return 1 # Dark
else:
return 0 # Light
else:
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 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
if not from_listener:
cls.system_mode_listener.deactivate()
elif mode_string.lower() == "light":
cls.appearance_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:
callback("Light")
elif cls.appearance_mode == 1:
for callback in cls.callback_list:
callback("Dark")
AppearanceModeTracker.init_listener_function()

View File

@ -0,0 +1,239 @@
import tkinter
import sys
from .customtkinter_frame import CTkFrame
from .appearance_mode_tracker import AppearanceModeTracker
from .customtkinter_color_manager import CTkColorManager
class CTkButton(tkinter.Frame):
""" tkinter custom button with border, rounded corners and hover effect """
def __init__(self,
bg_color=None,
fg_color=CTkColorManager.MAIN,
hover_color=CTkColorManager.MAIN_HOVER,
border_color=None,
border_width=0,
command=None,
width=120,
height=32,
corner_radius=8,
text_font=None,
text_color=CTkColorManager.TEXT,
text="CTkButton",
hover=True,
*args, **kwargs):
super().__init__(*args, **kwargs)
if bg_color is None:
if isinstance(self.master, CTkFrame):
self.bg_color = self.master.fg_color
else:
self.bg_color = self.master.cget("bg")
else:
self.bg_color = bg_color
AppearanceModeTracker.add(self.set_appearance_mode)
self.fg_color = fg_color
self.hover_color = hover_color
self.border_color = border_color
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
self.width = width
self.height = height
if corner_radius*2 > self.height:
self.corner_radius = self.height/2
elif corner_radius*2 > self.width:
self.corner_radius = self.width/2
else:
self.corner_radius = corner_radius
self.border_width = border_width
if self.corner_radius >= self.border_width:
self.inner_corner_radius = self.corner_radius - self.border_width
else:
self.inner_corner_radius = 0
self.text = text
self.text_color = text_color
if text_font is None:
if sys.platform == "darwin": # macOS
self.text_font = ("Avenir", 13)
elif "win" in sys.platform: # Windows
self.text_font = ("Century Gothic", 11)
else:
self.text_font = ("TkDefaultFont")
else:
self.text_font = text_font
self.function = command
self.hover = hover
self.configure(width=self.width, height=self.height)
if sys.platform == "darwin" and self.function is not None:
self.configure(cursor="pointinghand")
self.canvas = tkinter.Canvas(master=self,
highlightthicknes=0,
width=self.width,
height=self.height)
self.canvas.place(x=0, y=0)
if self.hover is True:
self.canvas.bind("<Enter>", self.on_enter)
self.canvas.bind("<Leave>", self.on_leave)
self.canvas.bind("<Button-1>", self.clicked)
self.canvas.bind("<Button-1>", self.clicked)
self.canvas_fg_parts = []
self.canvas_border_parts = []
self.text_part = None
self.draw()
def draw(self):
self.canvas.delete("all")
self.canvas_fg_parts = []
self.canvas_border_parts = []
if type(self.bg_color) == tuple and len(self.bg_color) == 2:
self.canvas.configure(bg=self.bg_color[self.appearance_mode])
else:
self.canvas.configure(bg=self.bg_color)
# border button parts
if self.border_width > 0:
if self.corner_radius > 0:
self.canvas_border_parts.append(self.canvas.create_oval(0,
0,
self.corner_radius * 2,
self.corner_radius * 2))
self.canvas_border_parts.append(self.canvas.create_oval(self.width - self.corner_radius * 2,
0,
self.width,
self.corner_radius * 2))
self.canvas_border_parts.append(self.canvas.create_oval(0,
self.height - self.corner_radius * 2,
self.corner_radius * 2,
self.height))
self.canvas_border_parts.append(self.canvas.create_oval(self.width - self.corner_radius * 2,
self.height - self.corner_radius * 2,
self.width,
self.height))
self.canvas_border_parts.append(self.canvas.create_rectangle(0,
self.corner_radius,
self.width,
self.height - self.corner_radius))
self.canvas_border_parts.append(self.canvas.create_rectangle(self.corner_radius,
0,
self.width - self.corner_radius,
self.height))
# inner button parts
if self.corner_radius > 0:
self.canvas_fg_parts.append(self.canvas.create_oval(self.border_width,
self.border_width,
self.border_width + self.inner_corner_radius * 2,
self.border_width + self.inner_corner_radius * 2))
self.canvas_fg_parts.append(self.canvas.create_oval(self.width - self.border_width - self.inner_corner_radius * 2,
self.border_width,
self.width - self.border_width,
self.border_width + self.inner_corner_radius * 2))
self.canvas_fg_parts.append(self.canvas.create_oval(self.border_width,
self.height - self.border_width - self.inner_corner_radius * 2,
self.border_width + self.inner_corner_radius * 2,
self.height-self.border_width))
self.canvas_fg_parts.append(self.canvas.create_oval(self.width - self.border_width - self.inner_corner_radius * 2,
self.height - self.border_width - self.inner_corner_radius * 2,
self.width - self.border_width,
self.height - self.border_width))
self.canvas_fg_parts.append(self.canvas.create_rectangle(self.border_width + self.inner_corner_radius,
self.border_width,
self.width - self.border_width - self.inner_corner_radius,
self.height - self.border_width))
self.canvas_fg_parts.append(self.canvas.create_rectangle(self.border_width,
self.border_width + self.inner_corner_radius,
self.width - self.border_width,
self.height - self.inner_corner_radius - self.border_width))
for part in self.canvas_fg_parts:
if type(self.fg_color) == tuple and len(self.hover_color) == 2:
self.canvas.itemconfig(part, fill=self.fg_color[self.appearance_mode], width=0)
else:
self.canvas.itemconfig(part, fill=self.fg_color, outline=self.fg_color, width=0)
for part in self.canvas_border_parts:
if type(self.border_color) == tuple and len(self.hover_color) == 2:
self.canvas.itemconfig(part, fill=self.border_color[self.appearance_mode], width=0)
else:
self.canvas.itemconfig(part, fill=self.border_color, outline=self.border_color, width=0)
self.text_part = self.canvas.create_text(self.width / 2,
self.height / 2,
text=self.text,
font=self.text_font)
if type(self.text_color) == tuple and len(self.text_color) == 2:
self.canvas.itemconfig(self.text_part, fill=self.text_color[self.appearance_mode])
else:
self.canvas.itemconfig(self.text_part, fill=self.text_color)
self.set_text(self.text)
def configure_color(self, bg_color=None, fg_color=None, hover_color=None, text_color=None):
if bg_color is not None:
self.bg_color = bg_color
else:
self.bg_color = self.master.cget("bg")
if fg_color is not None:
self.fg_color = fg_color
if hover_color is not None:
self.hover_color = hover_color
if text_color is not None:
self.text_color = text_color
self.draw()
def set_text(self, text):
self.canvas.itemconfig(self.text_part, text=text)
def on_enter(self, event=0):
for part in self.canvas_fg_parts:
if type(self.hover_color) == tuple and len(self.hover_color) == 2:
self.canvas.itemconfig(part, fill=self.hover_color[self.appearance_mode], width=0)
else:
self.canvas.itemconfig(part, fill=self.hover_color, width=0)
def on_leave(self, event=0):
for part in self.canvas_fg_parts:
if type(self.fg_color) == tuple and len(self.hover_color) == 2:
self.canvas.itemconfig(part, fill=self.fg_color[self.appearance_mode], width=0)
else:
self.canvas.itemconfig(part, fill=self.fg_color, width=0)
def clicked(self, event=0):
if self.function is not None:
self.function()
self.on_leave()
def set_appearance_mode(self, mode_string):
if mode_string.lower() == "dark":
self.appearance_mode = 1
elif mode_string.lower() == "light":
self.appearance_mode = 0
self.draw()

View File

@ -0,0 +1,29 @@
class CTkColorManager:
MAIN = ("#1C94CF", "#1C94CF")
MAIN_HOVER = ("#5FB4DD", "#5FB4DD")
ENTRY = ("white", "#222222")
TEXT = ("black", "white")
SLIDER_BG = ("#6B6B6B", "#222222")
PROGRESS_BG = ("#6B6B6B", "#222222")
FRAME = ("#D4D5D6", "#3F3F3F")
FRAME_2 = ("#505050", "#505050")
@classmethod
def set_theme_color(cls, hex_color, hex_color_hover):
cls.MAIN = (hex_color, hex_color)
cls.MAIN_HOVER = (hex_color_hover, hex_color_hover)
print(CTkColorManager.MAIN, id(CTkColorManager))
@classmethod
def set_theme(cls, main_color):
if main_color.lower() == "green":
cls.set_theme_color("#2EDEA4", "#82FCD4")
elif main_color.lower() == "blue":
cls.set_theme_color("#1C94CF", "#5FB4DD")
elif main_color.lower() == "blue":
cls.set_theme_color("#1C94CF", "#5FB4DD")

View File

@ -0,0 +1,113 @@
import tkinter
from .customtkinter_frame import CTkFrame
from .appearance_mode_tracker import AppearanceModeTracker
from .customtkinter_color_manager import CTkColorManager
class CTkEntry(tkinter.Frame):
def __init__(self,
master=None,
bg_color=None,
fg_color=CTkColorManager.ENTRY,
text_color=CTkColorManager.TEXT,
corner_radius=10,
width=120,
height=25,
*args,
**kwargs):
super().__init__(master=master)
AppearanceModeTracker.add(self.change_appearance_mode)
if bg_color is None:
if isinstance(self.master, CTkFrame):
self.bg_color = self.master.fg_color
else:
self.bg_color = self.master.cget("bg")
else:
self.bg_color = bg_color
self.fg_color = fg_color
self.text_color = text_color
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
self.width = width
self.height = height
self.corner_radius = corner_radius
self.configure(width=self.width, height=self.height)
self.canvas = tkinter.Canvas(master=self,
highlightthicknes=0,
width=self.width,
height=self.height)
self.canvas.place(x=0, y=0)
self.entry = tkinter.Entry(master=self,
bd=0,
highlightthicknes=0,
*args, **kwargs)
self.entry.place(relx=0.5, rely=0.5, relwidth=0.8, anchor=tkinter.CENTER)
self.fg_parts = []
self.draw()
def draw(self):
self.canvas.delete("all")
self.fg_parts = []
# frame_border
self.fg_parts.append(self.canvas.create_oval(0, 0,
self.corner_radius*2, self.corner_radius*2))
self.fg_parts.append(self.canvas.create_oval(self.width-self.corner_radius*2, 0,
self.width, self.corner_radius*2))
self.fg_parts.append(self.canvas.create_oval(0, self.height-self.corner_radius*2,
self.corner_radius*2, self.height))
self.fg_parts.append(self.canvas.create_oval(self.width-self.corner_radius*2, self.height-self.corner_radius*2,
self.width, self.height))
self.fg_parts.append(self.canvas.create_rectangle(0, self.corner_radius,
self.width, self.height-self.corner_radius))
self.fg_parts.append(self.canvas.create_rectangle(self.corner_radius, 0,
self.width-self.corner_radius, self.height))
for part in self.fg_parts:
if type(self.fg_color) == tuple:
self.canvas.itemconfig(part, fill=self.fg_color[self.appearance_mode], width=0)
else:
self.canvas.itemconfig(part, fill=self.fg_color, width=0)
if type(self.bg_color) == tuple:
self.canvas.configure(bg=self.bg_color[self.appearance_mode])
else:
self.canvas.configure(bg=self.bg_color)
if type(self.fg_color) == tuple:
self.entry.configure(bg=self.fg_color[self.appearance_mode],
highlightcolor=self.fg_color[self.appearance_mode])
else:
self.entry.configure(bg=self.fg_color,
highlightcolor=self.fg_color)
if type(self.text_color) == tuple:
self.entry.configure(fg=self.text_color[self.appearance_mode],
insertbackground=self.text_color[self.appearance_mode])
else:
self.entry.configure(fg=self.text_color,
insertbackground=self.text_color)
def change_appearance_mode(self, mode_string):
if mode_string.lower() == "dark":
self.appearance_mode = 1
elif mode_string.lower() == "light":
self.appearance_mode = 0
if isinstance(self.master, CTkFrame):
self.bg_color = self.master.fg_color
else:
self.bg_color = self.master.cget("bg")
self.draw()

View File

@ -0,0 +1,109 @@
import tkinter
from .appearance_mode_tracker import AppearanceModeTracker
from .customtkinter_color_manager import CTkColorManager
class CTkFrame(tkinter.Frame):
def __init__(self, *args,
bg_color=None,
fg_color=None,
corner_radius=10,
width=50,
height=20,
**kwargs):
super().__init__(*args, **kwargs)
AppearanceModeTracker.add(self.change_appearance_mode)
if bg_color is None:
if isinstance(self.master, CTkFrame):
self.bg_color = self.master.fg_color
else:
self.bg_color = self.master.cget("bg")
else:
self.bg_color = bg_color
if fg_color is None:
if isinstance(self.master, CTkFrame):
if self.master.fg_color == CTkColorManager.FRAME:
self.fg_color = CTkColorManager.FRAME_2
else:
self.fg_color = CTkColorManager.FRAME
else:
self.fg_color = CTkColorManager.FRAME
else:
self.fg_color = fg_color
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
self.width = width
self.height = height
self.corner_radius = corner_radius
self.configure(width=self.width, height=self.height)
self.canvas = tkinter.Canvas(master=self,
highlightthicknes=0,
width=self.width,
height=self.height)
if type(self.bg_color) == tuple:
self.canvas.configure(bg=self.bg_color[self.appearance_mode])
else:
self.canvas.configure(bg=self.bg_color)
self.canvas.place(x=0, y=0)
self.fg_parts = []
self.draw()
def draw(self):
#self.canvas.delete("all")
for part in self.fg_parts:
self.canvas.delete(part)
self.fg_parts = []
# frame_border
self.fg_parts.append(self.canvas.create_oval(0, 0,
self.corner_radius*2, self.corner_radius*2))
self.fg_parts.append(self.canvas.create_oval(self.width-self.corner_radius*2, 0,
self.width, self.corner_radius*2))
self.fg_parts.append(self.canvas.create_oval(0, self.height-self.corner_radius*2,
self.corner_radius*2, self.height))
self.fg_parts.append(self.canvas.create_oval(self.width-self.corner_radius*2, self.height-self.corner_radius*2,
self.width, self.height))
self.fg_parts.append(self.canvas.create_rectangle(0, self.corner_radius,
self.width, self.height-self.corner_radius))
self.fg_parts.append(self.canvas.create_rectangle(self.corner_radius, 0,
self.width-self.corner_radius, self.height))
for part in self.fg_parts:
if type(self.fg_color) == tuple:
self.canvas.itemconfig(part, fill=self.fg_color[self.appearance_mode], width=0)
else:
self.canvas.itemconfig(part, fill=self.fg_color, width=0)
if type(self.bg_color) == tuple:
self.canvas.configure(bg=self.bg_color[self.appearance_mode])
else:
self.canvas.configure(bg=self.bg_color)
for part in self.fg_parts:
self.canvas.tag_lower(part)
def change_appearance_mode(self, mode_string):
if mode_string.lower() == "dark":
self.appearance_mode = 1
elif mode_string.lower() == "light":
self.appearance_mode = 0
if isinstance(self.master, CTkFrame):
self.bg_color = self.master.fg_color
else:
self.bg_color = self.master.cget("bg")
self.draw()

View File

@ -0,0 +1,141 @@
import tkinter
import sys
from .customtkinter_frame import CTkFrame
from .appearance_mode_tracker import AppearanceModeTracker
from .customtkinter_color_manager import CTkColorManager
class CTkLabel(tkinter.Frame):
def __init__(self,
master=None,
bg_color=None,
fg_color=None,
text_color=CTkColorManager.TEXT,
corner_radius=8,
width=120,
height=25,
text="CTkLabel",
text_font=None,
*args,
**kwargs):
super().__init__(master=master)
AppearanceModeTracker.add(self.change_appearance_mode)
if bg_color is None:
if isinstance(self.master, CTkFrame):
self.bg_color = self.master.fg_color
else:
self.bg_color = self.master.cget("bg")
else:
self.bg_color = bg_color
if fg_color is None:
self.fg_color = self.bg_color
else:
self.fg_color = fg_color
self.text_color = text_color
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
self.width = width
self.height = height
self.corner_radius = corner_radius
self.text = text
if text_font is None:
if sys.platform == "darwin": # macOS
self.text_font = ("Avenir", 13)
elif "win" in sys.platform: # Windows
self.text_font = ("Century Gothic", 11)
else:
self.text_font = ("TkDefaultFont", 11)
else:
self.text_font = text_font
self.configure(width=self.width, height=self.height)
self.canvas = tkinter.Canvas(master=self,
highlightthicknes=0,
width=self.width,
height=self.height)
self.canvas.place(relx=0, rely=0, anchor=tkinter.NW)
self.text_label = tkinter.Label(master=self,
highlightthicknes=0,
bd=0,
text=self.text,
font=self.text_font,
*args, **kwargs)
self.text_label.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER)
self.fg_parts = []
self.draw()
def draw(self):
self.canvas.delete("all")
self.fg_parts = []
# frame_border
self.fg_parts.append(self.canvas.create_oval(0, 0,
self.corner_radius*2, self.corner_radius*2))
self.fg_parts.append(self.canvas.create_oval(self.width-self.corner_radius*2, 0,
self.width, self.corner_radius*2))
self.fg_parts.append(self.canvas.create_oval(0, self.height-self.corner_radius*2,
self.corner_radius*2, self.height))
self.fg_parts.append(self.canvas.create_oval(self.width-self.corner_radius*2, self.height-self.corner_radius*2,
self.width, self.height))
self.fg_parts.append(self.canvas.create_rectangle(0, self.corner_radius,
self.width, self.height-self.corner_radius))
self.fg_parts.append(self.canvas.create_rectangle(self.corner_radius, 0,
self.width-self.corner_radius, self.height))
if type(self.bg_color) == tuple:
self.canvas.configure(bg=self.bg_color[self.appearance_mode])
else:
self.canvas.configure(bg=self.bg_color)
for part in self.fg_parts:
if type(self.fg_color) == tuple:
self.canvas.itemconfig(part, fill=self.fg_color[self.appearance_mode], width=0)
else:
self.canvas.itemconfig(part, fill=self.fg_color, width=0)
if type(self.text_color) == tuple:
self.text_label.configure(fg=self.text_color[self.appearance_mode])
else:
self.text_label.configure(fg=self.text_color)
if type(self.fg_color) == tuple:
self.text_label.configure(bg=self.fg_color[self.appearance_mode])
else:
self.text_label.configure(bg=self.fg_color)
def configure_color(self, bg_color=None, fg_color=None, text_color=None):
if bg_color is not None:
self.bg_color = bg_color
if fg_color is not None:
self.fg_color = fg_color
if text_color is not None:
self.text_color = text_color
self.draw()
def set_text(self, text):
self.text_label.configure(text=text)
def change_appearance_mode(self, mode_string):
if mode_string.lower() == "dark":
self.appearance_mode = 1
elif mode_string.lower() == "light":
self.appearance_mode = 0
if isinstance(self.master, CTkFrame):
self.bg_color = self.master.fg_color
else:
self.bg_color = self.master.cget("bg")
self.draw()

View File

@ -0,0 +1,145 @@
import tkinter
from .customtkinter_frame import CTkFrame
from .appearance_mode_tracker import AppearanceModeTracker
from .customtkinter_color_manager import CTkColorManager
class CTkProgressBar(tkinter.Frame):
def __init__(self,
bg_color=None,
border_color=None,
fg_color=CTkColorManager.PROGRESS_BG,
progress_color=CTkColorManager.MAIN,
function=None,
width=160,
height=20,
border_width=5,
*args, **kwargs):
super().__init__(*args, **kwargs)
AppearanceModeTracker.add(self.change_appearance_mode)
if bg_color is None:
if isinstance(self.master, CTkFrame):
self.bg_color = self.master.fg_color
else:
self.bg_color = self.master.cget("bg")
else:
self.bg_color = bg_color
self.border_color = border_color
self.fg_color = fg_color
self.progress_color = progress_color
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
self.width = width
self.height = height
self.border_width = border_width
self.value = 0.5
self.configure(width=self.width, height=self.height)
self.canvas = tkinter.Canvas(master=self,
highlightthicknes=0,
width=self.width,
height=self.height)
self.canvas.place(x=0, y=0)
self.function = function
self.border_parts = []
self.fg_parts = []
self.progress_parts = []
self.draw()
def draw(self):
self.canvas.delete("all")
self.border_parts = []
self.fg_parts = []
self.progress_parts = []
# frame_border
self.border_parts.append(self.canvas.create_oval(0, 0,
self.height, self.height))
self.border_parts.append(self.canvas.create_rectangle(self.height/2, 0,
self.width-(self.height/2), self.height))
self.border_parts.append(self.canvas.create_oval(self.width-self.height, 0,
self.width, self.height))
# foreground
self.fg_parts.append(self.canvas.create_oval(self.border_width, self.border_width,
self.height-self.border_width, self.height-self.border_width))
self.fg_parts.append(self.canvas.create_rectangle(self.height/2, self.border_width,
self.width-(self.height/2), self.height-self.border_width))
self.fg_parts.append(self.canvas.create_oval(self.width-self.height+self.border_width, self.border_width,
self.width-self.border_width, self.height-self.border_width))
if type(self.bg_color) == tuple:
self.canvas.configure(bg=self.bg_color[self.appearance_mode])
else:
self.canvas.configure(bg=self.bg_color)
for part in self.border_parts:
if type(self.border_color) == tuple:
self.canvas.itemconfig(part, fill=self.border_color[self.appearance_mode], width=0)
else:
self.canvas.itemconfig(part, fill=self.border_color, width=0)
for part in self.fg_parts:
if type(self.fg_color) == tuple:
self.canvas.itemconfig(part, fill=self.fg_color[self.appearance_mode], width=0)
else:
self.canvas.itemconfig(part, fill=self.fg_color, width=0)
self.set(self.value)
def set(self, value):
self.value = value
if self.value > 1:
self.value = 1
elif self.value < 0:
self.value = 0
for part in self.progress_parts:
self.canvas.delete(part)
# progress
self.progress_parts.append(self.canvas.create_oval(self.border_width,
self.border_width,
self.height - self.border_width,
self.height - self.border_width))
self.progress_parts.append(self.canvas.create_rectangle(self.height / 2,
self.border_width,
self.height / 2 + (self.width - self.height) * self.value,
self.height - self.border_width))
self.progress_parts.append(self.canvas.create_oval(self.height / 2 + (self.width - self.height) * self.value - (self.height) / 2 + self.border_width,
self.border_width,
self.height / 2 + (self.width - self.height) * self.value + (self.height) / 2 - self.border_width,
self.height - self.border_width))
for part in self.progress_parts:
if type(self.progress_color) == tuple:
self.canvas.itemconfig(part, fill=self.progress_color[self.appearance_mode], width=0)
else:
self.canvas.itemconfig(part, fill=self.progress_color, width=0)
self.canvas.update()
self.canvas.update_idletasks()
def change_appearance_mode(self, mode_string):
if mode_string.lower() == "dark":
self.appearance_mode = 1
elif mode_string.lower() == "light":
self.appearance_mode = 0
if isinstance(self.master, CTkFrame):
self.bg_color = self.master.fg_color
else:
self.bg_color = self.master.cget("bg")
self.draw()

View File

@ -0,0 +1,186 @@
import tkinter
import sys
from .customtkinter_frame import CTkFrame
from .appearance_mode_tracker import AppearanceModeTracker
from .customtkinter_color_manager import CTkColorManager
class CTkSlider(tkinter.Frame):
def __init__(self,
bg_color=None,
border_color=None,
fg_color=CTkColorManager.SLIDER_BG,
button_color=CTkColorManager.MAIN,
button_hover_color=CTkColorManager.MAIN_HOVER,
width=160,
height=16,
border_width=5.5,
command=None,
*args, **kwargs):
super().__init__(*args, **kwargs)
AppearanceModeTracker.add(self.change_appearance_mode)
if bg_color is None:
if isinstance(self.master, CTkFrame):
self.bg_color = self.master.fg_color
else:
self.bg_color = self.master.cget("bg")
else:
self.bg_color = bg_color
self.border_color = border_color
self.fg_color = fg_color
self.button_color = button_color
self.button_hover_color = button_hover_color
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
self.width = width
self.height = height
self.border_width = border_width
self.callback_function = command
self.value = 0.5
self.hover_state = False
self.configure(width=self.width, height=self.height)
if sys.platform == "darwin":
self.configure(cursor="pointinghand")
self.canvas = tkinter.Canvas(master=self,
highlightthicknes=0,
width=self.width,
height=self.height)
self.canvas.place(x=0, y=0)
self.canvas.bind("<Enter>", self.on_enter)
self.canvas.bind("<Leave>", self.on_leave)
self.canvas.bind("<Button-1>", self.clicked)
self.canvas.bind("<B1-Motion>", self.clicked)
self.border_parts = []
self.fg_parts = []
self.button_parts = []
self.draw()
def draw(self):
self.canvas.delete("all")
self.border_parts = []
self.fg_parts = []
self.button_parts = []
# frame_border
self.border_parts.append(self.canvas.create_oval(0, 0,
self.height, self.height))
self.border_parts.append(self.canvas.create_rectangle(self.height/2, 0,
self.width-(self.height/2), self.height))
self.border_parts.append(self.canvas.create_oval(self.width-self.height, 0,
self.width, self.height))
# foreground
self.fg_parts.append(self.canvas.create_oval(self.border_width, self.border_width,
self.height-self.border_width, self.height-self.border_width))
self.fg_parts.append(self.canvas.create_rectangle(self.height/2, self.border_width,
self.width-(self.height/2), self.height-self.border_width))
self.fg_parts.append(self.canvas.create_oval(self.width-self.height+self.border_width, self.border_width,
self.width-self.border_width, self.height-self.border_width))
# button
self.button_parts.append(self.canvas.create_oval(self.value*self.width - self.height/2, 0,
self.value*self.width + self.height/2, self.height))
if type(self.bg_color) == tuple:
self.canvas.configure(bg=self.bg_color[self.appearance_mode])
else:
self.canvas.configure(bg=self.bg_color)
for part in self.border_parts:
if type(self.border_color) == tuple:
self.canvas.itemconfig(part, fill=self.border_color[self.appearance_mode], width=0)
else:
self.canvas.itemconfig(part, fill=self.border_color, width=0)
for part in self.fg_parts:
if type(self.fg_color) == tuple:
self.canvas.itemconfig(part, fill=self.fg_color[self.appearance_mode], width=0)
else:
self.canvas.itemconfig(part, fill=self.fg_color, width=0)
for part in self.button_parts:
if type(self.button_color) == tuple:
self.canvas.itemconfig(part, fill=self.button_color[self.appearance_mode], width=0)
else:
self.canvas.itemconfig(part, fill=self.button_color, width=0)
def clicked(self, event=0):
self.value = event.x / self.width
if self.value > 1:
self.value = 1
if self.value < 0:
self.value = 0
self.update()
if self.callback_function is not None:
self.callback_function(self.value)
def update(self):
for part in self.button_parts:
self.canvas.delete(part)
self.button_parts.append(self.canvas.create_oval(self.value * (self.width-self.height), 0,
self.value * (self.width-self.height) + self.height, self.height))
for part in self.button_parts:
if self.hover_state is True:
if type(self.button_hover_color) == tuple:
self.canvas.itemconfig(part, fill=self.button_hover_color[self.appearance_mode], width=0)
else:
self.canvas.itemconfig(part, fill=self.button_hover_color, width=0)
else:
if type(self.button_color) == tuple:
self.canvas.itemconfig(part, fill=self.button_color[self.appearance_mode], width=0)
else:
self.canvas.itemconfig(part, fill=self.button_color, width=0)
def on_enter(self, event=0):
self.hover_state = True
for part in self.button_parts:
if type(self.button_hover_color) == tuple:
self.canvas.itemconfig(part, fill=self.button_hover_color[self.appearance_mode], width=0)
else:
self.canvas.itemconfig(part, fill=self.button_hover_color, width=0)
def on_leave(self, event=0):
self.hover_state = False
for part in self.button_parts:
if type(self.button_color) == tuple:
self.canvas.itemconfig(part, fill=self.button_color[self.appearance_mode], width=0)
else:
self.canvas.itemconfig(part, fill=self.button_color, width=0)
def get(self):
return self.value
def set(self, value):
self.value = value
self.update()
if self.callback_function is not None:
self.callback_function(self.value)
def change_appearance_mode(self, mode_string):
if mode_string.lower() == "dark":
self.appearance_mode = 1
elif mode_string.lower() == "light":
self.appearance_mode = 0
if isinstance(self.master, CTkFrame):
self.bg_color = self.master.fg_color
else:
self.bg_color = self.master.cget("bg")
self.draw()

39
main.py Normal file
View File

@ -0,0 +1,39 @@
import tkinter
import customtkinter
customtkinter.enable_macos_darkmode()
customtkinter.deactivate_threading()
app = tkinter.Tk()
app.geometry("400x240")
app.title("TkinterCustomButton")
def button_function():
print("button pressed")
def slider_function(value):
progressbar_1.set(value)
frame_1 = customtkinter.CTkFrame(master=app, width=300, height=200, corner_radius=15)
frame_1.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER)
label_1 = customtkinter.CTkLabel(master=frame_1)
label_1.place(relx=0.5, rely=0.1, anchor=tkinter.CENTER)
progressbar_1 = customtkinter.CTkProgressBar(master=frame_1)
progressbar_1.place(relx=0.5, rely=0.25, anchor=tkinter.CENTER)
button_1 = customtkinter.CTkButton(master=frame_1, corner_radius=10, command=button_function)
button_1.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER)
slider_1 = customtkinter.CTkSlider(master=frame_1, command=slider_function)
slider_1.place(relx=0.5, rely=0.7, anchor=tkinter.CENTER)
entry_1 = customtkinter.CTkEntry(master=frame_1)
entry_1.place(relx=0.5, rely=0.85, anchor=tkinter.CENTER)
app.mainloop()
customtkinter.disable_macos_darkmode()