My toggle class [HUGE UPDATE, looks like iOS 7]

This is my toggle switch class, inspired by the native Apple UI. The syntax is myToggle = Toggle("MyBoolean", x, y) with myBoolean being a string with the name of the variable you wish to toggle. Here is my code:


--# ClipUpgrade
_clip = clip

function clip(x, y, w, h)
    if x then
        local m = modelMatrix()
        x = x * m[1] + m[13]
        y = y * m[6] + m[14]
        w = w * m[1]
        h = h * m[6]
        _clip(x, y, w, h)
    else
        _clip()
    end
end
--# Functions
function hit(u, v, x, y, w, h)
    if u > x and v > y and u < x + w and v < y + h then
        return true
    end
    return false
end
--# Toggle
Toggle = class()

function Toggle:init(var, x, y)
    _G[var] = _G[var] or false
    self.var = var
    self.x = x
    self.y = y
    self.w = 50
    self.h = 30
    self.toggle = -self.w / 2
end

function Toggle:draw()
    pushMatrix()
        pushStyle()
            translate(self.x, self.y)
            
            strokeWidth(self.h - 4)
            stroke(239, 239, 239, 255)
            line(self.w / 2 + self.toggle, 0, self.w, 0)
            stroke(0, 127, 239, 255)
            line(0, 0, self.w / 2 + self.toggle, 0)
            
            strokeWidth(self.h / 2 - 2)
            stroke(255, 255, 255, 255)
            line(self.w / 2 + self.toggle, -self.h / 4, self.w, -self.h / 4)
            stroke(79, 159, 238, 255)
            line(0, -self.h / 4, self.w / 2 + self.toggle, -self.h / 4)
            
            clip(-self.h / 2 + 4, -self.h / 2, self.w + self.h - 8, self.h)
            font("HelveticaNeue-Bold")
            fontSize(self.h / 3 * 2)
            fill(159, 159, 159, 255)
            text("ON", self.toggle - self.w / 4, 0)
            text("OFF",self.toggle + self.w * 1.25, 0)
            clip()
            
            noStroke()
            fill(255, 255, 255, 255)
            ellipse(self.w / 2 + self.toggle, 0, self.h)
            fill(223, 223, 223, 255)
            ellipse(self.w / 2 + self.toggle, 0, self.h - 4)
        popStyle()
    popMatrix()
end

function Toggle:touched(touch)
    if touch.state == BEGAN 
    and hit(touch.x, touch.y, self.x - self.h / 2, self.y - self.h / 2 , self.w + self.h, self.h) then
        self.selected = true
    elseif touch.state == MOVING and self.selected then
        self.toggle = math.min(self.w / 2, math.max(-self.w / 2, self.toggle + touch.deltaX))
    elseif touch.state == ENDED and self.selected then
        self.toggle = vec2(self.toggle, 0):normalize().x * self.w / 2
        self:switch(vec2(self.toggle, 0):normalize().x)
        self.selected = false
    end
end

function Toggle:switch(tog)
    _G[self.var] = tog == 1 and true or false
end
    
--# Main
function setup()
    watch("bool")
    bool = false
    boolTog = Toggle("bool", WIDTH / 2, HEIGHT / 2)
end

function draw()
    background(127, 127, 127, 255)
    boolTog:draw()
end

function touched(touch)
    boolTog:touched(touch)
end

P.S. This code also includes my modification to the clip function so it can work with transform and scale


Used it, I’m stealing it! Thanks for sharing.

If I want to get the value of “on” or “off” is that what “var” is?

I added .toggled to get a true/false.

When working with graphics I am far behind but I think I grasped what was needed?

--# ClipUpgrade
_clip = clip

function clip(x, y, w, h)
    if x then
        local m = modelMatrix()
        x = x * m[1] + m[13]
        y = y * m[6] + m[14]
        w = w * m[1]
        h = h * m[6]
        _clip(x, y, w, h)
    else
        _clip()
    end
