Color picker

File with module Custom color picker to make user life easier.

  1"""File with module Custom color picker to make user life easier.
  2"""
  3
  4import customtkinter as ctk
  5import re
  6
  7class ColorPicker(ctk.CTkToplevel):
  8    """Class used to pick custom theme color. Is also a module that can be reused with apps using customtkinter.
  9
 10    Args:
 11
 12     - ctk.CTkTopLevel : Inheritance from customtkinter CTkTopLevel window.
 13    """
 14    def __init__(self, fg_color: str | None=None, preview_size: int=100, r: int=0, g: int=0, b: int=0, font: ctk.CTkFont | None=None, 
 15                 border_color: str | None=None, slider_button_color: str | None=None, slider_progress_color: str | None=None, slider_fg_color: str | None=None, 
 16                 preview_border_color: str | None=None, button_fg_color: str | None=None, button_hover_color: str | None=None, icon: str | None=None,
 17                 corner_radius: int | None=None) -> None:
 18        """Constructor handling most important function calls and variable setup.
 19
 20        Args:
 21            fg_color (str | None, optional): Background color of the window. Defaults to None.
 22            preview_size (int, optional): _description_. Defaults to 100.
 23            r (int, optional): Default red intensity value. Defaults to 0.
 24            g (int, optional): Default green intensity value. Defaults to 0.
 25            b (int, optional): Default blue intensity value. Defaults to 0.
 26            font (ctk.CTkFont | None, optional): Custom font. Defaults to None.
 27        """
 28        super().__init__(fg_color=fg_color)
 29        self.fg_color: str| None = fg_color
 30        self.border_color: str| None = border_color
 31        self.slider_button_color: str| None = slider_button_color
 32        self.slider_progress_color: str| None = slider_progress_color
 33        self.slider_fg_color: str| None = slider_fg_color
 34        self.preview_border_color: str | None = preview_border_color
 35        self.button_fg_color: str | None = button_fg_color
 36        self.button_hover_color: str | None = button_hover_color
 37        self.corner_radius: int | None = corner_radius
 38        self.grab_set()
 39        self.attributes('-topmost', True)
 40        self.title('Color Picker')
 41        self.font: ctk.CTkFont | None = font if font else None
 42        self.font_size: int = font.cget('size') if font else 15
 43        self.preview_size: int = preview_size
 44        self.r_val: int = r
 45        self.g_val: int = g
 46        self.b_val: int = b
 47        self.hex_val: str | None = self.convert_to_hex()
 48        self.main_frame: ctk.CTkFrame = ctk.CTkFrame(
 49            master        = self,
 50            corner_radius = 0,
 51            fg_color      = self.fg_color
 52        )
 53        self.main_frame.pack(side=ctk.TOP, expand=True, ipadx=10, ipady=10)
 54        self.color_preview()
 55        self.r_g_b_sliders()
 56        self.update_sliders(None)
 57        self.bottom_frame: ctk.CTkFrame = ctk.CTkFrame(
 58            master        = self.main_frame,
 59            corner_radius = self.corner_radius,
 60            fg_color      = 'transparent'
 61        )
 62        self.bottom_frame.pack(side=ctk.BOTTOM, expand=True, ipadx=10, ipady=10)
 63        self.hex_color_label()
 64        self.ok_button()
 65        self.resizable(False, False)
 66        self.protocol('WM_DELETE_WINDOW', self.on_close)
 67        self.lift()
 68        self.center_window()
 69        self.after(201, lambda: self.iconbitmap(icon))
 70
 71    def center_window(self) -> None:
 72        """Function centering the TopLevel window. Screen size independent.
 73        """
 74        x: int = self.master.winfo_screenwidth()
 75        y: int = self.master.winfo_screenheight()
 76        app_width: int = self.winfo_width()
 77        app_height: int = self.winfo_height()
 78        self.geometry(f'+{(x//2)-app_width}+{(y//2)-app_height}')
 79
 80    def color_preview(self) -> None:
 81        """Function creating frame for color preview.
 82        """
 83        self.color_prev_box: ctk.CTkFrame = ctk.CTkFrame(
 84            master        = self.main_frame,
 85            fg_color      = self.convert_to_hex(),
 86            border_width  = 3,
 87            width         = self.preview_size, 
 88            height        = self.preview_size,
 89            corner_radius = self.corner_radius,
 90            border_color  = self.preview_border_color
 91        )
 92        self.color_prev_box.pack(side=ctk.RIGHT, padx=3, pady=3, expand=True)
 93
 94    @staticmethod
 95    def validate_hex_color(value_if_allowed: str) -> bool:
 96        """Function validating new character in hex entry box. Can take one character or longer string to allow pasting.
 97
 98        Args:
 99            value_if_allowed (str): New value to check.
100
101        Returns:
102            bool: True if hex color patter was met, False otherwise.
103        """
104        if len(value_if_allowed) == 0 or (re.compile(r'^#[0-9a-fA-F]{0,6}$').match(value_if_allowed)):
105            for char in value_if_allowed[1:]:
106                if char not in '0123456789ABCDEFabcdef':
107                    return False
108            return True
109        return False
110
111    def paste_hex_color(self, event) -> None:
112        """Function handling pasting custom color into hex color entry box.
113
114        Args:
115            event (Any): Event type. Doesn't matter but is required parameter by customtkinter.
116        """
117        clipboard = self.master.clipboard_get()
118        if self.validate_hex_color(clipboard):
119            self.hex_val_label.delete(0, ctk.END)
120            self.hex_val_label.insert(0, clipboard)
121        return None
122
123    def update_on_hex(self, event) -> None:
124        """Function handling all changes on entering last hex color. It changes RGB labels values, sliders values and color preview frame to the desired color.
125
126        Args:
127            event (Any): Event type. Doesn't matter but is required parameter by customtkinter.
128        """
129        if len(self.hex_val_label.get()) == 7:
130            self.r_val, self.g_val, self.b_val = self.convert_to_r_g_b()
131            self.r_val_label.delete(0, ctk.END)
132            self.g_val_label.delete(0, ctk.END)
133            self.b_val_label.delete(0, ctk.END)
134            self.r_val_label.insert(0, f'{self.r_val}')
135            self.g_val_label.insert(0, f'{self.g_val}')
136            self.b_val_label.insert(0, f'{self.b_val}')
137            self.update_sliders(None, r=self.r_val, g=self.g_val, b=self.b_val)
138
139    def hex_color_label(self) -> None:
140        """Function creating entry box for hex color.
141        """
142        vcmd = (self.register(self.validate_hex_color), '%P')
143        ctk.CTkLabel(self.bottom_frame, text='Hex: ', font=self.font if self.font else ctk.CTkFont('', self.font_size)).pack(side=ctk.LEFT, padx=3, pady=3)
144        self.hex_val_label: ctk.CTkEntry = ctk.CTkEntry(
145            master          = self.bottom_frame,
146            validate        = 'key',
147            validatecommand = vcmd,
148            corner_radius   = self.corner_radius,
149            font            = self.font if self.font else ctk.CTkFont('', self.font_size),
150            width           = (self.font_size*8),
151            border_color    = self.border_color
152        )
153        self.hex_val_label.pack(side=ctk.LEFT, padx=3, pady=3)
154        self.hex_val_label.insert(0, f'{self.hex_val}')
155        self.hex_val_label.bind('<Control-v>', self.paste_hex_color)
156        self.hex_val_label.bind('<KeyRelease>', lambda e: self.update_on_hex(e))
157
158    def ok_button(self) -> None:
159        """Function creating 'OK' button. After clicking the button if color is selected properly the value will be returned in master script.
160        """
161        ok_button: ctk.CTkButton = ctk.CTkButton(
162            master = self.bottom_frame,
163            text          = 'Ok',
164            command       = self.on_ok_button,
165            font          = self.font if self.font else ctk.CTkFont('', self.font_size),
166            width         = (self.font_size*3),
167            corner_radius = self.corner_radius,
168            border_width  = 2,
169            border_color  = self.border_color,
170            fg_color      = self.button_fg_color,
171            hover_color   = self.button_hover_color
172        )
173        ok_button.pack(side=ctk.RIGHT, padx=3, pady=3)
174
175    def new_slider_frame(self, frame: ctk.CTkFrame) -> ctk.CTkFrame:
176        """Function creating frame for slider used to change R or G or B value.
177
178        Args:
179            frame (ctk.CTkFrame): Parent Frame on which cell will be represented.
180
181        Returns:
182            ctk.CTkFrame: Ready packed frame.
183        """
184        slider_frame: ctk.CTkFrame = ctk.CTkFrame(
185            master        = frame,
186            fg_color      = 'transparent',
187            corner_radius = self.corner_radius
188        )
189        slider_frame.pack(side=ctk.TOP, padx=3, pady=3)
190        return slider_frame
191
192    @staticmethod
193    def validate_input(P: str) -> bool:
194        """Validation of R,G,B inputs from entry boxes.
195
196        Args:
197            P (str): New input character.
198
199        Returns:
200            bool: True if RGB encoding requirements are met, False otherwise. 
201        """
202        if P == '':
203            return True
204        if not P.isdigit():
205            return False
206        if len(P) > 3:
207            return False
208        value = int(P)
209        if 0 <= value <= 255:
210            return True
211        return False
212
213    def r_g_b_sliders(self) -> None:
214        """Function creating sliders to change RGB values using interactive sliders.
215        """
216    # sliders frame
217        vcmd = (self.register(self.validate_input), '%P')
218        frame: ctk.CTkFrame = ctk.CTkFrame(
219            master   = self.main_frame,
220            fg_color = 'transparent'
221        )
222        frame.pack(side=ctk.TOP)
223    # R value slider
224        slider_frame: ctk.CTkFrame = self.new_slider_frame(frame)
225        ctk.CTkLabel(
226            master = slider_frame,
227            text   = 'R: ',
228            font   = self.font if self.font else ctk.CTkFont('', self.font_size)
229        ).pack(side=ctk.LEFT, padx=3, pady=3)
230        self.r_val_label: ctk.CTkEntry = ctk.CTkEntry(
231            master          = slider_frame,
232            validate        = 'key',
233            validatecommand = vcmd,
234            corner_radius   = self.corner_radius,
235            font            = self.font if self.font else ctk.CTkFont('', self.font_size),
236            width           = (self.font_size*3),
237            border_color    = self.border_color
238        )
239        self.r_val_label.pack(side=ctk.LEFT, padx=3, pady=3)
240        self.r_slider: ctk.CTkSlider = ctk.CTkSlider(
241            master               = slider_frame,
242            from_                = 0,
243            to                   = 255,
244            number_of_steps      = 255,
245            command              = lambda e: self.slider_on_change(e, r=True),
246            button_corner_radius = self.corner_radius + 1 if self.corner_radius is not None else self.corner_radius,
247            button_length        = 12,
248            corner_radius        = self.corner_radius + 1 if self.corner_radius is not None else self.corner_radius,
249            button_color         = self.slider_button_color,
250            hover                = False,
251            progress_color       = self.slider_progress_color,
252            fg_color             = self.slider_fg_color
253        )
254    # G value slider
255        slider_frame = self.new_slider_frame(frame)
256        ctk.CTkLabel(slider_frame, text='G: ', font=self.font if self.font else ctk.CTkFont('', self.font_size)).pack(side=ctk.LEFT, padx=3, pady=3)
257        self.g_val_label = ctk.CTkEntry(
258            master          = slider_frame,
259            validate        = 'key',
260            validatecommand = vcmd,
261            corner_radius   = self.corner_radius,
262            font            = self.font if self.font else ctk.CTkFont('', self.font_size),
263            width           = (self.font_size*3),
264            border_color    = self.border_color
265        )
266        self.g_val_label.pack(side=ctk.LEFT, padx=3, pady=3)
267        self.g_slider: ctk.CTkSlider = ctk.CTkSlider(
268            master               = slider_frame, 
269            from_                = 0,
270            to                   = 255,
271            number_of_steps      = 255,
272            command              = lambda e: self.slider_on_change(e, g=True),
273            button_corner_radius = self.corner_radius + 1 if self.corner_radius is not None else self.corner_radius,
274            button_length        = 12,
275            corner_radius        = self.corner_radius + 1 if self.corner_radius is not None else self.corner_radius,
276            button_color         = self.slider_button_color,
277            hover                = False,
278            progress_color       = self.slider_progress_color,
279            fg_color             = self.slider_fg_color
280        )
281    # B value slider
282        slider_frame = self.new_slider_frame(frame)
283        ctk.CTkLabel(slider_frame, text='B: ', font=self.font if self.font else ctk.CTkFont('', self.font_size)).pack(side=ctk.LEFT, padx=3, pady=3)
284        self.b_val_label: ctk.CTkEntry = ctk.CTkEntry(
285            master          = slider_frame,
286            validate        = 'key',
287            validatecommand = vcmd,
288            corner_radius   = self.corner_radius,
289            font            = self.font if self.font else ctk.CTkFont('', self.font_size), 
290            width           = (self.font_size*3),
291            border_color    = self.border_color
292        )
293        self.b_val_label.pack(side=ctk.LEFT, padx=3, pady=3)
294        self.b_slider: ctk.CTkSlider = ctk.CTkSlider(
295            master               = slider_frame,
296            from_                = 0,
297            to                   = 255,
298            number_of_steps      = 255,
299            command              = lambda e: self.slider_on_change(e, b=True),
300            button_corner_radius = self.corner_radius + 1 if self.corner_radius is not None else self.corner_radius,
301            button_length        = 12,
302            corner_radius        = self.corner_radius + 1 if self.corner_radius is not None else self.corner_radius,
303            button_color         = self.slider_button_color,
304            hover                = False,
305            progress_color       = self.slider_progress_color,
306            fg_color             = self.slider_fg_color
307        )
308    # initial setup of all labels
309        self.r_val_label.insert(0, self.r_val)
310        self.g_val_label.insert(0, self.g_val)
311        self.b_val_label.insert(0, self.b_val)
312        self.r_slider.set(self.r_val)
313        self.g_slider.set(self.g_val)
314        self.b_slider.set(self.b_val)
315        self.r_slider.pack(side=ctk.LEFT, padx=3, pady=3)
316        self.g_slider.pack(side=ctk.LEFT, padx=3, pady=3)
317        self.b_slider.pack(side=ctk.LEFT, padx=3, pady=3)
318    # binding for preview update
319        self.r_val_label.bind('<KeyRelease>', lambda e: self.update_sliders(e))
320        self.g_val_label.bind('<KeyRelease>', lambda e: self.update_sliders(e))
321        self.b_val_label.bind('<KeyRelease>', lambda e: self.update_sliders(e))
322        self.r_val_label.bind('<KeyRelease>', lambda e: self.update_hex(e))
323        self.g_val_label.bind('<KeyRelease>', lambda e: self.update_hex(e))
324        self.b_val_label.bind('<KeyRelease>', lambda e: self.update_hex(e))
325
326    def update_hex(self, event=None) -> None:
327        self.hex_val_label.delete(0, ctk.END)
328        self.hex_val_label.insert(0, f'{self.convert_to_hex()}')
329
330    def update_sliders(self, event=None, r: int=-1, g: int=-1, b: int=-1) -> None:
331        """Function updating position of sliders and its corresponding RGB entry boxes to proper value after changing hex entry box.
332
333        Args:
334            event (Any): Event type. Doesn't matter but is required parameter by customtkinter.
335            r (int, optional): Given red color intensity. Defaults to -1.
336            g (int, optional): Given green color intensity. Defaults to -1.
337            b (int, optional): Given blue color intensity. Defaults to -1.
338        """
339        if r == -1:
340            r = int(self.r_val_label.get()) if self.r_val_label.get() != '' else 0
341        if g == -1:
342            g = int(self.g_val_label.get()) if self.g_val_label.get() != '' else 0
343        if b == -1:
344            b = int(self.b_val_label.get()) if self.b_val_label.get() != '' else 0
345        self.r_val = r
346        self.g_val = g
347        self.b_val = b
348        self.r_slider.set(r) if 0 < r <= 255 else self.r_slider.set(0)
349        self.g_slider.set(g) if 0 < g <= 255 else self.g_slider.set(0)
350        self.b_slider.set(b) if 0 < b <= 255 else self.b_slider.set(0)
351        self.color_prev_box.configure(fg_color=self.convert_to_hex())
352
353    def slider_on_change(self, event, r: bool=False, g: bool=False, b: bool=False) -> None:
354        """Updates corresponding RGB color code and hex color value based on value of slider.
355
356        Args:
357            event (Any): Event type. Doesn't matter but is required parameter by customtkinter.
358            r (bool, optional): Flag to set which slider was changed [r]. Defaults to False.
359            g (bool, optional): Flag to set which slider was changed [g]. Defaults to False.
360            b (bool, optional): Flag to set which slider was changed [b]. Defaults to False.
361        """
362        if r:
363            self.r_val = int(self.r_slider.get())
364            self.r_val_label.delete(0, ctk.END)
365            self.r_val_label.insert(0, self.r_val)
366        elif g:
367            self.g_val = int(self.g_slider.get())
368            self.g_val_label.delete(0, ctk.END)
369            self.g_val_label.insert(0, self.g_val)
370        elif b:
371            self.b_val = int(self.b_slider.get())
372            self.b_val_label.delete(0, ctk.END)
373            self.b_val_label.insert(0, self.b_val)
374        self.color_prev_box.configure(fg_color=self.convert_to_hex())
375        self.update_hex()
376
377    def convert_to_hex(self) -> str:
378        """Function converting RGB value to hex color code.
379
380        Returns:
381            str: f'{6 digit code}' | Regex example: ^#[0-9a-fA-F]{6}
382        """
383        return f'#{self.r_val:02x}{self.g_val:02x}{self.b_val:02x}'
384
385    def convert_to_r_g_b(self) -> tuple[int, int , int]:
386        """Function converting hex color code to RGB value.
387
388        Returns:
389            tuple[int, int , int]: Tuple of R, G and B values.
390        """
391        self.hex_val = self.hex_val_label.get()
392        hex_code: str = self.hex_val.lstrip('#') if self.hex_val else '000000'
393        r = int(hex_code[0:2], 16)
394        g = int(hex_code[2:4], 16)
395        b = int(hex_code[4:6], 16)
396        return (r, g, b)
397
398    def on_close(self) -> None:
399        """Custom closing function ensuring proper closing of the window. Sets hex_val to None to omit color change.
400        """
401        self.hex_val = None
402        self.grab_release()
403        self.destroy()
404
405    def on_ok_button(self) -> None:
406        """Custom closing function.
407        """
408        self.destroy()
409
410    def get_color(self) -> str | None:
411        """Function waiting for the window being destroyed.
412
413        Returns:
414            str | None: Hex color code if closed with OK button or None if closed with ❌.
415        """
416        self.master.wait_window(self)
417        return self.convert_to_hex() if self.hex_val else None
class ColorPicker(customtkinter.windows.ctk_toplevel.CTkToplevel):
  8class ColorPicker(ctk.CTkToplevel):
  9    """Class used to pick custom theme color. Is also a module that can be reused with apps using customtkinter.
 10
 11    Args:
 12
 13     - ctk.CTkTopLevel : Inheritance from customtkinter CTkTopLevel window.
 14    """
 15    def __init__(self, fg_color: str | None=None, preview_size: int=100, r: int=0, g: int=0, b: int=0, font: ctk.CTkFont | None=None, 
 16                 border_color: str | None=None, slider_button_color: str | None=None, slider_progress_color: str | None=None, slider_fg_color: str | None=None, 
 17                 preview_border_color: str | None=None, button_fg_color: str | None=None, button_hover_color: str | None=None, icon: str | None=None,
 18                 corner_radius: int | None=None) -> None:
 19        """Constructor handling most important function calls and variable setup.
 20
 21        Args:
 22            fg_color (str | None, optional): Background color of the window. Defaults to None.
 23            preview_size (int, optional): _description_. Defaults to 100.
 24            r (int, optional): Default red intensity value. Defaults to 0.
 25            g (int, optional): Default green intensity value. Defaults to 0.
 26            b (int, optional): Default blue intensity value. Defaults to 0.
 27            font (ctk.CTkFont | None, optional): Custom font. Defaults to None.
 28        """
 29        super().__init__(fg_color=fg_color)
 30        self.fg_color: str| None = fg_color
 31        self.border_color: str| None = border_color
 32        self.slider_button_color: str| None = slider_button_color
 33        self.slider_progress_color: str| None = slider_progress_color
 34        self.slider_fg_color: str| None = slider_fg_color
 35        self.preview_border_color: str | None = preview_border_color
 36        self.button_fg_color: str | None = button_fg_color
 37        self.button_hover_color: str | None = button_hover_color
 38        self.corner_radius: int | None = corner_radius
 39        self.grab_set()
 40        self.attributes('-topmost', True)
 41        self.title('Color Picker')
 42        self.font: ctk.CTkFont | None = font if font else None
 43        self.font_size: int = font.cget('size') if font else 15
 44        self.preview_size: int = preview_size
 45        self.r_val: int = r
 46        self.g_val: int = g
 47        self.b_val: int = b
 48        self.hex_val: str | None = self.convert_to_hex()
 49        self.main_frame: ctk.CTkFrame = ctk.CTkFrame(
 50            master        = self,
 51            corner_radius = 0,
 52            fg_color      = self.fg_color
 53        )
 54        self.main_frame.pack(side=ctk.TOP, expand=True, ipadx=10, ipady=10)
 55        self.color_preview()
 56        self.r_g_b_sliders()
 57        self.update_sliders(None)
 58        self.bottom_frame: ctk.CTkFrame = ctk.CTkFrame(
 59            master        = self.main_frame,
 60            corner_radius = self.corner_radius,
 61            fg_color      = 'transparent'
 62        )
 63        self.bottom_frame.pack(side=ctk.BOTTOM, expand=True, ipadx=10, ipady=10)
 64        self.hex_color_label()
 65        self.ok_button()
 66        self.resizable(False, False)
 67        self.protocol('WM_DELETE_WINDOW', self.on_close)
 68        self.lift()
 69        self.center_window()
 70        self.after(201, lambda: self.iconbitmap(icon))
 71
 72    def center_window(self) -> None:
 73        """Function centering the TopLevel window. Screen size independent.
 74        """
 75        x: int = self.master.winfo_screenwidth()
 76        y: int = self.master.winfo_screenheight()
 77        app_width: int = self.winfo_width()
 78        app_height: int = self.winfo_height()
 79        self.geometry(f'+{(x//2)-app_width}+{(y//2)-app_height}')
 80
 81    def color_preview(self) -> None:
 82        """Function creating frame for color preview.
 83        """
 84        self.color_prev_box: ctk.CTkFrame = ctk.CTkFrame(
 85            master        = self.main_frame,
 86            fg_color      = self.convert_to_hex(),
 87            border_width  = 3,
 88            width         = self.preview_size, 
 89            height        = self.preview_size,
 90            corner_radius = self.corner_radius,
 91            border_color  = self.preview_border_color
 92        )
 93        self.color_prev_box.pack(side=ctk.RIGHT, padx=3, pady=3, expand=True)
 94
 95    @staticmethod
 96    def validate_hex_color(value_if_allowed: str) -> bool:
 97        """Function validating new character in hex entry box. Can take one character or longer string to allow pasting.
 98
 99        Args:
100            value_if_allowed (str): New value to check.
101
102        Returns:
103            bool: True if hex color patter was met, False otherwise.
104        """
105        if len(value_if_allowed) == 0 or (re.compile(r'^#[0-9a-fA-F]{0,6}$').match(value_if_allowed)):
106            for char in value_if_allowed[1:]:
107                if char not in '0123456789ABCDEFabcdef':
108                    return False
109            return True
110        return False
111
112    def paste_hex_color(self, event) -> None:
113        """Function handling pasting custom color into hex color entry box.
114
115        Args:
116            event (Any): Event type. Doesn't matter but is required parameter by customtkinter.
117        """
118        clipboard = self.master.clipboard_get()
119        if self.validate_hex_color(clipboard):
120            self.hex_val_label.delete(0, ctk.END)
121            self.hex_val_label.insert(0, clipboard)
122        return None
123
124    def update_on_hex(self, event) -> None:
125        """Function handling all changes on entering last hex color. It changes RGB labels values, sliders values and color preview frame to the desired color.
126
127        Args:
128            event (Any): Event type. Doesn't matter but is required parameter by customtkinter.
129        """
130        if len(self.hex_val_label.get()) == 7:
131            self.r_val, self.g_val, self.b_val = self.convert_to_r_g_b()
132            self.r_val_label.delete(0, ctk.END)
133            self.g_val_label.delete(0, ctk.END)
134            self.b_val_label.delete(0, ctk.END)
135            self.r_val_label.insert(0, f'{self.r_val}')
136            self.g_val_label.insert(0, f'{self.g_val}')
137            self.b_val_label.insert(0, f'{self.b_val}')
138            self.update_sliders(None, r=self.r_val, g=self.g_val, b=self.b_val)
139
140    def hex_color_label(self) -> None:
141        """Function creating entry box for hex color.
142        """
143        vcmd = (self.register(self.validate_hex_color), '%P')
144        ctk.CTkLabel(self.bottom_frame, text='Hex: ', font=self.font if self.font else ctk.CTkFont('', self.font_size)).pack(side=ctk.LEFT, padx=3, pady=3)
145        self.hex_val_label: ctk.CTkEntry = ctk.CTkEntry(
146            master          = self.bottom_frame,
147            validate        = 'key',
148            validatecommand = vcmd,
149            corner_radius   = self.corner_radius,
150            font            = self.font if self.font else ctk.CTkFont('', self.font_size),
151            width           = (self.font_size*8),
152            border_color    = self.border_color
153        )
154        self.hex_val_label.pack(side=ctk.LEFT, padx=3, pady=3)
155        self.hex_val_label.insert(0, f'{self.hex_val}')
156        self.hex_val_label.bind('<Control-v>', self.paste_hex_color)
157        self.hex_val_label.bind('<KeyRelease>', lambda e: self.update_on_hex(e))
158
159    def ok_button(self) -> None:
160        """Function creating 'OK' button. After clicking the button if color is selected properly the value will be returned in master script.
161        """
162        ok_button: ctk.CTkButton = ctk.CTkButton(
163            master = self.bottom_frame,
164            text          = 'Ok',
165            command       = self.on_ok_button,
166            font          = self.font if self.font else ctk.CTkFont('', self.font_size),
167            width         = (self.font_size*3),
168            corner_radius = self.corner_radius,
169            border_width  = 2,
170            border_color  = self.border_color,
171            fg_color      = self.button_fg_color,
172            hover_color   = self.button_hover_color
173        )
174        ok_button.pack(side=ctk.RIGHT, padx=3, pady=3)
175
176    def new_slider_frame(self, frame: ctk.CTkFrame) -> ctk.CTkFrame:
177        """Function creating frame for slider used to change R or G or B value.
178
179        Args:
180            frame (ctk.CTkFrame): Parent Frame on which cell will be represented.
181
182        Returns:
183            ctk.CTkFrame: Ready packed frame.
184        """
185        slider_frame: ctk.CTkFrame = ctk.CTkFrame(
186            master        = frame,
187            fg_color      = 'transparent',
188            corner_radius = self.corner_radius
189        )
190        slider_frame.pack(side=ctk.TOP, padx=3, pady=3)
191        return slider_frame
192
193    @staticmethod
194    def validate_input(P: str) -> bool:
195        """Validation of R,G,B inputs from entry boxes.
196
197        Args:
198            P (str): New input character.
199
200        Returns:
201            bool: True if RGB encoding requirements are met, False otherwise. 
202        """
203        if P == '':
204            return True
205        if not P.isdigit():
206            return False
207        if len(P) > 3:
208            return False
209        value = int(P)
210        if 0 <= value <= 255:
211            return True
212        return False
213
214    def r_g_b_sliders(self) -> None:
215        """Function creating sliders to change RGB values using interactive sliders.
216        """
217    # sliders frame
218        vcmd = (self.register(self.validate_input), '%P')
219        frame: ctk.CTkFrame = ctk.CTkFrame(
220            master   = self.main_frame,
221            fg_color = 'transparent'
222        )
223        frame.pack(side=ctk.TOP)
224    # R value slider
225        slider_frame: ctk.CTkFrame = self.new_slider_frame(frame)
226        ctk.CTkLabel(
227            master = slider_frame,
228            text   = 'R: ',
229            font   = self.font if self.font else ctk.CTkFont('', self.font_size)
230        ).pack(side=ctk.LEFT, padx=3, pady=3)
231        self.r_val_label: ctk.CTkEntry = ctk.CTkEntry(
232            master          = slider_frame,
233            validate        = 'key',
234            validatecommand = vcmd,
235            corner_radius   = self.corner_radius,
236            font            = self.font if self.font else ctk.CTkFont('', self.font_size),
237            width           = (self.font_size*3),
238            border_color    = self.border_color
239        )
240        self.r_val_label.pack(side=ctk.LEFT, padx=3, pady=3)
241        self.r_slider: ctk.CTkSlider = ctk.CTkSlider(
242            master               = slider_frame,
243            from_                = 0,
244            to                   = 255,
245            number_of_steps      = 255,
246            command              = lambda e: self.slider_on_change(e, r=True),
247            button_corner_radius = self.corner_radius + 1 if self.corner_radius is not None else self.corner_radius,
248            button_length        = 12,
249            corner_radius        = self.corner_radius + 1 if self.corner_radius is not None else self.corner_radius,
250            button_color         = self.slider_button_color,
251            hover                = False,
252            progress_color       = self.slider_progress_color,
253            fg_color             = self.slider_fg_color
254        )
255    # G value slider
256        slider_frame = self.new_slider_frame(frame)
257        ctk.CTkLabel(slider_frame, text='G: ', font=self.font if self.font else ctk.CTkFont('', self.font_size)).pack(side=ctk.LEFT, padx=3, pady=3)
258        self.g_val_label = ctk.CTkEntry(
259            master          = slider_frame,
260            validate        = 'key',
261            validatecommand = vcmd,
262            corner_radius   = self.corner_radius,
263            font            = self.font if self.font else ctk.CTkFont('', self.font_size),
264            width           = (self.font_size*3),
265            border_color    = self.border_color
266        )
267        self.g_val_label.pack(side=ctk.LEFT, padx=3, pady=3)
268        self.g_slider: ctk.CTkSlider = ctk.CTkSlider(
269            master               = slider_frame, 
270            from_                = 0,
271            to                   = 255,
272            number_of_steps      = 255,
273            command              = lambda e: self.slider_on_change(e, g=True),
274            button_corner_radius = self.corner_radius + 1 if self.corner_radius is not None else self.corner_radius,
275            button_length        = 12,
276            corner_radius        = self.corner_radius + 1 if self.corner_radius is not None else self.corner_radius,
277            button_color         = self.slider_button_color,
278            hover                = False,
279            progress_color       = self.slider_progress_color,
280            fg_color             = self.slider_fg_color
281        )
282    # B value slider
283        slider_frame = self.new_slider_frame(frame)
284        ctk.CTkLabel(slider_frame, text='B: ', font=self.font if self.font else ctk.CTkFont('', self.font_size)).pack(side=ctk.LEFT, padx=3, pady=3)
285        self.b_val_label: ctk.CTkEntry = ctk.CTkEntry(
286            master          = slider_frame,
287            validate        = 'key',
288            validatecommand = vcmd,
289            corner_radius   = self.corner_radius,
290            font            = self.font if self.font else ctk.CTkFont('', self.font_size), 
291            width           = (self.font_size*3),
292            border_color    = self.border_color
293        )
294        self.b_val_label.pack(side=ctk.LEFT, padx=3, pady=3)
295        self.b_slider: ctk.CTkSlider = ctk.CTkSlider(
296            master               = slider_frame,
297            from_                = 0,
298            to                   = 255,
299            number_of_steps      = 255,
300            command              = lambda e: self.slider_on_change(e, b=True),
301            button_corner_radius = self.corner_radius + 1 if self.corner_radius is not None else self.corner_radius,
302            button_length        = 12,
303            corner_radius        = self.corner_radius + 1 if self.corner_radius is not None else self.corner_radius,
304            button_color         = self.slider_button_color,
305            hover                = False,
306            progress_color       = self.slider_progress_color,
307            fg_color             = self.slider_fg_color
308        )
309    # initial setup of all labels
310        self.r_val_label.insert(0, self.r_val)
311        self.g_val_label.insert(0, self.g_val)
312        self.b_val_label.insert(0, self.b_val)
313        self.r_slider.set(self.r_val)
314        self.g_slider.set(self.g_val)
315        self.b_slider.set(self.b_val)
316        self.r_slider.pack(side=ctk.LEFT, padx=3, pady=3)
317        self.g_slider.pack(side=ctk.LEFT, padx=3, pady=3)
318        self.b_slider.pack(side=ctk.LEFT, padx=3, pady=3)
319    # binding for preview update
320        self.r_val_label.bind('<KeyRelease>', lambda e: self.update_sliders(e))
321        self.g_val_label.bind('<KeyRelease>', lambda e: self.update_sliders(e))
322        self.b_val_label.bind('<KeyRelease>', lambda e: self.update_sliders(e))
323        self.r_val_label.bind('<KeyRelease>', lambda e: self.update_hex(e))
324        self.g_val_label.bind('<KeyRelease>', lambda e: self.update_hex(e))
325        self.b_val_label.bind('<KeyRelease>', lambda e: self.update_hex(e))
326
327    def update_hex(self, event=None) -> None:
328        self.hex_val_label.delete(0, ctk.END)
329        self.hex_val_label.insert(0, f'{self.convert_to_hex()}')
330
331    def update_sliders(self, event=None, r: int=-1, g: int=-1, b: int=-1) -> None:
332        """Function updating position of sliders and its corresponding RGB entry boxes to proper value after changing hex entry box.
333
334        Args:
335            event (Any): Event type. Doesn't matter but is required parameter by customtkinter.
336            r (int, optional): Given red color intensity. Defaults to -1.
337            g (int, optional): Given green color intensity. Defaults to -1.
338            b (int, optional): Given blue color intensity. Defaults to -1.
339        """
340        if r == -1:
341            r = int(self.r_val_label.get()) if self.r_val_label.get() != '' else 0
342        if g == -1:
343            g = int(self.g_val_label.get()) if self.g_val_label.get() != '' else 0
344        if b == -1:
345            b = int(self.b_val_label.get()) if self.b_val_label.get() != '' else 0
346        self.r_val = r
347        self.g_val = g
348        self.b_val = b
349        self.r_slider.set(r) if 0 < r <= 255 else self.r_slider.set(0)
350        self.g_slider.set(g) if 0 < g <= 255 else self.g_slider.set(0)
351        self.b_slider.set(b) if 0 < b <= 255 else self.b_slider.set(0)
352        self.color_prev_box.configure(fg_color=self.convert_to_hex())
353
354    def slider_on_change(self, event, r: bool=False, g: bool=False, b: bool=False) -> None:
355        """Updates corresponding RGB color code and hex color value based on value of slider.
356
357        Args:
358            event (Any): Event type. Doesn't matter but is required parameter by customtkinter.
359            r (bool, optional): Flag to set which slider was changed [r]. Defaults to False.
360            g (bool, optional): Flag to set which slider was changed [g]. Defaults to False.
361            b (bool, optional): Flag to set which slider was changed [b]. Defaults to False.
362        """
363        if r:
364            self.r_val = int(self.r_slider.get())
365            self.r_val_label.delete(0, ctk.END)
366            self.r_val_label.insert(0, self.r_val)
367        elif g:
368            self.g_val = int(self.g_slider.get())
369            self.g_val_label.delete(0, ctk.END)
370            self.g_val_label.insert(0, self.g_val)
371        elif b:
372            self.b_val = int(self.b_slider.get())
373            self.b_val_label.delete(0, ctk.END)
374            self.b_val_label.insert(0, self.b_val)
375        self.color_prev_box.configure(fg_color=self.convert_to_hex())
376        self.update_hex()
377
378    def convert_to_hex(self) -> str:
379        """Function converting RGB value to hex color code.
380
381        Returns:
382            str: f'{6 digit code}' | Regex example: ^#[0-9a-fA-F]{6}
383        """
384        return f'#{self.r_val:02x}{self.g_val:02x}{self.b_val:02x}'
385
386    def convert_to_r_g_b(self) -> tuple[int, int , int]:
387        """Function converting hex color code to RGB value.
388
389        Returns:
390            tuple[int, int , int]: Tuple of R, G and B values.
391        """
392        self.hex_val = self.hex_val_label.get()
393        hex_code: str = self.hex_val.lstrip('#') if self.hex_val else '000000'
394        r = int(hex_code[0:2], 16)
395        g = int(hex_code[2:4], 16)
396        b = int(hex_code[4:6], 16)
397        return (r, g, b)
398
399    def on_close(self) -> None:
400        """Custom closing function ensuring proper closing of the window. Sets hex_val to None to omit color change.
401        """
402        self.hex_val = None
403        self.grab_release()
404        self.destroy()
405
406    def on_ok_button(self) -> None:
407        """Custom closing function.
408        """
409        self.destroy()
410
411    def get_color(self) -> str | None:
412        """Function waiting for the window being destroyed.
413
414        Returns:
415            str | None: Hex color code if closed with OK button or None if closed with ❌.
416        """
417        self.master.wait_window(self)
418        return self.convert_to_hex() if self.hex_val else None

