Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

A more accurate slider, free to use!

edited March 2015 in Code Sharing Posts: 1,595

I meant to do this for a while but work and procrastination slowed me down. I had the trouble of not being able to accurately set the value on small sliders, to combat this I have created a slider that expands to give a much better accuracy. You can change the expand/contract animations by editing the easing in the tween functions. The relative mode makes it so the slider grip stays in the same place and moves the slider to compensate. Anyway here it is:

Slider = class()

function Slider:init(pos,width,sval,eval,val,...)
    if ... then self.text = ... else self.text = "" end
    self.val = val
    self.eval = eval
    self.sval = sval
    self.pos = pos
    if type(width) == "number" then
        self.width = width 
        self.w = {w = width}
    else
        self.width = width.x
        self.w = {w = width.x}
    end
    self.hold = nil
    self.txtcol = color(0,255)
    self.visible = true
    self.tween = nil
    self.res = 0
    self.type = "slider"
    self.info = nil
    self.holding = nil
end

function Slider:setVisible(bool)
    self.visible = bool
end

function Slider:textCol(col)
    self.txtcol = col
end

function Slider:setRes(res)
    self.res = res
end

function Slider:addZero() end

function Slider:draw()
    if not self.visible then return end
    local p,z,s,sv,ev,v = self.pos,self.width,self.w.w,self.sval,self.eval,self.val
    local h = (s/z)
    local ns = s/((ev-sv))
    local hl = self.hold
    if hl then
        hl = hl
        p = p + vec2((p.x-hl)*(h-1),0)
    end
    local tp = p-vec2((ns*(sv+ev)*0.5),0)+vec2(((s/(ev-sv)))*v,0)
    pushStyle()
        fill(50,h*70-70)
        rect(-10,-10,WIDTH+20,HEIGHT+20)
        lineCapMode(PROJECT)
        strokeWidth(h*30)
        stroke(0,h*70-70)
        line(p.x-s/2,p.y,p.x+s/2,p.y)
        lineCapMode(ROUND)
        strokeWidth(8+h*3)
        stroke(0,255)
        line(p.x-s/2,p.y,p.x+s/2,p.y)
        strokeWidth(7+h*3)
        stroke(255,255)
        line(p.x-s/2,p.y,p.x+s/2,p.y)
        fill(255,255)
        stroke(0,255)
        strokeWidth(1)
        ellipse(tp.x,tp.y,19+h*3)
        fill(255*(h-1),255)
        font("Marion-Bold")
        fontSize(12)
        text(math.floor((v)*(10^self.res))/(10^self.res),p.x,p.y-18-h*2)
        textMode(CORNER)
        text(self.text,p.x-s/2,p.y+10+h*2)
    popStyle()
end

function Slider:toucht(t)
    if not self.visible then return end
    if self.holding then return true end
    local p,s,z,sv,ev,v = self.pos,self.w.w,self.width,self.sval,self.eval,self.val
    local ns = ((ev-sv)/v)/s
    local h = (s/z)-1
    local hl = self.hold
    if hl then
        hl = hl
        p = p + vec2((p.x-hl)*(h),0)
    end
    --local tp = p-vec2(s/2,0)+vec2(((s/(ev-sv)))*v,0)
    local tp = p-vec2((ns*(sv+ev)*0.5),0)+vec2(((s/(ev-sv)))*v,0)
    if (t.x > p.x-s/2-h*20-20 and t.x < p.x+s/2+h*20+20
        and t.y > p.y-10-h*7.5 and t.y < p.y+10+h*7.5) or vec2(t.x,t.y):dist(tp)<15+h*2 then
        return true
    end
    return false
end

function Slider:getValue()
    return self.val
end

