Esempio n. 1
0
class ColorianUI:
    def __init__(self):
        """
        Creates Colorian UI initializing the color palettes and starts running
        in a new window.
        """

        self.__default_tint_amount = 25
        self.__default_shade_amount = 25
        self.__default_tone_amount = 90

        self.__color_picker_palette = Palette()
        random_color = self.__color_picker_palette.random_color()
        self.__color_picker_palette.set_picked_color(random_color)

        self.__color_wheel_hue_palette = Palette()
        picked_hue_color = self.__color_wheel_hue_palette.find_by_name(
            random_color.name())
        self.__color_wheel_hue_palette.set_picked_color(picked_hue_color)

        self.__color_wheel_tint_palette = Palette().to_tint(
            self.__default_tint_amount)
        picked_hue_color = self.__color_wheel_tint_palette.find_by_name(
            random_color.name())
        self.__color_wheel_tint_palette.set_picked_color(picked_hue_color)

        self.__color_wheel_shade_palette = Palette().to_shade(
            self.__default_shade_amount)
        picked_hue_color = self.__color_wheel_shade_palette.find_by_name(
            random_color.name())
        self.__color_wheel_shade_palette.set_picked_color(picked_hue_color)

        self.__color_wheel_tone_palette = Palette().to_tone(
            self.__default_tone_amount)
        picked_hue_color = self.__color_wheel_tone_palette.find_by_name(
            random_color.name())
        self.__color_wheel_tone_palette.set_picked_color(picked_hue_color)

        self.__selected_color_wheel_palette = self.__color_wheel_hue_palette

        self.__color_wheels = {
            'RYB (Web)': 'RYB',
            'RGB (Screen)': 'RGB',
            'CMYK (Print)': 'CMYK'
        }
        self.__hue_variants = {
            'Hue': 'HUE',
            'Tint': 'TINT',
            'Shade': 'SHADE',
            'Tone': 'TONE'
        }
        self.__color_schemes = [
            'Analogous', 'Complementary', 'Triadic', 'Tetradic', 'Square',
            'Split-complementary', 'Double split-complementary', 'Clash',
            'Intermediate'
        ]

        # Create custom widget theme
        self.__default_ui_dark_color = '#000000'
        self.__default_ui_frame_color = '#313131'
        self.__default_ui_bg_color = '#4B4B4C'
        self.__default_ui_fg_color = '#B0AFB0'
        self.__default_ui_focus_color = '#B0AFB0'
        self.__default_ui_highlight_color = '#696969'

        self.__main_window = tk.Tk()

        widget_style = ttk.Style()
        widget_style.theme_settings(
            'alt', {
                'TButton': {
                    'configure': {
                        'anchor': tk.E,
                        'background': self.__default_ui_highlight_color,
                        'foreground': self.__default_ui_dark_color,
                        'highlightthickness': 4,
                        'font': ('Helvetica', 16),
                        'bordercolor': self.__default_ui_fg_color,
                        'padding': 5,
                        'shiftrelief': 0
                    },
                    'map': {
                        'background': [('selected', False)],
                        'bordercolor':
                        [('active', self.__default_ui_focus_color),
                         ('focus', self.__default_ui_focus_color)]
                    }
                },
                'TCanvas': {
                    'configure': {
                        'background': self.__default_ui_frame_color
                    }
                },
                'TCombobox': {
                    'configure': {
                        'arrowcolor': self.__default_ui_bg_color,
                        'arrowsize': 26,
                        'foreground': self.__default_ui_fg_color,
                        'padding': '5'
                    },
                    'map': {
                        'background':
                        [('readonly', self.__default_ui_highlight_color)],
                        'bordercolor': [
                            ('readonly', self.__default_ui_highlight_color)
                        ],
                        'fieldbackground': [
                            ('readonly', self.__default_ui_bg_color)
                        ]
                    }
                },
                'TFrame': {
                    'configure': {
                        'background': self.__default_ui_frame_color
                    }
                },
                'TLabel': {
                    'configure': {
                        'background': self.__default_ui_frame_color,
                        'foreground': self.__default_ui_fg_color
                    }
                },
                'TScale': {
                    'configure': {
                        'background': self.__default_ui_frame_color,
                        'borderwidth': 10,
                        'groovewidth': 10,
                        'darkcolor': 'red',
                        'sliderwidth': 100,
                        'troughcolor': self.__default_ui_highlight_color,
                        'troughrelief': tk.SOLID
                    },
                    'map': {
                        'background': [('selected', False)]
                    }
                }
            })
        widget_style.theme_use('alt')

        # Initialize Main window and images
        self.__main_window.title('Colorian')
        self.__main_window.configure(bg=self.__default_ui_frame_color)

        self.__copy_icon_image = tk.PhotoImage(
            master=self.__main_window,
            file=resource_path('noun_copy_964433.png'))
        self.__save_icon_image = tk.PhotoImage(
            master=self.__main_window,
            file=resource_path('noun_sticker_964404.png'))

        # Initialize Color wheel dropdown
        color_wheels_list = list(self.__color_wheels.keys())
        self.__color_wheel_value = tk.StringVar(self.__main_window,
                                                color_wheels_list[0])

        self.__color_wheel_combobox = ttk.Combobox(
            self.__main_window,
            state='readonly',
            textvariable=self.__color_wheel_value,
            values=color_wheels_list,
            width=12)
        self.__color_wheel_combobox.bind('<<ComboboxSelected>>',
                                         self.set_color_wheel)
        self.__color_wheel_combobox.grid(row=0, column=0, padx=(20, 10))

        # Initialize Color picker
        self.__color_picker_frame = ttk.Frame(self.__main_window,
                                              height=50,
                                              width=600)
        self.__color_picker_frame.grid(row=0, column=1, columnspan=12)

        self.update_color_picker()

        # Initialize Hue wheel
        self.__pie_canvas = tk.Canvas(
            self.__main_window,
            bg=self.__default_ui_frame_color,
            height=400,
            highlightbackground=self.__default_ui_frame_color,
            width=480)
        self.__pie_canvas.grid(row=1, column=0, columnspan=7)

        # Initialize Color scheme panel
        self.__color_scheme_settings_frame = ttk.Frame(self.__main_window)
        self.__color_scheme_settings_frame.grid(row=1, column=7, columnspan=6)

        # Initialize Hue variation selector
        self.__hue_variant_selector_frame = ttk.Frame(
            self.__color_scheme_settings_frame)
        self.__hue_variant_selector_frame.grid(row=0, column=0, pady=10)

        hue_variant_list = list(self.__hue_variants.keys())
        self.__hue_variant_value = tk.StringVar(self.__main_window,
                                                hue_variant_list[0])

        for idx, title in enumerate(hue_variant_list):
            # Note! Uses tk to allow more customization over ttk
            hue_variant_radiobutton = tk.Radiobutton(
                self.__hue_variant_selector_frame,
                foreground=self.__default_ui_fg_color,
                background=self.__default_ui_frame_color,
                selectcolor=self.__default_ui_highlight_color,
                command=self.set_hue_variant,
                indicator=0,
                pady=10,
                relief=tk.SOLID,
                text=title,
                value=hue_variant_list[idx],
                variable=self.__hue_variant_value,
                width=30)
            hue_variant_radiobutton.grid(row=idx, column=0, sticky=tk.W)

        # Initialize Color scheme dropdown
        self.__color_scheme_value = tk.StringVar(
            self.__main_window,
            self.__selected_color_wheel_palette.get_color_scheme())

        self.__color_scheme_combobox = ttk.Combobox(
            self.__color_scheme_settings_frame,
            state='readonly',
            textvariable=self.__color_scheme_value,
            values=self.__color_schemes,
            width=25)

        self.__color_scheme_combobox.bind('<<ComboboxSelected>>',
                                          self.set_color_scheme)
        self.__color_scheme_combobox.grid(row=1, column=0, pady=(0, 5))

        # Initialize Hue brightness slider and preview panel
        self.__hue_brightness_label = ttk.Label(
            self.__color_scheme_settings_frame, text='Brightness')
        self.__hue_brightness_label.grid(row=2, column=0, pady=(2, 2))

        self.__hue_brightness_value = tk.DoubleVar(
            self.__main_window, random_color.get_brightness())
        hue_brightness_min_value = 0.0
        hue_brightness_max_value = 255.0

        self.__hue_brightness_scale = ttk.Scale(
            self.__color_scheme_settings_frame,
            command=self.set_hue_brightness,
            from_=hue_brightness_min_value,
            length=270,
            orient=tk.HORIZONTAL,
            to=hue_brightness_max_value,
            variable=self.__hue_brightness_value)

        # Note! Only updates brightness to color wheel and palette view on
        # mouseup event for better performance
        self.__hue_brightness_scale.bind('<ButtonRelease-1>',
                                         self.update_all_color_previews)
        self.__hue_brightness_scale.grid(row=3, column=0)

        self.__hue_preview_frame = tk.Frame(self.__color_scheme_settings_frame,
                                            background=random_color.hex(),
                                            height=140,
                                            width=270)

        self.__hue_preview_frame.grid(row=4, column=0)

        # Initialize Palette view
        self.__palette_view_frame = ttk.Frame(self.__main_window)
        self.__palette_view_frame.grid(row=2, column=0, columnspan=7)
        self.__palette_view_frame.grid_propagate(0)
        self.__palette_view_swatches_frame = ttk.Frame(
            self.__palette_view_frame, height=80, width=480)
        self.__palette_view_swatches_frame.pack()

        # Initialize Message display and Palette export to file button
        self.__palette_export_frame = ttk.Frame(self.__main_window)
        self.__palette_export_frame.grid(row=2, column=8, columnspan=6)

        self.__palette_display_label = ttk.Label(self.__palette_export_frame)
        self.__palette_display_label.grid(row=0, column=0, pady=(5, 5))

        self.__palette_export_button = ttk.Button(
            self.__palette_export_frame,
            command=self.export_palette_to_file,
            compound=tk.LEFT,
            image=self.__save_icon_image,
            state='readonly',
            text='Export to file',
            padding=(0, 10, 25, 10),
            width=12)

        self.__palette_export_button.grid(row=1,
                                          column=0,
                                          sticky=tk.NE,
                                          padx=(53, 0),
                                          pady=(0, 20))

        # Start UI graphics and event loop
        self.update_all_color_previews()
        self.__main_window.mainloop()

    def display_message(self, message):
        """
        Shows a temporary message that is removed after the time expires.

        :param message: str, the message to display.
        """

        if not isinstance(message, str):
            show_error('Invalid message text received!')
            return

        self.__palette_display_label.config(text=message)
        self.__main_window.after(
            2000, lambda: self.__palette_display_label.config(text=''))

    def set_color_wheel(self, event):
        """
        Update what color wheel is being used and picks a random color from it.
        Clears text highlighting in the Combobox after selection has been made.

        :param event: tkinter.Event, the event triggered by user.
        """

        self.__color_wheel_combobox.selection_clear()

        color_wheel_key = self.__color_wheels[self.__color_wheel_value.get()]
        self.__color_picker_palette.set_color_wheel(color_wheel_key)

        random_color = self.__color_picker_palette.random_color()
        self.__color_picker_palette.set_picked_color(random_color)

        self.update_color_picker()

    def update_color_picker(self):
        """
        Populates the color picker buttons in order defined in the
        palette. Implements the selection of color for updating it as the
        picked color and all the color previews.
        """

        for widget in self.__color_picker_frame.winfo_children():
            widget.destroy()

        for idx, color in enumerate(self.__color_picker_palette.values()):

            color_swatch_frame = ttk.Frame(self.__color_picker_frame,
                                           height=50,
                                           width=50)
            color_swatch_frame.rowconfigure(0, weight=1)
            color_swatch_frame.columnconfigure(0, weight=1)
            color_swatch_frame.grid_propagate(0)
            color_swatch_frame.grid(row=0, column=idx)

            def set_picked_color(color_value=color):
                self.__color_picker_palette \
                    .set_picked_color(color_value)

                picked_color_wheel_key = self.__color_picker_palette \
                    .get_color_wheel()
                color_scheme_key = self.__color_scheme_value.get()

                self.__color_wheel_hue_palette = Palette()
                self.__color_wheel_hue_palette \
                    .set_color_wheel(picked_color_wheel_key)
                root_color = self.__color_wheel_hue_palette \
                    .find_by_name(color_value.name())
                self.__color_wheel_hue_palette.set_picked_color(root_color)
                self.__color_wheel_hue_palette \
                    .sort_color_wheel(root_color)
                self.__color_wheel_hue_palette.set_color_scheme(
                    color_scheme_key)

                self.__color_wheel_tint_palette = Palette()
                self.__color_wheel_tint_palette \
                    .set_color_wheel(picked_color_wheel_key)
                root_color = self.__color_wheel_tint_palette \
                    .find_by_name(color_value.name())
                self.__color_wheel_tint_palette.set_picked_color(root_color)
                self.__color_wheel_tint_palette \
                    .sort_color_wheel(root_color)
                self.__color_wheel_tint_palette.to_tint(
                    self.__default_tint_amount)
                self.__color_wheel_tint_palette.set_color_scheme(
                    color_scheme_key)

                self.__color_wheel_shade_palette = Palette()
                self.__color_wheel_shade_palette \
                    .set_color_wheel(picked_color_wheel_key)
                root_color = self.__color_wheel_shade_palette \
                    .find_by_name(color_value.name())
                self.__color_wheel_shade_palette.set_picked_color(root_color)
                self.__color_wheel_shade_palette \
                    .sort_color_wheel(root_color)
                self.__color_wheel_shade_palette.to_shade(
                    self.__default_shade_amount)
                self.__color_wheel_shade_palette.set_color_scheme(
                    color_scheme_key)

                self.__color_wheel_tone_palette = Palette()
                self.__color_wheel_tone_palette \
                    .set_color_wheel(picked_color_wheel_key)
                root_color = self.__color_wheel_tone_palette \
                    .find_by_name(color_value.name())
                self.__color_wheel_tone_palette.set_picked_color(root_color)
                self.__color_wheel_tone_palette \
                    .sort_color_wheel(root_color)
                self.__color_wheel_tone_palette.to_tone(
                    self.__default_tone_amount)
                self.__color_wheel_tone_palette.set_color_scheme(
                    color_scheme_key)

                color_scheme = \
                    self.__selected_color_wheel_palette.get_color_scheme()

                hue_variant_key = self.__hue_variants[
                    self.__hue_variant_value.get()]

                if hue_variant_key == 'HUE':
                    self.__selected_color_wheel_palette = \
                        self.__color_wheel_hue_palette
                elif hue_variant_key == 'TINT':
                    self.__selected_color_wheel_palette = \
                        self.__color_wheel_tint_palette
                elif hue_variant_key == 'SHADE':
                    self.__selected_color_wheel_palette = \
                        self.__color_wheel_shade_palette
                elif hue_variant_key == 'TONE':
                    self.__selected_color_wheel_palette = \
                        self.__color_wheel_tone_palette

                self.__selected_color_wheel_palette.set_color_scheme(
                    color_scheme)

                self.update_hue_brightness_slider()
                self.update_all_color_previews()

            swatch_button_style = ttk.Style()
            swatch_button_style.configure(f'SwatchStyle{idx}.TButton',
                                          background=color.hex(),
                                          relief=tk.FLAT)

            color_swatch_button = ttk.Button(color_swatch_frame,
                                             command=set_picked_color,
                                             state='readonly',
                                             style=f'SwatchStyle{idx}.TButton')

            color_swatch_button.grid(sticky=tk.NSEW)

    def draw_hue_wheel(self):
        """
        Updates the hue wheel and implements the selection of hue and updates
        all color previews.
        """

        color_slices = self.__selected_color_wheel_palette.values()
        scheme_color_slices = \
            self.__selected_color_wheel_palette.get_scheme_colors()
        extend_degrees = 360.0 / len(color_slices)
        start_degrees = extend_degrees * 2.5

        for idx, color in enumerate(color_slices):

            def select_hue(event, selected_hue=color):
                self.__selected_color_wheel_palette \
                    .set_picked_color(selected_hue)
                self.update_hue_brightness_slider()
                self.update_all_color_previews()

            start_angle = -extend_degrees * idx + start_degrees
            tag_id = f'slice-{idx}'

            self.__pie_canvas.create_arc((50, 10, 440, 400),
                                         extent=extend_degrees,
                                         fill=color.hex(),
                                         outline=color.hex(),
                                         start=start_angle,
                                         tags=(tag_id, ))

            self.__pie_canvas.tag_bind(tag_id, '<1>', select_hue)

        for idx, color in enumerate(color_slices):

            start_angle = extend_degrees * idx + start_degrees

            if color in scheme_color_slices:
                self.__pie_canvas.create_arc((50, 10, 440, 400),
                                             extent=extend_degrees,
                                             outline='black',
                                             start=start_angle,
                                             width=3)

    def update_all_color_previews(self, event=None):
        """
        Aggregates all the functions to call when a color changes or gets
        modified and the UI widget states need to be updated.
        """

        self.draw_hue_wheel()
        self.update_hue_preview()
        self.update_palette_view()

    def set_hue_variant(self):
        """
        Updates the hue variant to the selected value and updates color
        previews.
        """

        hue_variant_key = self.__hue_variants[self.__hue_variant_value.get()]

        if hue_variant_key == 'HUE':
            self.__selected_color_wheel_palette = \
                self.__color_wheel_hue_palette
        elif hue_variant_key == 'TINT':
            self.__selected_color_wheel_palette = \
                self.__color_wheel_tint_palette
        elif hue_variant_key == 'SHADE':
            self.__selected_color_wheel_palette = \
                self.__color_wheel_shade_palette
        elif hue_variant_key == 'TONE':
            self.__selected_color_wheel_palette = \
                self.__color_wheel_tone_palette

        self.update_all_color_previews()

    def set_color_scheme(self, event):
        """
        Updates the color scheme to the selected value. Clears text
        highlighting in the Combobox after selection has been made.

        :param event: tkinter.Event, the event triggered by user.
        """

        self.__color_scheme_combobox.selection_clear()
        color_scheme_key = self.__color_scheme_value.get()

        self.__color_wheel_hue_palette.set_color_scheme(color_scheme_key)
        self.__color_wheel_tint_palette.set_color_scheme(color_scheme_key)
        self.__color_wheel_shade_palette.set_color_scheme(color_scheme_key)
        self.__color_wheel_tone_palette.set_color_scheme(color_scheme_key)

        self.update_all_color_previews()

    def set_hue_brightness(self, event):
        """
        Modifies the selected color's brightness according to the fetched
        slider's value. Updates hue brightness preview.

        :param event: tkinter.Event, the event triggered by user.
        """

        hue_brightness_value = round(self.__hue_brightness_value.get(), 1)

        selected_hue_color = self.__selected_color_wheel_palette \
            .get_picked_color()
        selected_hue_color.brightness(hue_brightness_value)

        self.update_hue_preview()

    def update_hue_brightness_slider(self):
        """
        Updates the hue brightness slider position according to selected color.
        """

        selected_hue_color = self.__selected_color_wheel_palette \
            .get_picked_color()

        self.__hue_brightness_value.set(selected_hue_color.get_brightness())

    def update_hue_preview(self):
        """
        Updates the hue preview to the selected color.
        """

        selected_hue_color = self.__selected_color_wheel_palette \
            .get_picked_color()

        self.__hue_preview_frame.config(bg=selected_hue_color.hex())

    def update_palette_view(self):
        """
        Generates the palette view buttons and implements the click to copy
        color code feature for the buttons.
        """

        for widget in self.__palette_view_swatches_frame.winfo_children():
            widget.destroy()

        for idx, color in enumerate(
                self.__selected_color_wheel_palette.get_scheme_colors()):
            palette_swatch_frame = ttk.Frame(
                self.__palette_view_swatches_frame, height=80, width=80)

            palette_swatch_frame.rowconfigure(0, weight=1)
            palette_swatch_frame.columnconfigure(0, weight=1)
            palette_swatch_frame.grid_propagate(0)
            palette_swatch_frame.grid(row=0, column=idx)

            def copy_color_to_clipboard(color_value=color):
                self.__main_window.clipboard_clear()
                self.__main_window.clipboard_append(color_value.hex())
                self.display_message('Copied to clipboard!')

            palette_button_style = ttk.Style()
            palette_button_style.configure(f'PaletteStyle{idx}.TButton',
                                           background=color.hex(),
                                           padding=(24, 40, 24, 0),
                                           relief=tk.FLAT)

            palette_swatch_button = ttk.Button(palette_swatch_frame,
                                               command=copy_color_to_clipboard,
                                               image=self.__copy_icon_image)

            palette_swatch_button.config(style=f'PaletteStyle{idx}.TButton')
            palette_swatch_button.grid(sticky=tk.NSEW)

    def export_palette_to_file(self):
        """
        Prompts for location to save the file. Creates a file and converts the
        current palette to a string representation to write it to the file.
        """

        try:
            date_and_time = str(datetime.now()).split(':')
            filename = f'Palette {date_and_time[0]}.{date_and_time[1]}.txt'

            file = asksaveasfile(mode='w',
                                 defaultextension='.txt',
                                 initialfile=filename)
            if file is None:
                return

            content = 'Colorian Palette\n' + \
                      '================\n\n'

            color_scheme = \
                self.__selected_color_wheel_palette.get_color_scheme()

            content += \
                color_scheme + ' color scheme\n' + \
                ('-' * (len(color_scheme) + 13)) + '\n'

            for color in (
                    self.__selected_color_wheel_palette.get_scheme_colors()):
                content += str(color) + '\n'

            content += '\n'

            content += 'Color wheel\n' + \
                       '-----------\n'

            for color in self.__selected_color_wheel_palette.values():
                content += str(color) + '\n'

            print(content, file=file)

            file.close()
        except OSError:
            show_error('Exporting to file ran into trouble!')
            return

        self.display_message('Palette exported!')