end
--# Functions
function hit(u, v, x, y, w, h)
    if u > x and v > y and u < x + w and v < y + h then
        return true
    end
    return false
end
--# Toggle
Toggle = class()

function Toggle:init(var, x, y)
    _G[var] = _G[var] or false
    self.var = var
    self.x = x
    self.y = y
    self.w = 50
    self.h = 30
    self.toggle = -self.w / 2
    self.toggled = false
end


function Toggle:draw()
    pushMatrix()
        pushStyle()
            translate(self.x, self.y)

            strokeWidth(self.h - 4)
            stroke(239, 239, 239, 255)
            line(self.w / 2 + self.toggle, 0, self.w, 0)
            stroke(0, 127, 239, 255)
            line(0, 0, self.w / 2 + self.toggle, 0)

            strokeWidth(self.h / 2 - 2)
            stroke(255, 255, 255, 255)
            line(self.w / 2 + self.toggle, -self.h / 4, self.w, -self.h / 4)
            stroke(79, 159, 238, 255)
            line(0, -self.h / 4, self.w / 2 + self.toggle, -self.h / 4)

            clip(-self.h / 2 + 4, -self.h / 2, self.w + self.h - 8, self.h)
            font("HelveticaNeue-Bold")
            fontSize(self.h / 3 * 2)
            fill(159, 159, 159, 255)
            text("ON", self.toggle - self.w / 4, 0)
            text("OFF",self.toggle + self.w * 1.25, 0)
            clip()

            noStroke()
            fill(255, 255, 255, 255)
            ellipse(self.w / 2 + self.toggle, 0, self.h)
            fill(223, 223, 223, 255)
            ellipse(self.w / 2 + self.toggle, 0, self.h - 4)
        popStyle()
    popMatrix()
end

function Toggle:touched(touch)
    if touch.state == BEGAN 
    and hit(touch.x, touch.y, self.x - self.h / 2, self.y - self.h / 2 , self.w + self.h, self.h) then
        self.selected = true
        return true
    elseif touch.state == MOVING and self.selected then
        self.toggle = math.min(self.w / 2, math.max(-self.w / 2, self.toggle + touch.deltaX))
        return true
    elseif touch.state == ENDED and self.selected then
        self.toggle = vec2(self.toggle, 0):normalize().x * self.w / 2
        self:switch(vec2(self.toggle, 0):normalize().x)
        self.selected = false
        if self.toggle < 0 then
            self.toggled = false
        else
            self.toggled = true
        end
        return true
    end
end

function Toggle:switch(tog)
    _G[self.var] = tog == 1 and true or false
end

Nice job @Jordan, thanks!

Thanks for all the comments! I have upgraded my class with a few new features (look in the Toggle class for the changes):


--# ClipUpgrade
_clip = clip

function clip(x, y, w, h)
    if x then
        local m = modelMatrix()
        x = x * m[1] + m[13]
        y = y * m[6] + m[14]
        w = w * m[1]
        h = h * m[6]
        _clip(x, y, w, h)
    else
        _clip()
    end
end
--# Functions
function hit(u, v, x, y, w, h)
    if u > x and v > y and u < x + w and v < y + h then
        return true
    end
    return false
end
--# Toggle
Toggle = class()

--[[ Version 1.1
Changes =
A new parameter, value, which is a boolean, the current toggle of the switch.
A white border around the toggle
The switch colour changes when touched
if the variable passed to toggle already exists, then the toggle will default to its value

Syntax =
myToggle = Toggle("VariableName", x, y)--like the parameter and watch functions, it takes the string of the name 
-- To move the toggle without redefining it
myToggle.x = newX
myToggle.y = newY
-- To switch the value
myToggle:switch()

]]
function Toggle:init(var, x, y)
    _G[var] = _G[var] and true or false
    self.var = var
    self.x = x
    self.y = y
    self.w = 50
    self.h = 30
    self.selected = true
    self.value = _G[var] and true or false
    self.toggle = (self.value and 1 or -1) * (self.w / 2)