function Slider:touched(t)
    if not self.visible then return end
    local p,z,s,sv,ev,v = self.pos,self.width,self.w.w,self.sval,self.eval,self.val
    local ns = s/((ev-sv))
    local h = (s/z)
    local hl = self.hold
    if hl then
        hl = hl
        p = p + vec2((p.x-hl)*(h-1),0)
    end
    local tp = p-vec2(s/2,0)+vec2(((s/(ev-sv)))*v,0)
    local cp = p-vec2((ns*(sv+ev)*0.5),0)+vec2(((s/(ev-sv)))*v,0)
    local np,val
    if t.state == BEGAN and self:toucht(t) then
        if not self.hold then
            if self.tween then
                tween.stop(self.tween)
            end
            self.hold = cp.x
            self.tween = tween(0.5,self.w,{w = self.width*2},tween.easing.backInOut,function() 
                self.tween = nil
            end)
        end
        self.holding = true
    end
    if t.state == MOVING and self.hold and not self.tween and self.holding then
        np = t.x
        np = np - (p.x-s/2)
        val = ((ev-sv)/s)*np+sv
        self.val = math.ceil(((clamp(val,sv,ev)-0.5)*(10^self.res)))/(10^self.res)
    end
    if t.state == ENDED then
        self.holding = nil
        if not self.tween then
            self.tween = tween(0.25,self.w,{w = self.width},tween.easing.backOut,function() 
                self.hold = nil 
                self.tween = nil
            end)
        end
    end
end

Edit: fixed a bug.
Edit2: fixed another bug. Should work properly now.

