init python:

    renpy.register_shader("color_picker.gradient",
        variables="""
            varying vec2 v_tex_coord;
            attribute vec2 a_tex_coord;
            uniform float u_hue;
        """,
        fragment_functions="""
            vec3 hsv2rgb(vec3 c) {
                vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
                vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
                return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
            }

            float lerp(float a, float b, float f){
                return a + f * (b - a);
            }
        """,
        vertex_300="""
            // Flip gradient
            v_tex_coord = vec2(a_tex_coord.s, 1.0 - a_tex_coord.t);
        """,
        fragment_300="""
            vec2 uv = v_tex_coord;
            vec4 pixel = vec4(hsv2rgb(vec3(u_hue, 1.0, 1.0)), 1.0);
            pixel = vec4(uv.y * lerp(1.0, pixel.x, uv.x), uv.y * lerp(1.0, pixel.y, uv.x), uv.y * lerp(1.0, pixel.z, uv.x), 1.0);
            gl_FragColor = pixel;
            // Gamma correction, but is it really needed here?
            //gl_FragColor = pow(pixel, vec4(1.0/2.2));
        """
    )

    renpy.register_shader("color_picker.hue",
        variables="""
            varying vec2 v_tex_coord;
            attribute vec2 a_tex_coord;
        """,
        fragment_functions="""
            vec3 hsv2rgb(vec3 c) {
                vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
                vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
                return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
            }
        """,
        vertex_300="""
            v_tex_coord = a_tex_coord;
        """,
        fragment_300="""
            vec2 uv = v_tex_coord;
            vec4 pixel = mix(vec4(hsv2rgb(vec3(uv.y, 1.0, 1.0)), 1.0), gl_FragColor, smoothstep(0.0, 1.0, gl_FragColor.x));
            // Gamma correction
            gl_FragColor = pow(pixel, vec4(1.0/2.2));
        """
    )

    renpy.register_shader("color_picker.alpha",
        variables="""
            uniform vec3 u_color;
            uniform vec2 u_model_size;
            varying float direction;
            attribute vec4 a_position;
        """,
        vertex_300="""
            direction = a_position.x / u_model_size.x;
        """,
        fragment_functions="""
            float pattern(vec2 uv, float size) {
                vec2 pos = floor(uv / size);
                return mod(pos.x + pos.y, 2.0);
            }
        """,
        fragment_300="""
            const float size = 8.0;
            float c = pattern(gl_FragCoord.xy, size);
            vec4 checkerboard = vec4(c, c, c, 1.0);

            vec4 gradient = vec4(u_color, 1.0);
            gradient = mix(gradient, vec4(0.0, 0.0, 0.0, 0.0), direction);

            gl_FragColor = mix(gradient, checkerboard, direction);
        """
    )

    class ColorPickerBase(renpy.Displayable):
        canvas = Solid("#ffffff")

        def __init__(self, controller, **kwargs):
            super(ColorPickerBase, self).__init__(**kwargs)

            self.controller = controller
            self.size = (0, 0)
            self.pos = (0, 0)

        def update(self):
            raise NotImplementedError(self.__class__.__name__)

        def render(self, width, height, st, at):
            raise NotImplementedError(self.__class__.__name__)

        def place_cursor(self, x, y):
            w, h = self.size
            x = max(min(x, w), 0)
            y = max(min(y, h), 0)

            self.pos = (x, y)
            renpy.redraw(self, 0)

        def unfocus(self):
            self.controller.focus = None

        def focus(self):
            self.controller.focus = self

        def focused(self):
            return (self.controller.focus == self)

        def apply(self):
            self.controller.apply()

        def per_interact(self):
            return

        def visit(self):
            return []

        def event(self, ev, x, y, st):
            w, h = self.size

            if not self.focused() and (not (0 < x <= w) or not (0 < y <= h)):
                return
            elif ev.type == pygame.MOUSEBUTTONDOWN and ev.button == 1:
                # Button pressed
                self.focus()
                return

            if not self.focused():
                return

            if ev.type == pygame.MOUSEBUTTONUP:
                # Button released
                self.place_cursor(x, y)
                self.update()
                self.unfocus()
                self.apply()
                renpy.restart_interaction()
                return ["released", self.controller.live_color]

            if ev.type == pygame.MOUSEMOTION and ev.buttons[0]:
                # Dragged
                self.place_cursor(x, y)
                self.update()
                return

    class ColorPickerHue(ColorPickerBase):
        cursor = renpy.displayable("cph_cursor")

        def __init__(self, controller, **kwargs):
            super(ColorPickerHue, self).__init__(controller, **kwargs)

        def update(self):
            _, y = self.pos
            _, h = self.size

            self.controller.hue = max(min(float(y) / h, 1.0), 0.0)
            self.controller.update()

        def place_cursor(self, x, y):
            w, h = self.size
            x = 0
            y = max(min(y, h), 0)

            self.pos = (x, y)
            renpy.redraw(self, 0)

        def render(self, width, height, st, at):
            self.size = (width, height)

            # Hue Slider
            sr = renpy.Render(width, height)
            slider = renpy.render(self.canvas, width, height, st ,at)

            sr.mesh = True
            sr.add_shader("color_picker.hue")
            sr.blit(slider, (0, 0))

            # Cursor

            cr = renpy.Render(width, height)
            cursor = renpy.render(self.cursor, width, height, st, at)
            cr.blit(cursor, self.pos)

            # Main

            rv = renpy.Render(width, height)
            rv.blit(sr, (0, 0))
            rv.blit(cr, (0, -3))

            return rv

    class ColorPickerSatVal(ColorPickerBase):
        cursor = renpy.displayable("cpsv_cursor")

        def __init__(self, controller, **kwargs):
            super(ColorPickerSatVal, self).__init__(controller, **kwargs)

        def update(self):
            x, y = self.pos
            w, h = self.size

            self.controller.saturation = max(min(float(x) / w, 1.0), 0.0)
            self.controller.value = 1 - max(min(float(y) / h, 1.0), 0.0)
            self.controller.update()

        def render(self, width, height, st, at):
            self.size = (width, height)

            # Gradient map

            gr = renpy.Render(width, height)
            gr.mesh = True
            gr.add_shader("color_picker.gradient")
            gr.add_uniform("u_hue", self.controller.hue)

            canvas = renpy.render(self.canvas, width, height, st ,at)

            gr.blit(canvas, (0, 0))

            # Cursor

            cr = renpy.Render(width, height)
            cursor = renpy.render(self.cursor, width, height, st, at)
            cr.blit(cursor, self.pos)

            # Main

            rv = renpy.Render(width, height)
            rv.blit(gr, (0, 0))
            rv.blit(cr, (-7, -8))

            return rv

    class ColorPickerPreview(ColorPickerBase):
        def __init__(self, controller, **kwargs):
            super(ColorPickerPreview, self).__init__(controller, **kwargs)

        def render(self, width, height, st, at):
            self.size = (width, height)

            # Live colour

            live_color = Solid(self.controller.live_color)
            lcr = renpy.render(live_color, width, height, st, at)

            # # Start colour

            # start_color = Solid(self.controller.start_color)
            # scr = renpy.render(start_color, one_third*2, height, st, at)

            # # # Default colour

            # default_color = Solid(self.controller.default_color)
            # dcr = renpy.render(default_color, width, height, st, at)

            # Main

            rv = renpy.Render(width, height)
            # Laid out from back to front
            #rv.blit(dcr, (0, 0))
            #rv.blit(scr, (0, 0))
            rv.blit(lcr, (0, 0))

            return rv

        def event(self, ev, x, y, st):
            return

    class ColorPickerAlpha(ColorPickerBase):
        cursor = renpy.displayable("cpa_cursor")

        def __init__(self, controller, **kwargs):
            super(ColorPickerAlpha, self).__init__(controller, **kwargs)

        def update(self):
            x, _ = self.pos
            w, _ = self.size

            self.controller.alpha = 1.0 - max(min(float(x) / w, 1.0), 0.0)
            self.controller.update()

        def place_cursor(self, x, y):
            w, h = self.size
            x = max(min(x, w), 0)
            y = 0

            self.pos = (x, y)
            renpy.redraw(self, 0)

        def render(self, width, height, st, at):
            self.size = (width, height)

            # Alpha Slider
            sr = renpy.Render(width, height)
            slider = renpy.render(self.canvas, width, height, st ,at)

            sr.mesh = True
            sr.add_shader("color_picker.alpha")
            sr.add_uniform("u_color", self.controller.live_color.rgb)
            sr.blit(slider, (0, 0))

            # Cursor

            cr = renpy.Render(width, height)
            cursor = renpy.render(self.cursor, width, height, st, at)
            cr.blit(cursor, self.pos)

            # Main

            rv = renpy.Render(width, height)
            rv.blit(sr, (0, 0))
            rv.blit(cr, (0, 0))

            return rv

    class ColorPicker(NoRollback):

        def __init__(self, start_color=(255, 255, 0, 255), default_color=(255, 0, 0, 255)):
            col = Color(tuple(start_color))
            self.live_color = col
            self.start_color = col

            default_color = Color(tuple(default_color))
            self.default_color = default_color

            self.hue, self.saturation, self.value = col.hsv
            self.alpha = col.alpha

            self.cph = ColorPickerHue(self)
            self.cpsv = ColorPickerSatVal(self)
            self.cpp = ColorPickerPreview(self)
            self.cpa = ColorPickerAlpha(self)
            self.focus = None

            h, s, v = col.hsv
            a = col.alpha
            self.cph.pos = (0, 255 * h)
            self.cpsv.pos = (255 * s, 255 * (1- v))
            self.cpa.pos = (255 * (1 - a), 0)

        def update(self):
            hsv = (self.hue, self.saturation, self.value)
            col = Color(hsv=hsv, alpha=self.alpha)
            self.live_color = col
            self.redraw()

        def redraw(self):
            renpy.redraw(self.cpsv, 0)
            renpy.redraw(self.cph, 0)
            renpy.redraw(self.cpp, 0)
            renpy.redraw(self.cpa, 0)

        def apply(self):
            self.add_history(self.live_color)

        def live_replace(self, color):
            h, s, v = color.hsv
            a = color.alpha
            self.hue, self.saturation, self.value = h, s, v
            self.alpha = a
            self.live_color = color

            self.cph.pos = (0, 255 * h)
            self.cpsv.pos = (255 * s, 255 * (1- v))
            self.cpa.pos = (255 * (1 - a), 0)

            self.redraw()

        def start_replace(self, color):
            self.start_color = color
            self.redraw()

        def default_replace(self, color):
            self.default_color = color
            self.redraw()

        @staticmethod
        def add_history(col):
            colorpicker.history.append(col)

            if len(colorpicker.history) > 10:
                del colorpicker.history[0]

        @staticmethod
        def add_favorite(index, col):
            colorpicker.favorites[index] = col

    cp = ColorPicker()