end

function Toggle:draw()
    pushMatrix()
        pushStyle()
            ellipseMode(CENTER)
            lineCapMode(ROUND)
            textMode(CENTER)
            smooth()
            
            translate(self.x, self.y)
            strokeWidth(self.h + 1)
            stroke(255, 255, 255, 255)
            line(0, 0, self.w, 0)
            strokeWidth(self.h - 4)
            stroke(239, 239, 239, 255)
            line(self.w / 2 + self.toggle, 0, self.w, 0)
            stroke(0, 127, 239, 255)
            line(0, 0, self.w / 2 + self.toggle, 0)
            
            strokeWidth(self.h / 2 - 2)
            stroke(255, 255, 255, 255)
            line(self.w / 2 + self.toggle, -self.h / 4, self.w, -self.h / 4)
            stroke(79, 159, 238, 255)
            line(0, -self.h / 4, self.w / 2 + self.toggle, -self.h / 4)
            
            clip(-self.h / 2 + 4, -self.h / 2, self.w + self.h - 8, self.h)
            font("HelveticaNeue-Bold")
            fontSize(self.h / 3 * 2)
            fill(159, 159, 159, 255)
            text("ON", self.toggle - self.w / 4, 0)
            text("OFF",self.toggle + self.w * 1.25, 0)
            clip()
            
            noStroke()
            fill(239, 255)
            ellipse(self.w / 2 + self.toggle, 0, self.h)
            fill(self.selected and 223 or 255, 255)
            ellipse(self.w / 2 + self.toggle, 0, self.h - 4)
        popStyle()
    popMatrix()
end

function Toggle:touched(touch)
    if touch.state == BEGAN 
    and hit(touch.x, touch.y, self.x - self.h / 2, self.y - self.h / 2 , self.w + self.h, self.h) then
        self.selected = true
    elseif touch.state == MOVING and self.selected then
        self.toggle = math.min(self.w / 2, math.max(-self.w / 2, self.toggle + touch.deltaX))
    elseif touch.state == ENDED and self.selected then
        if self.toggle == 0 then self.toggle = self.value and -1 or 1 end
        self.toggle = vec2(self.toggle, 0):normalize().x
        self.toggle = self.toggle * self.w / 2
        self:switch()
        self.selected = false
    end
end

function Toggle:switch(tog)
    self.value = not self.value 
    _G[self.var] = self.value
end
    
--# Main
function setup()
    watch("bool")
    bool = true
    boolTog = Toggle("bool", WIDTH / 2, HEIGHT / 2)
end

function draw()
    background(127, 127, 127, 255)

    boolTog:draw()
end

function touched(touch)
    boolTog:touched(touch)
end

Codeslinger’s Code Inspection Report. (abbrev.)

Hey, I abbreviated “abbreviated”, what a word play.

OK, back to more meaningful writing.

One

clip() has a predefined meaning, better name your version e.g. clipMatrix (clip according to the current matrix transformations). If you’re not glad now, you may be glad in some unknown future.

Two

In Toggle_init():


(1) _G[var] = _G[var] and true or false
. . .
(2) self.selected = true
(3) self.value = _G[var] and true or false

(3): global var is already true or false, see (1)

(2): really?

Three

In Toggle:touched():


(1) if self.toggle == 0 then self.toggle = self.value and -1 or 1 end
(1) self.toggle = vec2(self.toggle, 0):normalize().x
(1) self.toggle = self.toggle * self.w / 2
(2) self:switch()

Question: What does (1) do? What does (2) do? Can the results of (1) and (2) contradict each other?

Hint: Yes, they can.

Three and a half

self.toggle is the x position of the toggle with -w/2 and +w/2 being its natural resting postions and everything in between happens while interacting with it. 0 is the flipping coordinate.

Code cleanup suggestion to reduce the overkill of (1) and remove the bug:


    elseif touch.state == ENDED and self.selected then
        self.value = self.toggle > 0
        self:update()
        self.selected = false
    end