Class used to pick custom theme color. Is also a module that can be reused with apps using customtkinter.

Arguments:
  • - ctk.CTkTopLevel : Inheritance from customtkinter CTkTopLevel window.
ColorPicker( fg_color: str | None = None, preview_size: int = 100, r: int = 0, g: int = 0, b: int = 0, font: customtkinter.windows.widgets.font.ctk_font.CTkFont | None = None, border_color: str | None = None, slider_button_color: str | None = None, slider_progress_color: str | None = None, slider_fg_color: str | None = None, preview_border_color: str | None = None, button_fg_color: str | None = None, button_hover_color: str | None = None, icon: str | None = None, corner_radius: int | None = None)
15    def __init__(self, fg_color: str | None=None, preview_size: int=100, r: int=0, g: int=0, b: int=0, font: ctk.CTkFont | None=None, 
16                 border_color: str | None=None, slider_button_color: str | None=None, slider_progress_color: str | None=None, slider_fg_color: str | None=None, 
17                 preview_border_color: str | None=None, button_fg_color: str | None=None, button_hover_color: str | None=None, icon: str | None=None,
18                 corner_radius: int | None=None) -> None:
19        """Constructor handling most important function calls and variable setup.
20
21        Args:
22            fg_color (str | None, optional): Background color of the window. Defaults to None.
23            preview_size (int, optional): _description_. Defaults to 100.
24            r (int, optional): Default red intensity value. Defaults to 0.
25            g (int, optional): Default green intensity value. Defaults to 0.
26            b (int, optional): Default blue intensity value. Defaults to 0.
27            font (ctk.CTkFont | None, optional): Custom font. Defaults to None.
28        """
29        super().__init__(fg_color=fg_color)
30        self.fg_color: str| None = fg_color
31        self.border_color: str| None = border_color
32        self.slider_button_color: str| None = slider_button_color
33        self.slider_progress_color: str| None = slider_progress_color
34        self.slider_fg_color: str| None = slider_fg_color
35        self.preview_border_color: str | None = preview_border_color
36        self.button_fg_color: str | None = button_fg_color
37        self.button_hover_color: str | None = button_hover_color
38        self.corner_radius: int | None = corner_radius
39        self.grab_set()
40        self.attributes('-topmost', True)
41        self.title('Color Picker')
42        self.font: ctk.CTkFont | None = font if font else None
43        self.font_size: int = font.cget('size') if font else 15
44        self.preview_size: int = preview_size
45        self.r_val: int = r
46        self.g_val: int = g
47        self.b_val: int = b
48        self.hex_val: str | None = self.convert_to_hex()
49        self.main_frame: ctk.CTkFrame = ctk.CTkFrame(
50            master        = self,
51            corner_radius = 0,
52            fg_color      = self.fg_color
53        )
54        self.main_frame.pack(side=ctk.TOP, expand=True, ipadx=10, ipady=10)
55        self.color_preview()
56        self.r_g_b_sliders()
57        self.update_sliders(None)
58        self.bottom_frame: ctk.CTkFrame = ctk.CTkFrame(
59            master        = self.main_frame,
60            corner_radius = self.corner_radius,
61            fg_color      = 'transparent'
62        )
63        self.bottom_frame.pack(side=ctk.BOTTOM, expand=True, ipadx=10, ipady=10)
64        self.hex_color_label()
65        self.ok_button()
66        self.resizable(False, False)
67        self.protocol('WM_DELETE_WINDOW', self.on_close)
68        self.lift()
69        self.center_window()
70        self.after(201, lambda: self.iconbitmap(icon))

