mirror of
https://github.com/TomSchimansky/CustomTkinter.git
synced 2023-08-10 21:13:13 +03:00
new directory structure
This commit is contained in:
0
customtkinter/widgets/__init__.py
Normal file
0
customtkinter/widgets/__init__.py
Normal file
427
customtkinter/widgets/customtkinter_button.py
Normal file
427
customtkinter/widgets/customtkinter_button.py
Normal file
@ -0,0 +1,427 @@
|
||||
import tkinter
|
||||
import tkinter.ttk as ttk
|
||||
import sys
|
||||
|
||||
from .customtkinter_tk import CTk
|
||||
from .customtkinter_frame import CTkFrame
|
||||
from .customtkinter_canvas import CTkCanvas
|
||||
from customtkinter.appearance_mode_tracker import AppearanceModeTracker
|
||||
from ..customtkinter_theme_manager import CTkThemeManager
|
||||
from ..customtkinter_settings import CTkSettings
|
||||
from ..customtkinter_draw_engine import CTkDrawEngine
|
||||
|
||||
|
||||
class CTkButton(tkinter.Frame):
|
||||
""" tkinter custom button with border, rounded corners and hover effect """
|
||||
|
||||
def __init__(self, *args,
|
||||
bg_color=None,
|
||||
fg_color="default_theme",
|
||||
hover_color="default_theme",
|
||||
border_color="default_theme",
|
||||
border_width="default_theme",
|
||||
command=None,
|
||||
textvariable=None,
|
||||
width=120,
|
||||
height=30,
|
||||
corner_radius="default_theme",
|
||||
text_font="default_theme",
|
||||
text_color="default_theme",
|
||||
text_color_disabled="default_theme",
|
||||
text="CTkButton",
|
||||
hover=True,
|
||||
image=None,
|
||||
compound=tkinter.LEFT,
|
||||
state=tkinter.NORMAL,
|
||||
**kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# overwrite configure methods of master when master is tkinter widget, so that bg changes get applied on child CTk widget too
|
||||
if isinstance(self.master, (tkinter.Tk, tkinter.Frame)) and not isinstance(self.master, (CTk, CTkFrame)):
|
||||
master_old_configure = self.master.config
|
||||
|
||||
def new_configure(*args, **kwargs):
|
||||
if "bg" in kwargs:
|
||||
self.configure(bg_color=kwargs["bg"])
|
||||
elif "background" in kwargs:
|
||||
self.configure(bg_color=kwargs["background"])
|
||||
|
||||
# args[0] is dict when attribute gets changed by widget[<attribute>] syntax
|
||||
elif len(args) > 0 and type(args[0]) == dict:
|
||||
if "bg" in args[0]:
|
||||
self.configure(bg_color=args[0]["bg"])
|
||||
elif "background" in args[0]:
|
||||
self.configure(bg_color=args[0]["background"])
|
||||
master_old_configure(*args, **kwargs)
|
||||
|
||||
self.master.config = new_configure
|
||||
self.master.configure = new_configure
|
||||
|
||||
# add set_appearance_mode method to callback list of AppearanceModeTracker for appearance mode changes
|
||||
AppearanceModeTracker.add(self.set_appearance_mode, self)
|
||||
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
|
||||
|
||||
self.configure_basic_grid()
|
||||
|
||||
# color variables
|
||||
self.bg_color = self.detect_color_of_master() if bg_color is None else bg_color
|
||||
self.fg_color = CTkThemeManager.theme["color"]["button"] if fg_color == "default_theme" else fg_color
|
||||
self.hover_color = CTkThemeManager.theme["color"]["button_hover"] if hover_color == "default_theme" else hover_color
|
||||
self.border_color = CTkThemeManager.theme["color"]["button_border"] if border_color == "default_theme" else border_color
|
||||
|
||||
# shape and size
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.configure(width=self.width, height=self.height)
|
||||
self.corner_radius = CTkThemeManager.theme["shape"]["button_corner_radius"] if corner_radius == "default_theme" else corner_radius
|
||||
self.border_width = CTkThemeManager.theme["shape"]["button_border_width"] if border_width == "default_theme" else border_width
|
||||
|
||||
if self.corner_radius * 2 > self.height:
|
||||
self.corner_radius = self.height / 2
|
||||
elif self.corner_radius * 2 > self.width:
|
||||
self.corner_radius = self.width / 2
|
||||
|
||||
if self.corner_radius >= self.border_width:
|
||||
self.inner_corner_radius = self.corner_radius - self.border_width
|
||||
else:
|
||||
self.inner_corner_radius = 0
|
||||
|
||||
# text and font and image
|
||||
self.image = image
|
||||
self.image_label = None
|
||||
self.text = text
|
||||
self.text_label = None
|
||||
self.text_color = CTkThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color
|
||||
self.text_color_disabled = CTkThemeManager.theme["color"]["text_button_disabled"] if text_color_disabled == "default_theme" else text_color_disabled
|
||||
self.text_font = (CTkThemeManager.theme["text"]["font"], CTkThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font
|
||||
|
||||
# callback and hover functionality
|
||||
self.function = command
|
||||
self.textvariable = textvariable
|
||||
self.state = state
|
||||
self.hover = hover
|
||||
self.compound = compound
|
||||
self.click_animation_running = False
|
||||
|
||||
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")
|
||||
|
||||
self.draw_engine = CTkDrawEngine(self.canvas, CTkSettings.preferred_drawing_method)
|
||||
|
||||
# event bindings
|
||||
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)
|
||||
|
||||
# Each time an item is resized due to pack position mode, the binding Configure is called on the widget
|
||||
self.bind('<Configure>', self.update_dimensions)
|
||||
|
||||
self.set_cursor()
|
||||
self.draw() # initial draw
|
||||
|
||||
def destroy(self):
|
||||
AppearanceModeTracker.remove(self.set_appearance_mode)
|
||||
super().destroy()
|
||||
|
||||
def configure_basic_grid(self):
|
||||
# Configuration of a basic grid (2x2) in which all elements of CTkButtons are centered on one row and one column
|
||||
self.grid_rowconfigure(0, weight=1)
|
||||
self.grid_columnconfigure(0, weight=1)
|
||||
self.grid_rowconfigure(1, weight=1)
|
||||
self.grid_columnconfigure(1, weight=1)
|
||||
|
||||
def update_dimensions(self, event):
|
||||
# only redraw if dimensions changed (for performance)
|
||||
if self.width != event.width or self.height != event.height:
|
||||
self.width = event.width
|
||||
self.height = event.height
|
||||
|
||||
# self.canvas.config(width=self.width, height=self.height)
|
||||
self.draw(no_color_updates=True) # fast drawing without color changes
|
||||
|
||||
def detect_color_of_master(self):
|
||||
""" detect color of self.master widget to set correct bg_color """
|
||||
|
||||
if isinstance(self.master, CTkFrame): # master is CTkFrame
|
||||
return self.master.fg_color
|
||||
|
||||
elif isinstance(self.master, (ttk.Frame, ttk.LabelFrame, ttk.Notebook)): # master is ttk widget
|
||||
print("button on", self.master.winfo_class())
|
||||
try:
|
||||
ttk_style = ttk.Style()
|
||||
return ttk_style.lookup(self.master.winfo_class(), 'background')
|
||||
except Exception:
|
||||
return "#FFFFFF", "#000000"
|
||||
|
||||
else: # master is normal tkinter widget
|
||||
try:
|
||||
return self.master.cget("bg") # try to get bg color by .cget() method
|
||||
except Exception:
|
||||
return "#FFFFFF", "#000000"
|
||||
|
||||
def draw(self, no_color_updates=False):
|
||||
requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.width, self.height, self.corner_radius, self.border_width)
|
||||
|
||||
if no_color_updates is False or requires_recoloring:
|
||||
|
||||
self.canvas.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
|
||||
|
||||
# set color for the button border parts (outline)
|
||||
self.canvas.itemconfig("border_parts",
|
||||
outline=CTkThemeManager.single_color(self.border_color, self.appearance_mode),
|
||||
fill=CTkThemeManager.single_color(self.border_color, self.appearance_mode))
|
||||
|
||||
# set color for inner button parts
|
||||
if self.fg_color is None:
|
||||
self.canvas.itemconfig("inner_parts",
|
||||
outline=CTkThemeManager.single_color(self.bg_color, self.appearance_mode),
|
||||
fill=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
|
||||
else:
|
||||
self.canvas.itemconfig("inner_parts",
|
||||
outline=CTkThemeManager.single_color(self.fg_color, self.appearance_mode),
|
||||
fill=CTkThemeManager.single_color(self.fg_color, self.appearance_mode))
|
||||
|
||||
# create text label if text given
|
||||
if self.text is not None and self.text != "":
|
||||
|
||||
if self.text_label is None:
|
||||
self.text_label = tkinter.Label(master=self, font=self.text_font, textvariable=self.textvariable)
|
||||
|
||||
self.text_label.bind("<Enter>", self.on_enter)
|
||||
self.text_label.bind("<Leave>", self.on_leave)
|
||||
self.text_label.bind("<Button-1>", self.clicked)
|
||||
self.text_label.bind("<Button-1>", self.clicked)
|
||||
|
||||
if no_color_updates is False:
|
||||
# set text_label fg color (text color)
|
||||
self.text_label.configure(fg=CTkThemeManager.single_color(self.text_color, self.appearance_mode))
|
||||
|
||||
if self.state == tkinter.DISABLED:
|
||||
self.text_label.configure(fg=(CTkThemeManager.single_color(self.text_color_disabled, self.appearance_mode)))
|
||||
else:
|
||||
self.text_label.configure(fg=CTkThemeManager.single_color(self.text_color, self.appearance_mode))
|
||||
|
||||
if self.fg_color is None:
|
||||
self.text_label.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
|
||||
else:
|
||||
self.text_label.configure(bg=CTkThemeManager.single_color(self.fg_color, self.appearance_mode))
|
||||
|
||||
self.text_label.configure(text=self.text) # set text
|
||||
|
||||
else:
|
||||
# delete text_label if no text given
|
||||
if self.text_label is not None:
|
||||
self.text_label.destroy()
|
||||
self.text_label = None
|
||||
|
||||
# create image label if image given
|
||||
if self.image is not None:
|
||||
|
||||
if self.image_label is None:
|
||||
self.image_label = tkinter.Label(master=self)
|
||||
|
||||
self.image_label.bind("<Enter>", self.on_enter)
|
||||
self.image_label.bind("<Leave>", self.on_leave)
|
||||
self.image_label.bind("<Button-1>", self.clicked)
|
||||
self.image_label.bind("<Button-1>", self.clicked)
|
||||
|
||||
if no_color_updates is False:
|
||||
# set image_label bg color (background color of label)
|
||||
if self.state == tkinter.DISABLED:
|
||||
self.image_label.configure(bg=CTkThemeManager.multiply_hex_color(CTkThemeManager.single_color(self.fg_color, self.appearance_mode)))
|
||||
else:
|
||||
self.image_label.configure(bg=CTkThemeManager.single_color(self.fg_color, self.appearance_mode))
|
||||
|
||||
self.image_label.configure(image=self.image) # set image
|
||||
|
||||
else:
|
||||
# delete text_label if no text given
|
||||
if self.image_label is not None:
|
||||
self.image_label.destroy()
|
||||
self.image_label = None
|
||||
|
||||
# create grid layout with just an image given
|
||||
if self.image_label is not None and self.text_label is None:
|
||||
self.image_label.grid(row=0, column=0, rowspan=2, columnspan=2, sticky="")
|
||||
|
||||
# create grid layout with just text given
|
||||
if self.image_label is None and self.text_label is not None:
|
||||
self.text_label.grid(row=0, column=0, padx=self.corner_radius, pady=self.border_width, rowspan=2, columnspan=2, sticky="")
|
||||
|
||||
# create grid layout of image and text label in 2x2 grid system with given compound
|
||||
if self.image_label is not None and self.text_label is not None:
|
||||
if self.compound == tkinter.LEFT or self.compound == "left":
|
||||
self.image_label.grid(row=0, column=0, padx=self.corner_radius, sticky="e", rowspan=2, columnspan=1)
|
||||
self.text_label.grid(row=0, column=1, padx=self.corner_radius, sticky="w", rowspan=2, columnspan=1, pady=self.border_width)
|
||||
elif self.compound == tkinter.TOP or self.compound == "top":
|
||||
self.image_label.grid(row=0, column=0, padx=self.corner_radius, sticky="s", columnspan=2, rowspan=1)
|
||||
self.text_label.grid(row=1, column=0, padx=self.corner_radius, sticky="n", columnspan=2, rowspan=1, pady=self.border_width)
|
||||
elif self.compound == tkinter.RIGHT or self.compound == "right":
|
||||
self.image_label.grid(row=0, column=1, padx=self.corner_radius, sticky="w", rowspan=2, columnspan=1)
|
||||
self.text_label.grid(row=0, column=0, padx=self.corner_radius, sticky="e", rowspan=2, columnspan=1, pady=self.border_width)
|
||||
elif self.compound == tkinter.BOTTOM or self.compound == "bottom":
|
||||
self.image_label.grid(row=1, column=0, padx=self.corner_radius, sticky="n", columnspan=2, rowspan=1)
|
||||
self.text_label.grid(row=0, column=0, padx=self.corner_radius, sticky="s", columnspan=2, rowspan=1, pady=self.border_width)
|
||||
|
||||
def config(self, *args, **kwargs):
|
||||
self.configure(*args, **kwargs)
|
||||
|
||||
def configure(self, *args, **kwargs):
|
||||
require_redraw = False # some attribute changes require a call of self.draw() at the end
|
||||
|
||||
if "text" in kwargs:
|
||||
self.set_text(kwargs["text"])
|
||||
del kwargs["text"]
|
||||
|
||||
if "state" in kwargs:
|
||||
self.state = kwargs["state"]
|
||||
self.set_cursor()
|
||||
require_redraw = True
|
||||
del kwargs["state"]
|
||||
|
||||
if "image" in kwargs:
|
||||
self.set_image(kwargs["image"])
|
||||
del kwargs["image"]
|
||||
|
||||
if "compound" in kwargs:
|
||||
self.compound = kwargs["compound"]
|
||||
require_redraw = True
|
||||
del kwargs["compound"]
|
||||
|
||||
if "fg_color" in kwargs:
|
||||
self.fg_color = kwargs["fg_color"]
|
||||
require_redraw = True
|
||||
del kwargs["fg_color"]
|
||||
|
||||
if "border_color" in kwargs:
|
||||
self.border_color = kwargs["border_color"]
|
||||
require_redraw = True
|
||||
del kwargs["border_color"]
|
||||
|
||||
if "bg_color" in kwargs:
|
||||
if kwargs["bg_color"] is None:
|
||||
self.bg_color = self.detect_color_of_master()
|
||||
else:
|
||||
self.bg_color = kwargs["bg_color"]
|
||||
require_redraw = True
|
||||
del kwargs["bg_color"]
|
||||
|
||||
if "hover_color" in kwargs:
|
||||
self.hover_color = kwargs["hover_color"]
|
||||
require_redraw = True
|
||||
del kwargs["hover_color"]
|
||||
|
||||
if "text_color" in kwargs:
|
||||
self.text_color = kwargs["text_color"]
|
||||
require_redraw = True
|
||||
del kwargs["text_color"]
|
||||
|
||||
if "command" in kwargs:
|
||||
self.function = kwargs["command"]
|
||||
del kwargs["command"]
|
||||
|
||||
if "textvariable" in kwargs:
|
||||
self.textvariable = kwargs["textvariable"]
|
||||
if self.text_label is not None:
|
||||
self.text_label.configure(textvariable=self.textvariable)
|
||||
del kwargs["textvariable"]
|
||||
|
||||
super().configure(*args, **kwargs)
|
||||
|
||||
if require_redraw:
|
||||
self.draw()
|
||||
|
||||
def set_cursor(self):
|
||||
if self.state == tkinter.DISABLED:
|
||||
if sys.platform == "darwin" and self.function is not None and CTkSettings.hand_cursor_enabled:
|
||||
self.configure(cursor="arrow")
|
||||
elif sys.platform.startswith("win") and self.function is not None and CTkSettings.hand_cursor_enabled:
|
||||
self.configure(cursor="arrow")
|
||||
|
||||
elif self.state == tkinter.NORMAL:
|
||||
if sys.platform == "darwin" and self.function is not None and CTkSettings.hand_cursor_enabled:
|
||||
self.configure(cursor="pointinghand")
|
||||
elif sys.platform.startswith("win") and self.function is not None and CTkSettings.hand_cursor_enabled:
|
||||
self.configure(cursor="hand2")
|
||||
|
||||
def set_text(self, text):
|
||||
self.text = text
|
||||
self.draw()
|
||||
|
||||
def set_image(self, image):
|
||||
self.image = image
|
||||
self.draw()
|
||||
|
||||
def on_enter(self, event=0):
|
||||
if self.hover is True and self.state == tkinter.NORMAL:
|
||||
if self.hover_color is None:
|
||||
inner_parts_color = self.fg_color
|
||||
else:
|
||||
inner_parts_color = self.hover_color
|
||||
|
||||
# set color of inner button parts to hover color
|
||||
self.canvas.itemconfig("inner_parts",
|
||||
outline=CTkThemeManager.single_color(inner_parts_color, self.appearance_mode),
|
||||
fill=CTkThemeManager.single_color(inner_parts_color, self.appearance_mode))
|
||||
|
||||
# set text_label bg color to button hover color
|
||||
if self.text_label is not None:
|
||||
self.text_label.configure(bg=CTkThemeManager.single_color(inner_parts_color, self.appearance_mode))
|
||||
|
||||
# set image_label bg color to button hover color
|
||||
if self.image_label is not None:
|
||||
self.image_label.configure(bg=CTkThemeManager.single_color(inner_parts_color, self.appearance_mode))
|
||||
|
||||
def on_leave(self, event=0):
|
||||
self.click_animation_running = False
|
||||
|
||||
if self.hover is True:
|
||||
if self.fg_color is None:
|
||||
inner_parts_color = self.bg_color
|
||||
else:
|
||||
inner_parts_color = self.fg_color
|
||||
|
||||
# set color of inner button parts
|
||||
self.canvas.itemconfig("inner_parts",
|
||||
outline=CTkThemeManager.single_color(inner_parts_color, self.appearance_mode),
|
||||
fill=CTkThemeManager.single_color(inner_parts_color, self.appearance_mode))
|
||||
|
||||
# set text_label bg color (label color)
|
||||
if self.text_label is not None:
|
||||
self.text_label.configure(bg=CTkThemeManager.single_color(inner_parts_color, self.appearance_mode))
|
||||
|
||||
# set image_label bg color (image bg color)
|
||||
if self.image_label is not None:
|
||||
self.image_label.configure(bg=CTkThemeManager.single_color(inner_parts_color, self.appearance_mode))
|
||||
|
||||
def click_animation(self):
|
||||
if self.click_animation_running:
|
||||
self.on_enter()
|
||||
|
||||
def clicked(self, event=0):
|
||||
if self.function is not None:
|
||||
if self.state is not tkinter.DISABLED:
|
||||
|
||||
# click animation: change color with .on_leave() and back to normal after 100ms with click_animation()
|
||||
self.on_leave()
|
||||
self.click_animation_running = True
|
||||
self.after(100, self.click_animation)
|
||||
|
||||
self.function()
|
||||
|
||||
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
|
||||
|
||||
if isinstance(self.master, (CTkFrame, CTk)):
|
||||
self.bg_color = self.master.fg_color
|
||||
else:
|
||||
self.bg_color = self.master.cget("bg")
|
||||
|
||||
self.draw()
|
66
customtkinter/widgets/customtkinter_canvas.py
Normal file
66
customtkinter/widgets/customtkinter_canvas.py
Normal file
@ -0,0 +1,66 @@
|
||||
import tkinter
|
||||
from ..customtkinter_settings import CTkSettings
|
||||
|
||||
|
||||
class CTkCanvas(tkinter.Canvas):
|
||||
|
||||
radius_to_char_fine = CTkSettings.radius_to_char_fine # dict to map radius to font circle character
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.aa_circle_canvas_ids = set()
|
||||
|
||||
def get_char_from_radius(self, radius):
|
||||
if CTkSettings.scaling_factor == 1:
|
||||
if radius >= 20:
|
||||
return "A"
|
||||
else:
|
||||
return self.radius_to_char_fine[radius]
|
||||
|
||||
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=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.add(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)
|
||||
|
||||
|
394
customtkinter/widgets/customtkinter_checkbox.py
Normal file
394
customtkinter/widgets/customtkinter_checkbox.py
Normal file
@ -0,0 +1,394 @@
|
||||
import tkinter
|
||||
import tkinter.ttk as ttk
|
||||
import sys
|
||||
|
||||
from .customtkinter_tk import CTk
|
||||
from .customtkinter_frame import CTkFrame
|
||||
from .customtkinter_canvas import CTkCanvas
|
||||
from ..appearance_mode_tracker import AppearanceModeTracker
|
||||
from ..customtkinter_theme_manager import CTkThemeManager
|
||||
from ..customtkinter_settings import CTkSettings
|
||||
from ..customtkinter_draw_engine import CTkDrawEngine
|
||||
|
||||
|
||||
class CTkCheckBox(tkinter.Frame):
|
||||
""" tkinter custom checkbox with border, rounded corners and hover effect """
|
||||
|
||||
def __init__(self, *args,
|
||||
bg_color=None,
|
||||
fg_color="default_theme",
|
||||
hover_color="default_theme",
|
||||
border_color="default_theme",
|
||||
border_width="default_theme",
|
||||
checkmark_color="default_theme",
|
||||
width=24,
|
||||
height=24,
|
||||
corner_radius="default_theme",
|
||||
text_font="default_theme",
|
||||
text_color="default_theme",
|
||||
text="CTkCheckBox",
|
||||
text_color_disabled="default_theme",
|
||||
hover=True,
|
||||
command=None,
|
||||
state=tkinter.NORMAL,
|
||||
onvalue=1,
|
||||
offvalue=0,
|
||||
variable=None,
|
||||
textvariable=None,
|
||||
**kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# overwrite configure methods of master when master is tkinter widget, so that bg changes get applied on child CTk widget too
|
||||
if isinstance(self.master, (tkinter.Tk, tkinter.Frame)) and not isinstance(self.master, (CTk, CTkFrame)):
|
||||
master_old_configure = self.master.config
|
||||
|
||||
def new_configure(*args, **kwargs):
|
||||
if "bg" in kwargs:
|
||||
self.configure(bg_color=kwargs["bg"])
|
||||
elif "background" in kwargs:
|
||||
self.configure(bg_color=kwargs["background"])
|
||||
|
||||
# args[0] is dict when attribute gets changed by widget[<attribut>] syntax
|
||||
elif len(args) > 0 and type(args[0]) == dict:
|
||||
if "bg" in args[0]:
|
||||
self.configure(bg_color=args[0]["bg"])
|
||||
elif "background" in args[0]:
|
||||
self.configure(bg_color=args[0]["background"])
|
||||
master_old_configure(*args, **kwargs)
|
||||
|
||||
self.master.config = new_configure
|
||||
self.master.configure = new_configure
|
||||
|
||||
# add set_appearance_mode method to callback list of AppearanceModeTracker for appearance mode changes
|
||||
AppearanceModeTracker.add(self.set_appearance_mode, self)
|
||||
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
|
||||
|
||||
self.bg_color = self.detect_color_of_master() if bg_color is None else bg_color
|
||||
self.fg_color = CTkThemeManager.theme["color"]["button"] if fg_color == "default_theme" else fg_color
|
||||
self.hover_color = CTkThemeManager.theme["color"]["button_hover"] if hover_color == "default_theme" else hover_color
|
||||
self.border_color = CTkThemeManager.theme["color"]["checkbox_border"] if border_color == "default_theme" else border_color
|
||||
self.checkmark_color = CTkThemeManager.theme["color"]["checkmark"] if checkmark_color == "default_theme" else checkmark_color
|
||||
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.corner_radius = CTkThemeManager.theme["shape"]["checkbox_corner_radius"] if corner_radius == "default_theme" else corner_radius
|
||||
self.border_width = CTkThemeManager.theme["shape"]["checkbox_border_width"] if border_width == "default_theme" else border_width
|
||||
|
||||
if self.corner_radius*2 > self.height:
|
||||
self.corner_radius = self.height/2
|
||||
elif self.corner_radius*2 > self.width:
|
||||
self.corner_radius = self.width/2
|
||||
|
||||
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 = CTkThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color
|
||||
self.text_color_disabled = CTkThemeManager.theme["color"]["text_disabled"] if text_color_disabled == "default_theme" else text_color_disabled
|
||||
self.text_font = (CTkThemeManager.theme["text"]["font"], CTkThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font
|
||||
|
||||
self.function = command
|
||||
self.state = state
|
||||
self.hover = hover
|
||||
self.check_state = False
|
||||
self.onvalue = onvalue
|
||||
self.offvalue = offvalue
|
||||
self.variable: tkinter.Variable = variable
|
||||
self.variable_callback_blocked = False
|
||||
self.textvariable = textvariable
|
||||
self.variable_callback_name = None
|
||||
|
||||
# configure grid system
|
||||
self.grid_columnconfigure(0, weight=0)
|
||||
self.grid_columnconfigure(1, weight=0, minsize=6)
|
||||
self.grid_columnconfigure(2, weight=1)
|
||||
|
||||
self.canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self.width,
|
||||
height=self.height)
|
||||
self.canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=1)
|
||||
|
||||
self.draw_engine = CTkDrawEngine(self.canvas, CTkSettings.preferred_drawing_method)
|
||||
|
||||
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.toggle)
|
||||
self.canvas.bind("<Button-1>", self.toggle)
|
||||
|
||||
self.text_label = None
|
||||
|
||||
self.set_cursor()
|
||||
self.draw() # initial draw
|
||||
|
||||
if self.variable is not None:
|
||||
self.variable_callback_name = self.variable.trace_add("write", self.variable_callback)
|
||||
if self.variable.get() == self.onvalue:
|
||||
self.select(from_variable_callback=True)
|
||||
elif self.variable.get() == self.offvalue:
|
||||
self.deselect(from_variable_callback=True)
|
||||
|
||||
def destroy(self):
|
||||
AppearanceModeTracker.remove(self.set_appearance_mode)
|
||||
|
||||
if self.variable is not None:
|
||||
self.variable.trace_remove("write", self.variable_callback_name)
|
||||
|
||||
super().destroy()
|
||||
|
||||
def detect_color_of_master(self):
|
||||
""" detect color of self.master widget to set correct bg_color """
|
||||
|
||||
if isinstance(self.master, CTkFrame): # master is CTkFrame
|
||||
return self.master.fg_color
|
||||
|
||||
elif isinstance(self.master, (ttk.Frame, ttk.LabelFrame, ttk.Notebook)): # master is ttk widget
|
||||
print("button on", self.master.winfo_class())
|
||||
try:
|
||||
ttk_style = ttk.Style()
|
||||
return ttk_style.lookup(self.master.winfo_class(), 'background')
|
||||
except Exception:
|
||||
return "#FFFFFF", "#000000"
|
||||
|
||||
else: # master is normal tkinter widget
|
||||
try:
|
||||
return self.master.cget("bg") # try to get bg color by .cget() method
|
||||
except Exception:
|
||||
return "#FFFFFF", "#000000"
|
||||
|
||||
def draw(self):
|
||||
requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.width, self.height, self.corner_radius, self.border_width)
|
||||
|
||||
if self.check_state is True:
|
||||
self.draw_engine.draw_checkmark(self.width, self.height, self.height * 0.58)
|
||||
else:
|
||||
self.canvas.delete("checkmark")
|
||||
|
||||
self.canvas.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
|
||||
self.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
|
||||
|
||||
if self.check_state is True:
|
||||
self.canvas.itemconfig("inner_parts",
|
||||
outline=CTkThemeManager.single_color(self.fg_color, self.appearance_mode),
|
||||
fill=CTkThemeManager.single_color(self.fg_color, self.appearance_mode))
|
||||
self.canvas.itemconfig("border_parts",
|
||||
outline=CTkThemeManager.single_color(self.fg_color, self.appearance_mode),
|
||||
fill=CTkThemeManager.single_color(self.fg_color, self.appearance_mode))
|
||||
|
||||
if "create_line" in self.canvas.gettags("checkmark"):
|
||||
self.canvas.itemconfig("checkmark", fill=CTkThemeManager.single_color(self.checkmark_color, self.appearance_mode))
|
||||
else:
|
||||
self.canvas.itemconfig("checkmark", fill=CTkThemeManager.single_color(self.checkmark_color, self.appearance_mode))
|
||||
else:
|
||||
self.canvas.itemconfig("inner_parts",
|
||||
outline=CTkThemeManager.single_color(self.bg_color, self.appearance_mode),
|
||||
fill=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
|
||||
self.canvas.itemconfig("border_parts",
|
||||
outline=CTkThemeManager.single_color(self.border_color, self.appearance_mode),
|
||||
fill=CTkThemeManager.single_color(self.border_color, self.appearance_mode))
|
||||
|
||||
if self.text_label is None:
|
||||
self.text_label = tkinter.Label(master=self,
|
||||
bd=0,
|
||||
text=self.text,
|
||||
justify=tkinter.LEFT,
|
||||
font=self.text_font)
|
||||
self.text_label.grid(row=0, column=2, padx=0, pady=0, sticky="w")
|
||||
self.text_label["anchor"] = "w"
|
||||
|
||||
if self.state == tkinter.DISABLED:
|
||||
self.text_label.configure(fg=(CTkThemeManager.single_color(self.text_color_disabled, self.appearance_mode)))
|
||||
else:
|
||||
self.text_label.configure(fg=CTkThemeManager.single_color(self.text_color, self.appearance_mode))
|
||||
self.text_label.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
|
||||
|
||||
self.set_text(self.text)
|
||||
|
||||
def config(self, *args, **kwargs):
|
||||
self.configure(*args, **kwargs)
|
||||
|
||||
def configure(self, *args, **kwargs):
|
||||
require_redraw = False # some attribute changes require a call of self.draw()
|
||||
|
||||
if "text" in kwargs:
|
||||
self.set_text(kwargs["text"])
|
||||
del kwargs["text"]
|
||||
|
||||
if "state" in kwargs:
|
||||
self.state = kwargs["state"]
|
||||
self.set_cursor()
|
||||
require_redraw = True
|
||||
del kwargs["state"]
|
||||
|
||||
if "fg_color" in kwargs:
|
||||
self.fg_color = kwargs["fg_color"]
|
||||
require_redraw = True
|
||||
del kwargs["fg_color"]
|
||||
|
||||
if "bg_color" in kwargs:
|
||||
if kwargs["bg_color"] is None:
|
||||
self.bg_color = self.detect_color_of_master()
|
||||
else:
|
||||
self.bg_color = kwargs["bg_color"]
|
||||
require_redraw = True
|
||||
del kwargs["bg_color"]
|
||||
|
||||
if "hover_color" in kwargs:
|
||||
self.hover_color = kwargs["hover_color"]
|
||||
require_redraw = True
|
||||
del kwargs["hover_color"]
|
||||
|
||||
if "text_color" in kwargs:
|
||||
self.text_color = kwargs["text_color"]
|
||||
require_redraw = True
|
||||
del kwargs["text_color"]
|
||||
|
||||
if "border_color" in kwargs:
|
||||
self.border_color = kwargs["border_color"]
|
||||
require_redraw = True
|
||||
del kwargs["border_color"]
|
||||
|
||||
if "command" in kwargs:
|
||||
self.function = kwargs["command"]
|
||||
del kwargs["command"]
|
||||
|
||||
if "variable" in kwargs:
|
||||
if self.variable is not None:
|
||||
self.variable.trace_remove("write", self.variable_callback_name)
|
||||
|
||||
self.variable = kwargs["variable"]
|
||||
|
||||
if self.variable is not None and self.variable != "":
|
||||
self.variable_callback_name = self.variable.trace_add("write", self.variable_callback)
|
||||
if self.variable.get() == self.onvalue:
|
||||
self.select(from_variable_callback=True)
|
||||
elif self.variable.get() == self.offvalue:
|
||||
self.deselect(from_variable_callback=True)
|
||||
else:
|
||||
self.variable = None
|
||||
|
||||
del kwargs["variable"]
|
||||
|
||||
super().configure(*args, **kwargs)
|
||||
|
||||
if require_redraw:
|
||||
self.draw()
|
||||
|
||||
def set_cursor(self):
|
||||
if self.state == tkinter.DISABLED:
|
||||
if sys.platform == "darwin" and CTkSettings.hand_cursor_enabled:
|
||||
self.canvas.configure(cursor="arrow")
|
||||
elif sys.platform.startswith("win") and CTkSettings.hand_cursor_enabled:
|
||||
self.canvas.configure(cursor="arrow")
|
||||
|
||||
elif self.state == tkinter.NORMAL:
|
||||
if sys.platform == "darwin" and CTkSettings.hand_cursor_enabled:
|
||||
self.canvas.configure(cursor="pointinghand")
|
||||
elif sys.platform.startswith("win") and CTkSettings.hand_cursor_enabled:
|
||||
self.canvas.configure(cursor="hand2")
|
||||
|
||||
def set_text(self, text):
|
||||
self.text = text
|
||||
if self.text_label is not None:
|
||||
self.text_label.configure(text=self.text)
|
||||
else:
|
||||
sys.stderr.write("ERROR (CTkButton): Cant change text because checkbox has no text.")
|
||||
|
||||
def on_enter(self, event=0):
|
||||
if self.hover is True and self.state == tkinter.NORMAL:
|
||||
if self.check_state is True:
|
||||
self.canvas.itemconfig("inner_parts",
|
||||
fill=CTkThemeManager.single_color(self.hover_color, self.appearance_mode),
|
||||
outline=CTkThemeManager.single_color(self.hover_color, self.appearance_mode))
|
||||
self.canvas.itemconfig("border_parts",
|
||||
fill=CTkThemeManager.single_color(self.hover_color, self.appearance_mode),
|
||||
outline=CTkThemeManager.single_color(self.hover_color, self.appearance_mode))
|
||||
else:
|
||||
self.canvas.itemconfig("inner_parts",
|
||||
fill=CTkThemeManager.single_color(self.hover_color, self.appearance_mode),
|
||||
outline=CTkThemeManager.single_color(self.hover_color, self.appearance_mode))
|
||||
|
||||
def on_leave(self, event=0):
|
||||
if self.hover is True:
|
||||
if self.check_state is True:
|
||||
self.canvas.itemconfig("inner_parts",
|
||||
fill=CTkThemeManager.single_color(self.fg_color, self.appearance_mode),
|
||||
outline=CTkThemeManager.single_color(self.fg_color, self.appearance_mode))
|
||||
self.canvas.itemconfig("border_parts",
|
||||
fill=CTkThemeManager.single_color(self.fg_color, self.appearance_mode),
|
||||
outline=CTkThemeManager.single_color(self.fg_color, self.appearance_mode))
|
||||
else:
|
||||
self.canvas.itemconfig("inner_parts",
|
||||
fill=CTkThemeManager.single_color(self.bg_color, self.appearance_mode),
|
||||
outline=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
|
||||
self.canvas.itemconfig("border_parts",
|
||||
fill=CTkThemeManager.single_color(self.border_color, self.appearance_mode),
|
||||
outline=CTkThemeManager.single_color(self.border_color, self.appearance_mode))
|
||||
|
||||
def variable_callback(self, var_name, index, mode):
|
||||
if not self.variable_callback_blocked:
|
||||
if self.variable.get() == self.onvalue:
|
||||
self.select(from_variable_callback=True)
|
||||
elif self.variable.get() == self.offvalue:
|
||||
self.deselect(from_variable_callback=True)
|
||||
|
||||
def toggle(self, event=0):
|
||||
if self.state == tkinter.NORMAL:
|
||||
if self.check_state is True:
|
||||
self.check_state = False
|
||||
self.draw()
|
||||
else:
|
||||
self.check_state = True
|
||||
self.draw()
|
||||
|
||||
if self.function is not None:
|
||||
self.function()
|
||||
|
||||
if self.variable is not None:
|
||||
self.variable_callback_blocked = True
|
||||
self.variable.set(self.onvalue if self.check_state is True else self.offvalue)
|
||||
self.variable_callback_blocked = False
|
||||
|
||||
def select(self, from_variable_callback=False):
|
||||
self.check_state = True
|
||||
self.draw()
|
||||
|
||||
if self.function is not None:
|
||||
self.function()
|
||||
|
||||
if self.variable is not None and not from_variable_callback:
|
||||
self.variable_callback_blocked = True
|
||||
self.variable.set(self.onvalue)
|
||||
self.variable_callback_blocked = False
|
||||
|
||||
def deselect(self, from_variable_callback=False):
|
||||
self.check_state = False
|
||||
self.draw()
|
||||
|
||||
if self.function is not None:
|
||||
self.function()
|
||||
|
||||
if self.variable is not None and not from_variable_callback:
|
||||
self.variable_callback_blocked = True
|
||||
self.variable.set(self.offvalue)
|
||||
self.variable_callback_blocked = False
|
||||
|
||||
def get(self):
|
||||
return self.onvalue if self.check_state is True else self.offvalue
|
||||
|
||||
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
|
||||
|
||||
if isinstance(self.master, (CTkFrame, CTk)):
|
||||
self.bg_color = self.master.fg_color
|
||||
else:
|
||||
self.bg_color = self.master.cget("bg")
|
||||
|
||||
self.draw()
|
259
customtkinter/widgets/customtkinter_entry.py
Normal file
259
customtkinter/widgets/customtkinter_entry.py
Normal file
@ -0,0 +1,259 @@
|
||||
import tkinter
|
||||
import tkinter.ttk as ttk
|
||||
|
||||
from .customtkinter_tk import CTk
|
||||
from .customtkinter_frame import CTkFrame
|
||||
from .customtkinter_canvas import CTkCanvas
|
||||
from ..appearance_mode_tracker import AppearanceModeTracker
|
||||
from ..customtkinter_theme_manager import CTkThemeManager
|
||||
from ..customtkinter_settings import CTkSettings
|
||||
from ..customtkinter_draw_engine import CTkDrawEngine
|
||||
|
||||
|
||||
class CTkEntry(tkinter.Frame):
|
||||
def __init__(self, *args,
|
||||
master=None,
|
||||
bg_color=None,
|
||||
fg_color="default_theme",
|
||||
text_color="default_theme",
|
||||
placeholder_text_color="default_theme",
|
||||
text_font="default_theme",
|
||||
placeholder_text=None,
|
||||
corner_radius="default_theme",
|
||||
border_width="default_theme",
|
||||
border_color="default_theme",
|
||||
width=120,
|
||||
height=30,
|
||||
**kwargs):
|
||||
if master is None:
|
||||
super().__init__(*args)
|
||||
else:
|
||||
super().__init__(*args, master=master)
|
||||
|
||||
# overwrite configure methods of master when master is tkinter widget, so that bg changes get applied on child CTk widget too
|
||||
if isinstance(self.master, (tkinter.Tk, tkinter.Frame)) and not isinstance(self.master, (CTk, CTkFrame)):
|
||||
master_old_configure = self.master.config
|
||||
|
||||
def new_configure(*args, **kwargs):
|
||||
if "bg" in kwargs:
|
||||
self.configure(bg_color=kwargs["bg"])
|
||||
elif "background" in kwargs:
|
||||
self.configure(bg_color=kwargs["background"])
|
||||
|
||||
# args[0] is dict when attribute gets changed by widget[<attribut>] syntax
|
||||
elif len(args) > 0 and type(args[0]) == dict:
|
||||
if "bg" in args[0]:
|
||||
self.configure(bg_color=args[0]["bg"])
|
||||
elif "background" in args[0]:
|
||||
self.configure(bg_color=args[0]["background"])
|
||||
master_old_configure(*args, **kwargs)
|
||||
|
||||
self.master.config = new_configure
|
||||
self.master.configure = new_configure
|
||||
|
||||
# add set_appearance_mode method to callback list of AppearanceModeTracker for appearance mode changes
|
||||
AppearanceModeTracker.add(self.change_appearance_mode, self)
|
||||
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
|
||||
|
||||
self.configure_basic_grid()
|
||||
|
||||
self.bg_color = self.detect_color_of_master() if bg_color is None else bg_color
|
||||
self.fg_color = CTkThemeManager.theme["color"]["entry"] if fg_color == "default_theme" else fg_color
|
||||
self.text_color = CTkThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color
|
||||
self.placeholder_text_color = CTkThemeManager.theme["color"]["entry_placeholder_text"] if placeholder_text_color == "default_theme" else placeholder_text_color
|
||||
self.text_font = (CTkThemeManager.theme["text"]["font"], CTkThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font
|
||||
self.border_color = CTkThemeManager.theme["color"]["entry_border"] if border_color == "default_theme" else border_color
|
||||
|
||||
self.placeholder_text = placeholder_text
|
||||
self.placeholder_text_active = False
|
||||
self.pre_placeholder_arguments = {} # some set arguments of the entry will be changed for placeholder and then set back
|
||||
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.corner_radius = CTkThemeManager.theme["shape"]["button_corner_radius"] if corner_radius == "default_theme" else corner_radius
|
||||
self.border_width = CTkThemeManager.theme["shape"]["entry_border_width"] if border_width == "default_theme" else border_width
|
||||
|
||||
if self.corner_radius*2 > self.height:
|
||||
self.corner_radius = self.height/2
|
||||
elif self.corner_radius*2 > self.width:
|
||||
self.corner_radius = self.width/2
|
||||
|
||||
super().configure(width=self.width, height=self.height)
|
||||
|
||||
self.canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self.width,
|
||||
height=self.height)
|
||||
self.canvas.grid(column=0, row=0, sticky="we")
|
||||
|
||||
self.entry = tkinter.Entry(master=self,
|
||||
bd=0,
|
||||
width=1,
|
||||
highlightthickness=0,
|
||||
font=self.text_font,
|
||||
**kwargs)
|
||||
self.entry.grid(column=0, row=0, sticky="we", padx=self.corner_radius if self.corner_radius >= 6 else 6)
|
||||
|
||||
self.draw_engine = CTkDrawEngine(self.canvas, CTkSettings.preferred_drawing_method)
|
||||
|
||||
super().bind('<Configure>', self.update_dimensions)
|
||||
self.entry.bind('<FocusOut>', self.set_placeholder)
|
||||
self.entry.bind('<FocusIn>', self.clear_placeholder)
|
||||
|
||||
self.draw()
|
||||
self.set_placeholder()
|
||||
|
||||
def destroy(self):
|
||||
AppearanceModeTracker.remove(self.change_appearance_mode)
|
||||
super().destroy()
|
||||
|
||||
def configure_basic_grid(self):
|
||||
self.grid_rowconfigure(0, weight=1)
|
||||
self.grid_columnconfigure(0, weight=1)
|
||||
|
||||
def detect_color_of_master(self):
|
||||
""" detect color of self.master widget to set correct bg_color """
|
||||
|
||||
if isinstance(self.master, CTkFrame): # master is CTkFrame
|
||||
return self.master.fg_color
|
||||
|
||||
elif isinstance(self.master, (ttk.Frame, ttk.LabelFrame, ttk.Notebook)): # master is ttk widget
|
||||
print("button on", self.master.winfo_class())
|
||||
try:
|
||||
ttk_style = ttk.Style()
|
||||
return ttk_style.lookup(self.master.winfo_class(), 'background')
|
||||
except Exception:
|
||||
return "#FFFFFF", "#000000"
|
||||
|
||||
else: # master is normal tkinter widget
|
||||
try:
|
||||
return self.master.cget("bg") # try to get bg color by .cget() method
|
||||
except Exception:
|
||||
return "#FFFFFF", "#000000"
|
||||
|
||||
def update_dimensions(self, event):
|
||||
# only redraw if dimensions changed (for performance)
|
||||
if self.width != event.width or self.height != event.height:
|
||||
# print(event.x, event.width, self.width)
|
||||
self.width = event.width
|
||||
self.height = event.height
|
||||
|
||||
self.draw()
|
||||
|
||||
def set_placeholder(self, event=None):
|
||||
if self.placeholder_text is not None:
|
||||
if not self.placeholder_text_active and self.entry.get() == "":
|
||||
self.placeholder_text_active = True
|
||||
self.pre_placeholder_arguments = {"show": self.entry.cget("show")}
|
||||
self.entry.config(fg=CTkThemeManager.single_color(self.placeholder_text_color, self.appearance_mode), show="")
|
||||
self.entry.delete(0, tkinter.END)
|
||||
self.entry.insert(0, self.placeholder_text)
|
||||
|
||||
def clear_placeholder(self, event=None):
|
||||
if self.placeholder_text_active:
|
||||
self.placeholder_text_active = False
|
||||
self.entry.config(fg=CTkThemeManager.single_color(self.text_color, self.appearance_mode))
|
||||
self.entry.delete(0, tkinter.END)
|
||||
for argument, value in self.pre_placeholder_arguments.items():
|
||||
self.entry[argument] = value
|
||||
|
||||
def draw(self):
|
||||
self.canvas.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
|
||||
|
||||
requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.width, self.height, self.corner_radius, self.border_width)
|
||||
|
||||
if CTkThemeManager.single_color(self.fg_color, self.appearance_mode) is not None:
|
||||
self.canvas.itemconfig("inner_parts",
|
||||
fill=CTkThemeManager.single_color(self.fg_color, self.appearance_mode),
|
||||
outline=CTkThemeManager.single_color(self.fg_color, self.appearance_mode))
|
||||
self.entry.configure(bg=CTkThemeManager.single_color(self.fg_color, self.appearance_mode),
|
||||
highlightcolor=CTkThemeManager.single_color(self.fg_color, self.appearance_mode),
|
||||
fg=CTkThemeManager.single_color(self.text_color, self.appearance_mode),
|
||||
insertbackground=CTkThemeManager.single_color(self.text_color, self.appearance_mode))
|
||||
else:
|
||||
self.canvas.itemconfig("inner_parts",
|
||||
fill=CTkThemeManager.single_color(self.bg_color, self.appearance_mode),
|
||||
outline=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
|
||||
self.entry.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode),
|
||||
highlightcolor=CTkThemeManager.single_color(self.bg_color, self.appearance_mode),
|
||||
fg=CTkThemeManager.single_color(self.text_color, self.appearance_mode),
|
||||
insertbackground=CTkThemeManager.single_color(self.text_color, self.appearance_mode))
|
||||
|
||||
self.canvas.itemconfig("border_parts",
|
||||
fill=CTkThemeManager.single_color(self.border_color, self.appearance_mode),
|
||||
outline=CTkThemeManager.single_color(self.border_color, self.appearance_mode))
|
||||
|
||||
if self.placeholder_text_active:
|
||||
self.entry.config(fg=CTkThemeManager.single_color(self.placeholder_text_color, self.appearance_mode))
|
||||
|
||||
def bind(self, *args, **kwargs):
|
||||
self.entry.bind(*args, **kwargs)
|
||||
|
||||
def config(self, *args, **kwargs):
|
||||
self.configure(*args, **kwargs)
|
||||
|
||||
def configure(self, *args, **kwargs):
|
||||
require_redraw = False # some attribute changes require a call of self.draw() at the end
|
||||
|
||||
if "bg_color" in kwargs:
|
||||
self.bg_color = kwargs["bg_color"]
|
||||
del kwargs["bg_color"]
|
||||
require_redraw = True
|
||||
|
||||
if "fg_color" in kwargs:
|
||||
self.fg_color = kwargs["fg_color"]
|
||||
del kwargs["fg_color"]
|
||||
require_redraw = True
|
||||
|
||||
if "text_color" in kwargs:
|
||||
self.text_color = kwargs["text_color"]
|
||||
del kwargs["text_color"]
|
||||
require_redraw = True
|
||||
|
||||
if "corner_radius" in kwargs:
|
||||
self.corner_radius = kwargs["corner_radius"]
|
||||
|
||||
if self.corner_radius * 2 > self.height:
|
||||
self.corner_radius = self.height / 2
|
||||
elif self.corner_radius * 2 > self.width:
|
||||
self.corner_radius = self.width / 2
|
||||
|
||||
self.entry.grid(column=0, row=0, sticky="we", padx=self.corner_radius if self.corner_radius >= 6 else 6)
|
||||
del kwargs["corner_radius"]
|
||||
require_redraw = True
|
||||
|
||||
if "placeholder_text" in kwargs:
|
||||
pass
|
||||
|
||||
self.entry.configure(*args, **kwargs)
|
||||
|
||||
if require_redraw is True:
|
||||
self.draw()
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
self.entry.delete(*args, **kwargs)
|
||||
self.set_placeholder()
|
||||
return
|
||||
|
||||
def insert(self, *args, **kwargs):
|
||||
self.clear_placeholder()
|
||||
return self.entry.insert(*args, **kwargs)
|
||||
|
||||
def get(self):
|
||||
if self.placeholder_text_active:
|
||||
return ""
|
||||
else:
|
||||
return self.entry.get()
|
||||
|
||||
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, CTk)):
|
||||
self.bg_color = self.master.fg_color
|
||||
else:
|
||||
self.bg_color = self.master.cget("bg")
|
||||
|
||||
self.draw()
|
193
customtkinter/widgets/customtkinter_frame.py
Normal file
193
customtkinter/widgets/customtkinter_frame.py
Normal file
@ -0,0 +1,193 @@
|
||||
import tkinter
|
||||
import tkinter.ttk as ttk
|
||||
|
||||
from .customtkinter_tk import CTk
|
||||
from .customtkinter_canvas import CTkCanvas
|
||||
from ..appearance_mode_tracker import AppearanceModeTracker
|
||||
from ..customtkinter_theme_manager import CTkThemeManager
|
||||
from ..customtkinter_settings import CTkSettings
|
||||
from ..customtkinter_draw_engine import CTkDrawEngine
|
||||
|
||||
|
||||
class CTkFrame(tkinter.Frame):
|
||||
def __init__(self, *args,
|
||||
bg_color=None,
|
||||
fg_color="default_theme",
|
||||
border_color="default_theme",
|
||||
border_width="default_theme",
|
||||
corner_radius="default_theme",
|
||||
width=200,
|
||||
height=200,
|
||||
**kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# overwrite configure methods of master when master is tkinter widget, so that bg changes get applied on child CTk widget too
|
||||
if isinstance(self.master, (tkinter.Tk, tkinter.Frame)) and not isinstance(self.master, (CTk, CTkFrame)):
|
||||
master_old_configure = self.master.config
|
||||
|
||||
def new_configure(*args, **kwargs):
|
||||
if "bg" in kwargs:
|
||||
self.configure(bg_color=kwargs["bg"])
|
||||
elif "background" in kwargs:
|
||||
self.configure(bg_color=kwargs["background"])
|
||||
|
||||
# args[0] is dict when attribute gets changed by widget[<attribut>] syntax
|
||||
elif len(args) > 0 and type(args[0]) == dict:
|
||||
if "bg" in args[0]:
|
||||
self.configure(bg_color=args[0]["bg"])
|
||||
elif "background" in args[0]:
|
||||
self.configure(bg_color=args[0]["background"])
|
||||
master_old_configure(*args, **kwargs)
|
||||
|
||||
self.master.config = new_configure
|
||||
self.master.configure = new_configure
|
||||
|
||||
# add set_appearance_mode method to callback list of AppearanceModeTracker for appearance mode changes
|
||||
AppearanceModeTracker.add(self.change_appearance_mode, self)
|
||||
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
|
||||
|
||||
self.bg_color = self.detect_color_of_master() if bg_color is None else bg_color
|
||||
self.border_color = CTkThemeManager.theme["color"]["frame_border"] if border_color == "default_theme" else border_color
|
||||
|
||||
if fg_color == "default_theme":
|
||||
if isinstance(self.master, CTkFrame):
|
||||
if self.master.fg_color == CTkThemeManager.theme["color"]["frame_low"]:
|
||||
self.fg_color = CTkThemeManager.theme["color"]["frame_high"]
|
||||
else:
|
||||
self.fg_color = CTkThemeManager.theme["color"]["frame_low"]
|
||||
else:
|
||||
self.fg_color = CTkThemeManager.theme["color"]["frame_low"]
|
||||
else:
|
||||
self.fg_color = fg_color
|
||||
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.configure(width=self.width, height=self.height)
|
||||
|
||||
self.corner_radius = CTkThemeManager.theme["shape"]["frame_corner_radius"] if corner_radius == "default_theme" else corner_radius
|
||||
self.border_width = CTkThemeManager.theme["shape"]["frame_border_width"] if border_width == "default_theme" else border_width
|
||||
|
||||
if self.corner_radius * 2 > self.height:
|
||||
self.corner_radius = self.height / 2
|
||||
elif self.corner_radius * 2 > self.width:
|
||||
self.corner_radius = self.width / 2
|
||||
|
||||
if self.corner_radius >= self.border_width:
|
||||
self.inner_corner_radius = self.corner_radius - self.border_width
|
||||
else:
|
||||
self.inner_corner_radius = 0
|
||||
|
||||
self.canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self.width,
|
||||
height=self.height)
|
||||
self.canvas.place(x=0, y=0, relwidth=1, relheight=1)
|
||||
self.canvas.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
|
||||
|
||||
self.draw_engine = CTkDrawEngine(self.canvas, CTkSettings.preferred_drawing_method)
|
||||
|
||||
self.bind('<Configure>', self.update_dimensions)
|
||||
|
||||
self.draw()
|
||||
|
||||
def destroy(self):
|
||||
AppearanceModeTracker.remove(self.change_appearance_mode)
|
||||
super().destroy()
|
||||
|
||||
def detect_color_of_master(self):
|
||||
""" detect color of self.master widget to set correct bg_color """
|
||||
|
||||
if isinstance(self.master, CTkFrame): # master is CTkFrame
|
||||
return self.master.fg_color
|
||||
|
||||
elif isinstance(self.master, (ttk.Frame, ttk.LabelFrame, ttk.Notebook)): # master is ttk widget
|
||||
print("button on", self.master.winfo_class())
|
||||
try:
|
||||
ttk_style = ttk.Style()
|
||||
return ttk_style.lookup(self.master.winfo_class(), 'background')
|
||||
except Exception:
|
||||
return "#FFFFFF", "#000000"
|
||||
|
||||
else: # master is normal tkinter widget
|
||||
try:
|
||||
return self.master.cget("bg") # try to get bg color by .cget() method
|
||||
except Exception:
|
||||
return "#FFFFFF", "#000000"
|
||||
|
||||
def update_dimensions(self, event):
|
||||
# only redraw if dimensions changed (for performance)
|
||||
if self.width != event.width or self.height != event.height:
|
||||
self.width = event.width
|
||||
self.height = event.height
|
||||
|
||||
self.draw()
|
||||
|
||||
def draw(self):
|
||||
|
||||
requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.width, self.height, self.corner_radius, self.border_width)
|
||||
|
||||
self.canvas.itemconfig("inner_parts",
|
||||
fill=CTkThemeManager.single_color(self.fg_color, self.appearance_mode),
|
||||
outline=CTkThemeManager.single_color(self.fg_color, self.appearance_mode))
|
||||
self.canvas.itemconfig("border_parts",
|
||||
fill=CTkThemeManager.single_color(self.border_color, self.appearance_mode),
|
||||
outline=CTkThemeManager.single_color(self.border_color, self.appearance_mode))
|
||||
self.canvas.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
|
||||
|
||||
self.canvas.tag_lower("inner_parts")
|
||||
self.canvas.tag_lower("border_parts")
|
||||
|
||||
def config(self, *args, **kwargs):
|
||||
self.configure(*args, **kwargs)
|
||||
|
||||
def configure(self, *args, **kwargs):
|
||||
require_redraw = False # some attribute changes require a call of self.draw() at the end
|
||||
|
||||
if "fg_color" in kwargs:
|
||||
self.fg_color = kwargs["fg_color"]
|
||||
require_redraw = True
|
||||
del kwargs["fg_color"]
|
||||
|
||||
# check if CTk widgets are children of the frame and change their bg_color to new frame fg_color
|
||||
from .customtkinter_slider import CTkSlider
|
||||
from .customtkinter_progressbar import CTkProgressBar
|
||||
from .customtkinter_label import CTkLabel
|
||||
from .customtkinter_entry import CTkEntry
|
||||
from customtkinter.widgets.customtkinter_checkbox import CTkCheckBox
|
||||
from customtkinter.widgets.customtkinter_button import CTkButton
|
||||
|
||||
for child in self.winfo_children():
|
||||
if isinstance(child, (CTkButton, CTkLabel, CTkSlider, CTkCheckBox, CTkEntry, CTkProgressBar, CTkFrame)):
|
||||
child.configure(bg_color=self.fg_color)
|
||||
|
||||
if "bg_color" in kwargs:
|
||||
if kwargs["bg_color"] is None:
|
||||
self.bg_color = self.detect_color_of_master()
|
||||
else:
|
||||
self.bg_color = kwargs["bg_color"]
|
||||
require_redraw = True
|
||||
|
||||
del kwargs["bg_color"]
|
||||
|
||||
if "corner_radius" in kwargs:
|
||||
self.corner_radius = kwargs["corner_radius"]
|
||||
require_redraw = True
|
||||
del kwargs["corner_radius"]
|
||||
|
||||
super().configure(*args, **kwargs)
|
||||
|
||||
if require_redraw:
|
||||
self.draw()
|
||||
|
||||
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()
|
105
customtkinter/widgets/customtkinter_input_dialog.py
Normal file
105
customtkinter/widgets/customtkinter_input_dialog.py
Normal file
@ -0,0 +1,105 @@
|
||||
import tkinter
|
||||
import time
|
||||
|
||||
from .customtkinter_label import CTkLabel
|
||||
from .customtkinter_entry import CTkEntry
|
||||
from .customtkinter_frame import CTkFrame
|
||||
from .customtkinter_toplevel import CTkToplevel
|
||||
from .customtkinter_button import CTkButton
|
||||
from ..appearance_mode_tracker import AppearanceModeTracker
|
||||
from ..customtkinter_theme_manager import CTkThemeManager
|
||||
|
||||
|
||||
class CTkInputDialog:
|
||||
def __init__(self,
|
||||
master=None,
|
||||
title="CTkDialog",
|
||||
text="CTkDialog",
|
||||
fg_color="default_theme",
|
||||
hover_color="default_theme",
|
||||
border_color="default_theme"):
|
||||
|
||||
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
|
||||
self.master = master
|
||||
|
||||
self.user_input = None
|
||||
self.running = False
|
||||
|
||||
self.height = len(text.split("\n"))*20 + 150
|
||||
|
||||
self.window_bg_color = CTkThemeManager.theme["color"]["window_bg_color"]
|
||||
self.fg_color = CTkThemeManager.theme["color"]["button"] if fg_color == "default_theme" else fg_color
|
||||
self.hover_color = CTkThemeManager.theme["color"]["button_hover"] if hover_color == "default_theme" else hover_color
|
||||
self.border_color = CTkThemeManager.theme["color"]["button_hover"] if border_color == "default_theme" else border_color
|
||||
|
||||
self.top = CTkToplevel()
|
||||
self.top.geometry(f"280x{self.height}")
|
||||
self.top.resizable(False, False)
|
||||
self.top.title(title)
|
||||
self.top.lift()
|
||||
self.top.focus_force()
|
||||
self.top.grab_set()
|
||||
|
||||
self.label_frame = CTkFrame(master=self.top,
|
||||
corner_radius=0,
|
||||
fg_color=self.window_bg_color,
|
||||
width=300,
|
||||
height=self.height-100)
|
||||
self.label_frame.place(relx=0.5, rely=0, anchor=tkinter.N)
|
||||
|
||||
self.button_and_entry_frame = CTkFrame(master=self.top,
|
||||
corner_radius=0,
|
||||
fg_color=self.window_bg_color,
|
||||
width=300,
|
||||
height=100)
|
||||
self.button_and_entry_frame.place(relx=0.5, rely=1, anchor=tkinter.S)
|
||||
|
||||
self.myLabel = CTkLabel(master=self.label_frame,
|
||||
text=text,
|
||||
width=300,
|
||||
fg_color=None,
|
||||
height=self.height-100)
|
||||
self.myLabel.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER)
|
||||
|
||||
self.entry = CTkEntry(master=self.button_and_entry_frame,
|
||||
width=230)
|
||||
self.entry.place(relx=0.5, rely=0.15, anchor=tkinter.CENTER)
|
||||
|
||||
self.ok_button = CTkButton(master=self.button_and_entry_frame,
|
||||
text='Ok',
|
||||
width=100,
|
||||
command=self.ok_event,
|
||||
fg_color=self.fg_color,
|
||||
hover_color=self.hover_color,
|
||||
border_color=self.border_color)
|
||||
self.ok_button.place(relx=0.28, rely=0.65, anchor=tkinter.CENTER)
|
||||
|
||||
self.cancel_button = CTkButton(master=self.button_and_entry_frame,
|
||||
text='Cancel',
|
||||
width=100,
|
||||
command=self.cancel_event,
|
||||
fg_color=self.fg_color,
|
||||
hover_color=self.hover_color,
|
||||
border_color=self.border_color)
|
||||
self.cancel_button.place(relx=0.72, rely=0.65, anchor=tkinter.CENTER)
|
||||
|
||||
self.entry.entry.focus_force()
|
||||
self.entry.bind("<Return>", self.ok_event)
|
||||
|
||||
def ok_event(self, event=None):
|
||||
self.user_input = self.entry.get()
|
||||
self.running = False
|
||||
|
||||
def cancel_event(self):
|
||||
self.running = False
|
||||
|
||||
def get_input(self):
|
||||
self.running = True
|
||||
|
||||
while self.running:
|
||||
self.top.update()
|
||||
time.sleep(0.01)
|
||||
|
||||
time.sleep(0.05)
|
||||
self.top.destroy()
|
||||
return self.user_input
|
198
customtkinter/widgets/customtkinter_label.py
Normal file
198
customtkinter/widgets/customtkinter_label.py
Normal file
@ -0,0 +1,198 @@
|
||||
import tkinter
|
||||
import tkinter.ttk as ttk
|
||||
|
||||
from .customtkinter_tk import CTk
|
||||
from .customtkinter_frame import CTkFrame
|
||||
from .customtkinter_canvas import CTkCanvas
|
||||
from ..appearance_mode_tracker import AppearanceModeTracker
|
||||
from ..customtkinter_theme_manager import CTkThemeManager
|
||||
from ..customtkinter_settings import CTkSettings
|
||||
from ..customtkinter_draw_engine import CTkDrawEngine
|
||||
|
||||
|
||||
class CTkLabel(tkinter.Frame):
|
||||
def __init__(self, *args,
|
||||
master=None,
|
||||
bg_color=None,
|
||||
fg_color="default_theme",
|
||||
text_color="default_theme",
|
||||
corner_radius="default_theme",
|
||||
width=120,
|
||||
height=25,
|
||||
text="CTkLabel",
|
||||
text_font="default_theme",
|
||||
**kwargs):
|
||||
if master is None:
|
||||
super().__init__(*args)
|
||||
else:
|
||||
super().__init__(*args, master=master)
|
||||
|
||||
# overwrite configure methods of master when master is tkinter widget, so that bg changes get applied on child CTk widget too
|
||||
if isinstance(self.master, (tkinter.Tk, tkinter.Frame)) and not isinstance(self.master, (CTk, CTkFrame)):
|
||||
master_old_configure = self.master.config
|
||||
|
||||
def new_configure(*args, **kwargs):
|
||||
if "bg" in kwargs:
|
||||
self.configure(bg_color=kwargs["bg"])
|
||||
elif "background" in kwargs:
|
||||
self.configure(bg_color=kwargs["background"])
|
||||
|
||||
# args[0] is dict when attribute gets changed by widget[<attribut>] syntax
|
||||
elif len(args) > 0 and type(args[0]) == dict:
|
||||
if "bg" in args[0]:
|
||||
self.configure(bg_color=args[0]["bg"])
|
||||
elif "background" in args[0]:
|
||||
self.configure(bg_color=args[0]["background"])
|
||||
master_old_configure(*args, **kwargs)
|
||||
|
||||
self.master.config = new_configure
|
||||
self.master.configure = new_configure
|
||||
|
||||
# add set_appearance_mode method to callback list of AppearanceModeTracker for appearance mode changes
|
||||
AppearanceModeTracker.add(self.change_appearance_mode, self)
|
||||
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
|
||||
|
||||
self.bg_color = self.detect_color_of_master() if bg_color is None else bg_color
|
||||
self.fg_color = CTkThemeManager.theme["color"]["label"] if fg_color == "default_theme" else fg_color
|
||||
if self.fg_color is None:
|
||||
self.fg_color = self.bg_color
|
||||
self.text_color = CTkThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color
|
||||
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.corner_radius = CTkThemeManager.theme["shape"]["label_corner_radius"] if corner_radius == "default_theme" else corner_radius
|
||||
|
||||
if self.corner_radius * 2 > self.height:
|
||||
self.corner_radius = self.height / 2
|
||||
elif self.corner_radius * 2 > self.width:
|
||||
self.corner_radius = self.width / 2
|
||||
|
||||
self.text = text
|
||||
self.text_font = (CTkThemeManager.theme["text"]["font"], CTkThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font
|
||||
|
||||
self.grid_rowconfigure(0, weight=1)
|
||||
self.grid_columnconfigure(0, weight=1)
|
||||
|
||||
self.canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self.width,
|
||||
height=self.height)
|
||||
self.canvas.grid(row=0, column=0, sticky="nswe")
|
||||
|
||||
self.text_label = tkinter.Label(master=self,
|
||||
highlightthickness=0,
|
||||
bd=0,
|
||||
text=self.text,
|
||||
font=self.text_font,
|
||||
**kwargs)
|
||||
self.text_label.grid(row=0, column=0, padx=self.corner_radius)
|
||||
|
||||
self.draw_engine = CTkDrawEngine(self.canvas, CTkSettings.preferred_drawing_method)
|
||||
|
||||
super().configure(width=self.width, height=self.height)
|
||||
|
||||
self.bind('<Configure>', self.update_dimensions)
|
||||
self.draw()
|
||||
|
||||
def destroy(self):
|
||||
AppearanceModeTracker.remove(self.change_appearance_mode)
|
||||
super().destroy()
|
||||
|
||||
def detect_color_of_master(self):
|
||||
""" detect color of self.master widget to set correct bg_color """
|
||||
|
||||
if isinstance(self.master, CTkFrame): # master is CTkFrame
|
||||
return self.master.fg_color
|
||||
|
||||
elif isinstance(self.master, (ttk.Frame, ttk.LabelFrame, ttk.Notebook)): # master is ttk widget
|
||||
print("button on", self.master.winfo_class())
|
||||
try:
|
||||
ttk_style = ttk.Style()
|
||||
return ttk_style.lookup(self.master.winfo_class(), 'background')
|
||||
except Exception:
|
||||
return "#FFFFFF", "#000000"
|
||||
|
||||
else: # master is normal tkinter widget
|
||||
try:
|
||||
return self.master.cget("bg") # try to get bg color by .cget() method
|
||||
except Exception:
|
||||
return "#FFFFFF", "#000000"
|
||||
|
||||
def update_dimensions(self, event):
|
||||
# only redraw if dimensions changed (for performance)
|
||||
if self.width != event.width or self.height != event.height:
|
||||
self.width = event.width
|
||||
self.height = event.height
|
||||
|
||||
# self.canvas.config(width=self.width, height=self.height)
|
||||
self.draw()
|
||||
|
||||
def draw(self):
|
||||
requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.width, self.height, self.corner_radius, 0)
|
||||
|
||||
self.canvas.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
|
||||
|
||||
if CTkThemeManager.single_color(self.fg_color, self.appearance_mode) is not None:
|
||||
self.canvas.itemconfig("inner_parts",
|
||||
fill=CTkThemeManager.single_color(self.fg_color, self.appearance_mode),
|
||||
outline=CTkThemeManager.single_color(self.fg_color, self.appearance_mode))
|
||||
|
||||
self.text_label.configure(fg=CTkThemeManager.single_color(self.text_color, self.appearance_mode),
|
||||
bg=CTkThemeManager.single_color(self.fg_color, self.appearance_mode))
|
||||
else:
|
||||
self.canvas.itemconfig("inner_parts",
|
||||
fill=CTkThemeManager.single_color(self.bg_color, self.appearance_mode),
|
||||
outline=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
|
||||
|
||||
self.text_label.configure(fg=CTkThemeManager.single_color(self.text_color, self.appearance_mode),
|
||||
bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
|
||||
|
||||
def config(self, *args, **kwargs):
|
||||
self.configure(*args, **kwargs)
|
||||
|
||||
def configure(self, *args, **kwargs):
|
||||
require_redraw = False # some attribute changes require a call of self.draw() at the end
|
||||
|
||||
if "text" in kwargs:
|
||||
self.set_text(kwargs["text"])
|
||||
del kwargs["text"]
|
||||
|
||||
if "fg_color" in kwargs:
|
||||
self.fg_color = kwargs["fg_color"]
|
||||
require_redraw = True
|
||||
del kwargs["fg_color"]
|
||||
|
||||
if "bg_color" in kwargs:
|
||||
if kwargs["bg_color"] is None:
|
||||
self.bg_color = self.detect_color_of_master()
|
||||
else:
|
||||
self.bg_color = kwargs["bg_color"]
|
||||
require_redraw = True
|
||||
del kwargs["bg_color"]
|
||||
|
||||
if "text_color" in kwargs:
|
||||
self.text_color = kwargs["text_color"]
|
||||
require_redraw = True
|
||||
del kwargs["text_color"]
|
||||
|
||||
self.text_label.configure(*args, **kwargs)
|
||||
|
||||
if require_redraw:
|
||||
self.draw()
|
||||
|
||||
def set_text(self, text):
|
||||
self.text = text
|
||||
self.text_label.configure(text=self.text, width=len(self.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, CTk)):
|
||||
self.bg_color = self.master.fg_color
|
||||
else:
|
||||
self.bg_color = self.master.cget("bg")
|
||||
|
||||
self.draw()
|
233
customtkinter/widgets/customtkinter_progressbar.py
Normal file
233
customtkinter/widgets/customtkinter_progressbar.py
Normal file
@ -0,0 +1,233 @@
|
||||
import sys
|
||||
import tkinter
|
||||
import tkinter.ttk as ttk
|
||||
|
||||
from .customtkinter_tk import CTk
|
||||
from .customtkinter_frame import CTkFrame
|
||||
from .customtkinter_canvas import CTkCanvas
|
||||
from ..appearance_mode_tracker import AppearanceModeTracker
|
||||
from ..customtkinter_theme_manager import CTkThemeManager
|
||||
from ..customtkinter_draw_engine import CTkDrawEngine
|
||||
from ..customtkinter_settings import CTkSettings
|
||||
|
||||
|
||||
class CTkProgressBar(tkinter.Frame):
|
||||
""" tkinter custom progressbar, always horizontal, values are from 0 to 1 """
|
||||
|
||||
def __init__(self, *args,
|
||||
variable=None,
|
||||
bg_color=None,
|
||||
border_color="default_theme",
|
||||
fg_color="default_theme",
|
||||
progress_color="default_theme",
|
||||
corner_radius="default_theme",
|
||||
width=200,
|
||||
height=8,
|
||||
border_width="default_theme",
|
||||
**kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# overwrite configure methods of master when master is tkinter widget, so that bg changes get applied on child CTk widget too
|
||||
if isinstance(self.master, (tkinter.Tk, tkinter.Frame)) and not isinstance(self.master, (CTk, CTkFrame)):
|
||||
master_old_configure = self.master.config
|
||||
|
||||
def new_configure(*args, **kwargs):
|
||||
if "bg" in kwargs:
|
||||
self.configure(bg_color=kwargs["bg"])
|
||||
elif "background" in kwargs:
|
||||
self.configure(bg_color=kwargs["background"])
|
||||
|
||||
# args[0] is dict when attribute gets changed by widget[<attribut>] syntax
|
||||
elif len(args) > 0 and type(args[0]) == dict:
|
||||
if "bg" in args[0]:
|
||||
self.configure(bg_color=args[0]["bg"])
|
||||
elif "background" in args[0]:
|
||||
self.configure(bg_color=args[0]["background"])
|
||||
master_old_configure(*args, **kwargs)
|
||||
|
||||
self.master.config = new_configure
|
||||
self.master.configure = new_configure
|
||||
|
||||
# add set_appearance_mode method to callback list of AppearanceModeTracker for appearance mode changes
|
||||
AppearanceModeTracker.add(self.change_appearance_mode, self)
|
||||
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
|
||||
|
||||
self.bg_color = self.detect_color_of_master() if bg_color is None else bg_color
|
||||
self.border_color = CTkThemeManager.theme["color"]["progressbar_border"] if border_color == "default_theme" else border_color
|
||||
self.fg_color = CTkThemeManager.theme["color"]["progressbar"] if fg_color == "default_theme" else fg_color
|
||||
self.progress_color = CTkThemeManager.theme["color"]["progressbar_progress"] if progress_color == "default_theme" else progress_color
|
||||
|
||||
self.variable = variable
|
||||
self.variable_callback_blocked = False
|
||||
self.variable_callback_name = None
|
||||
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.corner_radius = CTkThemeManager.theme["shape"]["progressbar_corner_radius"] if corner_radius == "default_theme" else corner_radius
|
||||
self.border_width = CTkThemeManager.theme["shape"]["progressbar_border_width"] if border_width == "default_theme" else border_width
|
||||
self.value = 0.5
|
||||
|
||||
self.configure(width=self.width, height=self.height)
|
||||
|
||||
self.canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self.width,
|
||||
height=self.height)
|
||||
self.canvas.place(x=0, y=0, relwidth=1, relheight=1)
|
||||
|
||||
self.draw_engine = CTkDrawEngine(self.canvas, CTkSettings.preferred_drawing_method)
|
||||
|
||||
# Each time an item is resized due to pack position mode, the binding Configure is called on the widget
|
||||
self.bind('<Configure>', self.update_dimensions)
|
||||
|
||||
self.draw() # initial draw
|
||||
|
||||
if self.variable is not None:
|
||||
self.variable_callback_name = self.variable.trace_add("write", self.variable_callback)
|
||||
self.variable_callback_blocked = True
|
||||
self.set(self.variable.get(), from_variable_callback=True)
|
||||
self.variable_callback_blocked = False
|
||||
|
||||
def destroy(self):
|
||||
AppearanceModeTracker.remove(self.change_appearance_mode)
|
||||
|
||||
if self.variable is not None:
|
||||
self.variable.trace_remove("write", self.variable_callback_name)
|
||||
|
||||
super().destroy()
|
||||
|
||||
def detect_color_of_master(self):
|
||||
""" detect color of self.master widget to set correct bg_color """
|
||||
|
||||
if isinstance(self.master, CTkFrame): # master is CTkFrame
|
||||
return self.master.fg_color
|
||||
|
||||
elif isinstance(self.master, (ttk.Frame, ttk.LabelFrame, ttk.Notebook)): # master is ttk widget
|
||||
print("button on", self.master.winfo_class())
|
||||
try:
|
||||
ttk_style = ttk.Style()
|
||||
return ttk_style.lookup(self.master.winfo_class(), 'background')
|
||||
except Exception:
|
||||
return "#FFFFFF", "#000000"
|
||||
|
||||
else: # master is normal tkinter widget
|
||||
try:
|
||||
return self.master.cget("bg") # try to get bg color by .cget() method
|
||||
except Exception:
|
||||
return "#FFFFFF", "#000000"
|
||||
|
||||
@staticmethod
|
||||
def calc_optimal_height(user_height):
|
||||
if sys.platform == "darwin":
|
||||
return user_height # on macOS just use given value (canvas has Antialiasing)
|
||||
else:
|
||||
# make sure the value is always with uneven for better rendering of the ovals
|
||||
if user_height == 0:
|
||||
return 0
|
||||
elif user_height % 2 == 0:
|
||||
return user_height + 1
|
||||
else:
|
||||
return user_height
|
||||
|
||||
def update_dimensions(self, event):
|
||||
# only redraw if dimensions changed (for performance)
|
||||
if self.width != event.width or self.height != event.height:
|
||||
self.width = event.width
|
||||
self.height = event.height
|
||||
|
||||
self.draw()
|
||||
|
||||
def draw(self, no_color_updates=False):
|
||||
|
||||
requires_recoloring = self.draw_engine.draw_rounded_progress_bar_with_border(self.width, self.height, self.corner_radius, self.border_width, self.value, "w")
|
||||
|
||||
if no_color_updates is False or requires_recoloring:
|
||||
self.canvas.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
|
||||
self.canvas.itemconfig("border_parts",
|
||||
fill=CTkThemeManager.single_color(self.border_color, self.appearance_mode),
|
||||
outline=CTkThemeManager.single_color(self.border_color, self.appearance_mode))
|
||||
self.canvas.itemconfig("inner_parts",
|
||||
fill=CTkThemeManager.single_color(self.fg_color, self.appearance_mode),
|
||||
outline=CTkThemeManager.single_color(self.fg_color, self.appearance_mode))
|
||||
self.canvas.itemconfig("progress_parts",
|
||||
fill=CTkThemeManager.single_color(self.progress_color, self.appearance_mode),
|
||||
outline=CTkThemeManager.single_color(self.progress_color, self.appearance_mode))
|
||||
|
||||
def configure(self, *args, **kwargs):
|
||||
require_redraw = False # some attribute changes require a call of self.draw() at the end
|
||||
|
||||
if "bg_color" in kwargs:
|
||||
self.bg_color = kwargs["bg_color"]
|
||||
del kwargs["bg_color"]
|
||||
require_redraw = True
|
||||
|
||||
if "fg_color" in kwargs:
|
||||
self.fg_color = kwargs["fg_color"]
|
||||
del kwargs["fg_color"]
|
||||
require_redraw = True
|
||||
|
||||
if "border_color" in kwargs:
|
||||
self.border_color = kwargs["border_color"]
|
||||
del kwargs["border_color"]
|
||||
require_redraw = True
|
||||
|
||||
if "progress_color" in kwargs:
|
||||
self.progress_color = kwargs["progress_color"]
|
||||
del kwargs["progress_color"]
|
||||
require_redraw = True
|
||||
|
||||
if "border_width" in kwargs:
|
||||
self.border_width = kwargs["border_width"]
|
||||
del kwargs["border_width"]
|
||||
require_redraw = True
|
||||
|
||||
if "variable" in kwargs:
|
||||
if self.variable is not None:
|
||||
self.variable.trace_remove("write", self.variable_callback_name)
|
||||
|
||||
self.variable = kwargs["variable"]
|
||||
|
||||
if self.variable is not None and self.variable != "":
|
||||
self.variable_callback_name = self.variable.trace_add("write", self.variable_callback)
|
||||
self.set(self.variable.get(), from_variable_callback=True)
|
||||
else:
|
||||
self.variable = None
|
||||
|
||||
del kwargs["variable"]
|
||||
|
||||
super().configure(*args, **kwargs)
|
||||
|
||||
if require_redraw is True:
|
||||
self.draw()
|
||||
|
||||
def variable_callback(self, var_name, index, mode):
|
||||
if not self.variable_callback_blocked:
|
||||
self.set(self.variable.get(), from_variable_callback=True)
|
||||
|
||||
def set(self, value, from_variable_callback=False):
|
||||
self.value = value
|
||||
|
||||
if self.value > 1:
|
||||
self.value = 1
|
||||
elif self.value < 0:
|
||||
self.value = 0
|
||||
|
||||
self.draw(no_color_updates=True)
|
||||
|
||||
if self.variable is not None and not from_variable_callback:
|
||||
self.variable_callback_blocked = True
|
||||
self.variable.set(round(self.value) if isinstance(self.variable, tkinter.IntVar) else self.value)
|
||||
self.variable_callback_blocked = False
|
||||
|
||||
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()
|
354
customtkinter/widgets/customtkinter_radiobutton.py
Normal file
354
customtkinter/widgets/customtkinter_radiobutton.py
Normal file
@ -0,0 +1,354 @@
|
||||
import tkinter
|
||||
import tkinter.ttk as ttk
|
||||
import sys
|
||||
|
||||
from .customtkinter_tk import CTk
|
||||
from .customtkinter_frame import CTkFrame
|
||||
from .customtkinter_canvas import CTkCanvas
|
||||
from ..appearance_mode_tracker import AppearanceModeTracker
|
||||
from ..customtkinter_theme_manager import CTkThemeManager
|
||||
from ..customtkinter_settings import CTkSettings
|
||||
from ..customtkinter_draw_engine import CTkDrawEngine
|
||||
|
||||
|
||||
class CTkRadioButton(tkinter.Frame):
|
||||
def __init__(self, *args,
|
||||
bg_color=None,
|
||||
fg_color="default_theme",
|
||||
hover_color="default_theme",
|
||||
border_color="default_theme",
|
||||
border_width_unchecked="default_theme",
|
||||
border_width_checked="default_theme",
|
||||
width=22,
|
||||
height=22,
|
||||
corner_radius="default_theme",
|
||||
text_font="default_theme",
|
||||
text_color="default_theme",
|
||||
text="CTkRadioButton",
|
||||
text_color_disabled="default_theme",
|
||||
hover=True,
|
||||
command=None,
|
||||
state=tkinter.NORMAL,
|
||||
value=0,
|
||||
variable=None,
|
||||
textvariable=None,
|
||||
**kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# overwrite configure methods of master when master is tkinter widget, so that bg changes get applied on child CTk widget too
|
||||
if isinstance(self.master, (tkinter.Tk, tkinter.Frame)) and not isinstance(self.master, (CTk, CTkFrame)):
|
||||
master_old_configure = self.master.config
|
||||
|
||||
def new_configure(*args, **kwargs):
|
||||
if "bg" in kwargs:
|
||||
self.configure(bg_color=kwargs["bg"])
|
||||
elif "background" in kwargs:
|
||||
self.configure(bg_color=kwargs["background"])
|
||||
|
||||
# args[0] is dict when attribute gets changed by widget[<attribut>] syntax
|
||||
elif len(args) > 0 and type(args[0]) == dict:
|
||||
if "bg" in args[0]:
|
||||
self.configure(bg_color=args[0]["bg"])
|
||||
elif "background" in args[0]:
|
||||
self.configure(bg_color=args[0]["background"])
|
||||
master_old_configure(*args, **kwargs)
|
||||
|
||||
self.master.config = new_configure
|
||||
self.master.configure = new_configure
|
||||
|
||||
# add set_appearance_mode method to callback list of AppearanceModeTracker for appearance mode changes
|
||||
AppearanceModeTracker.add(self.set_appearance_mode, self)
|
||||
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
|
||||
|
||||
self.bg_color = self.detect_color_of_master() if bg_color is None else bg_color
|
||||
self.fg_color = CTkThemeManager.theme["color"]["button"] if fg_color == "default_theme" else fg_color
|
||||
self.hover_color = CTkThemeManager.theme["color"]["button_hover"] if hover_color == "default_theme" else hover_color
|
||||
self.border_color = CTkThemeManager.theme["color"]["checkbox_border"] if border_color == "default_theme" else border_color
|
||||
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.corner_radius = CTkThemeManager.theme["shape"]["radiobutton_corner_radius"] if corner_radius == "default_theme" else corner_radius
|
||||
self.border_width_unchecked = CTkThemeManager.theme["shape"]["radiobutton_border_width_unchecked"] if border_width_unchecked == "default_theme" else border_width_unchecked
|
||||
self.border_width_checked = CTkThemeManager.theme["shape"]["radiobutton_border_width_checked"] if border_width_checked == "default_theme" else border_width_checked
|
||||
self.border_width = self.border_width_unchecked
|
||||
|
||||
if self.corner_radius*2 > self.height:
|
||||
self.corner_radius = self.height/2
|
||||
elif self.corner_radius*2 > self.width:
|
||||
self.corner_radius = self.width/2
|
||||
|
||||
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 = CTkThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color
|
||||
self.text_color_disabled = CTkThemeManager.theme["color"]["text_disabled"] if text_color_disabled == "default_theme" else text_color_disabled
|
||||
self.text_font = (CTkThemeManager.theme["text"]["font"], CTkThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font
|
||||
|
||||
self.function = command
|
||||
self.state = state
|
||||
self.hover = hover
|
||||
self.check_state = False
|
||||
self.value = value
|
||||
self.variable: tkinter.Variable = variable
|
||||
self.variable_callback_blocked = False
|
||||
self.textvariable = textvariable
|
||||
self.variable_callback_name = None
|
||||
|
||||
# configure grid system
|
||||
self.grid_columnconfigure(0, weight=0)
|
||||
self.grid_columnconfigure(1, weight=0, minsize=6)
|
||||
self.grid_columnconfigure(2, weight=1)
|
||||
|
||||
self.canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self.width,
|
||||
height=self.height)
|
||||
self.canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=1)
|
||||
|
||||
self.draw_engine = CTkDrawEngine(self.canvas, CTkSettings.preferred_drawing_method)
|
||||
|
||||
self.canvas.bind("<Enter>", self.on_enter)
|
||||
self.canvas.bind("<Leave>", self.on_leave)
|
||||
self.canvas.bind("<Button-1>", self.invoke)
|
||||
self.canvas.bind("<Button-1>", self.invoke)
|
||||
|
||||
self.text_label = None
|
||||
|
||||
self.set_cursor()
|
||||
self.draw() # initial draw
|
||||
|
||||
if self.variable is not None:
|
||||
self.variable_callback_name = self.variable.trace_add("write", self.variable_callback)
|
||||
if self.variable.get() == self.value:
|
||||
self.select(from_variable_callback=True)
|
||||
else:
|
||||
self.deselect(from_variable_callback=True)
|
||||
|
||||
def destroy(self):
|
||||
AppearanceModeTracker.remove(self.set_appearance_mode)
|
||||
|
||||
if self.variable is not None:
|
||||
self.variable.trace_remove("write", self.variable_callback_name)
|
||||
|
||||
super().destroy()
|
||||
|
||||
def detect_color_of_master(self):
|
||||
""" detect color of self.master widget to set correct bg_color """
|
||||
|
||||
if isinstance(self.master, CTkFrame): # master is CTkFrame
|
||||
return self.master.fg_color
|
||||
|
||||
elif isinstance(self.master, (ttk.Frame, ttk.LabelFrame, ttk.Notebook)): # master is ttk widget
|
||||
print("button on", self.master.winfo_class())
|
||||
try:
|
||||
ttk_style = ttk.Style()
|
||||
return ttk_style.lookup(self.master.winfo_class(), 'background')
|
||||
except Exception:
|
||||
return "#FFFFFF", "#000000"
|
||||
|
||||
else: # master is normal tkinter widget
|
||||
try:
|
||||
return self.master.cget("bg") # try to get bg color by .cget() method
|
||||
except Exception:
|
||||
return "#FFFFFF", "#000000"
|
||||
|
||||
def draw(self):
|
||||
requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.width, self.height, self.corner_radius, self.border_width)
|
||||
|
||||
self.canvas.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
|
||||
self.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
|
||||
|
||||
if self.check_state is False:
|
||||
self.canvas.itemconfig("border_parts",
|
||||
outline=CTkThemeManager.single_color(self.border_color, self.appearance_mode),
|
||||
fill=CTkThemeManager.single_color(self.border_color, self.appearance_mode))
|
||||
else:
|
||||
self.canvas.itemconfig("border_parts",
|
||||
outline=CTkThemeManager.single_color(self.fg_color, self.appearance_mode),
|
||||
fill=CTkThemeManager.single_color(self.fg_color, self.appearance_mode))
|
||||
|
||||
self.canvas.itemconfig("inner_parts",
|
||||
outline=CTkThemeManager.single_color(self.bg_color, self.appearance_mode),
|
||||
fill=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
|
||||
|
||||
if self.text_label is None:
|
||||
self.text_label = tkinter.Label(master=self,
|
||||
bd=0,
|
||||
text=self.text,
|
||||
justify=tkinter.LEFT,
|
||||
font=self.text_font)
|
||||
self.text_label.grid(row=0, column=2, padx=0, pady=0, sticky="w")
|
||||
self.text_label["anchor"] = "w"
|
||||
|
||||
if self.state == tkinter.DISABLED:
|
||||
self.text_label.configure(fg=CTkThemeManager.single_color(self.text_color_disabled, self.appearance_mode))
|
||||
else:
|
||||
self.text_label.configure(fg=CTkThemeManager.single_color(self.text_color, self.appearance_mode))
|
||||
|
||||
self.text_label.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
|
||||
|
||||
self.set_text(self.text)
|
||||
|
||||
def config(self, *args, **kwargs):
|
||||
self.configure(*args, **kwargs)
|
||||
|
||||
def configure(self, *args, **kwargs):
|
||||
require_redraw = False # some attribute changes require a call of self.draw()
|
||||
|
||||
if "text" in kwargs:
|
||||
self.set_text(kwargs["text"])
|
||||
del kwargs["text"]
|
||||
|
||||
if "state" in kwargs:
|
||||
self.state = kwargs["state"]
|
||||
self.set_cursor()
|
||||
require_redraw = True
|
||||
del kwargs["state"]
|
||||
|
||||
if "fg_color" in kwargs:
|
||||
self.fg_color = kwargs["fg_color"]
|
||||
require_redraw = True
|
||||
del kwargs["fg_color"]
|
||||
|
||||
if "bg_color" in kwargs:
|
||||
if kwargs["bg_color"] is None:
|
||||
self.bg_color = self.detect_color_of_master()
|
||||
else:
|
||||
self.bg_color = kwargs["bg_color"]
|
||||
require_redraw = True
|
||||
del kwargs["bg_color"]
|
||||
|
||||
if "hover_color" in kwargs:
|
||||
self.hover_color = kwargs["hover_color"]
|
||||
require_redraw = True
|
||||
del kwargs["hover_color"]
|
||||
|
||||
if "text_color" in kwargs:
|
||||
self.text_color = kwargs["text_color"]
|
||||
require_redraw = True
|
||||
del kwargs["text_color"]
|
||||
|
||||
if "border_color" in kwargs:
|
||||
self.border_color = kwargs["border_color"]
|
||||
require_redraw = True
|
||||
del kwargs["border_color"]
|
||||
|
||||
if "border_width" in kwargs:
|
||||
self.border_width = kwargs["border_width"]
|
||||
require_redraw = True
|
||||
del kwargs["border_width"]
|
||||
|
||||
if "command" in kwargs:
|
||||
self.function = kwargs["command"]
|
||||
del kwargs["command"]
|
||||
|
||||
if "variable" in kwargs:
|
||||
if self.variable is not None:
|
||||
self.variable.trace_remove("write", self.variable_callback_name)
|
||||
|
||||
self.variable = kwargs["variable"]
|
||||
|
||||
if self.variable is not None and self.variable != "":
|
||||
self.variable_callback_name = self.variable.trace_add("write", self.variable_callback)
|
||||
if self.variable.get() == self.value:
|
||||
self.select(from_variable_callback=True)
|
||||
else:
|
||||
self.deselect(from_variable_callback=True)
|
||||
else:
|
||||
self.variable = None
|
||||
|
||||
del kwargs["variable"]
|
||||
|
||||
super().configure(*args, **kwargs)
|
||||
|
||||
if require_redraw:
|
||||
self.draw()
|
||||
|
||||
def set_cursor(self):
|
||||
if self.state == tkinter.DISABLED:
|
||||
if sys.platform == "darwin" and CTkSettings.hand_cursor_enabled:
|
||||
self.canvas.configure(cursor="arrow")
|
||||
elif sys.platform.startswith("win") and CTkSettings.hand_cursor_enabled:
|
||||
self.canvas.configure(cursor="arrow")
|
||||
|
||||
elif self.state == tkinter.NORMAL:
|
||||
if sys.platform == "darwin" and CTkSettings.hand_cursor_enabled:
|
||||
self.canvas.configure(cursor="pointinghand")
|
||||
elif sys.platform.startswith("win") and CTkSettings.hand_cursor_enabled:
|
||||
self.canvas.configure(cursor="hand2")
|
||||
|
||||
def set_text(self, text):
|
||||
self.text = text
|
||||
if self.text_label is not None:
|
||||
self.text_label.configure(text=self.text)
|
||||
else:
|
||||
sys.stderr.write("ERROR (CTkButton): Cant change text because radiobutton has no text.")
|
||||
|
||||
def on_enter(self, event=0):
|
||||
if self.hover is True and self.state == tkinter.NORMAL:
|
||||
self.canvas.itemconfig("border_parts",
|
||||
fill=CTkThemeManager.single_color(self.hover_color, self.appearance_mode),
|
||||
outline=CTkThemeManager.single_color(self.hover_color, self.appearance_mode))
|
||||
|
||||
def on_leave(self, event=0):
|
||||
if self.hover is True:
|
||||
if self.check_state is True:
|
||||
self.canvas.itemconfig("border_parts",
|
||||
fill=CTkThemeManager.single_color(self.fg_color, self.appearance_mode),
|
||||
outline=CTkThemeManager.single_color(self.fg_color, self.appearance_mode))
|
||||
else:
|
||||
self.canvas.itemconfig("border_parts",
|
||||
fill=CTkThemeManager.single_color(self.border_color, self.appearance_mode),
|
||||
outline=CTkThemeManager.single_color(self.border_color, self.appearance_mode))
|
||||
|
||||
def variable_callback(self, var_name, index, mode):
|
||||
if not self.variable_callback_blocked:
|
||||
if self.variable.get() == self.value:
|
||||
self.select(from_variable_callback=True)
|
||||
else:
|
||||
self.deselect(from_variable_callback=True)
|
||||
|
||||
def invoke(self, event=0):
|
||||
if self.function is not None:
|
||||
self.function()
|
||||
|
||||
if self.state == tkinter.NORMAL:
|
||||
if self.check_state is False:
|
||||
self.check_state = True
|
||||
self.select()
|
||||
|
||||
def select(self, from_variable_callback=False):
|
||||
self.check_state = True
|
||||
self.border_width = self.border_width_checked
|
||||
self.draw()
|
||||
|
||||
if self.variable is not None and not from_variable_callback:
|
||||
self.variable_callback_blocked = True
|
||||
self.variable.set(self.value)
|
||||
self.variable_callback_blocked = False
|
||||
|
||||
def deselect(self, from_variable_callback=False):
|
||||
self.check_state = False
|
||||
self.border_width = self.border_width_unchecked
|
||||
self.draw()
|
||||
|
||||
if self.variable is not None and not from_variable_callback:
|
||||
self.variable_callback_blocked = True
|
||||
self.variable.set("")
|
||||
self.variable_callback_blocked = False
|
||||
|
||||
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
|
||||
|
||||
if isinstance(self.master, (CTkFrame, CTk)):
|
||||
self.bg_color = self.master.fg_color
|
||||
else:
|
||||
self.bg_color = self.master.cget("bg")
|
||||
|
||||
self.draw()
|
370
customtkinter/widgets/customtkinter_slider.py
Normal file
370
customtkinter/widgets/customtkinter_slider.py
Normal file
@ -0,0 +1,370 @@
|
||||
import tkinter
|
||||
import tkinter.ttk as ttk
|
||||
import sys
|
||||
|
||||
from .customtkinter_tk import CTk
|
||||
from .customtkinter_frame import CTkFrame
|
||||
from .customtkinter_canvas import CTkCanvas
|
||||
from ..appearance_mode_tracker import AppearanceModeTracker
|
||||
from ..customtkinter_theme_manager import CTkThemeManager
|
||||
from ..customtkinter_settings import CTkSettings
|
||||
from ..customtkinter_draw_engine import CTkDrawEngine
|
||||
|
||||
|
||||
class CTkSlider(tkinter.Frame):
|
||||
""" tkinter custom slider, always horizontal """
|
||||
|
||||
def __init__(self, *args,
|
||||
bg_color=None,
|
||||
border_color=None,
|
||||
fg_color="default_theme",
|
||||
progress_color="default_theme",
|
||||
button_color="default_theme",
|
||||
button_hover_color="default_theme",
|
||||
from_=0,
|
||||
to=1,
|
||||
number_of_steps=None,
|
||||
width=160,
|
||||
height=16,
|
||||
corner_radius="default_theme",
|
||||
button_corner_radius="default_theme",
|
||||
border_width="default_theme",
|
||||
button_length="default_theme",
|
||||
command=None,
|
||||
variable=None,
|
||||
**kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# overwrite configure methods of master when master is tkinter widget, so that bg changes get applied on child CTk widget too
|
||||
if isinstance(self.master, (tkinter.Tk, tkinter.Frame)) and not isinstance(self.master, (CTk, CTkFrame)):
|
||||
master_old_configure = self.master.config
|
||||
|
||||
def new_configure(*args, **kwargs):
|
||||
if "bg" in kwargs:
|
||||
self.configure(bg_color=kwargs["bg"])
|
||||
elif "background" in kwargs:
|
||||
self.configure(bg_color=kwargs["background"])
|
||||
|
||||
# args[0] is dict when attribute gets changed by widget[<attribut>] syntax
|
||||
elif len(args) > 0 and type(args[0]) == dict:
|
||||
if "bg" in args[0]:
|
||||
self.configure(bg_color=args[0]["bg"])
|
||||
elif "background" in args[0]:
|
||||
self.configure(bg_color=args[0]["background"])
|
||||
master_old_configure(*args, **kwargs)
|
||||
|
||||
self.master.config = new_configure
|
||||
self.master.configure = new_configure
|
||||
|
||||
# add set_appearance_mode method to callback list of AppearanceModeTracker for appearance mode changes
|
||||
AppearanceModeTracker.add(self.change_appearance_mode, self)
|
||||
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
|
||||
|
||||
self.configure_basic_grid()
|
||||
|
||||
self.bg_color = self.detect_color_of_master() if bg_color is None else bg_color
|
||||
self.border_color = border_color
|
||||
self.fg_color = CTkThemeManager.theme["color"]["slider"] if fg_color == "default_theme" else fg_color
|
||||
self.progress_color = CTkThemeManager.theme["color"]["slider_progress"] if progress_color == "default_theme" else progress_color
|
||||
self.button_color = CTkThemeManager.theme["color"]["slider_button"] if button_color == "default_theme" else button_color
|
||||
self.button_hover_color = CTkThemeManager.theme["color"]["slider_button_hover"] if button_hover_color == "default_theme" else button_hover_color
|
||||
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.corner_radius = CTkThemeManager.theme["shape"]["slider_corner_radius"] if corner_radius == "default_theme" else corner_radius
|
||||
self.button_corner_radius = CTkThemeManager.theme["shape"]["slider_button_corner_radius"] if button_corner_radius == "default_theme" else button_corner_radius
|
||||
self.border_width = CTkThemeManager.theme["shape"]["slider_border_width"] if border_width == "default_theme" else border_width
|
||||
self.button_length = CTkThemeManager.theme["shape"]["slider_button_length"] if button_length == "default_theme" else button_length
|
||||
self.value = 0.5 # initial value of slider in percent
|
||||
self.hover_state = False
|
||||
self.from_ = from_
|
||||
self.to = to
|
||||
self.number_of_steps = number_of_steps
|
||||
self.output_value = self.from_ + (self.value * (self.to - self.from_))
|
||||
|
||||
if self.corner_radius < self.button_corner_radius:
|
||||
self.corner_radius = self.button_corner_radius
|
||||
|
||||
self.callback_function = command
|
||||
self.variable: tkinter.Variable = variable
|
||||
self.variable_callback_blocked = False
|
||||
self.variable_callback_name = None
|
||||
|
||||
self.configure(width=self.width, height=self.height)
|
||||
if sys.platform == "darwin":
|
||||
self.configure(cursor="pointinghand")
|
||||
elif sys.platform.startswith("win"):
|
||||
self.configure(cursor="hand2")
|
||||
|
||||
self.canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self.width,
|
||||
height=self.height)
|
||||
self.canvas.grid(column=0, row=0, sticky="nswe")
|
||||
|
||||
self.draw_engine = CTkDrawEngine(self.canvas, CTkSettings.preferred_drawing_method)
|
||||
|
||||
self.canvas.bind("<Enter>", self.on_enter)
|
||||
self.canvas.bind("<Leave>", self.on_leave)
|
||||
self.canvas.bind("<Button-1>", self.clicked)
|
||||
self.canvas.bind("<B1-Motion>", self.clicked)
|
||||
|
||||
# Each time an item is resized due to pack position mode, the binding Configure is called on the widget
|
||||
self.bind('<Configure>', self.update_dimensions)
|
||||
|
||||
self.draw() # initial draw
|
||||
|
||||
if self.variable is not None:
|
||||
self.variable_callback_name = self.variable.trace_add("write", self.variable_callback)
|
||||
self.variable_callback_blocked = True
|
||||
self.set(self.variable.get(), from_variable_callback=True)
|
||||
self.variable_callback_blocked = False
|
||||
|
||||
def destroy(self):
|
||||
# remove change_appearance_mode function from callback list of AppearanceModeTracker
|
||||
AppearanceModeTracker.remove(self.change_appearance_mode)
|
||||
|
||||
# remove variable_callback from variable callbacks if variable exists
|
||||
if self.variable is not None:
|
||||
self.variable.trace_remove("write", self.variable_callback_name)
|
||||
|
||||
super().destroy()
|
||||
|
||||
def configure_basic_grid(self):
|
||||
self.grid_rowconfigure(0, weight=1)
|
||||
self.grid_columnconfigure(0, weight=1)
|
||||
|
||||
def detect_color_of_master(self):
|
||||
""" detect color of self.master widget to set correct bg_color """
|
||||
|
||||
if isinstance(self.master, CTkFrame): # master is CTkFrame
|
||||
return self.master.fg_color
|
||||
|
||||
elif isinstance(self.master, (ttk.Frame, ttk.LabelFrame, ttk.Notebook)): # master is ttk widget
|
||||
print("button on", self.master.winfo_class())
|
||||
try:
|
||||
ttk_style = ttk.Style()
|
||||
return ttk_style.lookup(self.master.winfo_class(), 'background')
|
||||
except Exception:
|
||||
return "#FFFFFF", "#000000"
|
||||
|
||||
else: # master is normal tkinter widget
|
||||
try:
|
||||
return self.master.cget("bg") # try to get bg color by .cget() method
|
||||
except Exception:
|
||||
return "#FFFFFF", "#000000"
|
||||
|
||||
@staticmethod
|
||||
def calc_optimal_height(user_height):
|
||||
if sys.platform == "darwin":
|
||||
return user_height # on macOS just use given value (canvas has Antialiasing)
|
||||
else:
|
||||
# make sure the value is always with uneven for better rendering of the ovals
|
||||
if user_height == 0:
|
||||
return 0
|
||||
elif user_height % 2 == 0:
|
||||
return user_height + 1
|
||||
else:
|
||||
return user_height
|
||||
|
||||
def update_dimensions(self, event):
|
||||
# only redraw if dimensions changed (for performance)
|
||||
if self.width != event.width or self.height != event.height:
|
||||
self.width = event.width
|
||||
self.height = event.height
|
||||
|
||||
self.draw()
|
||||
|
||||
def draw(self, color_updates=True):
|
||||
|
||||
requires_recoloring = self.draw_engine.draw_rounded_slider_with_border_and_button(self.width, self.height, self.corner_radius, self.border_width,
|
||||
self.button_length, self.button_corner_radius, self.value, "w")
|
||||
|
||||
if color_updates or requires_recoloring:
|
||||
self.canvas.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
|
||||
|
||||
if self.border_color is None:
|
||||
self.canvas.itemconfig("border_parts", fill=CTkThemeManager.single_color(self.bg_color, self.appearance_mode),
|
||||
outline=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
|
||||
else:
|
||||
self.canvas.itemconfig("border_parts", fill=CTkThemeManager.single_color(self.border_color, self.appearance_mode),
|
||||
outline=CTkThemeManager.single_color(self.border_color, self.appearance_mode))
|
||||
|
||||
self.canvas.itemconfig("inner_parts", fill=CTkThemeManager.single_color(self.fg_color, self.appearance_mode),
|
||||
outline=CTkThemeManager.single_color(self.fg_color, self.appearance_mode))
|
||||
|
||||
if self.progress_color is None:
|
||||
self.canvas.itemconfig("progress_parts", fill=CTkThemeManager.single_color(self.fg_color, self.appearance_mode),
|
||||
outline=CTkThemeManager.single_color(self.fg_color, self.appearance_mode))
|
||||
else:
|
||||
self.canvas.itemconfig("progress_parts", fill=CTkThemeManager.single_color(self.progress_color, self.appearance_mode),
|
||||
outline=CTkThemeManager.single_color(self.progress_color, self.appearance_mode))
|
||||
|
||||
self.canvas.itemconfig("slider_parts", fill=CTkThemeManager.single_color(self.button_color, self.appearance_mode),
|
||||
outline=CTkThemeManager.single_color(self.button_color, self.appearance_mode))
|
||||
|
||||
def clicked(self, event=None):
|
||||
self.value = event.x / self.width
|
||||
|
||||
if self.value > 1:
|
||||
self.value = 1
|
||||
if self.value < 0:
|
||||
self.value = 0
|
||||
|
||||
self.output_value = self.round_to_step_size(self.from_ + (self.value * (self.to - self.from_)))
|
||||
self.value = (self.output_value - self.from_) / (self.to - self.from_)
|
||||
|
||||
self.draw(color_updates=False)
|
||||
|
||||
if self.callback_function is not None:
|
||||
self.callback_function(self.output_value)
|
||||
|
||||
if self.variable is not None:
|
||||
self.variable_callback_blocked = True
|
||||
self.variable.set(round(self.output_value) if isinstance(self.variable, tkinter.IntVar) else self.output_value)
|
||||
self.variable_callback_blocked = False
|
||||
|
||||
def on_enter(self, event=0):
|
||||
self.hover_state = True
|
||||
self.canvas.itemconfig("slider_parts", fill=CTkThemeManager.single_color(self.button_hover_color, self.appearance_mode),
|
||||
outline=CTkThemeManager.single_color(self.button_hover_color, self.appearance_mode))
|
||||
|
||||
def on_leave(self, event=0):
|
||||
self.hover_state = False
|
||||
self.canvas.itemconfig("slider_parts", fill=CTkThemeManager.single_color(self.button_color, self.appearance_mode),
|
||||
outline=CTkThemeManager.single_color(self.button_color, self.appearance_mode))
|
||||
|
||||
def round_to_step_size(self, value):
|
||||
if self.number_of_steps is not None:
|
||||
step_size = (self.to - self.from_) / self.number_of_steps
|
||||
value = self.to - (round((self.to - value) / step_size) * step_size)
|
||||
return value
|
||||
else:
|
||||
return value
|
||||
|
||||
def get(self):
|
||||
return self.output_value
|
||||
|
||||
def set(self, output_value, from_variable_callback=False):
|
||||
if self.from_ < self.to:
|
||||
if output_value > self.to:
|
||||
output_value = self.to
|
||||
elif output_value < self.from_:
|
||||
output_value = self.from_
|
||||
else:
|
||||
if output_value < self.to:
|
||||
output_value = self.to
|
||||
elif output_value > self.from_:
|
||||
output_value = self.from_
|
||||
|
||||
self.output_value = self.round_to_step_size(output_value)
|
||||
self.value = (self.output_value - self.from_) / (self.to - self.from_)
|
||||
|
||||
self.draw(color_updates=False)
|
||||
|
||||
if self.callback_function is not None:
|
||||
self.callback_function(self.output_value)
|
||||
|
||||
if self.variable is not None and not from_variable_callback:
|
||||
self.variable_callback_blocked = True
|
||||
self.variable.set(round(self.output_value) if isinstance(self.variable, tkinter.IntVar) else self.output_value)
|
||||
self.variable_callback_blocked = False
|
||||
|
||||
def variable_callback(self, var_name, index, mode):
|
||||
if not self.variable_callback_blocked:
|
||||
self.set(self.variable.get(), from_variable_callback=True)
|
||||
|
||||
def config(self, *args, **kwargs):
|
||||
self.configure(*args, **kwargs)
|
||||
|
||||
def configure(self, *args, **kwargs):
|
||||
require_redraw = False # some attribute changes require a call of self.draw() at the end
|
||||
|
||||
if "fg_color" in kwargs:
|
||||
self.fg_color = kwargs["fg_color"]
|
||||
require_redraw = True
|
||||
del kwargs["fg_color"]
|
||||
|
||||
if "bg_color" in kwargs:
|
||||
if kwargs["bg_color"] is None:
|
||||
self.bg_color = self.detect_color_of_master()
|
||||
else:
|
||||
self.bg_color = kwargs["bg_color"]
|
||||
require_redraw = True
|
||||
del kwargs["bg_color"]
|
||||
|
||||
if "progress_color" in kwargs:
|
||||
if kwargs["progress_color"] is None:
|
||||
self.progress_color = self.fg_color
|
||||
else:
|
||||
self.progress_color = kwargs["progress_color"]
|
||||
require_redraw = True
|
||||
del kwargs["progress_color"]
|
||||
|
||||
if "button_color" in kwargs:
|
||||
self.button_color = kwargs["button_color"]
|
||||
require_redraw = True
|
||||
del kwargs["button_color"]
|
||||
|
||||
if "button_hover_color" in kwargs:
|
||||
self.button_hover_color = kwargs["button_hover_color"]
|
||||
require_redraw = True
|
||||
del kwargs["button_hover_color"]
|
||||
|
||||
if "border_color" in kwargs:
|
||||
self.border_color = kwargs["border_color"]
|
||||
require_redraw = True
|
||||
del kwargs["border_color"]
|
||||
|
||||
if "border_width" in kwargs:
|
||||
self.border_width = kwargs["border_width"]
|
||||
require_redraw = True
|
||||
del kwargs["border_width"]
|
||||
|
||||
if "from_" in kwargs:
|
||||
self.from_ = kwargs["from_"]
|
||||
del kwargs["from_"]
|
||||
|
||||
if "to" in kwargs:
|
||||
self.to = kwargs["to"]
|
||||
del kwargs["to"]
|
||||
|
||||
if "number_of_steps" in kwargs:
|
||||
self.number_of_steps = kwargs["number_of_steps"]
|
||||
del kwargs["number_of_steps"]
|
||||
|
||||
if "command" in kwargs:
|
||||
self.callback_function = kwargs["command"]
|
||||
del kwargs["command"]
|
||||
|
||||
if "variable" in kwargs:
|
||||
if self.variable is not None:
|
||||
self.variable.trace_remove("write", self.variable_callback_name)
|
||||
|
||||
self.variable = kwargs["variable"]
|
||||
|
||||
if self.variable is not None and self.variable != "":
|
||||
self.variable_callback_name = self.variable.trace_add("write", self.variable_callback)
|
||||
self.set(self.variable.get(), from_variable_callback=True)
|
||||
else:
|
||||
self.variable = None
|
||||
|
||||
del kwargs["variable"]
|
||||
|
||||
super().configure(*args, **kwargs)
|
||||
|
||||
if require_redraw:
|
||||
self.draw()
|
||||
|
||||
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()
|
365
customtkinter/widgets/customtkinter_switch.py
Normal file
365
customtkinter/widgets/customtkinter_switch.py
Normal file
@ -0,0 +1,365 @@
|
||||
import tkinter
|
||||
import tkinter.ttk as ttk
|
||||
import sys
|
||||
|
||||
from .customtkinter_tk import CTk
|
||||
from .customtkinter_frame import CTkFrame
|
||||
from .customtkinter_canvas import CTkCanvas
|
||||
from ..appearance_mode_tracker import AppearanceModeTracker
|
||||
from ..customtkinter_theme_manager import CTkThemeManager
|
||||
from ..customtkinter_settings import CTkSettings
|
||||
from ..customtkinter_draw_engine import CTkDrawEngine
|
||||
|
||||
|
||||
class CTkSwitch(tkinter.Frame):
|
||||
def __init__(self, *args,
|
||||
text="CTkSwitch",
|
||||
text_font="default_theme",
|
||||
text_color="default_theme",
|
||||
bg_color=None,
|
||||
border_color=None,
|
||||
fg_color="default_theme",
|
||||
progress_color="default_theme",
|
||||
button_color="default_theme",
|
||||
button_hover_color="default_theme",
|
||||
width=36,
|
||||
height=18,
|
||||
corner_radius="default_theme",
|
||||
# button_corner_radius="default_theme",
|
||||
border_width="default_theme",
|
||||
button_length="default_theme",
|
||||
command=None,
|
||||
onvalue=1,
|
||||
offvalue=0,
|
||||
variable=None,
|
||||
textvariable=None,
|
||||
**kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# overwrite configure methods of master when master is tkinter widget, so that bg changes get applied on child CTk widget too
|
||||
if isinstance(self.master, (tkinter.Tk, tkinter.Frame)) and not isinstance(self.master, (CTk, CTkFrame)):
|
||||
master_old_configure = self.master.config
|
||||
|
||||
def new_configure(*args, **kwargs):
|
||||
if "bg" in kwargs:
|
||||
self.configure(bg_color=kwargs["bg"])
|
||||
elif "background" in kwargs:
|
||||
self.configure(bg_color=kwargs["background"])
|
||||
|
||||
# args[0] is dict when attribute gets changed by widget[<attribut>] syntax
|
||||
elif len(args) > 0 and type(args[0]) == dict:
|
||||
if "bg" in args[0]:
|
||||
self.configure(bg_color=args[0]["bg"])
|
||||
elif "background" in args[0]:
|
||||
self.configure(bg_color=args[0]["background"])
|
||||
master_old_configure(*args, **kwargs)
|
||||
|
||||
self.master.config = new_configure
|
||||
self.master.configure = new_configure
|
||||
|
||||
# add set_appearance_mode method to callback list of AppearanceModeTracker for appearance mode changes
|
||||
AppearanceModeTracker.add(self.change_appearance_mode, self)
|
||||
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
|
||||
|
||||
self.bg_color = self.detect_color_of_master() if bg_color is None else bg_color
|
||||
self.border_color = border_color
|
||||
self.fg_color = CTkThemeManager.theme["color"]["switch"] if fg_color == "default_theme" else fg_color
|
||||
self.progress_color = CTkThemeManager.theme["color"]["switch_progress"] if progress_color == "default_theme" else progress_color
|
||||
self.button_color = CTkThemeManager.theme["color"]["switch_button"] if button_color == "default_theme" else button_color
|
||||
self.button_hover_color = CTkThemeManager.theme["color"]["switch_button_hover"] if button_hover_color == "default_theme" else button_hover_color
|
||||
self.text_color = CTkThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color
|
||||
|
||||
self.text = text
|
||||
self.text_font = (CTkThemeManager.theme["text"]["font"], CTkThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.corner_radius = CTkThemeManager.theme["shape"]["switch_corner_radius"] if corner_radius == "default_theme" else corner_radius
|
||||
# self.button_corner_radius = CTkThemeManager.theme["shape"]["switch_button_corner_radius"] if button_corner_radius == "default_theme" else button_corner_radius
|
||||
self.border_width = CTkThemeManager.theme["shape"]["switch_border_width"] if border_width == "default_theme" else border_width
|
||||
self.button_length = CTkThemeManager.theme["shape"]["switch_button_length"] if button_length == "default_theme" else button_length
|
||||
self.hover_state = False
|
||||
self.check_state = False # True if switch is activated
|
||||
self.onvalue = onvalue
|
||||
self.offvalue = offvalue
|
||||
|
||||
#if self.corner_radius < self.button_corner_radius:
|
||||
# self.corner_radius = self.button_corner_radius
|
||||
|
||||
self.callback_function = command
|
||||
self.variable: tkinter.Variable = variable
|
||||
self.variable_callback_blocked = False
|
||||
self.variable_callback_name = None
|
||||
self.textvariable = textvariable
|
||||
|
||||
# configure grid system
|
||||
self.grid_columnconfigure(0, weight=1)
|
||||
self.grid_columnconfigure(1, weight=0, minsize=6)
|
||||
self.grid_columnconfigure(2, weight=0)
|
||||
|
||||
self.canvas = CTkCanvas(master=self,
|
||||
highlightthickness=0,
|
||||
width=self.width,
|
||||
height=self.height)
|
||||
self.canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=1, sticky="nswe")
|
||||
|
||||
self.draw_engine = CTkDrawEngine(self.canvas, CTkSettings.preferred_drawing_method)
|
||||
|
||||
if sys.platform == "darwin" and CTkSettings.hand_cursor_enabled:
|
||||
self.canvas.configure(cursor="pointinghand")
|
||||
elif sys.platform.startswith("win") and CTkSettings.hand_cursor_enabled:
|
||||
self.canvas.configure(cursor="hand2")
|
||||
|
||||
self.canvas.bind("<Enter>", self.on_enter)
|
||||
self.canvas.bind("<Leave>", self.on_leave)
|
||||
self.canvas.bind("<Button-1>", self.toggle)
|
||||
|
||||
self.text_label = None
|
||||
|
||||
self.draw() # initial draw
|
||||
|
||||
if self.variable is not None:
|
||||
self.variable_callback_name = self.variable.trace_add("write", self.variable_callback)
|
||||
if self.variable.get() == self.onvalue:
|
||||
self.select(from_variable_callback=True)
|
||||
elif self.variable.get() == self.offvalue:
|
||||
self.deselect(from_variable_callback=True)
|
||||
|
||||
def destroy(self):
|
||||
# remove change_appearance_mode function from callback list of AppearanceModeTracker
|
||||
AppearanceModeTracker.remove(self.change_appearance_mode)
|
||||
|
||||
# remove variable_callback from variable callbacks if variable exists
|
||||
if self.variable is not None:
|
||||
self.variable.trace_remove("write", self.variable_callback_name)
|
||||
|
||||
super().destroy()
|
||||
|
||||
def detect_color_of_master(self):
|
||||
""" detect color of self.master widget to set correct bg_color """
|
||||
|
||||
if isinstance(self.master, CTkFrame): # master is CTkFrame
|
||||
return self.master.fg_color
|
||||
|
||||
elif isinstance(self.master, (ttk.Frame, ttk.LabelFrame, ttk.Notebook)): # master is ttk widget
|
||||
print("button on", self.master.winfo_class())
|
||||
try:
|
||||
ttk_style = ttk.Style()
|
||||
return ttk_style.lookup(self.master.winfo_class(), 'background')
|
||||
except Exception:
|
||||
return "#FFFFFF", "#000000"
|
||||
|
||||
else: # master is normal tkinter widget
|
||||
try:
|
||||
return self.master.cget("bg") # try to get bg color by .cget() method
|
||||
except Exception:
|
||||
return "#FFFFFF", "#000000"
|
||||
|
||||
def draw(self, color_updates=True):
|
||||
|
||||
if self.check_state is True:
|
||||
requires_recoloring = self.draw_engine.draw_rounded_slider_with_border_and_button(self.width, self.height, self.corner_radius, self.border_width,
|
||||
self.button_length, self.corner_radius, 1, "w")
|
||||
else:
|
||||
requires_recoloring = self.draw_engine.draw_rounded_slider_with_border_and_button(self.width, self.height, self.corner_radius, self.border_width,
|
||||
self.button_length, self.corner_radius, 0, "w")
|
||||
|
||||
if color_updates or requires_recoloring:
|
||||
self.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
|
||||
self.canvas.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
|
||||
|
||||
if self.border_color is None:
|
||||
self.canvas.itemconfig("border_parts", fill=CTkThemeManager.single_color(self.bg_color, self.appearance_mode),
|
||||
outline=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
|
||||
else:
|
||||
self.canvas.itemconfig("border_parts", fill=CTkThemeManager.single_color(self.border_color, self.appearance_mode),
|
||||
outline=CTkThemeManager.single_color(self.border_color, self.appearance_mode))
|
||||
|
||||
self.canvas.itemconfig("inner_parts", fill=CTkThemeManager.single_color(self.fg_color, self.appearance_mode),
|
||||
outline=CTkThemeManager.single_color(self.fg_color, self.appearance_mode))
|
||||
|
||||
if self.progress_color is None:
|
||||
self.canvas.itemconfig("progress_parts", fill=CTkThemeManager.single_color(self.fg_color, self.appearance_mode),
|
||||
outline=CTkThemeManager.single_color(self.fg_color, self.appearance_mode))
|
||||
else:
|
||||
self.canvas.itemconfig("progress_parts", fill=CTkThemeManager.single_color(self.progress_color, self.appearance_mode),
|
||||
outline=CTkThemeManager.single_color(self.progress_color, self.appearance_mode))
|
||||
|
||||
self.canvas.itemconfig("slider_parts", fill=CTkThemeManager.single_color(self.button_color, self.appearance_mode),
|
||||
outline=CTkThemeManager.single_color(self.button_color, self.appearance_mode))
|
||||
|
||||
# self.canvas.configure(bg="red")
|
||||
|
||||
if self.text_label is None:
|
||||
self.text_label = tkinter.Label(master=self,
|
||||
bd=0,
|
||||
text=self.text,
|
||||
justify=tkinter.LEFT,
|
||||
font=self.text_font)
|
||||
self.text_label.grid(row=0, column=2, padx=0, pady=0, sticky="w")
|
||||
self.text_label["anchor"] = "w"
|
||||
if self.textvariable is not None:
|
||||
self.text_label.configure(textvariable=self.textvariable)
|
||||
|
||||
self.text_label.configure(fg=CTkThemeManager.single_color(self.text_color, self.appearance_mode))
|
||||
self.text_label.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
|
||||
|
||||
self.set_text(self.text)
|
||||
|
||||
def set_text(self, text):
|
||||
self.text = text
|
||||
if self.text_label is not None:
|
||||
self.text_label.configure(text=self.text)
|
||||
else:
|
||||
sys.stderr.write("ERROR (CTkSwitch): Cant change text because checkbox has no text.")
|
||||
|
||||
def toggle(self, event=None):
|
||||
if self.check_state is True:
|
||||
self.check_state = False
|
||||
else:
|
||||
self.check_state = True
|
||||
|
||||
self.draw(color_updates=False)
|
||||
|
||||
if self.callback_function is not None:
|
||||
self.callback_function()
|
||||
|
||||
if self.variable is not None:
|
||||
self.variable_callback_blocked = True
|
||||
self.variable.set(self.onvalue if self.check_state is True else self.offvalue)
|
||||
self.variable_callback_blocked = False
|
||||
|
||||
def select(self, from_variable_callback=False):
|
||||
self.check_state = True
|
||||
|
||||
self.draw(color_updates=False)
|
||||
|
||||
if self.callback_function is not None:
|
||||
self.callback_function()
|
||||
|
||||
if self.variable is not None and not from_variable_callback:
|
||||
self.variable_callback_blocked = True
|
||||
self.variable.set(self.onvalue)
|
||||
self.variable_callback_blocked = False
|
||||
|
||||
def deselect(self, from_variable_callback=False):
|
||||
self.check_state = False
|
||||
|
||||
self.draw(color_updates=False)
|
||||
|
||||
if self.callback_function is not None:
|
||||
self.callback_function()
|
||||
|
||||
if self.variable is not None and not from_variable_callback:
|
||||
self.variable_callback_blocked = True
|
||||
self.variable.set(self.offvalue)
|
||||
self.variable_callback_blocked = False
|
||||
|
||||
def get(self):
|
||||
return self.onvalue if self.check_state is True else self.offvalue
|
||||
|
||||
def on_enter(self, event=0):
|
||||
self.hover_state = True
|
||||
self.canvas.itemconfig("slider_parts", fill=CTkThemeManager.single_color(self.button_hover_color, self.appearance_mode),
|
||||
outline=CTkThemeManager.single_color(self.button_hover_color, self.appearance_mode))
|
||||
|
||||
def on_leave(self, event=0):
|
||||
self.hover_state = False
|
||||
self.canvas.itemconfig("slider_parts", fill=CTkThemeManager.single_color(self.button_color, self.appearance_mode),
|
||||
outline=CTkThemeManager.single_color(self.button_color, self.appearance_mode))
|
||||
|
||||
def variable_callback(self, var_name, index, mode):
|
||||
if not self.variable_callback_blocked:
|
||||
if self.variable.get() == self.onvalue:
|
||||
self.select(from_variable_callback=True)
|
||||
elif self.variable.get() == self.offvalue:
|
||||
self.deselect(from_variable_callback=True)
|
||||
|
||||
def config(self, *args, **kwargs):
|
||||
self.configure(*args, **kwargs)
|
||||
|
||||
def configure(self, *args, **kwargs):
|
||||
require_redraw = False # some attribute changes require a call of self.draw() at the end
|
||||
|
||||
if "fg_color" in kwargs:
|
||||
self.fg_color = kwargs["fg_color"]
|
||||
require_redraw = True
|
||||
del kwargs["fg_color"]
|
||||
|
||||
if "bg_color" in kwargs:
|
||||
if kwargs["bg_color"] is None:
|
||||
self.bg_color = self.detect_color_of_master()
|
||||
else:
|
||||
self.bg_color = kwargs["bg_color"]
|
||||
require_redraw = True
|
||||
del kwargs["bg_color"]
|
||||
|
||||
if "progress_color" in kwargs:
|
||||
if kwargs["progress_color"] is None:
|
||||
self.progress_color = self.fg_color
|
||||
else:
|
||||
self.progress_color = kwargs["progress_color"]
|
||||
require_redraw = True
|
||||
del kwargs["progress_color"]
|
||||
|
||||
if "button_color" in kwargs:
|
||||
self.button_color = kwargs["button_color"]
|
||||
require_redraw = True
|
||||
del kwargs["button_color"]
|
||||
|
||||
if "button_hover_color" in kwargs:
|
||||
self.button_hover_color = kwargs["button_hover_color"]
|
||||
require_redraw = True
|
||||
del kwargs["button_hover_color"]
|
||||
|
||||
if "border_color" in kwargs:
|
||||
self.border_color = kwargs["border_color"]
|
||||
require_redraw = True
|
||||
del kwargs["border_color"]
|
||||
|
||||
if "border_width" in kwargs:
|
||||
self.border_width = kwargs["border_width"]
|
||||
require_redraw = True
|
||||
del kwargs["border_width"]
|
||||
|
||||
if "command" in kwargs:
|
||||
self.callback_function = kwargs["command"]
|
||||
del kwargs["command"]
|
||||
|
||||
if "textvariable" in kwargs:
|
||||
self.text_label.configure(textvariable=kwargs["textvariable"])
|
||||
del kwargs["textvariable"]
|
||||
|
||||
if "variable" in kwargs:
|
||||
if self.variable is not None:
|
||||
self.variable.trace_remove("write", self.variable_callback_name)
|
||||
|
||||
self.variable = kwargs["variable"]
|
||||
|
||||
if self.variable is not None and self.variable != "":
|
||||
self.variable_callback_name = self.variable.trace_add("write", self.variable_callback)
|
||||
if self.variable.get() == self.onvalue:
|
||||
self.select(from_variable_callback=True)
|
||||
elif self.variable.get() == self.offvalue:
|
||||
self.deselect(from_variable_callback=True)
|
||||
else:
|
||||
self.variable = None
|
||||
|
||||
del kwargs["variable"]
|
||||
|
||||
super().configure(*args, **kwargs)
|
||||
|
||||
if require_redraw:
|
||||
self.draw()
|
||||
|
||||
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()
|
186
customtkinter/widgets/customtkinter_tk.py
Normal file
186
customtkinter/widgets/customtkinter_tk.py
Normal file
@ -0,0 +1,186 @@
|
||||
import tkinter
|
||||
from distutils.version import StrictVersion as Version
|
||||
import sys
|
||||
import os
|
||||
import platform
|
||||
import ctypes
|
||||
|
||||
from ..appearance_mode_tracker import AppearanceModeTracker
|
||||
from ..customtkinter_theme_manager import CTkThemeManager
|
||||
|
||||
|
||||
class CTk(tkinter.Tk):
|
||||
def __init__(self, *args,
|
||||
fg_color="default_theme",
|
||||
**kwargs):
|
||||
|
||||
self.enable_macos_dark_title_bar()
|
||||
super().__init__(*args, **kwargs)
|
||||
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
|
||||
|
||||
self.fg_color = CTkThemeManager.theme["color"]["window_bg_color"] if fg_color == "default_theme" else fg_color
|
||||
|
||||
if "bg" in kwargs:
|
||||
self.fg_color = kwargs["bg"]
|
||||
del kwargs["bg"]
|
||||
elif "background" in kwargs:
|
||||
self.fg_color = kwargs["background"]
|
||||
del kwargs["background"]
|
||||
|
||||
# add set_appearance_mode method to callback list of AppearanceModeTracker for appearance mode changes
|
||||
AppearanceModeTracker.add(self.set_appearance_mode, self)
|
||||
super().configure(bg=CTkThemeManager.single_color(self.fg_color, self.appearance_mode))
|
||||
super().title("CTk")
|
||||
|
||||
self.window_exists = False # indicates if the window is already shown through .update or .mainloop
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
if self.appearance_mode == 1:
|
||||
self.windows_set_titlebar_color("dark")
|
||||
else:
|
||||
self.windows_set_titlebar_color("light")
|
||||
|
||||
def destroy(self):
|
||||
AppearanceModeTracker.remove(self.set_appearance_mode)
|
||||
self.disable_macos_dark_title_bar()
|
||||
super().destroy()
|
||||
|
||||
def update(self):
|
||||
if self.window_exists is False:
|
||||
self.deiconify()
|
||||
self.window_exists = True
|
||||
super().update()
|
||||
|
||||
def mainloop(self, *args, **kwargs):
|
||||
if self.window_exists is False:
|
||||
self.deiconify()
|
||||
self.window_exists = True
|
||||
super().mainloop(*args, **kwargs)
|
||||
|
||||
def resizable(self, *args, **kwargs):
|
||||
super().resizable(*args, **kwargs)
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
if self.appearance_mode == 1:
|
||||
self.windows_set_titlebar_color("dark")
|
||||
else:
|
||||
self.windows_set_titlebar_color("light")
|
||||
|
||||
def config(self, *args, **kwargs):
|
||||
self.configure(*args, **kwargs)
|
||||
|
||||
def configure(self, *args, **kwargs):
|
||||
bg_changed = False
|
||||
|
||||
if "bg" in kwargs:
|
||||
self.fg_color = kwargs["bg"]
|
||||
bg_changed = True
|
||||
kwargs["bg"] = CTkThemeManager.single_color(self.fg_color, self.appearance_mode)
|
||||
elif "background" in kwargs:
|
||||
self.fg_color = kwargs["background"]
|
||||
bg_changed = True
|
||||
kwargs["background"] = CTkThemeManager.single_color(self.fg_color, self.appearance_mode)
|
||||
elif "fg_color" in kwargs:
|
||||
self.fg_color = kwargs["fg_color"]
|
||||
kwargs["bg"] = CTkThemeManager.single_color(self.fg_color, self.appearance_mode)
|
||||
del kwargs["fg_color"]
|
||||
bg_changed = True
|
||||
|
||||
elif len(args) > 0 and type(args[0]) == dict:
|
||||
if "bg" in args[0]:
|
||||
self.fg_color=args[0]["bg"]
|
||||
bg_changed = True
|
||||
args[0]["bg"] = CTkThemeManager.single_color(self.fg_color, self.appearance_mode)
|
||||
elif "background" in args[0]:
|
||||
self.fg_color=args[0]["background"]
|
||||
bg_changed = True
|
||||
args[0]["background"] = CTkThemeManager.single_color(self.fg_color, self.appearance_mode)
|
||||
|
||||
if bg_changed:
|
||||
from .customtkinter_slider import CTkSlider
|
||||
from .customtkinter_progressbar import CTkProgressBar
|
||||
from .customtkinter_label import CTkLabel
|
||||
from .customtkinter_frame import CTkFrame
|
||||
from .customtkinter_entry import CTkEntry
|
||||
from customtkinter.widgets.customtkinter_checkbox import CTkCheckBox
|
||||
from customtkinter.widgets.customtkinter_button import CTkButton
|
||||
|
||||
for child in self.winfo_children():
|
||||
if isinstance(child, (CTkFrame, CTkButton, CTkLabel, CTkSlider, CTkCheckBox, CTkEntry, CTkProgressBar)):
|
||||
child.configure(bg_color=self.fg_color)
|
||||
|
||||
super().configure(*args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def enable_macos_dark_title_bar():
|
||||
if sys.platform == "darwin": # macOS
|
||||
if Version(platform.python_version()) < Version("3.10"):
|
||||
if Version(tkinter.Tcl().call("info", "patchlevel")) >= Version("8.6.9"): # Tcl/Tk >= 8.6.9
|
||||
os.system("defaults write -g NSRequiresAquaSystemAppearance -bool No")
|
||||
|
||||
@staticmethod
|
||||
def disable_macos_dark_title_bar():
|
||||
if sys.platform == "darwin": # macOS
|
||||
if Version(platform.python_version()) < Version("3.10"):
|
||||
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 windows_set_titlebar_color(self, color_mode: str):
|
||||
"""
|
||||
Set the titlebar color of the window to light or dark theme on Microsoft Windows.
|
||||
|
||||
Credits for this function:
|
||||
https://stackoverflow.com/questions/23836000/can-i-change-the-title-bar-in-tkinter/70724666#70724666
|
||||
|
||||
MORE INFO:
|
||||
https://docs.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
|
||||
"""
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
|
||||
super().withdraw() # hide window so that it can be redrawn after the titlebar change so that the color change is visible
|
||||
if not self.window_exists:
|
||||
super().update()
|
||||
|
||||
if color_mode.lower() == "dark":
|
||||
value = 1
|
||||
elif color_mode.lower() == "light":
|
||||
value = 0
|
||||
else:
|
||||
return
|
||||
|
||||
try:
|
||||
hwnd = ctypes.windll.user32.GetParent(self.winfo_id())
|
||||
DWMWA_USE_IMMERSIVE_DARK_MODE = 20
|
||||
DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = 19
|
||||
|
||||
# try with DWMWA_USE_IMMERSIVE_DARK_MODE
|
||||
if ctypes.windll.dwmapi.DwmSetWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE,
|
||||
ctypes.byref(ctypes.c_int(value)),
|
||||
ctypes.sizeof(ctypes.c_int(value))) != 0:
|
||||
|
||||
# try with DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20h1
|
||||
ctypes.windll.dwmapi.DwmSetWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1,
|
||||
ctypes.byref(ctypes.c_int(value)),
|
||||
ctypes.sizeof(ctypes.c_int(value)))
|
||||
|
||||
except Exception as err:
|
||||
print(err)
|
||||
|
||||
if self.window_exists:
|
||||
self.deiconify()
|
||||
|
||||
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
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
if self.appearance_mode == 1:
|
||||
self.windows_set_titlebar_color("dark")
|
||||
else:
|
||||
self.windows_set_titlebar_color("light")
|
||||
|
||||
super().configure(bg=CTkThemeManager.single_color(self.fg_color, self.appearance_mode))
|
169
customtkinter/widgets/customtkinter_toplevel.py
Normal file
169
customtkinter/widgets/customtkinter_toplevel.py
Normal file
@ -0,0 +1,169 @@
|
||||
import tkinter
|
||||
from distutils.version import StrictVersion as Version
|
||||
import sys
|
||||
import os
|
||||
import platform
|
||||
import ctypes
|
||||
|
||||
from ..appearance_mode_tracker import AppearanceModeTracker
|
||||
from ..customtkinter_theme_manager import CTkThemeManager
|
||||
|
||||
|
||||
class CTkToplevel(tkinter.Toplevel):
|
||||
def __init__(self, *args,
|
||||
fg_color="default_theme",
|
||||
**kwargs):
|
||||
|
||||
self.enable_macos_dark_title_bar()
|
||||
super().__init__(*args, **kwargs)
|
||||
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
|
||||
|
||||
self.fg_color = CTkThemeManager.theme["color"]["window_bg_color"] if fg_color == "default_theme" else fg_color
|
||||
|
||||
if "bg" in kwargs:
|
||||
self.fg_color = kwargs["bg"]
|
||||
del kwargs["bg"]
|
||||
elif "background" in kwargs:
|
||||
self.fg_color = kwargs["background"]
|
||||
del kwargs["background"]
|
||||
|
||||
# add set_appearance_mode method to callback list of AppearanceModeTracker for appearance mode changes
|
||||
AppearanceModeTracker.add(self.set_appearance_mode, self)
|
||||
super().configure(bg=CTkThemeManager.single_color(self.fg_color, self.appearance_mode))
|
||||
super().title("CTkToplevel")
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
if self.appearance_mode == 1:
|
||||
self.windows_set_titlebar_color("dark")
|
||||
else:
|
||||
self.windows_set_titlebar_color("light")
|
||||
|
||||
def destroy(self):
|
||||
AppearanceModeTracker.remove(self.set_appearance_mode)
|
||||
self.disable_macos_dark_title_bar()
|
||||
super().destroy()
|
||||
|
||||
def resizable(self, *args, **kwargs):
|
||||
super().resizable(*args, **kwargs)
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
if self.appearance_mode == 1:
|
||||
self.windows_set_titlebar_color("dark")
|
||||
else:
|
||||
self.windows_set_titlebar_color("light")
|
||||
|
||||
def config(self, *args, **kwargs):
|
||||
self.configure(*args, **kwargs)
|
||||
|
||||
def configure(self, *args, **kwargs):
|
||||
bg_changed = False
|
||||
|
||||
if "bg" in kwargs:
|
||||
self.fg_color = kwargs["bg"]
|
||||
bg_changed = True
|
||||
kwargs["bg"] = CTkThemeManager.single_color(self.fg_color, self.appearance_mode)
|
||||
elif "background" in kwargs:
|
||||
self.fg_color = kwargs["background"]
|
||||
bg_changed = True
|
||||
kwargs["background"] = CTkThemeManager.single_color(self.fg_color, self.appearance_mode)
|
||||
elif "fg_color" in kwargs:
|
||||
self.fg_color = kwargs["fg_color"]
|
||||
kwargs["bg"] = CTkThemeManager.single_color(self.fg_color, self.appearance_mode)
|
||||
del kwargs["fg_color"]
|
||||
bg_changed = True
|
||||
|
||||
elif len(args) > 0 and type(args[0]) == dict:
|
||||
if "bg" in args[0]:
|
||||
self.fg_color=args[0]["bg"]
|
||||
bg_changed = True
|
||||
args[0]["bg"] = CTkThemeManager.single_color(self.fg_color, self.appearance_mode)
|
||||
elif "background" in args[0]:
|
||||
self.fg_color=args[0]["background"]
|
||||
bg_changed = True
|
||||
args[0]["background"] = CTkThemeManager.single_color(self.fg_color, self.appearance_mode)
|
||||
|
||||
if bg_changed:
|
||||
from .customtkinter_slider import CTkSlider
|
||||
from .customtkinter_progressbar import CTkProgressBar
|
||||
from .customtkinter_label import CTkLabel
|
||||
from .customtkinter_frame import CTkFrame
|
||||
from .customtkinter_entry import CTkEntry
|
||||
from customtkinter.widgets.customtkinter_checkbox import CTkCheckBox
|
||||
from customtkinter.widgets.customtkinter_button import CTkButton
|
||||
|
||||
for child in self.winfo_children():
|
||||
if isinstance(child, (CTkFrame, CTkButton, CTkLabel, CTkSlider, CTkCheckBox, CTkEntry, CTkProgressBar)):
|
||||
child.configure(bg_color=self.fg_color)
|
||||
|
||||
super().configure(*args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def enable_macos_dark_title_bar():
|
||||
if sys.platform == "darwin": # macOS
|
||||
if Version(platform.python_version()) < Version("3.10"):
|
||||
if Version(tkinter.Tcl().call("info", "patchlevel")) >= Version("8.6.9"): # Tcl/Tk >= 8.6.9
|
||||
os.system("defaults write -g NSRequiresAquaSystemAppearance -bool No")
|
||||
|
||||
@staticmethod
|
||||
def disable_macos_dark_title_bar():
|
||||
if sys.platform == "darwin": # macOS
|
||||
if Version(platform.python_version()) < Version("3.10"):
|
||||
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 windows_set_titlebar_color(self, color_mode: str):
|
||||
"""
|
||||
Set the titlebar color of the window to light or dark theme on Microsoft Windows.
|
||||
|
||||
Credits for this function:
|
||||
https://stackoverflow.com/questions/23836000/can-i-change-the-title-bar-in-tkinter/70724666#70724666
|
||||
|
||||
MORE INFO:
|
||||
https://docs.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
|
||||
"""
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
|
||||
super().withdraw() # hide window so that it can be redrawn after the titlebar change so that the color change is visible
|
||||
super().update()
|
||||
|
||||
if color_mode.lower() == "dark":
|
||||
value = 1
|
||||
elif color_mode.lower() == "light":
|
||||
value = 0
|
||||
else:
|
||||
return
|
||||
|
||||
try:
|
||||
hwnd = ctypes.windll.user32.GetParent(self.winfo_id())
|
||||
DWMWA_USE_IMMERSIVE_DARK_MODE = 20
|
||||
DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = 19
|
||||
|
||||
# try with DWMWA_USE_IMMERSIVE_DARK_MODE
|
||||
if ctypes.windll.dwmapi.DwmSetWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE,
|
||||
ctypes.byref(ctypes.c_int(value)),
|
||||
ctypes.sizeof(ctypes.c_int(value))) != 0:
|
||||
# try with DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20h1
|
||||
ctypes.windll.dwmapi.DwmSetWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1,
|
||||
ctypes.byref(ctypes.c_int(value)),
|
||||
ctypes.sizeof(ctypes.c_int(value)))
|
||||
|
||||
except Exception as err:
|
||||
print(err)
|
||||
|
||||
self.deiconify()
|
||||
|
||||
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
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
if self.appearance_mode == 1:
|
||||
self.windows_set_titlebar_color("dark")
|
||||
else:
|
||||
self.windows_set_titlebar_color("light")
|
||||
|
||||
super().configure(bg=CTkThemeManager.single_color(self.fg_color, self.appearance_mode))
|
Reference in New Issue
Block a user