default colorpicker.history = []
default colorpicker.favorites = [Color((167, 77, 42)), Color((237, 179, 14)), Color((89, 116, 194)), Color((216, 163, 10)), Color((58, 115, 75)), Color((205, 205, 206)), Color((251, 198, 10)), Color((51, 43, 54))] + ([Color((255, 255, 255))] * 22)

screen colorpickerscreen(item=None):
    zorder 30
    modal True

    if item:
        default is_cheating = config.developer or cheat_wardrobe_alpha
        default is_blacklisted = item.type.startswith(tuple(item.blacklist_unequip))
        default is_allowed = item.type.startswith(("makeup", "tattoo"))
    default transparency = item and not is_blacklisted and (is_allowed or is_cheating)

    frame:
        style_prefix "colorpicker"
        background "cp_frame"
        align (0.2, 0.5)
        padding (18, 18)

        has vbox

        if item:
            $ layers = [Color(tuple(i)) for i in item.color]

            label "Layers" xalign 0.0

            hbox:
                spacing 4
                for i, col in enumerate(layers):
                    button:
                        xysize (32, 32)
                        background col
                        foreground "cp_borders"
                        action Return(["layer", i])
                        alternate Return(["replace", col])
                        text str(i+1) style "colorpicker_innertext"

        hbox:
            vbox:
                label "Colour Picker"

                frame:
                    add cp.cpsv xysize (255, 255)

                if transparency:
                    label "Transparency"

                    frame:
                        add cp.cpa xysize (255, 30)

            vbox:
                label "Hue"

                frame:
                    add cp.cph xysize (30, 255)

            vbox:

                label "Previews"

                hbox:
                    spacing 4
                    frame:
                        add cp.cpp xysize (32, 32)
                    frame:
                        button:
                            xysize (32, 32)
                            background cp.start_color
                            action Return(["replace", cp.start_color])
                            text "Old" style "colorpicker_innertext" size 8
                    frame:
                        button:
                            xysize (32, 32)
                            background cp.default_color
                            action Return(["replace", cp.default_color])
                            text "Org" style "colorpicker_innertext" size 8

                label "Swatches"

                grid 5 6:
                    xalign 0.5
                    spacing 4

                    for i, col in enumerate(colorpicker.favorites):
                        frame:
                            button:
                                xysize (12, 12)
                                background col
                                action Return(["replace", col])
                                alternate Function(cp.add_favorite, i, cp.live_color)

                label "History"

                grid 5 2:
                    xalign 0.5
                    spacing 4

                    for col in colorpicker.history:
                        frame:
                            button:
                                xysize (12, 12)
                                background col
                                action Return(["replace", col])

        hbox:
            align (1.0, 1.0)

            if item:
                textbutton "Reset":
                    action [Function(item.reset_color), Function(cp.live_replace, cp.default_color)]

            textbutton "Finish":
                action Return(["finish", cp.live_color])
                keysym ["K_ESCAPE", "K_RETURN"]

    if config.developer:
        vbox:
            pos (15, 15)
            spacing 0
            style_prefix "colorpickerdev"

            text "Hue:[cp.hue]"
            text "Sat:[cp.saturation]"
            text "Val:[cp.value]"
            text "Alpha:[cp.alpha]"

            if item:
                textbutton "Colour code: [item.color]":
                    action Function(set_clipboard, str(item.color))
                    #alternate [Function(item.set_color, evaluate(get_clipboard()))]
                    keysym ["ctrl_K_c"]
                    #alternate_keysym ["ctrl_K_v"]