Constructor handling most important function calls and variable setup.

Arguments:
  • fg_color (str | None, optional): Background color of the window. Defaults to None.
  • preview_size (int, optional): _description_. Defaults to 100.
  • r (int, optional): Default red intensity value. Defaults to 0.
  • g (int, optional): Default green intensity value. Defaults to 0.
  • b (int, optional): Default blue intensity value. Defaults to 0.
  • font (ctk.CTkFont | None, optional): Custom font. Defaults to None.
fg_color: str | None
border_color: str | None
slider_button_color: str | None
slider_progress_color: str | None
slider_fg_color: str | None
preview_border_color: str | None
button_fg_color: str | None
button_hover_color: str | None
corner_radius: int | None
font: customtkinter.windows.widgets.font.ctk_font.CTkFont | None
font_size: int
preview_size: int
r_val: int
g_val: int
b_val: int
hex_val: str | None
main_frame: customtkinter.windows.widgets.ctk_frame.CTkFrame
bottom_frame: customtkinter.windows.widgets.ctk_frame.CTkFrame
def center_window(self) -> None:
72    def center_window(self) -> None:
73        """Function centering the TopLevel window. Screen size independent.
74        """
75        x: int = self.master.winfo_screenwidth()
76        y: int = self.master.winfo_screenheight()
77        app_width: int = self.winfo_width()
78        app_height: int = self.winfo_height()
79        self.geometry(f'+{(x//2)-app_width}+{(y//2)-app_height}')