. . .

-- Private: Updates internal variables according to the state of |self.value|.
function Toggle:update()
    if self.value then
        self.toggle = self.w / 2
    else
        self.toggle = -self.w / 2
    end
end

function Toggle:switch()
    self.value = not self.value
    _G[self.var] = self.value
    self:update()
end

Thank you @Codeslinger!!! For one I think that is a good suggestion, but I wrote that to overwrite the clip code, because I had a whole section of code with clip, and when I used it with translate… It was ugly. Two = complete mistakes, thanks for noticing, self.selected should actually be false, and that should have been changed when I updated my code Three = 1. that was to fix the error where if you stopped it directly in the middle, it would break, 2. It defaults it to 1 or -1, please explain that contradiction a bit further…
Three and a half = I will look into that

Details on topic:

Three

I know why you have written every single line of (1), you simply have thought too complex about the necessary logic.

Before I give an answer let me rewrite my question in other words:

How many possible outcomes does (1) have? On what does the outcome depend on? (For everybody else who tries to solve this riddle, my cleaned up version of (1) helps.)

How many possible outcomes does (2) have? On what does the outcome depend on?

If you don’t see the contradiction, print the value of the linked variable on the screen and play around with the button carelessly. I mean carelessly like you see it sometimes in videos where somebody wants to show something on a touch screen and makes a supposedly cool flick with his finger, because you don’t do it in an uncool way, just to discover that the UI “doesn’t live up to the expectations”.

We can’t do anything against careless swipers or me sounding like a teacher, but let’s fight the bad logic.

Incidentally, I worked quite hard to get my 3D shape explorer to react correctly to a “flick”. I was quite proud of that when I got it working!

.@Codeslinger, I have fixed my code as such, and for 3, I just made it much simpler. Now I have noticed some functionality I would add in, the ability to tap on the toggle, and it would do its business, any help?

First the solution for the other readers:

The code in (1) sets the toggle to either ON or OFF, depending on whether the toggle is located more to the left or right of the middle axis. The code in (2) always inverts the value of the variable “value” when a touch interaction has ended, no matter what you actually did. If you test the toggle button by always truly toggling it, you’ll notice no defect, but if you move it a bit without actually changing the sides, the code in (2) will toggle, but not the visual knob.

Now to your question: I don’t have much experience with taps so I can only provide ideas. Is it possible to tap on the screen without any movement at all? Try this:

On BEGAN, remember that you need to detect a movement (self.moved = false). When in MOVING set self.moved = true. When in ENDED check self.moved, if it is false then it was a tap.
If this is not possible then you have to remember the largest distance of the current touch to its origin while moving. If this value is small enough then it was a touch.

On BEGAN, remember that you need to detect a movement (self.moved = false). When in MOVING set self.moved = true. When in ENDED check self.moved, if it is false then it was a tap.

This is the correct way to detect a tap.

Before reading your posts, I made something which I quite like, and it also works for “flicks”. I measure the length of the tap, and if it is less than .25 of a second, I consider it a tap. (P.S. @Codeslinger, I’m not renaming clip to clipMatrix or something just yet, I like it the way it is)


--# ClipUpgrade
_clip = clip

function clip(x, y, w, h)
    if x then
        local m = modelMatrix()
        x = x * m[1] + m[13]
        y = y * m[6] + m[14]
        w = w * m[1]
        h = h * m[6]
        _clip(x, y, w, h)
    else
        _clip()
    end
end
--# Functions
function hit(u, v, x, y, w, h)
    if u > x and v > y and u < x + w and v < y + h then
        return true
    end
    return false
end
--# Toggle
Toggle = class()