Comments

  • Posts: 505

    simple and useful!

  • Posts: 1,595

    Slider using a shader for fun:


    --# Main -- Slider -- Use this function to perform your initial setup function setup() sl = Slider(vec2(WIDTH/2,HEIGHT/2),200,0,400,100,"Test Slider:") sl:textCol(color(255)) end function touched(t) sl:touched(t) end function clamp(val,min,max) val = math.min(math.max(val,min),max) return val end -- This function gets called once every frame function draw() -- This sets a dark background color background(34, 135, 71, 255) -- This sets the line thickness -- Do your drawing here sl:draw() end shadr = {} shadr.fs = [[ varying highp vec2 vTexCoord; uniform lowp vec4 color; uniform highp float time; uniform highp float len; void main() { mediump vec2 vTexCoordn = 1.0-2.0*vTexCoord; lowp float dist = sqrt((vTexCoord.x-time)*(vTexCoord.x-time)+vTexCoordn.y*vTexCoordn.y); highp float mp = cos(vTexCoord.x-time)*len*dist*50.0; vTexCoordn.x = 0.0; //Premult gl_FragColor = color*(1.9-length(vTexCoordn)*1.7*mp); } ]] shadr.vs = [[ uniform mat4 modelViewProjection; attribute vec4 position; attribute vec4 color; attribute vec2 texCoord; varying lowp vec4 vColor; varying highp vec2 vTexCoord; void main() { vColor = color; vTexCoord = texCoord; gl_Position = modelViewProjection * position; }]] --# Slider Slider = class() function Slider:init(pos,width,sval,eval,val,...) if ... then self.text = ... else self.text = "" end self.val = val self.eval = eval self.sval = sval self.pos = pos if type(width) == "number" then self.width = width self.w = {w = width} else self.width = width.x self.w = {w = width.x} end self.hold = nil self.txtcol = color(0,255) self.visible = true self.tween = nil self.res = 0 self.type = "slider" self.info = nil self.m = mesh() self.r = self.m:addRect(pos.x,pos.y,width+20,300) self.m.shader = shader(shadr.vs,shadr.fs) self.m.shader.len = 10 self.m.shader.color = vec4(0.2,0.3,0.9,1.0) self.m.shader.time = 0.1 end function Slider:setVisible(bool) self.visible = bool end function Slider:textCol(col) self.txtcol = col end function Slider:setRes(res) self.res = res end function Slider:addZero() end function Slider:draw() if not self.visible then return end local p,z,s,sv,ev,v = self.pos,self.width,self.w.w,self.sval,self.eval,self.val local h = (s/z) local ns = s/((ev-sv)) local hl = self.hold if hl then hl = hl p = p + vec2((p.x-hl)*(h-1),0) self.m:setRect(self.r,p.x,p.y,s+20,300) end local tp = p-vec2((ns*(sv+ev)*0.5),0)+vec2(((s/(ev-sv)))*v,0) pushStyle() fill(50,h*70-70) rect(-10,-10,WIDTH+20,HEIGHT+20) lineCapMode(PROJECT) strokeWidth(h*30) stroke(0,h*70-70) line(p.x-s/2,p.y,p.x+s/2,p.y) --[[lineCapMode(ROUND) strokeWidth(8+h*3) stroke(30, 255) line(p.x-s/2,p.y,p.x+s/2,p.y) strokeWidth(7+h*3) stroke(120,255) line(p.x-s/2,p.y,p.x+s/2,p.y) fill(255,255) stroke(0,255) strokeWidth(1) ellipse(tp.x,tp.y,19+h*3)]] self.m:draw() fill(255*(h-1),255) font("Marion-Bold") fontSize(12) text(math.ceil(v*(10^self.res))/(10^self.res),p.x,p.y-22-h*2) textMode(CORNER) text(self.text,p.x-s/2,p.y+10+h*2) popStyle() end function Slider:toucht(t) if not self.visible then return end local p,s,z,sv,ev,v = self.pos,self.w.w,self.width,self.sval,self.eval,self.val local ns = ((ev-sv)/v)/s local h = (s/z)-1 local hl = self.hold if hl then hl = hl p = p + vec2((p.x-hl)*(h),0) end --local tp = p-vec2(s/2,0)+vec2(((s/(ev-sv)))*v,0) local tp = p-vec2((ns*(sv+ev)*0.5),0)+vec2(((s/(ev-sv)))*v,0) if (t.x > p.x-s/2-h*20-20 and t.x < p.x+s/2+h*20+20 and t.y > p.y-10-h*7.5 and t.y < p.y+10+h*7.5) or vec2(t.x,t.y):dist(tp)<15+h*2 then return true end return false end function Slider:getValue() return self.val end function Slider:touched(t) if not self.visible then return end local p,z,s,sv,ev,v = self.pos,self.width,self.w.w,self.sval,self.eval,self.val local ns = s/((ev-sv)) local h = (s/z) local hl = self.hold if hl then hl = hl p = p + vec2((p.x-hl)*(h-1),0) end local tp = p-vec2(s/2,0)+vec2(((s/(ev-sv)))*v,0) local cp = p-vec2((ns*(sv+ev)*0.5),0)+vec2(((s/(ev-sv)))*v,0) local np,val if t.state == BEGAN and self:toucht(t) and not self.hold then if self.tween then tween.stop(self.tween) end self.hold = cp.x self.tween = tween(0.5,self.w,{w = self.width*2},tween.easing.quintInOut,function() self.tween = nil end) end if t.state == MOVING and self.hold and not self.tween then np = t.x np = np - (p.x-s/2) val = ((ev-sv)/s)*np+sv self.val = math.ceil((clamp(val,sv,ev)*(10^self.res)))/(10^self.res) self.m.shader.time = (self.val/(ev-sv))*h*0.4+0.1 end if t.state == ENDED and not self.tween then self.tween = tween(0.25,self.w,{w = self.width},tween.easing.backOut,function() self.hold = nil self.tween = nil end) end end

    Could use it if I could make it look better..

  • Posts: 536

    @luatee maybe i'm doing something wrong, but still bugs for me on touched ended

  • dave1707dave1707 Mod
    Posts: 8,464

    @Luatee If you tap the slider, you get a touch BEGAN, but you don't get a touch ENDED and the slider stays selected. Not sure if that's what you're after.

  • edited March 2015 Posts: 1,595

    @piinthesky @dave1707 sorry more clear now, no this is the wanted behaviour. You can change this by making the tween shorter, the reason I did it like this was for a smoother animation. If you wait until it springs out then stop touching it should work properly.

    You could disable this by taking out 'and not self.tween' in the ENDED part of the Slider touched function. You'll see why it's not the desired functionality but it could be worked around I guess.

Sign In or Register to comment.