Function centering the TopLevel window. Screen size independent.

def color_preview(self) -> None:
81    def color_preview(self) -> None:
82        """Function creating frame for color preview.
83        """
84        self.color_prev_box: ctk.CTkFrame = ctk.CTkFrame(
85            master        = self.main_frame,
86            fg_color      = self.convert_to_hex(),
87            border_width  = 3,
88            width         = self.preview_size, 
89            height        = self.preview_size,
90            corner_radius = self.corner_radius,
91            border_color  = self.preview_border_color
92        )
93        self.color_prev_box.pack(side=ctk.RIGHT, padx=3, pady=3, expand=True)

Function creating frame for color preview.

@staticmethod
def validate_hex_color(value_if_allowed: str) -> bool:
 95    @staticmethod
 96    def validate_hex_color(value_if_allowed: str) -> bool:
 97        """Function validating new character in hex entry box. Can take one character or longer string to allow pasting.
 98
 99        Args:
100            value_if_allowed (str): New value to check.
101
102        Returns:
103            bool: True if hex color patter was met, False otherwise.
104        """
105        if len(value_if_allowed) == 0 or (re.compile(r'^#[0-9a-fA-F]{0,6}$').match(value_if_allowed)):
106            for char in value_if_allowed[1:]:
107                if char not in '0123456789ABCDEFabcdef':
108                    return False
109            return True
110        return False

