[Python] CustomTkinter 簡易カスタムテーマ作成ツール

# 1. はじめに

CustomTkinterは簡単におしゃれなGUIを実現可能なライブラリで素晴らしいのですが、いまいちどう設定していいのかわかりにくいところもあります。

そこで以下のリンク先でウィジェットプレビューツールを作りました。

https://qiita.com/takafi/items/1714a8db183ee0e7f2fc

しかしながら、このウィジェットプレビューツールを拡張してカスタムテーマ作成ツールを作るのは設定項目が多すぎて、ちょっと使いにくそうだったので、もう少し簡易的にカスタムテーマを作れるツールを検討してみました。

できた画面は以下の通りです。

![theme_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3079481/3a31dc5a-a75a-9edf-8767-8eba1b2a1599.png)

動かしてみると以下のような感じです。

![theme_600_5fps.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3079481/c409ccbc-f64b-ba5b-ea36-52674f1c9151.gif)


## 1.1. 開発環境

Windows 10 Pro 64bit
Python 3.9.13
CustomTkinter 5.1.2


# 2. CustomTkinterのカスタムテーマ設定

CustomTkinterにはテーマ設定をjsonでまとめておいて、一括読み込みする手段が用意されています。

```Python:json設定ファイルの読み込み方
customtkinter.set_default_color_theme("./custom_theme.json")
```

参考となる<a href="https://github.com/TomSchimansky/CustomTkinter/tree/master/customtkinter/assets/themes">3種類のデフォルトテーマのjson</a>が公開されており、中身は以下のような形で各ウィジェットをキーとしてその下にオプション引数の設定が記述されています。

```json:
{
  "CTk": {
    "fg_color": ["gray92", "gray14"]
  },
  "CTkToplevel": {
    "fg_color": ["gray92", "gray14"]
  },
  "CTkFrame": {
    "corner_radius": 6,
    "border_width": 0,
    "fg_color": ["gray86", "gray17"],
    "top_fg_color": ["gray81", "gray20"],
    "border_color": ["gray65", "gray28"]
  },

...(後略)...
```

しかし用意されているとはいえ、このjsonを狙いのテーマにまとめる作業は大変そうで気が遠くなってしまいます。


# 3. 簡易カスタムテーマ作成ツール