--[[ 
Version 1.1
Changes =
A new parameter, value, which is a boolean, the current toggle of the switch.
A white border around the toggle
The switch colour changes when touched
if the variable passed to toggle already exists, then the toggle will default to its value

Version 1.2
Changes = --(Thanks to @Codeslinger)
Fixes the default value for selected to false
Various speed ups and optimisations 
Enlarged the hit radius of the toggle
Made the text slightly copy the colouring of the toggle (If you look closely at a native toggle, you will notice this)

Syntax =
myToggle = Toggle("VariableName", x, y)--like the parameter and watch functions, it takes the string of the name 
-- To move the toggle without redefining it
myToggle.x = newX
myToggle.y = newY
-- To switch the value
myToggle:switch()


]]
function Toggle:init(var, x, y)
    _G[var] = _G[var] and true or false
    self.var = var
    self.x = x
    self.y = y
    self.w = 50
    self.h = 30
    self.selected = false
    self.value = _G[var]
    self.toggle = (self.value and 1 or -1) * (self.w / 2)
end

function Toggle:draw()
    pushMatrix()
        pushStyle()
            ellipseMode(CENTER)
            lineCapMode(ROUND)
            textMode(CENTER)
            smooth()
            
            translate(self.x, self.y)
            strokeWidth(self.h + 1)
            stroke(255, 255, 255, 255)
            line(0, 0, self.w, 0)
            strokeWidth(self.h - 4)
            stroke(239, 239, 239, 255)
            line(self.w / 2 + self.toggle, 0, self.w, 0)
            stroke(0, 127, 239, 255)
            line(0, 0, self.w / 2 + self.toggle, 0)
            
            strokeWidth(self.h / 2 - 2)
            stroke(255, 255, 255, 255)
            line(self.w / 2 + self.toggle, -self.h / 4, self.w, -self.h / 4)
            stroke(79, 159, 238, 255)
            line(0, -self.h / 4, self.w / 2 + self.toggle, -self.h / 4)
            
            clip(-self.h / 2 + 4, -self.h / 2, self.w + self.h - 8, self.h)
            font("HelveticaNeue-Bold")
            fontSize(self.h / 3 * 2)
            fill(0, 0, 0, 127)
            text("ON", self.toggle - self.w / 4, 0)
            text("OFF",self.toggle + self.w * 1.25, 0)
            clip()
            
            noStroke()
            fill(239, 255)
            ellipse(self.w / 2 + self.toggle, 0, self.h)
            fill(self.selected and 223 or 255, 255)
            ellipse(self.w / 2 + self.toggle, 0, self.h - 4)
        popStyle()
    popMatrix()
end

function Toggle:touched(touch)
    if touch.state == BEGAN 
    and hit(touch.x, touch.y, 
    self.x - self.h / 2 - 5, self.y - self.h / 2 - 5, 
    self.w + self.h + 10, self.h + 10) then
        self.selected = true
        self.startTime = ElapsedTime
    elseif touch.state == MOVING and self.selected then
        self.toggle = math.min(self.w / 2, math.max(-self.w / 2, self.toggle + touch.deltaX))
    elseif touch.state == ENDED and self.selected then
        if ElapsedTime - self.startTime < .25 then
            self.value = not self.value
            self:switch()
            self.selected = false
            return
        end
        if self.value then
            self.value = self.toggle > self.w / 4
        else
            self.value = self.toggle > -self.w / 4
        end
        self:switch()
        self.selected = false
    end
end

function Toggle:switch()
    _G[self.var] = self.value
    self.toggle = (self.value and 1 or -1) * self.w / 2
end
    
--# Main
function setup()
    watch("bool")
    bool = true
    boolTog = Toggle("bool", WIDTH / 2, HEIGHT / 2)
end

function draw()
    background(0, 0, 0, 255)

    boolTog:draw()
end

function touched(touch)
    boolTog:touched(touch)
end

Whoops, that never looked good… #:-s Administrators? Where is the option to delete a post?

@Jordan what’s wrong with it? I can delete it if you really want.

I accidentally made a duplicate post. It never looked good, the formatting was all messed up.

I’ve been meaning to update this for a while. I finally got around to it…



--# Main
-- UI