Function validating new character in hex entry box. Can take one character or longer string to allow pasting.

Arguments:
  • value_if_allowed (str): New value to check.
Returns:

bool: True if hex color patter was met, False otherwise.

def paste_hex_color(self, event) -> None:
112    def paste_hex_color(self, event) -> None:
113        """Function handling pasting custom color into hex color entry box.
114
115        Args:
116            event (Any): Event type. Doesn't matter but is required parameter by customtkinter.
117        """
118        clipboard = self.master.clipboard_get()
119        if self.validate_hex_color(clipboard):
120            self.hex_val_label.delete(0, ctk.END)
121            self.hex_val_label.insert(0, clipboard)
122        return None

Function handling pasting custom color into hex color entry box.

Arguments:
  • event (Any): Event type. Doesn't matter but is required parameter by customtkinter.
def update_on_hex(self, event) -> None:
124    def update_on_hex(self, event) -> None:
125        """Function handling all changes on entering last hex color. It changes RGB labels values, sliders values and color preview frame to the desired color.
126
127        Args:
128            event (Any): Event type. Doesn't matter but is required parameter by customtkinter.
129        """
130        if len(self.hex_val_label.get()) == 7:
131            self.r_val, self.g_val, self.b_val = self.convert_to_r_g_b()
132            self.r_val_label.delete(0, ctk.END)
133            self.g_val_label.delete(0, ctk.END)
134            self.b_val_label.delete(0, ctk.END)
135            self.r_val_label.insert(0, f'{self.r_val}')
136            self.g_val_label.insert(0, f'{self.g_val}')
137            self.b_val_label.insert(0, f'{self.b_val}')
138            self.update_sliders(None, r=self.r_val, g=self.g_val, b=self.b_val)

