From ccd104032934ad15248c40b271f1cf6c34293f41 Mon Sep 17 00:00:00 2001 From: Tom Schimansky Date: Wed, 25 Aug 2021 17:02:16 +0200 Subject: [PATCH] added CTkCheckBox --- Readme.md | 49 ++- customtkinter/__init__.py | 5 +- customtkinter/customtkinter_button.py | 10 +- customtkinter/customtkinter_checkbox.py | 322 +++++++++++++++++++ customtkinter/customtkinter_color_manager.py | 5 +- customtkinter/customtkinter_dialog.py | 5 +- customtkinter/customtkinter_label.py | 5 +- customtkinter/customtkinter_progressbar.py | 3 +- customtkinter/customtkinter_slider.py | 5 +- examples/simple_example.py | 22 +- examples/simple_example_images.py | 7 +- setup.py | 2 +- 12 files changed, 405 insertions(+), 35 deletions(-) create mode 100644 customtkinter/customtkinter_checkbox.py diff --git a/Readme.md b/Readme.md index 613d24d..8733f81 100644 --- a/Readme.md +++ b/Readme.md @@ -97,7 +97,7 @@ enable the macOS darkmode, and used multpiple CTkFrames. It has some kind of a menu on the left side, and I used all CustomTkinter elements there are at the moment.Maybe this is a good reference if you want to create your own application with this library. -(Code: /complex_example.py) +(Code: /complex_example.py) With macOS darkmode turned on, it looks like this: @@ -228,6 +228,53 @@ text_color | entry text color, tuple: (light_color, dark_color) or single color text_font | entry text font, tuple: (font_name, size) +### CTkCheckBox +Examle Code: + +```python +checkbox = customtkinter.CTkCheckBox(master=root_tk, + text="CTkCheckBox") +checkbox.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER) +``` +
+Show all arguments: + +argument | value +--- | --- +master | root, tkinter.Frame or CTkFrame +text | string +width | box width in px +height | box height in px +corner_radius | corner radius in px +border_width | box border width in px +fg_color | forground (inside) color, tuple: (light_color, dark_color) or single color +bg_color | background color, tuple: (light_color, dark_color) or single color +border_color | border color, tuple: (light_color, dark_color) or single color +hover_color | hover color, tuple: (light_color, dark_color) or single color +text_color | text color, tuple: (light_color, dark_color) or single color +text_font | button text font, tuple: (font_name, size) +hover | enable/disable hover effect: True, False +state | tkinter.NORMAL (standard) or tkinter.DISABLED (not clickable, darker color) + +CTkCheckBox Methods: +```python +CTkCheckBox.get() # 1 or 0 (checked or not checked) +CTkCheckBox.set_text(new_text) +CTkCheckBox.select() # turns on checkbox +CTkCheckBox.deselect() # turns off checkbox +CTkCheckBox.toggle() # change check state of checkbox +CTkCheckBox.configure_color(bg_color=new_bg_color, + fg_color=new_fg_color, + hover_color=new_hover_color, + text_color=new_text_color) + +CTkCheckBox.configure(state=tkinter.DISABLED) +CTkCheckBox.configure(state=tkinter.NORMAL) +checkbox_state = CTkCheckBox.state +``` + +
+ ### CTkSlider Example Code: ```python diff --git a/customtkinter/__init__.py b/customtkinter/__init__.py index 75402dd..abae2e2 100644 --- a/customtkinter/__init__.py +++ b/customtkinter/__init__.py @@ -7,6 +7,7 @@ from .customtkinter_progressbar import CTkProgressBar from .customtkinter_label import CTkLabel from .customtkinter_entry import CTkEntry from .customtkinter_dialog import CTkDialog +from .customtkinter_checkbox import CTkCheckBox from .appearance_mode_tracker import AppearanceModeTracker, SystemAppearanceModeListenerNoThread from .customtkinter_color_manager import CTkColorManager @@ -53,10 +54,6 @@ def get_appearance_mode(): return "Dark" -def set_theme(main_color): - CTkColorManager.set_theme(main_color) - - def deactivate_threading(): AppearanceModeTracker.init_listener_function(no_thread=True) sys.stderr.write("WARNING (customtkinter.deactivate_threading): Automatic threaded search for a change of the " + diff --git a/customtkinter/customtkinter_button.py b/customtkinter/customtkinter_button.py index 93f5d3c..fe54229 100644 --- a/customtkinter/customtkinter_button.py +++ b/customtkinter/customtkinter_button.py @@ -38,11 +38,11 @@ class CTkButton(tkinter.Frame): AppearanceModeTracker.add(self.set_appearance_mode) - if fg_color is None: - self.fg_color = self.bg_color - else: - self.fg_color = fg_color - self.hover_color = hover_color + self.fg_color = self.bg_color if fg_color is None else fg_color + self.hover_color = self.bg_color if hover_color is None else hover_color + + self.fg_color = self.bg_color if self.fg_color is None else self.fg_color + self.border_color = border_color self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark" diff --git a/customtkinter/customtkinter_checkbox.py b/customtkinter/customtkinter_checkbox.py new file mode 100644 index 0000000..c438586 --- /dev/null +++ b/customtkinter/customtkinter_checkbox.py @@ -0,0 +1,322 @@ +import tkinter +import sys + +from .customtkinter_frame import CTkFrame +from .appearance_mode_tracker import AppearanceModeTracker +from .customtkinter_color_manager import CTkColorManager + + +class CTkCheckBox(tkinter.Frame): + """ tkinter custom checkbox with border, rounded corners and hover effect """ + + def __init__(self, + bg_color=None, + fg_color=CTkColorManager.MAIN, + hover_color=CTkColorManager.MAIN_HOVER, + border_color=CTkColorManager.CHECKBOX_LINES, + border_width=3, + width=24, + height=24, + corner_radius=5, + text_font=None, + text_color=CTkColorManager.TEXT, + text="CTkCheckBox", + hover=True, + state=tkinter.NORMAL, + *args, **kwargs): + super().__init__(*args, **kwargs) + + if bg_color is None: + if isinstance(self.master, CTkFrame): + self.bg_color = self.master.fg_color + else: + self.bg_color = self.master.cget("bg") + else: + self.bg_color = bg_color + + AppearanceModeTracker.add(self.set_appearance_mode) + + self.fg_color = CTkColorManager.MAIN if fg_color is None else fg_color + self.hover_color = CTkColorManager.MAIN_HOVER if hover_color is None else hover_color + self.border_color = border_color + + self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark" + + self.width = width + self.height = height + + if corner_radius*2 > self.height: + self.corner_radius = self.height/2 + elif corner_radius*2 > self.width: + self.corner_radius = self.width/2 + else: + self.corner_radius = corner_radius + + self.border_width = border_width + + if self.corner_radius >= self.border_width: + self.inner_corner_radius = self.corner_radius - self.border_width + else: + self.inner_corner_radius = 0 + + self.text = text + self.text_color = text_color + if text_font is None: + if sys.platform == "darwin": # macOS + self.text_font = ("Avenir", 13) + elif "win" in sys.platform: # Windows + self.text_font = ("Century Gothic", 11) + else: + self.text_font = ("TkDefaultFont") + else: + self.text_font = text_font + + self.state = state + self.hover = hover + self.check_state = False + + self.canvas = tkinter.Canvas(master=self, + highlightthicknes=0, + width=self.width, + height=self.height, + cursor="pointinghand") + self.canvas.pack(side='left') + + if self.hover is True: + self.canvas.bind("", self.on_enter) + self.canvas.bind("", self.on_leave) + + self.canvas.bind("", self.toggle) + self.canvas.bind("", self.toggle) + + self.canvas_fg_parts = [] + self.canvas_border_parts = [] + self.canvas_check_parts = [] + self.text_label = None + + self.draw() + + def draw(self): + self.canvas.delete("all") + self.canvas_fg_parts = [] + self.canvas_border_parts = [] + self.canvas_check_parts = [] + + if type(self.bg_color) == tuple and len(self.bg_color) == 2: + self.canvas.configure(bg=self.bg_color[self.appearance_mode]) + else: + self.canvas.configure(bg=self.bg_color) + + # border button parts + if self.border_width > 0: + + if self.corner_radius > 0: + self.canvas_border_parts.append(self.canvas.create_oval(0, + 0, + self.corner_radius * 2, + self.corner_radius * 2)) + self.canvas_border_parts.append(self.canvas.create_oval(self.width - self.corner_radius * 2, + 0, + self.width, + self.corner_radius * 2)) + self.canvas_border_parts.append(self.canvas.create_oval(0, + self.height - self.corner_radius * 2, + self.corner_radius * 2, + self.height)) + self.canvas_border_parts.append(self.canvas.create_oval(self.width - self.corner_radius * 2, + self.height - self.corner_radius * 2, + self.width, + self.height)) + + self.canvas_border_parts.append(self.canvas.create_rectangle(0, + self.corner_radius, + self.width, + self.height - self.corner_radius)) + self.canvas_border_parts.append(self.canvas.create_rectangle(self.corner_radius, + 0, + self.width - self.corner_radius, + self.height)) + + # inner button parts + + if self.corner_radius > 0: + self.canvas_fg_parts.append(self.canvas.create_oval(self.border_width, + self.border_width, + self.border_width + self.inner_corner_radius * 2, + self.border_width + self.inner_corner_radius * 2)) + self.canvas_fg_parts.append(self.canvas.create_oval(self.width - self.border_width - self.inner_corner_radius * 2, + self.border_width, + self.width - self.border_width, + self.border_width + self.inner_corner_radius * 2)) + self.canvas_fg_parts.append(self.canvas.create_oval(self.border_width, + self.height - self.border_width - self.inner_corner_radius * 2, + self.border_width + self.inner_corner_radius * 2, + self.height-self.border_width)) + self.canvas_fg_parts.append(self.canvas.create_oval(self.width - self.border_width - self.inner_corner_radius * 2, + self.height - self.border_width - self.inner_corner_radius * 2, + self.width - self.border_width, + self.height - self.border_width)) + + self.canvas_fg_parts.append(self.canvas.create_rectangle(self.border_width + self.inner_corner_radius, + self.border_width, + self.width - self.border_width - self.inner_corner_radius, + self.height - self.border_width)) + self.canvas_fg_parts.append(self.canvas.create_rectangle(self.border_width, + self.border_width + self.inner_corner_radius, + self.width - self.border_width, + self.height - self.inner_corner_radius - self.border_width)) + + for part in self.canvas_fg_parts: + if type(self.bg_color) == tuple and len(self.bg_color) == 2: + self.canvas.itemconfig(part, fill=self.bg_color[self.appearance_mode], width=0) + else: + self.canvas.itemconfig(part, fill=self.bg_color, outline=self.bg_color, width=0) + + for part in self.canvas_border_parts: + if type(self.border_color) == tuple and len(self.border_color) == 2: + self.canvas.itemconfig(part, fill=self.border_color[self.appearance_mode], width=0) + else: + self.canvas.itemconfig(part, fill=self.border_color, outline=self.border_color, width=0) + + if self.text_label is not None: + self.text_label.pack_forget() + + self.text_label = tkinter.Label(master=self, + text=self.text, + font=self.text_font) + self.text_label.pack(side='right', padx="4") + + if type(self.text_color) == tuple and len(self.text_color) == 2: + self.text_label.configure(fg=self.text_color[self.appearance_mode]) + else: + self.text_label.configure(fg=self.text_color) + + if type(self.bg_color) == tuple and len(self.text_color) == 2: + self.configure(bg=self.bg_color[self.appearance_mode]) + self.text_label.configure(bg=self.bg_color[self.appearance_mode]) + else: + self.configure(bg=self.bg_color) + self.text_label.configure(bg=self.bg_color) + + self.set_text(self.text) + + def configure_color(self, bg_color=None, fg_color=None, hover_color=None, text_color=None): + if bg_color is not None: + self.bg_color = bg_color + else: + self.bg_color = self.master.cget("bg") + + if fg_color is not None: + self.fg_color = fg_color + + if hover_color is not None: + self.hover_color = hover_color + + if text_color is not None: + self.text_color = text_color + + self.draw() + + def config(self, *args, **kwargs): + self.configure(*args, **kwargs) + + def configure(self, *args, **kwargs): + if "text" in kwargs: + self.set_text(kwargs["text"]) + del kwargs["text"] + + if "state" in kwargs: + self.set_state(kwargs["state"]) + del kwargs["state"] + + super().configure(*args, **kwargs) + + def set_state(self, state): + self.state = state + + if self.state == tkinter.DISABLED: + self.hover = False + if sys.platform == "darwin": + self.canvas.configure(cursor="arrow") + + elif self.state == tkinter.NORMAL: + self.hover = True + if sys.platform == "darwin": + self.canvas.configure(cursor="pointinghand") + + self.draw() + + def set_text(self, text): + self.text = text + if self.text_label is not None: + self.text_label.configure(text=self.text, width=len(self.text)) + else: + sys.stderr.write("ERROR (CTkButton): Cant change text because button has no text.") + + def on_enter(self, event=0): + if self.hover is True: + for part in self.canvas_fg_parts: + if type(self.hover_color) == tuple and len(self.hover_color) == 2: + self.canvas.itemconfig(part, fill=self.hover_color[self.appearance_mode], width=0) + else: + self.canvas.itemconfig(part, fill=self.hover_color, width=0) + + def on_leave(self, event=0): + if self.hover is True: + if self.check_state == True: + for part in self.canvas_fg_parts: + if type(self.fg_color) == tuple and len(self.fg_color) == 2: + self.canvas.itemconfig(part, fill=self.fg_color[self.appearance_mode], width=0) + else: + self.canvas.itemconfig(part, fill=self.fg_color, width=0) + else: + for part in self.canvas_fg_parts: + if type(self.bg_color) == tuple and len(self.bg_color) == 2: + self.canvas.itemconfig(part, fill=self.bg_color[self.appearance_mode], width=0) + else: + self.canvas.itemconfig(part, fill=self.bg_color, width=0) + + def toggle(self, event=0): + if self.state == tkinter.NORMAL: + if self.check_state is True: + self.check_state = False + for part in self.canvas_fg_parts: + if type(self.bg_color) == tuple and len(self.bg_color) == 2: + self.canvas.itemconfig(part, fill=self.bg_color[self.appearance_mode], width=0) + else: + self.canvas.itemconfig(part, fill=self.bg_color, width=0) + else: + self.check_state = True + for part in self.canvas_fg_parts: + if type(self.fg_color) == tuple and len(self.fg_color) == 2: + self.canvas.itemconfig(part, fill=self.fg_color[self.appearance_mode], width=0) + else: + self.canvas.itemconfig(part, fill=self.fg_color, width=0) + + def select(self): + self.check_state = True + for part in self.canvas_fg_parts: + if type(self.fg_color) == tuple and len(self.fg_color) == 2: + self.canvas.itemconfig(part, fill=self.fg_color[self.appearance_mode], width=0) + else: + self.canvas.itemconfig(part, fill=self.fg_color, width=0) + + def deselect(self): + self.check_state = False + for part in self.canvas_fg_parts: + if type(self.bg_color) == tuple and len(self.bg_color) == 2: + self.canvas.itemconfig(part, fill=self.bg_color[self.appearance_mode], width=0) + else: + self.canvas.itemconfig(part, fill=self.bg_color, width=0) + + def get(self): + return 1 if self.check_state is True else 0 + + def set_appearance_mode(self, mode_string): + if mode_string.lower() == "dark": + self.appearance_mode = 1 + elif mode_string.lower() == "light": + self.appearance_mode = 0 + + self.draw() + diff --git a/customtkinter/customtkinter_color_manager.py b/customtkinter/customtkinter_color_manager.py index b047442..595b132 100644 --- a/customtkinter/customtkinter_color_manager.py +++ b/customtkinter/customtkinter_color_manager.py @@ -10,6 +10,7 @@ class CTkColorManager: PROGRESS_BG = ("#6B6B6B", "#222222") FRAME = ("#D4D5D6", "#3F3F3F") FRAME_2 = ("#BFBEC1", "#505050") + CHECKBOX_LINES = ("black", "#ededed") DARKEN_COLOR_FACTOR = 0.8 # used for generate color for disabled button @@ -34,8 +35,8 @@ class CTkColorManager: @classmethod def set_theme_color(cls, hex_color, hex_color_hover): - cls.MAIN = (hex_color, hex_color) - cls.MAIN_HOVER = (hex_color_hover, hex_color_hover) + cls.MAIN = hex_color + cls.MAIN_HOVER = hex_color_hover @classmethod def set_theme(cls, main_color): diff --git a/customtkinter/customtkinter_dialog.py b/customtkinter/customtkinter_dialog.py index 54d6379..bd303cb 100644 --- a/customtkinter/customtkinter_dialog.py +++ b/customtkinter/customtkinter_dialog.py @@ -20,8 +20,9 @@ class CTkDialog: self.running = False self.height = len(text.split("\n"))*20 + 150 - self.fg_color = fg_color - self.hover_color = hover_color + + self.fg_color = CTkColorManager.MAIN if fg_color is None else fg_color + self.hover_color = CTkColorManager.MAIN_HOVER if hover_color is None else hover_color self.top = tkinter.Toplevel() self.top.geometry("300x{}".format(self.height)) diff --git a/customtkinter/customtkinter_label.py b/customtkinter/customtkinter_label.py index 268becd..9dce823 100644 --- a/customtkinter/customtkinter_label.py +++ b/customtkinter/customtkinter_label.py @@ -31,10 +31,7 @@ class CTkLabel(tkinter.Frame): else: self.bg_color = bg_color - if fg_color is None: - self.fg_color = self.bg_color - else: - self.fg_color = fg_color + self.fg_color = self.bg_color if fg_color is None else fg_color self.text_color = text_color self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark" diff --git a/customtkinter/customtkinter_progressbar.py b/customtkinter/customtkinter_progressbar.py index 63102d2..fba65b2 100644 --- a/customtkinter/customtkinter_progressbar.py +++ b/customtkinter/customtkinter_progressbar.py @@ -29,7 +29,8 @@ class CTkProgressBar(tkinter.Frame): self.border_color = border_color self.fg_color = fg_color - self.progress_color = progress_color + self.progress_color = CTkColorManager.MAIN if progress_color is None else progress_color + self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark" self.width = width diff --git a/customtkinter/customtkinter_slider.py b/customtkinter/customtkinter_slider.py index 2daf72a..1328be5 100644 --- a/customtkinter/customtkinter_slider.py +++ b/customtkinter/customtkinter_slider.py @@ -32,8 +32,9 @@ class CTkSlider(tkinter.Frame): self.border_color = border_color self.fg_color = fg_color - self.button_color = button_color - self.button_hover_color = button_hover_color + self.button_color = self.bg_color if button_color is None else button_color + self.button_hover_color = self.bg_color if button_hover_color is None else button_hover_color + self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark" self.width = width diff --git a/examples/simple_example.py b/examples/simple_example.py index 50c7511..4abe2f5 100644 --- a/examples/simple_example.py +++ b/examples/simple_example.py @@ -3,21 +3,22 @@ import customtkinter # <- import the CustomTkinter module customtkinter.enable_macos_darkmode() customtkinter.set_appearance_mode("System") # Other: "Dark", "Light" +# customtkinter.set_theme_color(("red2", "red3"), ("red3", "red4")) root_tk = tkinter.Tk() # create the Tk window like you normally do -root_tk.geometry("400x240") +root_tk.geometry("400x300") root_tk.title("CustomTkinter Test") def button_function(): - print("button pressed") + print("CheckBox value:", checkbox_1.get()) def slider_function(value): progressbar_1.set(value) -frame_1 = customtkinter.CTkFrame(master=root_tk, width=300, height=200, corner_radius=15) +frame_1 = customtkinter.CTkFrame(master=root_tk, width=300, height=260, corner_radius=15) frame_1.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER) label_1 = customtkinter.CTkLabel(master=frame_1) @@ -27,18 +28,17 @@ progressbar_1 = customtkinter.CTkProgressBar(master=frame_1) progressbar_1.place(relx=0.5, rely=0.25, anchor=tkinter.CENTER) button_1 = customtkinter.CTkButton(master=frame_1, corner_radius=10, command=button_function) -button_1.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER) - -#button_1.configure(state="disabled") - -for child in button_1.winfo_children(): - child.configure(state='disable') +button_1.place(relx=0.5, rely=0.4, anchor=tkinter.CENTER) +# button_1.configure(state="disabled") slider_1 = customtkinter.CTkSlider(master=frame_1, command=slider_function) -slider_1.place(relx=0.5, rely=0.7, anchor=tkinter.CENTER) +slider_1.place(relx=0.5, rely=0.55, anchor=tkinter.CENTER) entry_1 = customtkinter.CTkEntry(master=frame_1) -entry_1.place(relx=0.5, rely=0.85, anchor=tkinter.CENTER) +entry_1.place(relx=0.5, rely=0.75, anchor=tkinter.CENTER) + +checkbox_1 = customtkinter.CTkCheckBox(master=frame_1) +checkbox_1.place(relx=0.5, rely=0.9, anchor=tkinter.CENTER) root_tk.mainloop() customtkinter.disable_macos_darkmode() diff --git a/examples/simple_example_images.py b/examples/simple_example_images.py index 911eec1..c224c59 100644 --- a/examples/simple_example_images.py +++ b/examples/simple_example_images.py @@ -1,6 +1,9 @@ import tkinter import customtkinter # <- import the CustomTkinter module from PIL import Image, ImageTk # <- import PIL for the images +import os + +PATH = os.path.dirname(os.path.realpath(__file__)) customtkinter.enable_macos_darkmode() customtkinter.set_appearance_mode("System") # Other: "Dark", "Light" @@ -15,8 +18,8 @@ def button_function(): # load images as PhotoImage -settings_image = ImageTk.PhotoImage(Image.open("test_images/settings.png").resize((40, 40))) -bell_image = ImageTk.PhotoImage(Image.open("test_images/bell.png").resize((40, 40))) +settings_image = ImageTk.PhotoImage(Image.open(PATH + "/test_images/settings.png").resize((40, 40))) +bell_image = ImageTk.PhotoImage(Image.open(PATH + "/test_images/bell.png").resize((40, 40))) frame_1 = customtkinter.CTkFrame(master=root_tk, width=300, height=200, corner_radius=15) frame_1.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER) diff --git a/setup.py b/setup.py index 118b840..b3499bb 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ def read(filename): setup(name="customtkinter", - version="1.1", + version="1.2", author="Tom Schimansky", license="Creative Commons Zero v1.0 Universal", url="https://github.com/TomSchimansky/CustomTkinter",