├── CTkThemeMaker ├── CTkExample.py └── ThemeMaker.py ├── LICENSE ├── README.md └── requirements.txt /CTkThemeMaker/CTkExample.py: -------------------------------------------------------------------------------- 1 | import tkinter 2 | import tkinter.messagebox 3 | import customtkinter 4 | from PIL import Image 5 | import os 6 | 7 | customtkinter.set_appearance_mode("System") 8 | 9 | DIRPATH = os.path.dirname(os.path.abspath(__file__)) 10 | 11 | themepath = os.path.join(DIRPATH, "CTkTheme_test.json") 12 | 13 | if os.path.exists(themepath): 14 | customtkinter.set_default_color_theme(themepath) 15 | else: 16 | customtkinter.set_default_color_theme("blue") 17 | 18 | class App(customtkinter.CTk): 19 | def __init__(self): 20 | super().__init__() 21 | 22 | # configure window 23 | self.title("CustomTkinter Example") 24 | self.geometry(f"{1000}x{580}") 25 | self.bind("<1>", lambda event: event.widget.focus_set()) 26 | # configure grid layout (4x4) 27 | self.grid_columnconfigure(1, weight=1) 28 | self.grid_columnconfigure((2, 3), weight=0) 29 | self.grid_rowconfigure((0, 1, 2), weight=1) 30 | # create sidebar frame with widgets 31 | self.sidebar_frame = customtkinter.CTkFrame(self, width=140, corner_radius=0) 32 | self.sidebar_frame.grid(row=0, column=0, rowspan=4, sticky="nsew") 33 | self.sidebar_frame.grid_rowconfigure(4, weight=1) 34 | self.logo_label = customtkinter.CTkLabel(self.sidebar_frame, text="CustomTkinter", font=customtkinter.CTkFont(size=20, weight="bold")) 35 | self.logo_label.grid(row=0, column=0, padx=20, pady=(20, 10)) 36 | self.sidebar_button_1 = customtkinter.CTkButton(self.sidebar_frame, command=lambda: print("Button Clicked")) 37 | self.sidebar_button_1.grid(row=1, column=0, padx=20, pady=10) 38 | self.sidebar_button_2 = customtkinter.CTkButton(self.sidebar_frame, command=lambda: print("Button Clicked")) 39 | self.sidebar_button_2.grid(row=3, column=0, padx=20, pady=10) 40 | 41 | self.image = customtkinter.CTkImage(Image.open(os.path.join(os.path.dirname(customtkinter.__file__), "assets","icons", 42 | "CustomTkinter_icon_Windows.ico")), size=(100,100)) 43 | self.Image_label = customtkinter.CTkLabel(self.sidebar_frame, text="", image=self.image) 44 | self.Image_label.grid(row=4, column=0, padx=20, pady=10) 45 | self.appearance_mode_label = customtkinter.CTkLabel(self.sidebar_frame, text="Appearance Mode:", anchor="w") 46 | self.appearance_mode_label.grid(row=5, column=0, padx=20, pady=(10, 0)) 47 | self.appearance_mode_optionemenu = customtkinter.CTkOptionMenu(self.sidebar_frame, values=["Light", "Dark", "System"], 48 | command=self.change_appearance_mode_event) 49 | self.appearance_mode_optionemenu.grid(row=6, column=0, padx=20, pady=(10, 10)) 50 | self.scaling_label = customtkinter.CTkLabel(self.sidebar_frame, text="UI Scaling:", anchor="w") 51 | self.scaling_label.grid(row=7, column=0, padx=20, pady=(10, 0)) 52 | self.scaling_optionemenu = customtkinter.CTkOptionMenu(self.sidebar_frame, values=["80%", "90%", "100%", "110%", "120%"], 53 | command=self.change_scaling_event) 54 | self.scaling_optionemenu.grid(row=8, column=0, padx=20, pady=(10, 20)) 55 | 56 | # create main entry and button 57 | self.entry = customtkinter.CTkEntry(self, placeholder_text="CTkEntry") 58 | self.entry.grid(row=3, column=1, columnspan=2, padx=(20, 0), pady=(20, 20), sticky="nsew") 59 | 60 | self.main_button_1 = customtkinter.CTkButton(master=self, fg_color="transparent", border_width=2) 61 | self.main_button_1.grid(row=3, column=3, padx=(20, 20), pady=(20, 20), sticky="nsew") 62 | 63 | # create textbox 64 | self.textbox = customtkinter.CTkTextbox(self, width=250) 65 | self.textbox.grid(row=0, column=1, padx=(20, 0), pady=(20, 0), sticky="nsew") 66 | 67 | # create tabview 68 | self.tabview = customtkinter.CTkTabview(self, width=250) 69 | self.tabview.grid(row=0, column=2, padx=(20, 0), pady=(20, 0), sticky="nsew") 70 | self.tabview.add("CTkTabview") 71 | self.tabview.add("Frame") 72 | self.tabview.tab("CTkTabview").grid_columnconfigure(0, weight=1) # configure grid of individual tabs 73 | self.tabview.tab("Frame").grid_columnconfigure(0, weight=1) 74 | 75 | self.optionmenu_1 = customtkinter.CTkOptionMenu(self.tabview.tab("CTkTabview"), dynamic_resizing=False, 76 | values=["Value 1", "Value 2"]) 77 | self.optionmenu_1.grid(row=0, column=0, padx=20, pady=(20, 10)) 78 | self.combobox_1 = customtkinter.CTkComboBox(self.tabview.tab("CTkTabview"), 79 | values=["Value 1", "Value 2"]) 80 | self.combobox_1.grid(row=1, column=0, padx=20, pady=(10, 10)) 81 | self.string_input_button = customtkinter.CTkButton(self.tabview.tab("CTkTabview"), text="Open CTkInputDialog", 82 | command=self.open_input_dialog_event) 83 | self.string_input_button.grid(row=2, column=0, padx=20, pady=(10, 10)) 84 | self.label_tab_2 = customtkinter.CTkFrame(self.tabview.tab("Frame"), height=150) 85 | self.label_tab_2.grid(row=0, column=0, padx=(5,0), pady=20) 86 | 87 | self.scrollbar = customtkinter.CTkScrollbar(self.tabview.tab("Frame"), height=150) 88 | self.scrollbar.grid(row=0, column=1, padx=5, pady=20, sticky="nse") 89 | 90 | self.scrollbar.set(1,0.1) 91 | 92 | # create radiobutton frame 93 | self.radiobutton_frame = customtkinter.CTkFrame(self) 94 | self.radiobutton_frame.grid(row=0, column=3, padx=(20, 20), pady=(20, 0), sticky="nsew") 95 | self.radio_var = tkinter.IntVar(value=0) 96 | self.label_radio_group = customtkinter.CTkLabel(master=self.radiobutton_frame, text="CTkRadioButtons") 97 | self.label_radio_group.grid(row=0, column=2, columnspan=1, padx=10, pady=10, sticky="") 98 | self.radio_button_1 = customtkinter.CTkRadioButton(master=self.radiobutton_frame, variable=self.radio_var, value=0) 99 | self.radio_button_1.grid(row=1, column=2, pady=10, padx=20, sticky="n") 100 | self.radio_button_2 = customtkinter.CTkRadioButton(master=self.radiobutton_frame, variable=self.radio_var, value=1) 101 | self.radio_button_2.grid(row=2, column=2, pady=10, padx=20, sticky="n") 102 | self.radio_button_3 = customtkinter.CTkRadioButton(master=self.radiobutton_frame, variable=self.radio_var, value=2) 103 | self.radio_button_3.grid(row=3, column=2, pady=10, padx=20, sticky="n") 104 | 105 | # create checkbox and switch frame 106 | self.checkbox_slider_frame = customtkinter.CTkFrame(self) 107 | self.checkbox_slider_frame.grid(row=1, column=3, padx=(20, 20), pady=(20, 0), sticky="nsew") 108 | self.checkbox_1 = customtkinter.CTkCheckBox(master=self.checkbox_slider_frame) 109 | self.checkbox_1.grid(row=1, column=0, pady=(20, 10), padx=20, sticky="n") 110 | self.checkbox_2 = customtkinter.CTkCheckBox(master=self.checkbox_slider_frame) 111 | self.checkbox_2.grid(row=2, column=0, pady=10, padx=20, sticky="n") 112 | self.switch_1 = customtkinter.CTkSwitch(master=self.checkbox_slider_frame, command=lambda: print("switch 1 toggle")) 113 | self.switch_1.grid(row=3, column=0, pady=10, padx=20, sticky="n") 114 | self.switch_2 = customtkinter.CTkSwitch(master=self.checkbox_slider_frame) 115 | self.switch_2.grid(row=4, column=0, pady=(10, 20), padx=20, sticky="n") 116 | 117 | # create slider and progressbar frame 118 | self.slider_progressbar_frame = customtkinter.CTkFrame(self) 119 | self.slider_progressbar_frame.grid(row=1, column=1, columnspan=2, padx=(20, 0), pady=(20, 0), sticky="nsew") 120 | self.slider_progressbar_frame.grid_columnconfigure(0, weight=1) 121 | self.slider_progressbar_frame.grid_rowconfigure(4, weight=1) 122 | self.seg_button_1 = customtkinter.CTkSegmentedButton(self.slider_progressbar_frame) 123 | self.seg_button_1.grid(row=0, column=0, padx=(20, 10), pady=(10, 10), sticky="ew") 124 | self.progressbar_1 = customtkinter.CTkProgressBar(self.slider_progressbar_frame) 125 | self.progressbar_1.grid(row=1, column=0, padx=(20, 10), pady=(10, 10), sticky="ew") 126 | self.progressbar_2 = customtkinter.CTkProgressBar(self.slider_progressbar_frame) 127 | self.progressbar_2.grid(row=2, column=0, padx=(20, 10), pady=(10, 10), sticky="ew") 128 | self.slider_1 = customtkinter.CTkSlider(self.slider_progressbar_frame, from_=0, to=1, number_of_steps=4) 129 | self.slider_1.grid(row=3, column=0, padx=(20, 10), pady=(10, 10), sticky="ew") 130 | self.slider_2 = customtkinter.CTkSlider(self.slider_progressbar_frame, orientation="vertical") 131 | self.slider_2.grid(row=0, column=1, rowspan=5, padx=(10, 10), pady=(10, 10), sticky="ns") 132 | self.progressbar_3 = customtkinter.CTkProgressBar(self.slider_progressbar_frame, orientation="vertical") 133 | self.progressbar_3.grid(row=0, column=2, rowspan=5, padx=(10, 20), pady=(10, 10), sticky="ns") 134 | 135 | label = customtkinter.CTkLabel(self.sidebar_frame, text="create toplevel", font=("",13)) 136 | label.grid() 137 | 138 | label.bind("", lambda event: self.new_window()) 139 | label.bind("", lambda event: label.configure(font=("",13,"underline"), cursor="hand2")) 140 | label.bind("", lambda event: label.configure(font=("",13), cursor="arrow")) 141 | 142 | # set default values 143 | self.sidebar_button_2.configure(state="disabled", text="Disabled CTkButton") 144 | self.checkbox_2.configure(state="disabled") 145 | self.switch_2.configure(state="disabled") 146 | self.checkbox_1.select() 147 | self.switch_1.select() 148 | self.radio_button_3.configure(state="disabled") 149 | self.appearance_mode_optionemenu.set("Dark") 150 | self.scaling_optionemenu.set("100%") 151 | self.optionmenu_1.set("CTkOptionmenu") 152 | self.combobox_1.set("CTkComboBox") 153 | self.slider_1.configure(command=self.progressbar_2.set) 154 | self.slider_2.configure(command=self.progressbar_3.set) 155 | self.progressbar_1.configure(mode="indeterminnate") 156 | self.progressbar_1.start() 157 | self.textbox.insert("0.0", "Write Something...") 158 | self.seg_button_1.configure(values=["CTkSegmentedButton", "Value 2", "Value 3"]) 159 | self.seg_button_1.set("Value 2") 160 | 161 | def open_input_dialog_event(self): 162 | dialog = customtkinter.CTkInputDialog(text="Type in a number:", title="CTkInputDialog") 163 | print("CTkInputDialog: ", dialog.get_input()) 164 | 165 | def new_window(self): 166 | top_level = customtkinter.CTkToplevel() 167 | customtkinter.CTkLabel(top_level, text="A TopLevel Window").grid(padx=100, pady=100) 168 | top_level.attributes("-topmost", 1) 169 | self.after(100, lambda: top_level.attributes("-topmost", 0)) 170 | 171 | def change_appearance_mode_event(self, new_appearance_mode: str): 172 | customtkinter.set_appearance_mode(new_appearance_mode) 173 | 174 | def change_scaling_event(self, new_scaling: str): 175 | new_scaling_float = int(new_scaling.replace("%", "")) / 100 176 | customtkinter.set_widget_scaling(new_scaling_float) 177 | 178 | if __name__ == "__main__": 179 | app = App() 180 | app.mainloop() 181 | -------------------------------------------------------------------------------- /CTkThemeMaker/ThemeMaker.py: -------------------------------------------------------------------------------- 1 | import tkinter 2 | import customtkinter 3 | from tkinter.colorchooser import askcolor 4 | import json 5 | import os 6 | import subprocess 7 | import sys 8 | import copy 9 | 10 | """ 11 | Author: Akash Bora (Akascape) 12 | License: MIT 13 | Quick Guide: 14 | This program can be used to create custom themes for customtkinter. 15 | You can easily create and edit themes for your applications. 16 | Customtkinter themefiles are .json files that can be used with customtkinter using the 'set_default_color_theme' method. 17 | Example: customtkinter.set_default_color_theme("Path//my_theme.json") 18 | A customtkinter theme has one dark and one light color attribute for each widget type and you have to choose the 2 colors for each widget type. 19 | (You can switch between them with the 'set_appearance_mode' method) 20 | Currently it is not possible to switch themes, so only appearance_mode can be changed. 21 | Default reset color is "transparent" which has no color, means it take the color from the background instead. 22 | (transparent is not supported in all widgets) 23 | """ 24 | 25 | class App(customtkinter.CTk): 26 | 27 | #--------------------Main Structure of the Theme File--------------------# 28 | 29 | json_data = { 30 | "CTk": { 31 | "fg_color": ["gray92", "gray14"] 32 | }, 33 | "CTkToplevel": { 34 | "fg_color": ["gray92", "gray14"] 35 | }, 36 | "CTkFrame": { 37 | "corner_radius": 6, 38 | "border_width": 0, 39 | "fg_color": ["gray86", "gray17"], 40 | "top_fg_color": ["gray81", "gray20"], 41 | "border_color": ["gray65", "gray28"] 42 | }, 43 | "CTkButton": { 44 | "corner_radius": 6, 45 | "border_width": 0, 46 | "fg_color": ["#3B8ED0", "#1F6AA5"], 47 | "hover_color": ["#36719F", "#144870"], 48 | "border_color": ["#3E454A", "#949A9F"], 49 | "text_color": ["#DCE4EE", "#DCE4EE"], 50 | "text_color_disabled": ["gray74", "gray60"] 51 | }, 52 | "CTkLabel": { 53 | "corner_radius": 0, 54 | "fg_color": "transparent", 55 | "text_color": ["gray10", "#DCE4EE"] 56 | }, 57 | "CTkEntry": { 58 | "corner_radius": 6, 59 | "border_width": 2, 60 | "fg_color": ["#F9F9FA", "#343638"], 61 | "border_color": ["#979DA2", "#565B5E"], 62 | "text_color":["gray10", "#DCE4EE"], 63 | "placeholder_text_color": ["gray52", "gray62"] 64 | }, 65 | "CTkCheckBox": { 66 | "corner_radius": 6, 67 | "border_width": 3, 68 | "fg_color": ["#3B8ED0", "#1F6AA5"], 69 | "border_color": ["#3E454A", "#949A9F"], 70 | "hover_color": ["#3B8ED0", "#1F6AA5"], 71 | "checkmark_color": ["#DCE4EE", "gray90"], 72 | "text_color": ["gray10", "#DCE4EE"], 73 | "text_color_disabled": ["gray60", "gray45"] 74 | }, 75 | "CTkSwitch": { 76 | "corner_radius": 1000, 77 | "border_width": 3, 78 | "button_length": 0, 79 | "fg_color": ["#939BA2", "#4A4D50"], 80 | "progress_color": ["#3B8ED0", "#1F6AA5"], 81 | "button_color": ["gray36", "#D5D9DE"], 82 | "button_hover_color": ["gray20", "gray100"], 83 | "text_color": ["gray10", "#DCE4EE"], 84 | "text_color_disabled": ["gray60", "gray45"] 85 | }, 86 | "CTkRadioButton": { 87 | "corner_radius": 1000, 88 | "border_width_checked": 6, 89 | "border_width_unchecked": 3, 90 | "fg_color": ["#3B8ED0", "#1F6AA5"], 91 | "border_color": ["#3E454A", "#949A9F"], 92 | "hover_color": ["#36719F", "#144870"], 93 | "text_color": ["gray10", "#DCE4EE"], 94 | "text_color_disabled": ["gray60", "gray45"] 95 | }, 96 | "CTkProgressBar": { 97 | "corner_radius": 1000, 98 | "border_width": 0, 99 | "fg_color": ["#939BA2", "#4A4D50"], 100 | "progress_color": ["#3B8ED0", "#1F6AA5"], 101 | "border_color": ["gray", "gray"] 102 | }, 103 | "CTkSlider": { 104 | "corner_radius": 1000, 105 | "button_corner_radius": 1000, 106 | "border_width": 6, 107 | "button_length": 0, 108 | "fg_color": ["#939BA2", "#4A4D50"], 109 | "progress_color": ["gray40", "#AAB0B5"], 110 | "button_color": ["#3B8ED0", "#1F6AA5"], 111 | "button_hover_color": ["#36719F", "#144870"] 112 | }, 113 | "CTkOptionMenu": { 114 | "corner_radius": 6, 115 | "fg_color": ["#3B8ED0", "#1F6AA5"], 116 | "button_color": ["#36719F", "#144870"], 117 | "button_hover_color": ["#27577D", "#203A4F"], 118 | "text_color": ["#DCE4EE", "#DCE4EE"], 119 | "text_color_disabled": ["gray74", "gray60"] 120 | }, 121 | "CTkComboBox": { 122 | "corner_radius": 6, 123 | "border_width": 2, 124 | "fg_color": ["#F9F9FA", "#343638"], 125 | "border_color": ["#979DA2", "#565B5E"], 126 | "button_color": ["#979DA2", "#565B5E"], 127 | "button_hover_color": ["#6E7174", "#7A848D"], 128 | "text_color": ["gray10", "#DCE4EE"], 129 | "text_color_disabled": ["gray50", "gray45"] 130 | }, 131 | "CTkScrollbar": { 132 | "corner_radius": 1000, 133 | "border_spacing": 4, 134 | "fg_color": "transparent", 135 | "button_color": ["gray55", "gray41"], 136 | "button_hover_color": ["gray40", "gray53"] 137 | }, 138 | "CTkSegmentedButton": { 139 | "corner_radius": 6, 140 | "border_width": 2, 141 | "fg_color": ["#979DA2", "gray29"], 142 | "selected_color": ["#3B8ED0", "#1F6AA5"], 143 | "selected_hover_color": ["#36719F", "#144870"], 144 | "unselected_color": ["#979DA2", "gray29"], 145 | "unselected_hover_color": ["gray70", "gray41"], 146 | "text_color": ["#DCE4EE", "#DCE4EE"], 147 | "text_color_disabled": ["gray74", "gray60"] 148 | }, 149 | "CTkTextbox": { 150 | "corner_radius": 6, 151 | "border_width": 0, 152 | "fg_color": ["#F9F9FA", "#1D1E1E"], 153 | "border_color": ["#979DA2", "#565B5E"], 154 | "text_color":["gray10", "#DCE4EE"], 155 | "scrollbar_button_color": ["gray55", "gray41"], 156 | "scrollbar_button_hover_color": ["gray40", "gray53"] 157 | }, 158 | "CTkScrollableFrame": { 159 | "label_fg_color": ["gray78", "gray23"] 160 | }, 161 | "DropdownMenu": { 162 | "fg_color": ["gray90", "gray20"], 163 | "hover_color": ["gray75", "gray28"], 164 | "text_color": ["gray10", "gray90"] 165 | }, 166 | "CTkFont": { 167 | "macOS": { 168 | "family": "SF Display", 169 | "size": 13, 170 | "weight": "normal" 171 | }, 172 | "Windows": { 173 | "family": "Roboto", 174 | "size": 13, 175 | "weight": "normal" 176 | }, 177 | "Linux": { 178 | "family": "Roboto", 179 | "size": 13, 180 | "weight": "normal" 181 | } 182 | } 183 | } 184 | 185 | 186 | #--------------------Widget Type and Content--------------------# 187 | 188 | widgets = {'CTk':['fg_color'], 189 | 'CTkToplevel':['fg_color'], 190 | 'CTkFrame':['fg_color', 'top_fg_color', 'border_color'], 191 | 'CTkButton':['fg_color','hover_color','border_color','text_color','text_color_disabled'], 192 | 'CTkCheckBox':["fg_color", "border_color", "hover_color","checkmark_color", "text_color", 193 | "text_color_disabled"], 194 | 'CTkEntry':['fg_color','text_color','border_color','placeholder_text_color'], 195 | 'CTkLabel':['fg_color', 'text_color'], 196 | 'CTkProgressBar':['fg_color','progress_color','border_color'], 197 | 'CTkSlider':["fg_color", "progress_color", "button_color", "button_hover_color"], 198 | 'CTkSwitch':["fg_Color", "progress_color", "button_color", "button_hover_color", 199 | "text_color", "text_color_disabled"], 200 | 'CTkOptionMenu':["fg_color", "button_color", "button_hover_color","text_color", 201 | "text_color_disabled"], 202 | 'CTkComboBox':["fg_color", "border_color", "button_color", "button_hover_color", 203 | "text_color", "text_color_disabled"], 204 | 'CTkScrollbar':["fg_color", "button_color", "button_hover_color"], 205 | 'CTkRadioButton':["fg_color", "border_color", "hover_color", "text_color", "text_color_disabled"], 206 | 'CTkTextbox':["fg_color", "border_color", "text_color", "scrollbar_button_color", 207 | "scrollbar_button_hover_color"], 208 | 'CTkSegmentedButton':["fg_color", "selected_color", "selected_hover_color", "unselected_color", 209 | "unselected_hover_color", "text_color", "text_color_disabled"], 210 | 'CTkScrollableFrame':["label_fg_color"], 211 | 'DropdownMenu':["fg_color", "hover_color", "text_color"]} 212 | 213 | widgetlist = [key for key in widgets] 214 | current = widgetlist[0] 215 | 216 | for i in json_data: 217 | for key, value in json_data.get(i).items(): 218 | if value=="transparent": 219 | json_data[i][key] = ["transparent", "transparent"] 220 | 221 | def __init__(self): 222 | 223 | #--------------------Main root Window--------------------# 224 | super().__init__(fg_color=customtkinter.ThemeManager.theme["CTkFrame"]["top_fg_color"]) 225 | customtkinter.set_default_color_theme("blue") 226 | self.title("CustomTkinter ThemeMaker") 227 | self.geometry("500x450") 228 | self.protocol("WM_DELETE_WINDOW", self.on_closing) 229 | self.grid_columnconfigure((0,1,2,3,4,5), weight=1) 230 | self.grid_rowconfigure(2, weight=1) 231 | 232 | self.frame_info = customtkinter.CTkFrame(master=self, height=80) 233 | self.frame_info.grid(row=0, column=0, columnspan=6, sticky="nswe", padx=20, pady=20) 234 | self.frame_info.grid_columnconfigure(0, weight=1) 235 | 236 | self.widget_type = customtkinter.CTkLabel(master=self.frame_info, text=self.current, corner_radius=10, width=200, height=20, 237 | fg_color=("white", "gray38")) 238 | self.widget_type.grid(row=0, column=0, sticky="nswe", padx=80, pady=20) 239 | 240 | self.left_button = customtkinter.CTkButton(master=self.frame_info, text="<", width=20, height=20, corner_radius=10, 241 | fg_color=("white", "gray38"), command=self.change_mode_left, text_color=("black","white")) 242 | self.left_button.grid(row=0, column=0, sticky="nsw", padx=20, pady=20) 243 | 244 | self.right_button = customtkinter.CTkButton(master=self.frame_info, text=">", width=20, height=20, corner_radius=10, 245 | fg_color=("white", "gray38"), command=self.change_mode_right, text_color=("black","white")) 246 | self.right_button.grid(row=0, column=0, sticky="nse", padx=20, pady=20) 247 | 248 | self.menu = customtkinter.CTkOptionMenu(master=self, fg_color=("white", "gray38"), button_color=("white", "gray38"), text_color=("black","white"), 249 | height=30, values=list(self.widgets.items())[0][1], command=self.update) 250 | self.menu.grid(row=1, column=0, columnspan=6, sticky="nswe", padx=20) 251 | 252 | self.button_light = customtkinter.CTkButton(master=self, height=100, width=200, corner_radius=10, border_color="white", 253 | text_color="grey50", border_width=2, text="Light", hover=False, command=self.change_color_light) 254 | self.button_light.grid(row=2, column=0, sticky="nswe", columnspan=3, padx=(20,5), pady=20) 255 | 256 | self.button_dark = customtkinter.CTkButton(master=self, height=100, width=200, corner_radius=10, border_color="white", 257 | text_color="gray80", border_width=2, text="Dark", hover=False, 258 | command=self.change_color_dark) 259 | self.button_dark.grid(row=2, column=3, sticky="nswe", columnspan=3, padx=(5,20), pady=20) 260 | 261 | self.button_load = customtkinter.CTkButton(master=self, height=40, width=110, text="Load Theme", command=self.load) 262 | self.button_load.grid(row=3, column=0, columnspan=2, sticky="nswe", padx=(20,5), pady=(0,20)) 263 | 264 | self.button_export = customtkinter.CTkButton(master=self, height=40, width=110, text="Save Theme", command=self.save) 265 | self.button_export.grid(row=3, column=2, columnspan=2, sticky="nswe", padx=(5,5), pady=(0,20)) 266 | 267 | self.button_reset = customtkinter.CTkButton(master=self, height=40, width=110, text="Reset", command=self.reset) 268 | self.button_reset.grid(row=3, column=4, columnspan=2, sticky="nswe", padx=(5,20), pady=(0,20)) 269 | 270 | self.palette = customtkinter.CTkButton(master=self, height=40, width=110, text="Color Palette", command=self.show_colors) 271 | self.palette.grid(row=4, column=0, columnspan=3, sticky="nswe", padx=(20,5), pady=(0,20)) 272 | 273 | self.quick_test = customtkinter.CTkButton(master=self, height=40, width=110, text="QUICK TEST", command=self.test) 274 | self.quick_test.grid(row=4, column=3, columnspan=3, sticky="nswe", padx=(5,20), pady=(0,20)) 275 | 276 | 277 | self.update(None) 278 | 279 | #--------------------App Functions--------------------# 280 | 281 | def change_mode_right(self): 282 | """ changing current widget type wih right button """ 283 | self.widgetlist.append(self.widgetlist.pop(0)) 284 | self.current = self.widgetlist[0] 285 | self.widget_type.configure(text=self.current) 286 | self.menu.configure(values=self.widgets[self.current]) 287 | self.menu.set(self.widgets[self.current][0]) 288 | self.update(self.menu.get()) 289 | 290 | def change_mode_left(self): 291 | """ changing current widget type with left button """ 292 | self.widgetlist.insert(0, self.widgetlist.pop()) 293 | self.current = self.widgetlist[0] 294 | self.widget_type.configure(text=self.current) 295 | self.menu.configure(values=self.widgets[self.current]) 296 | self.menu.set(self.widgets[self.current][0]) 297 | self.update(self.menu.get()) 298 | 299 | def update(self, value): 300 | """ updating the widgets and their colors """ 301 | for i in self.json_data[self.current]: 302 | if i==self.menu.get(): 303 | if (self.json_data[self.current][i])[0]!="transparent": 304 | self.button_light.configure(fg_color=(self.json_data[self.current][i])[0]) 305 | else: 306 | self.button_light.configure(fg_color="transparent") 307 | if (self.json_data[self.current][i])[1]!="transparent": 308 | self.button_dark.configure(fg_color=(self.json_data[self.current][i])[1]) 309 | else: 310 | self.button_dark.configure(fg_color="transparent") 311 | 312 | def change_color_light(self): 313 | """ choosing the color for Light mode of the theme """ 314 | default = self.button_light._apply_appearance_mode(self.button_light._fg_color) 315 | if default=="transparent": 316 | default = "white" 317 | color1 = askcolor(title="Choose color for "+self.menu.get()+" (Light)", 318 | initialcolor=default) 319 | if color1[1] is not None: 320 | self.button_light.configure(fg_color=color1[1]) 321 | for i in self.json_data[self.current]: 322 | if i==self.menu.get(): 323 | (self.json_data[self.current][i])[0] = color1[1] 324 | 325 | def change_color_dark(self): 326 | """ choosing the color for Dark mode of the theme """ 327 | default = self.button_dark._apply_appearance_mode(self.button_dark._fg_color) 328 | if default=="transparent": 329 | default = "white" 330 | color2 = askcolor(title="Choose color for "+self.menu.get()+" (Dark)", 331 | initialcolor=default) 332 | if color2[1] is not None: 333 | self.button_dark.configure(fg_color=color2[1]) 334 | for i in self.json_data[self.current]: 335 | if i==self.menu.get(): 336 | (self.json_data[self.current][i])[1] = color2[1] 337 | 338 | def save(self): 339 | """ exporting the theme file """ 340 | save_file = tkinter.filedialog.asksaveasfilename(initialfile="Untitled.json", defaultextension=".json", 341 | filetypes=[('json', ['*.json']),('All Files', '*.*')]) 342 | try: 343 | export_data = copy.deepcopy(self.json_data) 344 | for i in export_data: 345 | for j in export_data[i]: 346 | if export_data[i][j]==["transparent", "transparent"]: 347 | export_data[i][j] = "transparent" 348 | if save_file: 349 | with open(save_file, "w") as f: 350 | json.dump(export_data, f, indent=2) 351 | f.close() 352 | tkinter.messagebox.showinfo("Exported!","Theme saved successfully!") 353 | except: 354 | tkinter.messagebox.showerror("Error!","Something went wrong!") 355 | 356 | def load(self): 357 | """ load any theme file """ 358 | global json_data 359 | open_json = tkinter.filedialog.askopenfilename(filetypes=[('json', ['*.json']),('All Files', '*.*')]) 360 | try: 361 | if open_json: 362 | with open(open_json) as f: 363 | self.json_data = json.load(f) 364 | 365 | for i in self.json_data: 366 | for key, value in self.json_data.get(i).items(): 367 | if value=="transparent": 368 | self.json_data[i][key] = ["transparent", "transparent"] 369 | 370 | self.update(self.menu.get()) 371 | except: 372 | tkinter.messagebox.showerror("Error!","Unable to load this theme file!") 373 | 374 | def reset(self): 375 | """ resetting the current colors of the widget to null (default value) """ 376 | for i in self.json_data[self.current]: 377 | if i==self.menu.get(): 378 | self.json_data[self.current][i][0] = "transparent" 379 | self.button_light.configure(fg_color="transparent") 380 | self.json_data[self.current][i][1] = "transparent" 381 | self.button_dark.configure(fg_color="transparent") 382 | 383 | def test(self): 384 | """ function for quickly testing the theme """ 385 | DIRPATH = os.path.dirname(os.path.abspath(__file__)) 386 | 387 | program = os.path.join(DIRPATH, "CTkExample.py") 388 | 389 | if not os.path.exists(program): 390 | tkinter.messagebox.showerror("Sorry!","Cannot test the theme, example program is missing!") 391 | return 392 | 393 | export_data = copy.deepcopy(self.json_data) 394 | for i in export_data: 395 | for j in export_data[i]: 396 | if export_data[i][j]==["transparent", "transparent"]: 397 | export_data[i][j] = "transparent" 398 | 399 | with open(os.path.join(DIRPATH, "CTkTheme_test.json"), "w") as f: 400 | json.dump(export_data, f, indent=2) 401 | 402 | if sys.platform.startswith("win"): 403 | subprocess.run(["python", program]) 404 | else: 405 | subprocess.run(["python3", program]) 406 | 407 | def replace_color(self, color, button, mode): 408 | """ replace a specific color """ 409 | if color=="transparent": 410 | default = "white" 411 | else: 412 | default = color 413 | new_color = askcolor(title=f"Replace this color: {color}", initialcolor=default)[1] 414 | if new_color is None: 415 | new_color = "transparent" 416 | if mode: 417 | for i in self.json_data: 418 | for j in self.json_data[i]: 419 | if type(self.json_data[i][j]) is list: 420 | if self.json_data[i][j][1]==color: 421 | self.json_data[i][j][1] = new_color 422 | 423 | else: 424 | for i in self.json_data: 425 | for j in self.json_data[i]: 426 | if type(self.json_data[i][j]) is list: 427 | if self.json_data[i][j][0]==color: 428 | self.json_data[i][j][0] = new_color 429 | try: button.configure(text=new_color, fg_color=new_color) 430 | except: pass 431 | self.update(self.menu.get()) 432 | 433 | def show_colors(self): 434 | """ show the color palette for the theme """ 435 | toplevel = customtkinter.CTkToplevel() 436 | toplevel.resizable(True, True) 437 | toplevel.geometry("500x700") 438 | toplevel.title("Color Palette") 439 | toplevel.transient(self) 440 | toplevel.grab_set() 441 | 442 | frame_light = customtkinter.CTkScrollableFrame(toplevel, label_text="Light Colors") 443 | frame_light.pack(fill="both", expand=True, side="left", padx=(10,5), pady=10) 444 | 445 | frame_dark = customtkinter.CTkScrollableFrame(toplevel, label_text="Dark Colors") 446 | frame_dark.pack(fill="both", expand=True, side="right", padx=(5,10), pady=10) 447 | 448 | set_dark = set() 449 | set_light = set() 450 | 451 | for i in self.json_data: 452 | for j in self.json_data[i]: 453 | if type(self.json_data[i][j]) is list: 454 | set_dark.add(self.json_data[i][j][1]) 455 | set_light.add(self.json_data[i][j][0]) 456 | 457 | for color in set_dark: 458 | button = customtkinter.CTkButton(frame_dark, text=color, fg_color=color, hover=False) 459 | button.configure(command=lambda x=color, y=button: self.replace_color(x, y, 1)) 460 | button.pack(fill="x", expand=True, padx=10, pady=5) 461 | 462 | for color in set_light: 463 | button = customtkinter.CTkButton(frame_light, text=color, fg_color=color, hover=False) 464 | button.configure(command=lambda x=color, y=button: self.replace_color(x, y, 0)) 465 | button.pack(fill="x", expand=True, padx=10, pady=5) 466 | 467 | def on_closing(self): 468 | """ close the program """ 469 | quit_ = tkinter.messagebox.askokcancel(title="Exit?", message= "Do you want to exit?") 470 | if quit_: 471 | self.destroy() 472 | 473 | if __name__ == "__main__": 474 | app = App() 475 | app.mainloop() 476 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Akash Bora 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CTkThemeMaker 2 | A quick and easy theme builder for customtkinter! 3 | 4 | ## Features: 5 | - Create custom themes 6 | - Save themes 7 | - Load themes 8 | - **Quickly test** your themes with a built-in example 9 | - Simple interface 10 | - Color Palette for quick color replacements 11 | - No extra package installation required, just run the program and make your theme 😤 12 | 13 | ## Download 14 | ### [GitHub repo size](https://github.com/Akascape/CTkThemeMaker/archive/refs/heads/main.zip) 15 | 16 | **Compatible ctk version: 5.2.0+** 17 | 18 | Extract the zip file and run `CTkThemeMaker.py` 19 | ### [](https://github.com/Akascape/CTkThemeMaker/discussions/new?category=contribute-theme) 20 | 21 | ![Screenshot](https://github.com/Akascape/CTkThemeMaker/assets/89206401/69f91aa8-377e-4017-8a7d-9c7fb0ce110d) 22 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | tk 2 | customtkinter 3 | pillow 4 | --------------------------------------------------------------------------------