Function handling all changes on entering last hex color. It changes RGB labels values, sliders values and color preview frame to the desired color.

Arguments:
  • event (Any): Event type. Doesn't matter but is required parameter by customtkinter.
def hex_color_label(self) -> None:
140    def hex_color_label(self) -> None:
141        """Function creating entry box for hex color.
142        """
143        vcmd = (self.register(self.validate_hex_color), '%P')
144        ctk.CTkLabel(self.bottom_frame, text='Hex: ', font=self.font if self.font else ctk.CTkFont('', self.font_size)).pack(side=ctk.LEFT, padx=3, pady=3)
145        self.hex_val_label: ctk.CTkEntry = ctk.CTkEntry(
146            master          = self.bottom_frame,
147            validate        = 'key',
148            validatecommand = vcmd,
149            corner_radius   = self.corner_radius,
150            font            = self.font if self.font else ctk.CTkFont('', self.font_size),
151            width           = (self.font_size*8),
152            border_color    = self.border_color
153        )
154        self.hex_val_label.pack(side=ctk.LEFT, padx=3, pady=3)
155        self.hex_val_label.insert(0, f'{self.hex_val}')
156        self.hex_val_label.bind('<Control-v>', self.paste_hex_color)
157        self.hex_val_label.bind('<KeyRelease>', lambda e: self.update_on_hex(e))