-- Use this function to perform your initial setup
function setup()
    displayMode(FULLSCREEN)
    parameter.watch("1/DeltaTime")
    t = Toggle(WIDTH / 4, HEIGHT / 2, "bool") 
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    if bool then
        background(127, 127, 127, 255)
    else
        background(0)
    end

    -- Do your drawing here
    t:draw()
    translate(-WIDTH / 8 * 4, -HEIGHT * 1.5)
    scale(4, 4)
    t:draw()
end

function touched(touch)
    t:touched(touch)
end
--# mClip
function mClip(x, y, w, h) 
    local m = modelMatrix()
    x = x * m[1] + m[13]
    y = y * m[6] + m[14]
    w = w * m[1]
    h = h * m[6]
    clip(x, y, w, h)
end
--# Toggle
local vS = [[
//
// A basic vertex shader
//

//This is the current model * view * projection matrix
// Codea sets it automatically
uniform mat4 modelViewProjection;

//This is the current mesh vertex position, color and tex coord
// Set automatically
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;

//This is an output variable that will be passed to the fragment shader
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;

void main()
{
    //Pass the mesh color to the fragment shader
    vColor = color;
    vTexCoord = texCoord;
    
    //Multiply the vertex position by our combined transform
    gl_Position = modelViewProjection * position;
}
]]

local fS = [[
//
// A basic fragment shader
//

//Default precision qualifier
precision highp float;

uniform highp vec2 r;
//The interpolated vertex color for this fragment
varying lowp vec4 vColor;

//The interpolated texture coordinate for this fragment
varying highp vec2 vTexCoord;

void main()
{
    //Sample the texture at the interpolated coordinate
    lowp vec4 col = vColor;
    if (
        vTexCoord.x < r.x && vTexCoord.y < r.y && 
        ((vTexCoord.y - r.y) * (vTexCoord.y - r.y)) / (r.y * r.y) + 
        ((vTexCoord.x - r.x) * (vTexCoord.x - r.x)) / (r.x * r.x) >= 1. ||
        vTexCoord.x < r.x && vTexCoord.y > 1. - r.y && 
        ((vTexCoord.y - (1. - r.y)) * (vTexCoord.y - (1. - r.y))) / (r.y * r.y) + 
        ((vTexCoord.x - r.x) * (vTexCoord.x - r.x)) / (r.x * r.x) >= 1. ||
        vTexCoord.x > 1. - r.x && vTexCoord.y < r.y && 
        ((vTexCoord.y - r.y) * (vTexCoord.y - r.y)) / (r.y * r.y) + 
        ((vTexCoord.x - (1. - r.x)) * (vTexCoord.x - (1. - r.x))) / (r.x * r.x) >= 1. ||
        vTexCoord.x > 1. - r.x && vTexCoord.y > 1. - r.y && 
        ((vTexCoord.y - (1. - r.y)) * (vTexCoord.y - (1. - r.y))) / (r.y * r.y) + 
        ((vTexCoord.x - (1. - r.x)) * (vTexCoord.x - (1. - r.x))) / (r.x * r.x) >= 1.
    ) {
        discard;
    }
    //Set the output color to the texture color
    gl_FragColor = col;
}
]]
Toggle = class()