style colorpickerdev_text:
    size 12
    color "#fff"
    outlines [(1, "#00000080", 1, 0)]

style colorpickerdev_button_text is colorpickerdev_text:
    color "#cccccc"
    hover_color "#ffffff"

style colorpicker_innertext is colorpickerdev_text:
    color "#ffffff00"
    outlines []
    hover_color "#ffffff"
    hover_outlines [(1, "#00000080", 1, 0)]

style colorpicker_label:
    align (0.5, 0.5)

style colorpicker_label_text:
    color "#402313"

style colorpicker_button_text:
    outlines []

style colorpicker_frame:
    background "cp_borders"
    hover_background image_hover("cp_borders")
    padding (3, 3)

style colorpicker_vbox:
    spacing 6

style colorpicker_hbox:
    spacing 22

image cp_frame = ConditionSwitch(
    "not game.daytime", Frame("gui/dark_frame.png", 8, 8),
    "True", Frame("gui/light_frame.png", 8, 8)
    )

image cp_borders = ConditionSwitch(
    "not game.daytime", Frame("interface/color_picker/gray/cursor_sq.webp", 3, 3),
    "True", Frame("interface/color_picker/gold/cursor_sq.webp", 3, 3)
    )

image cph_cursor = ConditionSwitch(
    "not game.daytime", "interface/color_picker/gray/cursor_h.webp",
    "True", "interface/color_picker/gold/cursor_h.webp"
    )

image cpa_cursor = ConditionSwitch(
    "not game.daytime", "interface/color_picker/gray/cursor_v.webp",
    "True", "interface/color_picker/gold/cursor_v.webp"
    )

image cpsv_cursor = ConditionSwitch(
    "not game.daytime", "interface/color_picker/gray/cursor_sq.webp",
    "True", "interface/color_picker/gold/cursor_sq.webp"
    )
