Suuuuuuuuuuuuuper Simple Color Picker

Want a super-simple color picker that won’t clutter your UI? Try this!

It’s the SwipeColorPicker, and here’s how it works. It draws a circle on the screen, which can be resized and repositioned as you like. The circle shows a solid color. If you touch inside the circle and drag in any direction, you can change the color. Dragging vertically changes saturation and brightness, and dragging horizontally changes hue. That’s it! Place this one circle anywhere in your UI, at any visible size, and you’ve got yourself a color picker.

It’s not the best color picker, but it’ll get you up and running in under a minute, and save you a lot of time re-inventing the wheel.

Many thanks to @SkyTheCoder for sharing the hsv-to-rgb conversion method on these forums. And if you want a better color picker, check his out: http://codea.io/talk/discussion/3285/color-chooser. Just be sure to skip the long argument over pentagons vs. circles. :wink:

Here’s my widget:



--# Main
-- SwipeColorPicker

-- Use this function to perform your initial setup
function setup()
    swiper = SwipeColorPicker()
end

-- This function gets called once every frame
function draw()
    background(12, 12, 12, 255)
    swiper:draw()    
end

function touched(touch)
    swiper:touched(touch)
end

--# SwipeColorPicker
SwipeColorPicker = class()

function SwipeColorPicker:init(x)
    self.hsv = vec4(0.5,1.0,1.0,1.0)
    self.color = self:updateRGB()
    self.size = 80
    self.position = vec2(200,200)
    self.strokeSize = 3
    self.active = false
    self.sizePickerMode = false
end

function SwipeColorPicker:swipeHorizontal(amount)
    amount = amount / WIDTH
    self:hsvShift(amount, 0)
end

function SwipeColorPicker:hsvShift(h,v)
    self.hsv.x = self.hsv.x + h
    if self.hsv.y < 1 then
        self.hsv.y = self.hsv.y - v 
        if self.hsv.y < 0 then
            self.hsv.y = 0
        end
        if self.hsv.y > 1 then
            self.hsv.z = self.hsv.z - math.fmod(self.hsv.y, 1)
            self.hsv.y = 1
        end
    else 
        self.hsv.z = self.hsv.z + v
        if self.hsv.z > 1 then
            self.hsv.y = self.hsv.y -  math.fmod(self.hsv.z, 1)
            self.hsv.z = 1
        end
    end
    self:updateRGB()
end

function SwipeColorPicker:swipeVertical(amount)
    amount = amount / HEIGHT * 3
    self:hsvShift(0, amount)
end

function SwipeColorPicker:draw()
    -- Codea does not automatically call this method
    -- background(self.color)
    pushStyle()
    ellipseMode(CENTER)
    if self.active then
        fill(197, 167, 167, 109)
        ellipse(self.position.x, self.position.y-0.5, self.size+6)
        ellipse(self.position.x, self.position.y+1.5, self.size+6)
    end
    -- the bezels
    fill(self.color)
    ellipse(self.position.x, self.position.y + 2, self.size)
    fill(0, 0, 0, 179)
    ellipse(self.position.x, self.position.y + 2, self.size)
    fill(self.color)
    ellipse(self.position.x, self.position.y - 2, self.size)
    fill(255, 255, 255, 145)
    ellipse(self.position.x, self.position.y - 2, self.size)
    -- the color
    fill(self.color)
    ellipse(self.position.x, self.position.y, self.size)
    if self.sizePickerMode then
        stroke(self.color)
        strokeWidth(self.strokeSize)
        line(self.position.x - 20,self.position.y + self.size - (self.size / 3),
        self.position.x + 20,self.position.y + self.size - (self.size / 3))
        strokeWidth(0)
    end
    popStyle()    
end

function SwipeColorPicker:touched(touch)
    local insideCircle
    if touch.state == BEGAN then
        local radius = self.size * 0.5
        local closeEnoughX = math.abs(touch.x-self.position.x) < radius
        local closeEnoughY = math.abs(touch.y-self.position.y) < radius
        if closeEnoughX and closeEnoughY then
            self.active = true
        end
    end
    if self.active then
        if touch.tapCount == 2 and self.sizePickerMode == false then
            self.sizePickerMode = true
        end
        if self.sizePickerMode then
            self.strokeSize = self.strokeSize + touch.deltaY
            if self.strokeSize < 0.5 then
                self.strokeSize =  0.5
            end
        else
            if math.abs(touch.deltaX) > math.abs(touch.deltaY) then
                self:swipeHorizontal(touch.deltaX)
            else
                self:swipeVertical(touch.deltaY)
            end
        end
        if touch.state == ENDED then
            self.active = false
            self.sizePickerMode = false
        end
    end
end

function SwipeColorPicker:updateRGB()
    self.color = color(self:hsvToRGB(self.hsv.x, self.hsv.y,self.hsv.z))
    return self.color
end

function SwipeColorPicker:hsvToRGB(h, s, v)
    --thanks to SkyTheCoder on the Codea forums
    local r, g, b
    
    local i = math.floor(h * 6)
    local f = h * 6 - i
    local p = v * (1 - s)
    local q = v * (1 - f * s)
    local t = v * (1 - (1 - f) * s)
    
    local switch = i % 6
    
    if switch == 0 then
        r = v
        g = t
        b = p
    elseif switch == 1 then
        r = q
        g = v
        b = p
    elseif switch == 2 then
        r = p
        g = v
        b = t
    elseif switch == 3 then
        r = p
        g = q
        b = v
    elseif switch == 4 then
        r = t
        g = p
        b = v
    elseif switch == 5 then
        r = v
        g = p
        b = q
    end
    
    return math.floor(r * 255), math.floor(g * 255), math.floor(b * 255)
end

While it is a complete solution, should anyone feel so inspired, I think it could use a better-looking bezel and a better “active state” indicator.

edit

I updated the code above: it now includes a stroke-size setter. If you double-tap the swath, a line appears above it. Then dragging up or down changes the stroke size. Letting go reverts back to color-choosing mode.

It’s less easily configurable now, because the stroke preview position will have to be adjusted depending on screen placement, but that wouldn’t be too hard. Still a lot of bang for the buck!