Function creating entry box for hex color.

def ok_button(self) -> None:
159    def ok_button(self) -> None:
160        """Function creating 'OK' button. After clicking the button if color is selected properly the value will be returned in master script.
161        """
162        ok_button: ctk.CTkButton = ctk.CTkButton(
163            master = self.bottom_frame,
164            text          = 'Ok',
165            command       = self.on_ok_button,
166            font          = self.font if self.font else ctk.CTkFont('', self.font_size),
167            width         = (self.font_size*3),
168            corner_radius = self.corner_radius,
169            border_width  = 2,
170            border_color  = self.border_color,
171            fg_color      = self.button_fg_color,
172            hover_color   = self.button_hover_color
173        )
174        ok_button.pack(side=ctk.RIGHT, padx=3, pady=3)

Function creating 'OK' button. After clicking the button if color is selected properly the value will be returned in master script.

def new_slider_frame( self, frame: customtkinter.windows.widgets.ctk_frame.CTkFrame) -> customtkinter.windows.widgets.ctk_frame.CTkFrame:
176    def new_slider_frame(self, frame: ctk.CTkFrame) -> ctk.CTkFrame:
177        """Function creating frame for slider used to change R or G or B value.
178
179        Args:
180            frame (ctk.CTkFrame): Parent Frame on which cell will be represented.
181
182        Returns:
183            ctk.CTkFrame: Ready packed frame.
184        """
185        slider_frame: ctk.CTkFrame = ctk.CTkFrame(
186            master        = frame,
187            fg_color      = 'transparent',
188            corner_radius = self.corner_radius
189        )
190        slider_frame.pack(side=ctk.TOP, padx=3, pady=3)
191        return slider_frame

Function creating frame for slider used to change R or G or B value.

Arguments:
  • frame (ctk.CTkFrame): Parent Frame on which cell will be represented.
Returns:

ctk.CTkFrame: Ready packed frame.

@staticmethod
def validate_input(P: str) -> bool:
193    @staticmethod
194    def validate_input(P: str) -> bool:
195        """Validation of R,G,B inputs from entry boxes.
196
197        Args:
198            P (str): New input character.
199
200        Returns:
201            bool: True if RGB encoding requirements are met, False otherwise. 
202        """
203        if P == '':
204            return True
205        if not P.isdigit():
206            return False
207        if len(P) > 3:
208            return False
209        value = int(P)
210        if 0 <= value <= 255:
211            return True
212        return False

Validation of R,G,B inputs from entry boxes.

Arguments:
  • P (str): New input character.
Returns:

bool: True if RGB encoding requirements are met, False otherwise.

def r_g_b_sliders(self) -> None:
214    def r_g_b_sliders(self) -> None:
215        """Function creating sliders to change RGB values using interactive sliders.
216        """
217    # sliders frame
218        vcmd = (self.register(self.validate_input), '%P')
219        frame: ctk.CTkFrame = ctk.CTkFrame(
220            master   = self.main_frame,
221            fg_color = 'transparent'
222        )
223        frame.pack(side=ctk.TOP)
224    # R value slider
225        slider_frame: ctk.CTkFrame = self.new_slider_frame(frame)
226        ctk.CTkLabel(
227            master = slider_frame,
228            text   = 'R: ',
229            font   = self.font if self.font else ctk.CTkFont('', self.font_size)
230        ).pack(side=ctk.LEFT, padx=3, pady=3)
231        self.r_val_label: ctk.CTkEntry = ctk.CTkEntry(
232            master          = slider_frame,
233            validate        = 'key',
234            validatecommand = vcmd,
235            corner_radius   = self.corner_radius,
236            font            = self.font if self.font else ctk.CTkFont('', self.font_size),
237            width           = (self.font_size*3),
238            border_color    = self.border_color
239        )
240        self.r_val_label.pack(side=ctk.LEFT, padx=3, pady=3)
241        self.r_slider: ctk.CTkSlider = ctk.CTkSlider(
242            master               = slider_frame,
243            from_                = 0,
244            to                   = 255,
245            number_of_steps      = 255,
246            command              = lambda e: self.slider_on_change(e, r=True),
247            button_corner_radius = self.corner_radius + 1 if self.corner_radius is not None else self.corner_radius,
248            button_length        = 12,
249            corner_radius        = self.corner_radius + 1 if self.corner_radius is not None else self.corner_radius,
250            button_color         = self.slider_button_color,
251            hover                = False,
252            progress_color       = self.slider_progress_color,
253            fg_color             = self.slider_fg_color
254        )
255    # G value slider
256        slider_frame = self.new_slider_frame(frame)
257        ctk.CTkLabel(slider_frame, text='G: ', font=self.font if self.font else ctk.CTkFont('', self.font_size)).pack(side=ctk.LEFT, padx=3, pady=3)
258        self.g_val_label = ctk.CTkEntry(
259            master          = slider_frame,
260            validate        = 'key',
261            validatecommand = vcmd,
262            corner_radius   = self.corner_radius,
263            font            = self.font if self.font else ctk.CTkFont('', self.font_size),
264            width           = (self.font_size*3),
265            border_color    = self.border_color
266        )
267        self.g_val_label.pack(side=ctk.LEFT, padx=3, pady=3)
268        self.g_slider: ctk.CTkSlider = ctk.CTkSlider(
269            master               = slider_frame, 
270            from_                = 0,
271            to                   = 255,
272            number_of_steps      = 255,
273            command              = lambda e: self.slider_on_change(e, g=True),
274            button_corner_radius = self.corner_radius + 1 if self.corner_radius is not None else self.corner_radius,
275            button_length        = 12,
276            corner_radius        = self.corner_radius + 1 if self.corner_radius is not None else self.corner_radius,
277            button_color         = self.slider_button_color,
278            hover                = False,
279            progress_color       = self.slider_progress_color,
280            fg_color             = self.slider_fg_color
281        )
282    # B value slider
283        slider_frame = self.new_slider_frame(frame)
284        ctk.CTkLabel(slider_frame, text='B: ', font=self.font if self.font else ctk.CTkFont('', self.font_size)).pack(side=ctk.LEFT, padx=3, pady=3)
285        self.b_val_label: ctk.CTkEntry = ctk.CTkEntry(
286            master          = slider_frame,
287            validate        = 'key',
288            validatecommand = vcmd,
289            corner_radius   = self.corner_radius,
290            font            = self.font if self.font else ctk.CTkFont('', self.font_size), 
291            width           = (self.font_size*3),
292            border_color    = self.border_color
293        )
294        self.b_val_label.pack(side=ctk.LEFT, padx=3, pady=3)
295        self.b_slider: ctk.CTkSlider = ctk.CTkSlider(
296            master               = slider_frame,
297            from_                = 0,
298            to                   = 255,
299            number_of_steps      = 255,
300            command              = lambda e: self.slider_on_change(e, b=True),
301            button_corner_radius = self.corner_radius + 1 if self.corner_radius is not None else self.corner_radius,
302            button_length        = 12,
303            corner_radius        = self.corner_radius + 1 if self.corner_radius is not None else self.corner_radius,
304            button_color         = self.slider_button_color,
305            hover                = False,
306            progress_color       = self.slider_progress_color,
307            fg_color             = self.slider_fg_color
308        )
309    # initial setup of all labels
310        self.r_val_label.insert(0, self.r_val)
311        self.g_val_label.insert(0, self.g_val)
312        self.b_val_label.insert(0, self.b_val)
313        self.r_slider.set(self.r_val)
314        self.g_slider.set(self.g_val)
315        self.b_slider.set(self.b_val)
316        self.r_slider.pack(side=ctk.LEFT, padx=3, pady=3)
317        self.g_slider.pack(side=ctk.LEFT, padx=3, pady=3)
318        self.b_slider.pack(side=ctk.LEFT, padx=3, pady=3)
319    # binding for preview update
320        self.r_val_label.bind('<KeyRelease>', lambda e: self.update_sliders(e))
321        self.g_val_label.bind('<KeyRelease>', lambda e: self.update_sliders(e))
322        self.b_val_label.bind('<KeyRelease>', lambda e: self.update_sliders(e))
323        self.r_val_label.bind('<KeyRelease>', lambda e: self.update_hex(e))
324        self.g_val_label.bind('<KeyRelease>', lambda e: self.update_hex(e))
325        self.b_val_label.bind('<KeyRelease>', lambda e: self.update_hex(e))