function Toggle:init(x, y, name, initial, callback)
    if callback then
        self.callback = callback
        self.bool = initial
        self.name = name
        _G[self.name] = self.bool
    elseif initial ~= nil then
        if type(initial) == "function" then
            self.callback = initial
            self.name = name
            _G[self.name] = _G[self.name] or true
            self.bool = _G[self.name]
        else
            self.bool = initial
            self.callback = function () end
            self.name = name
            _G[self.name] = self.bool
        end
    else
        self.name = name
        self.callback = function () end
        _G[name] = _G[name] or true
        self.bool = _G[name]
    end
    self.selected = nil
    self.tapped = false
    
    self.x = x
    self.y = y
    self.w = 39
    self.h = 14
    self.p = (self.bool and 1 or -1) * (self.w - self.h)
    self.pM = self.w - self.h
    local x, y, w, h = -self.w, -self.h, self.w, self.h
    local v = {
        vec2(x, y), vec2(x, h), vec2(w, h),
        vec2(x, y), vec2(w, y), vec2(w, h)
    }
    local tC = {
        vec2(0, 0), vec2(0, 1), vec2(1, 1),
        vec2(0, 0), vec2(1, 0), vec2(1, 1)
    }
    self.on = mesh()
    self.on.vertices = v
    self.on.texCoords = tC
    local l, d = color(15, 177, 239, 255), color(18, 120, 216, 255)
    self.on.colors = {
        l, d, d, l, l, d
    }
    self.on.shader = shader(vS, fS)
    self.on.shader.r = vec2(self.h/ (self.w * 2), 0.5)
    self.off = mesh()
    self.off.vertices = v
    self.off.texCoords = tC
    local l, d = color(255, 255, 255, 255), color(223, 223, 223, 255)
    self.off.colors = {
        l, d, d, l, l, d
    }
    self.off.shader = shader(vS, fS)
    self.off.shader.r = vec2(self.h / (self.w * 2), 0.5)
    self.shadow = mesh()
    local s = 1.5
    self.shadow.vertices = {
        vec2(x - s, y - s), vec2(x - s, h + s), vec2(w + s, h + s),
        vec2(x - s, y - s), vec2(w + s, y - s), vec2(w + s, h + s)
    }
    self.shadow.texCoords = tC
    local g = color(100, 127)
    self.shadow.colors = {
        g, g, g, g, g, g
    }
    self.shadow.shader = shader(vS, fS)
    self.shadow.shader.r = vec2((self.h + 1) / ((self.w + 1) * 2), 0.5)
end

function Toggle:draw()
    pushMatrix()
        translate(self.x, self.y)
        pushStyle()
            ellipseMode(RADIUS)
            font("Arial-BoldMT")
            fontSize(self.h * 1.5)
            smooth()
            
            self.shadow:draw()
            self.off:draw()
            mClip(-self.w, -self.h - 1, self.w + self.p, self.h * 2 + 2)
                self.on:draw()
            mClip(-self.w + 1, -self.h, self.w * 2 - 2, self.h * 2)
                fill(255, 255, 255, 191)
                text("ON", -self.w / 2 - self.h + self.p, 0) 
                fill(0, 127)
                text("OFF", self.w / 2 + self.h + self.p + 2, 0) 
            clip()
            if self.selected then
                fill(211)
            else
                fill(222)
            end
            stroke(140)
            strokeWidth(1)
            ellipse(self.p, 0, self.h + 2)
        popStyle()
    popMatrix()
end

function Toggle:touched(touch)
    if math.abs(touch.x - self.x) < self.w and math.abs(touch.y - self.y) < self.h and touch.state == BEGAN and self.selected == nil then
        self.selected = touch.id
        self.tapped = true
    elseif self.selected and touch.id == self.selected then
        if touch.state == MOVING then
            self.p = math.max(math.min(self.pM, self.p + touch.deltaX), -self.pM)
            self.tapped = false
        end
        if touch.state == ENDED then
            self.selected = nil
            if self.tapped then
                self:switch()
                return
            end
            if self.bool then
                if self.p < self.pM / 3 then
                    self:switch()
                else
                    self:reset()
                end
            else
                if self.p > -self.pM / 3 then
                    self:switch()
                else
                    self:reset()
                end
            end
        end
    end
end

function Toggle:switch()
    self.bool = not self.bool
    _G[self.name] = self.bool
    self.callback(self.bool)
    tween(0.25, self, { p = (self.bool and 1 or -1) * self.pM })
end

function Toggle:reset()
    tween(0.25, self, { p = (self.bool and 1 or -1) * self.pM } )
end

This includes a roundRect shader too!

Very nice.
Btw, i like your clip() function. I wish i had seen this idea before, it makes clipping much simpler.