"""Fabelous CTK Color Picker for customtkinter ---------------------------------------- Based on the original work by Akash Bora (Akascape) Contributions from Victor Vimbert-Guerlais (helloHackYnow) Original: https://github.com/Akascape/CTkColorPicker/tree/main """ import customtkinter as Ctk from PIL import Image, ImageTk from icons.icons import Icons import scripts.get_sys_info as system_code import math class AskColor(Ctk.CTkToplevel): def __init__(self, master, font = None, width: int = 250, title: str = "Choose Color", initial_color: str = None, text: str = "apply", corner_radius: int = 16, slider_border: int = 1, **button_kwargs): super().__init__(master, **button_kwargs) system_code.load_json_file() self.icons = Icons(path=system_code.data_path) self.selected_color = None self.title(title) WIDTH = width if width >= 250 else 250 HEIGHT = WIDTH + 110 self.font = font if font is not None else Ctk.CTkFont(family="kDefaultFont", size=16) self.image_dimension = self._apply_window_scaling(WIDTH - 100) self.target_dimension = self._apply_window_scaling(20) self.initial_color = initial_color self.maxsize(WIDTH, HEIGHT) self.minsize(WIDTH, HEIGHT) self.resizable(width=False, height=False) self.transient(self.master) self.lift() self.after(10) self.protocol("WM_DELETE_WINDOW", self._on_closing) self.default_hex_color = "#ffffff" self.default_rgb = [255, 255, 255] self.rgb_color = self.default_rgb[:] self.button_text = text self.corner_radius = corner_radius self.slider_border = 10 if slider_border >= 10 else slider_border self.frame = Ctk.CTkFrame(master=self) self.frame.grid(sticky="nswe", padx=5, pady=5) self.fg_color = self.fg_color = self._apply_appearance_mode(Ctk.ThemeManager.theme["CTkFrame"]["fg_color"]) self.canvas = Ctk.CTkCanvas(self.frame, height=self.image_dimension, width=self.image_dimension, highlightthickness=0, bg=self.fg_color) self.canvas.grid(row=0, column=0, columnspan=2, pady=20) self.canvas.bind("", self.on_mouse_drag) self.img1 = self.icons.get_image("ColorWheel").resize((self.image_dimension, self.image_dimension), Image.Resampling.LANCZOS) self.img2 = self.icons.get_image("Target").resize((self.target_dimension, self.target_dimension), Image.Resampling.LANCZOS) self.wheel = ImageTk.PhotoImage(self.img1) self.target = ImageTk.PhotoImage(self.img2) self.canvas.create_image(self.image_dimension/2, self.image_dimension/2, image=self.wheel) self.set_initial_color(initial_color) self.brightness_slider_value = Ctk.IntVar() self.brightness_slider_value.set(255) self.slider = Ctk.CTkSlider(master=self.frame, height=20, border_width=self.slider_border, button_length=15, progress_color=self.default_hex_color, from_=0, to=255, variable=self.brightness_slider_value, number_of_steps=256, button_corner_radius=self.corner_radius, corner_radius=self.corner_radius, command=lambda x:self.update_colors()) self.slider.grid(row=1, column=0, columnspan=2, pady=(0, 15), padx=20-self.slider_border) self.label = Ctk.CTkLabel(master=self.frame, text_color="#000000", height=50, width=75, fg_color=self.default_hex_color, corner_radius=self.corner_radius, text="") self.color_entry = Ctk.CTkEntry(master=self.frame, height=50, corner_radius=self.corner_radius, font=self.font, width=100) self.color_entry.configure(placeholder_text=self.default_hex_color) # Insert the new text self.button = Ctk.CTkButton(master=self.frame, text=self.button_text, height=50, corner_radius=self.corner_radius,width = 200, command=self._ok_event, **button_kwargs) self.label.grid(row=2, column=0, padx=(5, 20), pady=10, sticky="e") self.color_entry.grid(row=2, column=1, padx=(5, 5), pady=10, sticky="w") self.button.grid(row=3, column=0, columnspan=3, padx=5, pady=10) self.after(150, lambda: self.label.focus()) self.grab_set() def get(self): # Use the stored selected_color instead of accessing the widget return self.selected_color def _ok_event(self, event=None): input_string = self.color_entry.get() self.selected_color = self.check_rgb_hex_color(input_string) # Store the selected color self.grab_release() self.destroy() del self.img1 del self.img2 del self.wheel del self.target def check_rgb_hex_color(self, input_string): if not input_string: return self.default_hex_color if not input_string.startswith("#"): input_string = "#" + input_string hex_color = input_string.lstrip("#") if len(hex_color) != 6: return None try: int(hex_color, 16) return input_string except ValueError: return None def _on_closing(self): self._color = None self.grab_release() self.destroy() del self.img1 del self.img2 del self.wheel del self.target def on_mouse_drag(self, event): x = event.x y = event.y self.canvas.delete("all") self.canvas.create_image(self.image_dimension/2, self.image_dimension/2, image=self.wheel) d_from_center = math.sqrt(((self.image_dimension/2)-x)**2 + ((self.image_dimension/2)-y)**2) if d_from_center < self.image_dimension/2: self.target_x, self.target_y = x, y else: self.target_x, self.target_y = self.projection_on_circle(x, y, self.image_dimension/2, self.image_dimension/2, self.image_dimension/2 -1) self.canvas.create_image(self.target_x, self.target_y, image=self.target) self.get_target_color() self.update_colors() def get_target_color(self): try: self.rgb_color = self.img1.getpixel((self.target_x, self.target_y)) r = self.rgb_color[0] g = self.rgb_color[1] b = self.rgb_color[2] self.rgb_color = [r, g, b] except AttributeError: self.rgb_color = self.default_rgb def update_colors(self): brightness = self.brightness_slider_value.get() self.get_target_color() r = int(self.rgb_color[0] * (brightness/255)) g = int(self.rgb_color[1] * (brightness/255)) b = int(self.rgb_color[2] * (brightness/255)) self.rgb_color = [r, g, b] self.default_hex_color = "#{:02x}{:02x}{:02x}".format(*self.rgb_color) self.slider.configure(progress_color=self.default_hex_color) self.label.configure(fg_color=self.default_hex_color) self.color_entry.delete(0, Ctk.END) # Delete any existing text in the color_entry widget self.color_entry.configure(placeholder_text=self.default_hex_color) # Insert the new text def projection_on_circle(self, point_x, point_y, circle_x, circle_y, radius): angle = math.atan2(point_y - circle_y, point_x - circle_x) projection_x = circle_x + radius * math.cos(angle) projection_y = circle_y + radius * math.sin(angle) return projection_x, projection_y def set_initial_color(self, initial_color): """ Sets the initial color of the target if it matches the specified `initial_color`. Falls back to the center of the image if no matching color is found or `initial_color` is invalid. Parameters: - initial_color (str): The hexadecimal color value (e.g., '#RRGGBB') to match in the image. Note: This method is in beta stage and may not accurately handle all colors. """ if not initial_color or not initial_color.startswith("#"): self._place_image_at_center() return r, g, b = self._hex_to_rgb(initial_color) for i in range(self.image_dimension): for j in range(self.image_dimension): if self._pixel_matches_color(i, j, (r, g, b)): self._place_image_at(i, j) return self._place_image_at_center() def _hex_to_rgb(self, hex_color): """ Converts a hexadecimal color to an RGB tuple. Parameters: - hex_color (str): The hexadecimal color string (e.g., '#RRGGBB'). Returns: (tuple): A tuple containing the RGB values (r, g, b). """ try: return tuple(int(hex_color.lstrip('#')[i:i+2], 16) for i in (0, 2, 4)) except ValueError: return None def _pixel_matches_color(self, x, y, color): """ Checks if the pixel at (x, y) matches the specified `color`. Parameters: - x (int): The x-coordinate of the pixel. - y (int): The y-coordinate of the pixel. - color (tuple): The RGB tuple to match. Returns: (bool): True if the pixel matches the `color`; False otherwise. """ try: return self.img1.getpixel((x, y))[:3] == color except IndexError: # Outside the image bounds return False def _place_image_at(self, x, y): """ Places the image at the specified (x, y) coordinates. Parameters: - x (int): The x-coordinate where the image should be placed. - y (int): The y-coordinate where the image should be placed. """ self.default_hex_color = self.initial_color self.canvas.create_image(x, y, image=self.target) self.target_x = x self.target_y = y def _place_image_at_center(self): """ Places the image at the center of the canvas. """ center = self.image_dimension // 2 self.canvas.create_image(center, center, image=self.target) if __name__ == "__main__": app = AskColor(master=None, font=None) app.wait_window(app) # This waits until the window is closed color = app.get() # Access the color stored before the window was destroyed print("Selected color:", color)