Function creating sliders to change RGB values using interactive sliders.

def update_hex(self, event=None) -> None:
327    def update_hex(self, event=None) -> None:
328        self.hex_val_label.delete(0, ctk.END)
329        self.hex_val_label.insert(0, f'{self.convert_to_hex()}')
def update_sliders(self, event=None, r: int = -1, g: int = -1, b: int = -1) -> None:
331    def update_sliders(self, event=None, r: int=-1, g: int=-1, b: int=-1) -> None:
332        """Function updating position of sliders and its corresponding RGB entry boxes to proper value after changing hex entry box.
333
334        Args:
335            event (Any): Event type. Doesn't matter but is required parameter by customtkinter.
336            r (int, optional): Given red color intensity. Defaults to -1.
337            g (int, optional): Given green color intensity. Defaults to -1.
338            b (int, optional): Given blue color intensity. Defaults to -1.
339        """
340        if r == -1:
341            r = int(self.r_val_label.get()) if self.r_val_label.get() != '' else 0
342        if g == -1:
343            g = int(self.g_val_label.get()) if self.g_val_label.get() != '' else 0
344        if b == -1:
345            b = int(self.b_val_label.get()) if self.b_val_label.get() != '' else 0
346        self.r_val = r
347        self.g_val = g
348        self.b_val = b
349        self.r_slider.set(r) if 0 < r <= 255 else self.r_slider.set(0)
350        self.g_slider.set(g) if 0 < g <= 255 else self.g_slider.set(0)
351        self.b_slider.set(b) if 0 < b <= 255 else self.b_slider.set(0)
352        self.color_prev_box.configure(fg_color=self.convert_to_hex())

Function updating position of sliders and its corresponding RGB entry boxes to proper value after changing hex entry box.

Arguments:
  • event (Any): Event type. Doesn't matter but is required parameter by customtkinter.
  • r (int, optional): Given red color intensity. Defaults to -1.
  • g (int, optional): Given green color intensity. Defaults to -1.
  • b (int, optional): Given blue color intensity. Defaults to -1.
def slider_on_change(self, event, r: bool = False, g: bool = False, b: bool = False) -> None:
354    def slider_on_change(self, event, r: bool=False, g: bool=False, b: bool=False) -> None:
355        """Updates corresponding RGB color code and hex color value based on value of slider.
356
357        Args:
358            event (Any): Event type. Doesn't matter but is required parameter by customtkinter.
359            r (bool, optional): Flag to set which slider was changed [r]. Defaults to False.
360            g (bool, optional): Flag to set which slider was changed [g]. Defaults to False.
361            b (bool, optional): Flag to set which slider was changed [b]. Defaults to False.
362        """
363        if r:
364            self.r_val = int(self.r_slider.get())
365            self.r_val_label.delete(0, ctk.END)
366            self.r_val_label.insert(0, self.r_val)
367        elif g:
368            self.g_val = int(self.g_slider.get())
369            self.g_val_label.delete(0, ctk.END)
370            self.g_val_label.insert(0, self.g_val)
371        elif b:
372            self.b_val = int(self.b_slider.get())
373            self.b_val_label.delete(0, ctk.END)
374            self.b_val_label.insert(0, self.b_val)
375        self.color_prev_box.configure(fg_color=self.convert_to_hex())
376        self.update_hex()

Updates corresponding RGB color code and hex color value based on value of slider.

Arguments:
  • event (Any): Event type. Doesn't matter but is required parameter by customtkinter.
  • r (bool, optional): Flag to set which slider was changed [r]. Defaults to False.
  • g (bool, optional): Flag to set which slider was changed [g]. Defaults to False.
  • b (bool, optional): Flag to set which slider was changed [b]. Defaults to False.
def convert_to_hex(self) -> str:
378    def convert_to_hex(self) -> str:
379        """Function converting RGB value to hex color code.
380
381        Returns:
382            str: f'{6 digit code}' | Regex example: ^#[0-9a-fA-F]{6}
383        """
384        return f'#{self.r_val:02x}{self.g_val:02x}{self.b_val:02x}'

Function converting RGB value to hex color code.

Returns:

str: f'{6 digit code}' | Regex example: ^#[0-9a-fA-F]{6}

def convert_to_r_g_b(self) -> tuple[int, int, int]:
386    def convert_to_r_g_b(self) -> tuple[int, int , int]:
387        """Function converting hex color code to RGB value.
388
389        Returns:
390            tuple[int, int , int]: Tuple of R, G and B values.
391        """
392        self.hex_val = self.hex_val_label.get()
393        hex_code: str = self.hex_val.lstrip('#') if self.hex_val else '000000'
394        r = int(hex_code[0:2], 16)
395        g = int(hex_code[2:4], 16)
396        b = int(hex_code[4:6], 16)
397        return (r, g, b)

Function converting hex color code to RGB value.

Returns:

tuple[int, int , int]: Tuple of R, G and B values.

def on_close(self) -> None:
399    def on_close(self) -> None:
400        """Custom closing function ensuring proper closing of the window. Sets hex_val to None to omit color change.
401        """
402        self.hex_val = None
403        self.grab_release()
404        self.destroy()

Custom closing function ensuring proper closing of the window. Sets hex_val to None to omit color change.

def on_ok_button(self) -> None:
406    def on_ok_button(self) -> None:
407        """Custom closing function.
408        """
409        self.destroy()

Custom closing function.

def get_color(self) -> str | None:
411    def get_color(self) -> str | None:
412        """Function waiting for the window being destroyed.
413
414        Returns:
415            str | None: Hex color code if closed with OK button or None if closed with ❌.
416        """
417        self.master.wait_window(self)
418        return self.convert_to_hex() if self.hex_val else None

Function waiting for the window being destroyed.

Returns:

str | None: Hex color code if closed with OK button or None if closed with ❌.