上記のカスタムテーマについては<a href="https://github.com/TomSchimansky/CustomTkinter/wiki/Themes">公式Wiki</a>に記載がありますが、そもそもオプション引数だけではどれがどの色かわからん！ということで、[1.はじめに](#1はじめに) で紹介したプレビューツールを作ってみたりしていました。

しかしプレビューツールを作っている際に、変更がすぐ反映されるのは楽だし、面白いなーと思いましたが、これでカスタムテーマを作るのはやはり面倒だなと気付きました笑

そこでもう少し簡易的にカスタムテーマ作成を行えるツールを考えてみました。

## 3.1. 概要

CustomTkinterは背景などは基本的にグレーの階調を変えているだけで、メインとなる彩度の高い色は一部のみであるため、デフォルトテーマで設定されている **グレー以外の色を抽出して、それを置換する** ことで新たなカスタムテーマを作成するツールとしました。


## 3.2. 使い方

1. 上段フレームの3つのスライダーを左右に振る
（3つのスライダーはそれぞれ色相(Hue)、彩度(Saturation)、明度(Brightness/Value)を表しています）
2. 設定した配色が中段フレーム右側のウィジェットの色として確認できる。
3. 希望の配色になったら、下段フレームでファイル名を指定して、カスタムテーマのjsonが保存可能

## 3.3. ソースコードの説明

### 3.3.1. 色の設定について
スライダーで変化する色のベースとなっているのはデフォルトのblue、dark-blue、greenで設定されている色をHSVモデルで表記した値です。

HSVモデルは色相(Hue)、彩度(Saturation)、明度(Brightness/Value)の頭文字を取ったもので、一般的に知られるRGBとは異なる色の表し方をしたものですが、RGBより人にとって直感的に認識が可能です。

そのためスライダーでHSVの値を指定して、HSVからRGB、RGBから16進数表記に変換させて色の設定に使っています。

必要なライブラリは`colorsys`と`matplotlib.colors`です。`colorsys`は標準ライブラリなのでインストール不要で、`matplotlib`は`pip install`しておいてください。

```python:
import colorsys
import matplotlib.colors as mcolors
```

HSV値からRGB値への変換、RGB値から16進数表記への変換は以下のとおりです。

```Python:
#hue, saturation, brightnessは0～1
colorsys.hsv_to_rgb(hue, saturation, brightness)
```

```Python:
#R, G, Bは0～1
mcolors.to_hex((R, G, B))
```

したがって、以下のような配列を用意することで色相をグラデーションした配列を用意可能です。

```Python:
color_bar_list = [f'{mcolors.to_hex(colorsys.hsv_to_rgb(hue/72, 0.7, 0.65))}' for hue in range(72)]
```

### 3.3.2. ベースとなる色について

CustomTkinter において、blue.jsonでボタンなどの配色に使われる青い色を抜き出して16進数表記からRGBとHSVへ変換すると下表のようになります。RGBでは各数値が複雑に変化してしまいますが、HSVでは色相(Hue)は一定で、彩度・明度が変化していることがわかります。

|16進数表記|R|G|B|H|S|V|
|:----|:----|:----|:----|:----|:----|:----|
|`#27577D`|39|87|125|206|68|49|
|`#36719F`|54|113|159|206|66|62|
|`#3B8ED0`|59|142|208|206|71|81|
|`#1F6AA5`|31|106|165|206|81|64|
|`#144870`|20|72|112|206|82|43|
|`#203A4F`|32|58|79|206|59|30|

そのため、色相は全範囲（0～1）で変化させつつ、彩度と明度を範囲を絞って変化させることで、CustomTkinter のウィジェットの配色として破綻しない領域で自由に決められるようにしました。

実装としては以下のようにしています。

```Python:
    def color_set(self, event):
        hue, sat, val = self.slider_hsv[0].get(), self.slider_hsv[1].get(), self.slider_hsv[2].get()
        bases = [[0.53,0.4], [0.5,0.5], [0.55,0.7], [0.45, 0.25], [0.65,0.33], [0.66,0.49]]
        self.colors = [f'{mcolors.to_hex(colorsys.hsv_to_rgb(hue, base[0] + sat*0.3, base[1] + val*0.2))}' for i, base in enumerate(bases)]
        for i in range(len(bases), len(bases)*2):
            self.color_label[i].configure(fg_color=self.colors[i-len(bases)])
            self.label_color2.configure(fg_color=self.colors[5])
            self.color_label[i].delete(0, tk.END)
            self.color_label[i].insert(0, self.colors[i-len(bases)])
```

HSVの基となる値をスライダー（0～1で設定）から取得、ベースとなる彩度・明度は事前に配列で用意しておき、`colorsys.hsv_to_rgb(hue, base[0] + sat*0.3, base[1] + val*0.2)`という形で、ベースへの足し算で変化させるようにしています。

変化量としては彩度がベースから`+0.3`まで、明度がベースから`+0.2`までとすることで配色範囲を絞っています。

### 3.3.3. 本ツールで保存されるカスタムテーマのjsonファイルについて

ベースとなるjsonはpyファイル内に埋め込んでいます。

というのも、blue.jsonの色を抜き出していただけるとわかると思いますが、`#DCE4EE` などわずかに青みがかったグレーが存在しますが、これはgreen.jsonでも使われており、単なるグレーとしての位置づけとして設定されています。

デフォルトのテーマには、こうしたほぼグレーだけど`gray**`(**は0～100の数値)で書かれていない色が10数個存在します。

作者も気にしていないのだと思いますが、個人的には気になりますし、わかりにくいので、今回微妙な色がついたほぼグレー（`#DCE4EE`など）はすべて近しい`gray**`へ変換したjsonをpyファイル内に用意しています。

## 3.4. ソースコード


<details><summary>ソースコード</summary><div>

```python
import json
import colorsys
import tkinter as tk
from tkinter import filedialog
import customtkinter as ctk
from functools import partial
import matplotlib.colors as mcolors


class App(ctk.CTk):
    def __init__(self):
        super().__init__()
        self.title("Custom Theme Maker for CustomTkinter")
        self.geometry("600x420")
        # default color settings
        ctk.set_appearance_mode("dark") # "system":default value, "dark", "light"
        ctk.set_default_color_theme("blue")  # Themes: "blue" (standard), "green", "dark-blue"
        # set grid layout 1x2
        self.grid_rowconfigure(1, weight=1)
        self.grid_columnconfigure(0, weight=1)
        # font settings
        self.save_font = ctk.CTkFont(family='Yu Gothic', size=13, weight='bold')

        # color list
        self.blue = ['#27577D', '#36719F', '#3B8ED0', '#203A4F', '#144870', '#1F6AA5']
        self.dark_blue = ['#234567', '#325882', '#3a7ebf', '#1e2c40', '#14375e', '#1f538d']
        self.green = ['#0b6e3d', '#0C955A', '#2CC985', '#17472e', '#106A43', '#2FA572']
        self.color_bar_list = [f'{mcolors.to_hex(colorsys.hsv_to_rgb(hue/72, 0.7, 0.65))}' for hue in range(72)]

        # create frame
        self.frame = [ctk.CTkFrame(self, corner_radius=10) for _ in range(3)]
        self.frame[0].grid(row=0, column=0, padx=20, pady=(20,10), sticky='nsew')
        self.frame[1].grid(row=1, column=0, padx=20, pady=(0,10), ipadx=10, ipady=10, sticky='nsew')
        self.frame[2].grid(row=2, column=0, padx=20, pady=(0,20), sticky='nsew')
        self.frame[0].grid_columnconfigure(1, weight=1)
        self.frame[1].grid_rowconfigure((1,2,3), weight=1)
        self.frame[1].grid_columnconfigure((0,1,2,3), weight=1)
        self.frame[2].grid_columnconfigure(0, weight=1)

        # frame[0] - Color bar
        self.color_bar_frame = ctk.CTkFrame(self.frame[0], corner_radius=0, height=30, fg_color='transparent')
        self.color_bar_frame.grid(row=0, column=1, padx=(6, 16), pady=10, sticky='nsew')
        self.color_bar_frame.grid_rowconfigure(0, weight=1)
        self.color_bar_frame.grid_columnconfigure(tuple(range(len(self.color_bar_list))), weight=1)
        self.color_bar = [ctk.CTkLabel(self.color_bar_frame, corner_radius=0, fg_color=f'{color}', text='', padx=0, pady=0) for color in self.color_bar_list]
        for i in range(72):
            self.color_bar[i].grid(row=0, column=i, padx=0, pady=0, sticky='nsew')

        # frame[0] - Slider for selecting color
        self.label_hsv = [ctk.CTkLabel(self.frame[0], text=f'{item}') for item in ['Hue', 'Saturation', 'Brightness']]
        self.slider_hsv = [ctk.CTkSlider(self.frame[0], from_=0, to=1, number_of_steps=i, orientation="horizontal", command=self.color_set) for i in [72, 11, 11]]
        for i in range(3):
            self.label_hsv[i].grid(row=i+1, column=0, padx=(10,0), pady=(0 if i<2 else (0,10)), sticky='nsew')
            self.slider_hsv[i].grid(row=i+1, column=1, padx=(0,10), pady=(0 if i<2 else (0,10)), sticky='ew')

        # frame[1] - color sample
        self.label_color1 = ctk.CTkOptionMenu(self.frame[1], anchor="center", corner_radius=6,
                                              values=['blue.json', 'dark-blue.json', 'green.json'],
                                              command=self.reference_color)
        self.label_color1.grid(row=0, column=0, columnspan=2, padx=(10,3), pady=(10,3), sticky='ew')
        self.label_color2 = ctk.CTkLabel(self.frame[1], corner_radius=6, fg_color='#27577D', text='custom theme')
        self.label_color2.grid(row=0, column=2, columnspan=2, padx=(3,10), pady=(10,3), sticky='ew')
        self.color_label = [ctk.CTkEntry(self.frame[1], corner_radius=6, border_width=0, fg_color=f'{color}', justify="center") for color in self.blue*2]
        for i, color in enumerate(self.blue*2):
            self.color_label[i].grid(row=i%3+1, column=i//3, padx=((10,3) if i//3==0 else (3,10) if i//3==3 else 3), pady=((3,10) if i%3==3 else 3), sticky='nsew')
            self.color_label[i].insert(0, f'{color}')

        # frame[2] - save json file
        self.file_name_entry = ctk.CTkEntry(self.frame[2], border_width=1, placeholder_text="file name of json file to save")
        self.file_name_entry.grid(row=0, column=0, padx=(10,3), pady=(10,3), sticky='nsew')
        self.file_name_button = ctk.CTkButton(self.frame[2], text='Browse', font=self.save_font,
                                              fg_color='transparent', border_width=1, command=self.file_select)
        self.file_name_button.grid(row=0, column=1, padx=(3,10), pady=(10,3), sticky='e')
        self.save_button = ctk.CTkButton(self.frame[2], text='Save as json', font=self.save_font, command=self.save_json)
        self.save_button.grid(row=1, column=1, padx=(3,10), pady=(3,10), sticky='e')

    # 参考用カラーの表示設定
    def reference_color(self, choice):
        # OptionMenuから選択した情報を元にcolorに配色リストを代入
        color = self.blue if choice == 'blue.json' else self.green if choice == 'green.json' else self.dark_blue
        # 参考用カラーの適用
        self.label_color1.configure(fg_color=color[5], button_color=color[4], button_hover_color=color[3])
        self.save_button.configure(fg_color=color[5])
        for i in range(len(color)):
            self.color_label[i].configure(fg_color=color[i])
            self.color_label[i].delete(0, tk.END)
            self.color_label[i].insert(0, color[i])

    # カスタムテーマとなる配色の表示設定
    def color_set(self, event):
        # スライダーからHSVの数値を取得（0～1）
        hue, sat, val = self.slider_hsv[0].get(), self.slider_hsv[1].get(), self.slider_hsv[2].get()
        # 初期値となるHSV値のうちSaturationとValue(Brightness)のリスト
        bases = [[0.53,0.4], [0.5,0.5], [0.55,0.7], [0.45, 0.25], [0.65,0.33], [0.66,0.49]]
        # スライダーから取得したHSV値と初期値から16進数表記のカラーコードを生成
        self.colors = [f'{mcolors.to_hex(colorsys.hsv_to_rgb(hue, base[0] + sat*0.3, base[1] + val*0.2))}' for i, base in enumerate(bases)]
        # ウィジェットへ適用
        for i in range(len(bases), len(bases)*2):
            self.color_label[i].configure(fg_color=self.colors[i-len(bases)])
            self.label_color2.configure(fg_color=self.colors[5])
            self.color_label[i].delete(0, tk.END)
            self.color_label[i].insert(0, self.colors[i-len(bases)])

    # 名前をつけて保存のダイアログボックス表示
    def file_select(self):
        file_name = filedialog.asksaveasfilename(title="名前をつけて保存", filetypes=[("json file", ".json")], initialdir = "./")
        if file_name[-5:] != '.json' and file_name != '':
            file_name += '.json'
        if file_name != '':
            self.file_name_entry.delete(0, tk.END)
            self.file_name_entry.insert(0, file_name)

    # jsonファイルの保存
    def save_json(self):
        default_json = '''{
            "CTk": {
                "fg_color": ["gray95", "gray10"]
            },
            "CTkToplevel": {
                "fg_color": ["gray95", "gray10"]
            },
            "CTkFrame": {
                "corner_radius": 6,
                "border_width": 0,
                "fg_color": ["gray90", "gray13"],
                "top_fg_color": ["gray85", "gray16"],
                "border_color": ["gray65", "gray28"]
            },
            "CTkButton": {
                "corner_radius": 6,
                "border_width": 0,
                "fg_color": ["#3a7ebf", "#1f538d"],
                "hover_color": ["#325882", "#14375e"],
                "border_color": ["grey27", "grey60"],
                "text_color": ["grey89", "grey89"],
                "text_color_disabled": ["gray74", "gray60"]
            },
            "CTkLabel": {
                "corner_radius": 0,
                "fg_color": "transparent",
                "text_color": ["gray14", "gray84"]
            },
            "CTkEntry": {
                "corner_radius": 6,
                "border_width": 2,
                "fg_color": ["gray98", "gray21"],
                "border_color": ["gray61", "grey35"],
                "text_color": ["gray14", "gray84"],
                "placeholder_text_color": ["gray52", "gray62"]
            },
            "CTkCheckbox": {
                "corner_radius": 6,
                "border_width": 3,
                "fg_color": ["#3a7ebf", "#1f538d"],
                "border_color": ["grey27", "grey60"],
                "hover_color": ["#325882", "#14375e"],
                "checkmark_color": ["grey89", "gray90"],
                "text_color": ["gray14", "gray84"],
                "text_color_disabled": ["gray60", "gray45"]
            },
            "CTkSwitch": {
                "corner_radius": 1000,
                "border_width": 3,
                "button_length": 0,
                "fg_Color": ["gray60", "grey30"],
                "progress_color": ["#3a7ebf", "#1f538d"],
                "button_color": ["gray36", "gray85"],
                "button_hover_color": ["gray20", "gray100"],
                "text_color": ["gray14", "gray84"],
                "text_color_disabled": ["gray60", "gray45"]
            },
            "CTkRadiobutton": {
                "corner_radius": 1000,
                "border_width_checked": 6,
                "border_width_unchecked": 3,
                "fg_color": ["#3a7ebf", "#1f538d"],
                "border_color": ["grey27", "grey60"],
                "hover_color": ["#325882", "#14375e"],
                "text_color": ["gray14", "gray84"],
                "text_color_disabled": ["gray60", "gray45"]
            },
            "CTkProgressBar": {
                "corner_radius": 1000,
                "border_width": 0,
                "fg_color": ["gray60", "grey30"],
                "progress_color": ["#3a7ebf", "#1f538d"],
                "border_color": ["gray", "gray"]
            },
            "CTkSlider": {
                "corner_radius": 1000,
                "button_corner_radius": 1000,
                "border_width": 6,
                "button_length": 0,
                "fg_color": ["gray60", "grey30"],
                "progress_color": ["gray40", "gray69"],
                "button_color": ["#3a7ebf", "#1f538d"],
                "button_hover_color": ["#325882", "#14375e"]
            },
            "CTkOptionMenu": {
                "corner_radius": 6,
                "fg_color": ["#3a7ebf", "#1f538d"],
                "button_color": ["#325882", "#14375e"],
                "button_hover_color": ["#234567", "#1e2c40"],
                "text_color": ["grey89", "grey89"],
                "text_color_disabled": ["gray74", "gray60"]
            },
            "CTkComboBox": {
                "corner_radius": 6,
                "border_width": 2,
                "fg_color": ["gray98", "gray21"],
                "border_color": ["gray61", "grey35"],
                "button_color": ["gray61", "grey35"],
                "button_hover_color": ["grey44", "grey52"],
                "text_color": ["gray14", "gray84"],
                "text_color_disabled": ["gray50", "gray45"]
            },
            "CTkScrollbar": {
                "corner_radius": 1000,
                "border_spacing": 4,
                "fg_color": "transparent",
                "button_color": ["gray55", "gray41"],
                "button_hover_color": ["gray40", "gray53"]
            },
            "CTkSegmentedButton": {
                "corner_radius": 6,
                "border_width": 2,
                "fg_color": ["gray61", "gray29"],
                "selected_color": ["#3a7ebf", "#1f538d"],
                "selected_hover_color": ["#325882", "#14375e"],
                "unselected_color": ["gray61", "gray29"],
                "unselected_hover_color": ["gray70", "gray41"],
                "text_color": ["grey89", "grey89"],
                "text_color_disabled": ["gray74", "gray60"]
            },
            "CTkTextbox": {
                "corner_radius": 6,
                "border_width": 0,
                "fg_color": ["gray100", "gray20"],
                "border_color": ["gray61", "grey35"],
                "text_color": ["gray14", "gray84"],
                "scrollbar_button_color": ["gray55", "gray41"],
                "scrollbar_button_hover_color": ["gray40", "gray53"]
            },
            "CTkScrollableFrame": {
                "label_fg_color": ["gray80", "gray21"]
            },
            "DropdownMenu": {
                "fg_color": ["gray90", "gray20"],
                "hover_color": ["gray75", "gray28"],
                "text_color": ["gray14", "gray84"]
            },
            "CTkFont": {
                "macOS": {
                    "family": "SF Display",
                    "size": 13,
                    "weight": "normal"
                },
                "Windows": {
                    "family": "Roboto",
                    "size": 13,
                    "weight": "normal"
                },
                "Linux": {
                    "family": "Roboto",
                    "size": 13,
                    "weight": "normal"
                }
            }
        }
        '''
        # エントリーボックスからファイル名を取得
        file_name = self.file_name_entry.get()
        # 置換元の色のリスト
        replace_words = ['#234567', '#325882', '#3a7ebf', '#1e2c40', '#14375e', '#1f538d']
        # エントリーボックスから置換先のリストを取得
        colors = [self.color_label[i+len(replace_words)].get() for i in range(len(replace_words))]
        # 色を置換
        for i, replace_word in enumerate(replace_words):
            default_json = default_json.replace(replace_word, colors[i])
        # 文字列からjsonに変換
        default_json = json.loads(default_json)
        # jsonファイルを保存
        if file_name != '':
            with open(file_name, 'w') as f:
                json.dump(default_json, f, indent=4)

def main():
    app = App()
    app.mainloop()

if __name__ == "__main__":
    main()
```

</div></details>


## 3.5. 作例

実際に作ってみた作例を以下に載せてみます。
<a href="https://github.com/TomSchimansky/CustomTkinter/blob/master/examples/complex_example.py">公式サイトのcomplex_example.py</a>へ適用したものになります。

### 3.5.1. red

<details><summary>red.json</summary><div>

```json:red.json
{
    "CTk": {
        "fg_color": ["gray95", "gray10"]
    },
    "CTkToplevel": {
        "fg_color": ["gray95", "gray10"]
    },
    "CTkFrame": {
        "corner_radius": 6,
        "border_width": 0,
        "fg_color": ["gray90", "gray13"],
        "top_fg_color": ["gray85", "gray16"],
        "border_color": ["gray65", "gray28"]
    },
    "CTkButton": {
        "corner_radius": 6,
        "border_width": 0,
        "fg_color": ["#ca2f2f", "#941212"],
        "hover_color": ["#972a2a", "#6b0e0e"],
        "border_color": ["grey27", "grey60"],
        "text_color": ["grey89", "grey89"],
        "text_color_disabled": ["gray74", "gray60"]
    },
    "CTkLabel": {
        "corner_radius": 0,
        "fg_color": "transparent",
        "text_color": ["gray14", "gray84"]
    },
    "CTkEntry": {
        "corner_radius": 6,
        "border_width": 2,
        "fg_color": ["gray98", "gray21"],
        "border_color": ["gray61", "grey35"],
        "text_color": ["gray14", "gray84"],
        "placeholder_text_color": ["gray52", "gray62"]
    },
    "CTkCheckbox": {
        "corner_radius": 6,
        "border_width": 3,
        "fg_color": ["#ca2f2f", "#941212"],
        "border_color": ["grey27", "grey60"],
        "hover_color": ["#972a2a", "#6b0e0e"],
        "checkmark_color": ["grey89", "gray90"],
        "text_color": ["gray14", "gray84"],
        "text_color_disabled": ["gray60", "gray45"]
    },
    "CTkSwitch": {
        "corner_radius": 1000,
        "border_width": 3,
        "button_length": 0,
        "fg_Color": ["gray60", "grey30"],
        "progress_color": ["#ca2f2f", "#941212"],
        "button_color": ["gray36", "gray85"],
        "button_hover_color": ["gray20", "gray100"],
        "text_color": ["gray14", "gray84"],
        "text_color_disabled": ["gray60", "gray45"]
    },
    "CTkRadiobutton": {
        "corner_radius": 1000,
        "border_width_checked": 6,
        "border_width_unchecked": 3,
        "fg_color": ["#ca2f2f", "#941212"],
        "border_color": ["grey27", "grey60"],
        "hover_color": ["#972a2a", "#6b0e0e"],
        "text_color": ["gray14", "gray84"],
        "text_color_disabled": ["gray60", "gray45"]
    },
    "CTkProgressBar": {
        "corner_radius": 1000,
        "border_width": 0,
        "fg_color": ["gray60", "grey30"],
        "progress_color": ["#ca2f2f", "#941212"],
        "border_color": ["gray", "gray"]
    },
    "CTkSlider": {
        "corner_radius": 1000,
        "button_corner_radius": 1000,
        "border_width": 6,
        "button_length": 0,
        "fg_color": ["gray60", "grey30"],
        "progress_color": ["gray40", "gray69"],
        "button_color": ["#ca2f2f", "#941212"],
        "button_hover_color": ["#972a2a", "#6b0e0e"]
    },
    "CTkOptionMenu": {
        "corner_radius": 6,
        "fg_color": ["#ca2f2f", "#941212"],
        "button_color": ["#972a2a", "#6b0e0e"],
        "button_hover_color": ["#7d2020", "#571d1d"],
        "text_color": ["grey89", "grey89"],
        "text_color_disabled": ["gray74", "gray60"]
    },
    "CTkComboBox": {
        "corner_radius": 6,
        "border_width": 2,
        "fg_color": ["gray98", "gray21"],
        "border_color": ["gray61", "grey35"],
        "button_color": ["gray61", "grey35"],
        "button_hover_color": ["grey44", "grey52"],
        "text_color": ["gray14", "gray84"],
        "text_color_disabled": ["gray50", "gray45"]
    },
    "CTkScrollbar": {
        "corner_radius": 1000,
        "border_spacing": 4,
        "fg_color": "transparent",
        "button_color": ["gray55", "gray41"],
        "button_hover_color": ["gray40", "gray53"]
    },
    "CTkSegmentedButton": {
        "corner_radius": 6,
        "border_width": 2,
        "fg_color": ["gray61", "gray29"],
        "selected_color": ["#ca2f2f", "#941212"],
        "selected_hover_color": ["#972a2a", "#6b0e0e"],
        "unselected_color": ["gray61", "gray29"],
        "unselected_hover_color": ["gray70", "gray41"],
        "text_color": ["grey89", "grey89"],
        "text_color_disabled": ["gray74", "gray60"]
    },
    "CTkTextbox": {
        "corner_radius": 6,
        "border_width": 0,
        "fg_color": ["gray100", "gray20"],
        "border_color": ["gray61", "grey35"],
        "text_color": ["gray14", "gray84"],
        "scrollbar_button_color": ["gray55", "gray41"],
        "scrollbar_button_hover_color": ["gray40", "gray53"]
    },
    "CTkScrollableFrame": {
        "label_fg_color": ["gray80", "gray21"]
    },
    "DropdownMenu": {
        "fg_color": ["gray90", "gray20"],
        "hover_color": ["gray75", "gray28"],
        "text_color": ["gray14", "gray84"]
    },
    "CTkFont": {
        "macOS": {
            "family": "SF Display", "size": 13, "weight": "normal"
        },
        "Windows": {
            "family": "Roboto", "size": 13, "weight": "normal"
        },
        "Linux": {
            "family": "Roboto", "size": 13, "weight": "normal"
        }
    }
}
```

</div></details>

![theme_red.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3079481/2f3339b3-0c23-839a-c957-a7f35dc24a14.png)

### 3.5.2. magenta


<details><summary>magenta.json</summary><div>

```json:magenta.json
{
    "CTk": {
        "fg_color": ["gray95", "gray10"]
    },
    "CTkToplevel": {
        "fg_color": ["gray95", "gray10"]
    },
    "CTkFrame": {
        "corner_radius": 6,
        "border_width": 0,
        "fg_color": ["gray90", "gray13"],
        "top_fg_color": ["gray85", "gray16"],
        "border_color": ["gray65", "gray28"]
    },
    "CTkButton": {
        "corner_radius": 6,
        "border_width": 0,
        "fg_color": ["#c01d6f", "#8b0648"],
        "hover_color": ["#8d1c55", "#620533"],
        "border_color": ["grey27", "grey60"],
        "text_color": ["grey89", "grey89"],
        "text_color_disabled": ["gray74", "gray60"]
    },
    "CTkLabel": {
        "corner_radius": 0,
        "fg_color": "transparent",
        "text_color": ["gray14", "gray84"]
    },
    "CTkEntry": {
        "corner_radius": 6,
        "border_width": 2,
        "fg_color": ["gray98", "gray21"],
        "border_color": ["gray61", "grey35"],
        "text_color": ["gray14", "gray84"],
        "placeholder_text_color": ["gray52", "gray62"]
    },
    "CTkCheckbox": {
        "corner_radius": 6,
        "border_width": 3,
        "fg_color": ["#c01d6f", "#8b0648"],
        "border_color": ["grey27", "grey60"],
        "hover_color": ["#8d1c55", "#620533"],
        "checkmark_color": ["grey89", "gray90"],
        "text_color": ["gray14", "gray84"],
        "text_color_disabled": ["gray60", "gray45"]
    },
    "CTkSwitch": {
        "corner_radius": 1000,
        "border_width": 3,
        "button_length": 0,
        "fg_Color": ["gray60", "grey30"],
        "progress_color": ["#c01d6f", "#8b0648"],
        "button_color": ["gray36", "gray85"],
        "button_hover_color": ["gray20", "gray100"],
        "text_color": ["gray14", "gray84"],
        "text_color_disabled": ["gray60", "gray45"]
    },
    "CTkRadiobutton": {
        "corner_radius": 1000,
        "border_width_checked": 6,
        "border_width_unchecked": 3,
        "fg_color": ["#c01d6f", "#8b0648"],
        "border_color": ["grey27", "grey60"],
        "hover_color": ["#8d1c55", "#620533"],
        "text_color": ["gray14", "gray84"],
        "text_color_disabled": ["gray60", "gray45"]
    },
    "CTkProgressBar": {
        "corner_radius": 1000,
        "border_width": 0,
        "fg_color": ["gray60", "grey30"],
        "progress_color": ["#c01d6f", "#8b0648"],
        "border_color": ["gray", "gray"]
    },
    "CTkSlider": {
        "corner_radius": 1000,
        "button_corner_radius": 1000,
        "border_width": 6,
        "button_length": 0,
        "fg_color": ["gray60", "grey30"],
        "progress_color": ["gray40", "gray69"],
        "button_color": ["#c01d6f", "#8b0648"],
        "button_hover_color": ["#8d1c55", "#620533"]
    },
    "CTkOptionMenu": {
        "corner_radius": 6,
        "fg_color": ["#c01d6f", "#8b0648"],
        "button_color": ["#8d1c55", "#620533"],
        "button_hover_color": ["#741444", "#4e1331"],
        "text_color": ["grey89", "grey89"],
        "text_color_disabled": ["gray74", "gray60"]
    },
    "CTkComboBox": {
        "corner_radius": 6,
        "border_width": 2,
        "fg_color": ["gray98", "gray21"],
        "border_color": ["gray61", "grey35"],
        "button_color": ["gray61", "grey35"],
        "button_hover_color": ["grey44", "grey52"],
        "text_color": ["gray14", "gray84"],
        "text_color_disabled": ["gray50", "gray45"]
    },
    "CTkScrollbar": {
        "corner_radius": 1000,
        "border_spacing": 4,
        "fg_color": "transparent",
        "button_color": ["gray55", "gray41"],
        "button_hover_color": ["gray40", "gray53"]
    },
    "CTkSegmentedButton": {
        "corner_radius": 6,
        "border_width": 2,
        "fg_color": ["gray61", "gray29"],
        "selected_color": ["#c01d6f", "#8b0648"],
        "selected_hover_color": ["#8d1c55", "#620533"],
        "unselected_color": ["gray61", "gray29"],
        "unselected_hover_color": ["gray70", "gray41"],
        "text_color": ["grey89", "grey89"],
        "text_color_disabled": ["gray74", "gray60"]
    },
    "CTkTextbox": {
        "corner_radius": 6,
        "border_width": 0,
        "fg_color": ["gray100", "gray20"],
        "border_color": ["gray61", "grey35"],
        "text_color": ["gray14", "gray84"],
        "scrollbar_button_color": ["gray55", "gray41"],
        "scrollbar_button_hover_color": ["gray40", "gray53"]
    },
    "CTkScrollableFrame": {
        "label_fg_color": ["gray80", "gray21"]
    },
    "DropdownMenu": {
        "fg_color": ["gray90", "gray20"],
        "hover_color": ["gray75", "gray28"],
        "text_color": ["gray14", "gray84"]
    },
    "CTkFont": {
        "macOS": {
            "family": "SF Display", "size": 13, "weight": "normal"
        },
        "Windows": {
            "family": "Roboto", "size": 13, "weight": "normal"
        },
        "Linux": {
            "family": "Roboto", "size": 13, "weight": "normal"
        }
    }
}
```

</div></details>

![theme_magenta.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3079481/d6cf72da-6b59-3d71-f7d0-e2e3f7bb1f50.png)

### 3.5.3. purple


<details><summary>purple.json</summary><div>

```json:purple.json
{
    "CTk": {
        "fg_color": ["gray95", "gray10"]
    },
    "CTkToplevel": {
        "fg_color": ["gray95", "gray10"]
    },
    "CTkFrame": {
        "corner_radius": 6,
        "border_width": 0,
        "fg_color": ["gray90", "gray13"],
        "top_fg_color": ["gray85", "gray16"],
        "border_color": ["gray65", "gray28"]
    },
    "CTkButton": {
        "corner_radius": 6,
        "border_width": 0,
        "fg_color": ["#cb45e5", "#9821b0"],
        "hover_color": ["#9f3eb2", "#631773"],
        "border_color": ["grey27", "grey60"],
        "text_color": ["#eadded", "#eadded"],
        "text_color_disabled": ["gray74", "gray60"]
    },
    "CTkLabel": {
        "corner_radius": 0,
        "fg_color": "transparent",
        "text_color": ["gray14", "gray84"]
    },
    "CTkEntry": {
        "corner_radius": 6,
        "border_width": 2,
        "fg_color": ["gray98", "gray21"],
        "border_color": ["gray61", "grey35"],
        "text_color": ["gray14", "gray84"],
        "placeholder_text_color": ["gray52", "gray62"]
    },
    "CTkCheckbox": {
        "corner_radius": 6,
        "border_width": 3,
        "fg_color": ["#cb45e5", "#9821b0"],
        "border_color": ["grey27", "grey60"],
        "hover_color": ["#9f3eb2", "#631773"],
        "checkmark_color": ["#eadded", "gray90"],
        "text_color": ["gray14", "gray84"],
        "text_color_disabled": ["gray60", "gray45"]
    },
    "CTkSwitch": {
        "corner_radius": 1000,
        "border_width": 3,
        "button_length": 0,
        "fg_Color": ["gray60", "grey30"],
        "progress_color": ["#cb45e5", "#9821b0"],
        "button_color": ["gray36", "gray85"],
        "button_hover_color": ["gray20", "gray100"],
        "text_color": ["gray14", "gray84"],
        "text_color_disabled": ["gray60", "gray45"]
    },
    "CTkRadiobutton": {
        "corner_radius": 1000,
        "border_width_checked": 6,
        "border_width_unchecked": 3,
        "fg_color": ["#cb45e5", "#9821b0"],
        "border_color": ["grey27", "grey60"],
        "hover_color": ["#9f3eb2", "#631773"],
        "text_color": ["gray14", "gray84"],
        "text_color_disabled": ["gray60", "gray45"]
    },
    "CTkProgressBar": {
        "corner_radius": 1000,
        "border_width": 0,
        "fg_color": ["gray60", "grey30"],
        "progress_color": ["#cb45e5", "#9821b0"],
        "border_color": ["gray", "gray"]
    },
    "CTkSlider": {
        "corner_radius": 1000,
        "button_corner_radius": 1000,
        "border_width": 6,
        "button_length": 0,
        "fg_color": ["gray60", "grey30"],
        "progress_color": ["gray40", "gray69"],
        "button_color": ["#cb45e5", "#9821b0"],
        "button_hover_color": ["#9f3eb2", "#631773"]
    },
    "CTkOptionMenu": {
        "corner_radius": 6,
        "fg_color": ["#cb45e5", "#9821b0"],
        "button_color": ["#9f3eb2", "#631773"],
        "button_hover_color": ["#883199", "#683473"],
        "text_color": ["#eadded", "#eadded"],
        "text_color_disabled": ["gray74", "gray60"]
    },
    "CTkComboBox": {
        "corner_radius": 6,
        "border_width": 2,
        "fg_color": ["gray98", "gray21"],
        "border_color": ["gray61", "grey35"],
        "button_color": ["gray61", "grey35"],
        "button_hover_color": ["grey44", "grey52"],
        "text_color": ["gray14", "gray84"],
        "text_color_disabled": ["gray50", "gray45"]
    },
    "CTkScrollbar": {
        "corner_radius": 1000,
        "border_spacing": 4,
        "fg_color": "transparent",
        "button_color": ["gray55", "gray41"],
        "button_hover_color": ["gray40", "gray53"]
    },
    "CTkSegmentedButton": {
        "corner_radius": 6,
        "border_width": 2,
        "fg_color": ["gray61", "gray29"],
        "selected_color": ["#cb45e5", "#9821b0"],
        "selected_hover_color": ["#9f3eb2", "#631773"],
        "unselected_color": ["gray61", "gray29"],
        "unselected_hover_color": ["gray70", "gray41"],
        "text_color": ["#eadded", "#eadded"],
        "text_color_disabled": ["gray74", "gray60"]
    },
    "CTkTextbox": {
        "corner_radius": 6,
        "border_width": 0,
        "fg_color": ["gray100", "gray20"],
        "border_color": ["gray61", "grey35"],
        "text_color": ["gray14", "gray84"],
        "scrollbar_button_color": ["gray55", "gray41"],
        "scrollbar_button_hover_color": ["gray40", "gray53"]
    },
    "CTkScrollableFrame": {
        "label_fg_color": ["gray80", "gray21"]
    },
    "DropdownMenu": {
        "fg_color": ["gray90", "gray20"],
        "hover_color": ["gray75", "gray28"],
        "text_color": ["gray14", "gray84"]
    },
    "CTkFont": {
        "macOS": {
            "family": "SF Display", "size": 13, "weight": "normal"
        },
        "Windows": {
            "family": "Roboto", "size": 13, "weight": "normal"
        },
        "Linux": {
            "family": "Roboto", "size": 13, "weight": "normal"
        }
    }
}
```

</div></details>

![theme_purple.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3079481/e016a7a6-6a36-6b9b-ce14-359c064c877a.png)

### 3.5.4. torquoise


<details><summary>torquoise.json</summary><div>

```json:torquoise.json
{
    "CTk": {
        "fg_color": ["gray95", "gray10"]
    },
    "CTkToplevel": {
        "fg_color": ["gray95", "gray10"]
    },
    "CTkFrame": {
        "corner_radius": 6,
        "border_width": 0,
        "fg_color": ["gray90", "gray13"],
        "top_fg_color": ["gray85", "gray16"],
        "border_color": ["gray65", "gray28"]
    },
    "CTkButton": {
        "corner_radius": 6,
        "border_width": 0,
        "fg_color": ["#2fcaca", "#129494"],
        "hover_color": ["#2a9797", "#0e6b6b"],
        "border_color": ["grey27", "grey60"],
        "text_color": ["grey89", "grey89"],
        "text_color_disabled": ["gray74", "gray60"]
    },
    "CTkLabel": {
        "corner_radius": 0,
        "fg_color": "transparent",
        "text_color": ["gray14", "gray84"]
    },
    "CTkEntry": {
        "corner_radius": 6,
        "border_width": 2,
        "fg_color": ["gray98", "gray21"],
        "border_color": ["gray61", "grey35"],
        "text_color": ["gray14", "gray84"],
        "placeholder_text_color": ["gray52", "gray62"]
    },
    "CTkCheckbox": {
        "corner_radius": 6,
        "border_width": 3,
        "fg_color": ["#2fcaca", "#129494"],
        "border_color": ["grey27", "grey60"],
        "hover_color": ["#2a9797", "#0e6b6b"],
        "checkmark_color": ["grey89", "gray90"],
        "text_color": ["gray14", "gray84"],
        "text_color_disabled": ["gray60", "gray45"]
    },
    "CTkSwitch": {
        "corner_radius": 1000,
        "border_width": 3,
        "button_length": 0,
        "fg_Color": ["gray60", "grey30"],
        "progress_color": ["#2fcaca", "#129494"],
        "button_color": ["gray36", "gray85"],
        "button_hover_color": ["gray20", "gray100"],
        "text_color": ["gray14", "gray84"],
        "text_color_disabled": ["gray60", "gray45"]
    },
    "CTkRadiobutton": {
        "corner_radius": 1000,
        "border_width_checked": 6,
        "border_width_unchecked": 3,
        "fg_color": ["#2fcaca", "#129494"],
        "border_color": ["grey27", "grey60"],
        "hover_color": ["#2a9797", "#0e6b6b"],
        "text_color": ["gray14", "gray84"],
        "text_color_disabled": ["gray60", "gray45"]
    },
    "CTkProgressBar": {
        "corner_radius": 1000,
        "border_width": 0,
        "fg_color": ["gray60", "grey30"],
        "progress_color": ["#2fcaca", "#129494"],
        "border_color": ["gray", "gray"]
    },
    "CTkSlider": {
        "corner_radius": 1000,
        "button_corner_radius": 1000,
        "border_width": 6,
        "button_length": 0,
        "fg_color": ["gray60", "grey30"],
        "progress_color": ["gray40", "gray69"],
        "button_color": ["#2fcaca", "#129494"],
        "button_hover_color": ["#2a9797", "#0e6b6b"]
    },
    "CTkOptionMenu": {
        "corner_radius": 6,
        "fg_color": ["#2fcaca", "#129494"],
        "button_color": ["#2a9797", "#0e6b6b"],
        "button_hover_color": ["#207d7d", "#1d5757"],
        "text_color": ["grey89", "grey89"],
        "text_color_disabled": ["gray74", "gray60"]
    },
    "CTkComboBox": {
        "corner_radius": 6,
        "border_width": 2,
        "fg_color": ["gray98", "gray21"],
        "border_color": ["gray61", "grey35"],
        "button_color": ["gray61", "grey35"],
        "button_hover_color": ["grey44", "grey52"],
        "text_color": ["gray14", "gray84"],
        "text_color_disabled": ["gray50", "gray45"]
    },
    "CTkScrollbar": {
        "corner_radius": 1000,
        "border_spacing": 4,
        "fg_color": "transparent",
        "button_color": ["gray55", "gray41"],
        "button_hover_color": ["gray40", "gray53"]
    },
    "CTkSegmentedButton": {
        "corner_radius": 6,
        "border_width": 2,
        "fg_color": ["gray61", "gray29"],
        "selected_color": ["#2fcaca", "#129494"],
        "selected_hover_color": ["#2a9797", "#0e6b6b"],
        "unselected_color": ["gray61", "gray29"],
        "unselected_hover_color": ["gray70", "gray41"],
        "text_color": ["grey89", "grey89"],
        "text_color_disabled": ["gray74", "gray60"]
    },
    "CTkTextbox": {
        "corner_radius": 6,
        "border_width": 0,
        "fg_color": ["gray100", "gray20"],
        "border_color": ["gray61", "grey35"],
        "text_color": ["gray14", "gray84"],
        "scrollbar_button_color": ["gray55", "gray41"],
        "scrollbar_button_hover_color": ["gray40", "gray53"]
    },
    "CTkScrollableFrame": {
        "label_fg_color": ["gray80", "gray21"]
    },
    "DropdownMenu": {
        "fg_color": ["gray90", "gray20"],
        "hover_color": ["gray75", "gray28"],
        "text_color": ["gray14", "gray84"]
    },
    "CTkFont": {
        "macOS": {
            "family": "SF Display", "size": 13, "weight": "normal"
        },
        "Windows": {
            "family": "Roboto", "size": 13, "weight": "normal"
        },
        "Linux": {
            "family": "Roboto", "size": 13, "weight": "normal"
        }
    }
}
```

</div></details>

![theme_torquoise.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3079481/32afd585-1370-9bc2-e694-b0be9755421f.png)


# 4. さいごに

CustomTkinterをどうやったら簡単に使えるか考えていたら、結果的にプレビューツールやカスタムテーマ作成ツールに行き着いていました。

意図せず色設定の勉強まで必要でしたし、やってみたいことベースで考えているとどんどん広がって大変ですが、その分面白くもあるので、今後もいろいろ作ってみようと思います。

# 参考

色の設定については以下を参考としてました。

https://blog1.mammb.com/entry/2020/01/20/090000

https://blog.shikoan.com/colorsys/

https://cyanatlas.hatenablog.com/entry/2020/07/16/101653