Help with smooth scrolling

I was trying to make the iOS 7 style list. I’m done with the whole thing but can’t get how to scroll smoothly through the list. Here’s the code.

Sometimes I want to stop at a point but it scrolls forcefully and sometimes it doesn’t scroll enough. Anyway I could get a balance between the two?

The part related to scrolling is in the touched function when touch.state Ends, I’m using Tweens to scroll, I’d appreciate if you could help me out in getting the values of the duration of the tween and how much to scroll. Thanks!


--# RollList
RollList = class()

function RollList:init(x, y, list, selection)
    local a, b
    
    self.x = x
    self.y = y
    self.w = 0
    
    self.list = {}
    pushStyle()
    font("AmericanTypewriter")
    fontSize(30)
    for k,v in pairs(list) do
        self.list[#self.list + 1] = v
        a,b = textSize(v)
        self.w = math.max(self.w, a)
    end
    popStyle()
    self.w = self.w/2
    
    local s = selection
    if s == nil then
        s = 1
    end
    
    self.deltaY = -35*s
    self.Selection = self.list[s]
end

function RollList:draw()
    pushMatrix()
    pushStyle()
    fill(127, 127, 127, 255)
    font("AmericanTypewriter")
    fontSize(25)
    
    for k,v in pairs(self.list) do
        local t = (self.deltaY + k*35)/90
        local a = 90*math.sin(t)
        t = math.deg(t)
        if t < 90 and t > 0 then
            pushMatrix()
            pushStyle()
            clip(self.x - self.w, self.y + 17, 2*self.w, 90)
            translate(self.x, self.y + a)
            rotate(t, 1, 0, 0)
            text(v, 0, 0)
            clip()
            popStyle()
            popMatrix()
        elseif t > -90 and t < 0 then
            pushMatrix()
            pushStyle()
            clip(self.x - self.w, self.y -107, 2*self.w, 90)
            translate(self.x, self.y + a)
            rotate(t, 1, 0, 0)
            text(v, 0, 0)
            clip()
            popStyle()
            popMatrix()
        end
        if t > -20 and t < 20 then
            pushMatrix()
            pushStyle()
            fill(0, 0, 0, 255)
            fontSize(28)
            clip(self.x - self.w, self.y - 17, 2*self.w, 34)
            translate(self.x, self.y + a)
            rotate(t, 1, 0, 0)
            text(v, 0, 0)
            clip()
            popMatrix()
            popStyle()
            if math.abs(a) < 10 then
                self.Selection = v
                self.SelectionK = k
                if self.Selection ~= self.PrevSelection then
                    sound(DATA, "ZgBAIQArQFZBQEBAAAAAAAnUjT0AAAAAGABAf0BHQEBAQEBA")
                end
            end
        end
    end
    
    strokeWidth(2)
    stroke(127, 127, 127, 255)
    translate(0, 0, 6)
    line(self.x - self.w, self.y + 17, self.x + self.w, self.y + 17)
    line(self.x - self.w, self.y - 17, self.x + self.w, self.y - 17)
    
    popStyle()
    popMatrix()
    self.PrevSelection = self.Selection
    
end

function RollList:touched(touch)
    if touch.state == BEGAN and math.abs(touch.x - self.x) < self.w and math.abs(touch.y - self.y) < 90 then
        if self.Tween then
            tween.stop(self.Tween)
        end
        self.AvgDeltaY = 0
        self.Touching = true
        self.ty = touch.y
        self.pty = touch.y
        
    elseif touch.state == MOVING and self.Touching then
        self.AvgDeltaY = self.AvgDeltaY + touch.deltaY/10
        self.deltaY = self.deltaY + touch.deltaY
        self.ty = self.pty - touch.y
        self.pty = touch.y
        
    elseif touch.state == ENDED and self.Touching then
        if math.abs(self.ty) > 1 then
            local a,b = math.modf((self.deltaY + self.AvgDeltaY*50)/35)*35
            local t = 3
            if (self.SelectionK < 10 and a > 0) or (self.SelectionK > #self.list - 10 and a < 0) then
                t = 1
            end
            self.Tween = tween(t, self, {deltaY = math.max(math.min(a,-35), -#self.list*35 ) }, 
                                         tween.easing.quartOut)
        else
            a = -self.SelectionK*35
            self.Tween = tween(1, self, {deltaY = math.max(math.min(a,-35), -#self.list*35 ) }, 
                                         tween.easing.quartOut)
        end
        
        self.Touching = false
    end
end



--# Main
--The name of the project must match your Codea project name if dependencies are used. 
--Project: User Interface
--Version: 0.0.1
--Comments:

function setup()
    f = 60
    local t = {}
    t = spriteList("Cargo Bot")
    list = RollList(WIDTH/2, HEIGHT/2, t, #t/2)
    parameter.watch("f")
end

function draw()
    f = .9*f + .1/DeltaTime
    background(255, 255, 255, 255)
    stroke(255, 0, 0, 255)
    strokeWidth(100)
    fill(255, 0, 0, 255)
    --translate(-100, 0)
    list:draw()
    text(list.Selection, WIDTH/2, HEIGHT/2 - 200)
end

function touched(touch)
    list:touched(touch)
end

What do you think of this?


--# RollList
RollList = class()

function RollList:init(x, y, list, selection)
    local a, b

    self.x = x
    self.y = y
    self.w = 0

    self.list = {}
    pushStyle()
    font("AmericanTypewriter")
    fontSize(30)
    for k,v in pairs(list) do
        self.list[#self.list + 1] = v
        a,b = textSize(v)
        self.w = math.max(self.w, a)
    end
    popStyle()
    self.w = self.w/2

    local s = selection
    if s == nil then
        s = 1
    end

    self.deltaY = -35*s
    self.Selection = self.list[s]
end

function RollList:draw()
    pushMatrix()
    pushStyle()
    fill(127, 127, 127, 255)
    font("AmericanTypewriter")
    fontSize(25)

    for k,v in pairs(self.list) do
        local t = (self.deltaY + k*35)/90
        local a = 90*math.sin(t)
        t = math.deg(t)
        if t < 90 and t > 0 then
            pushMatrix()
            pushStyle()
            clip(self.x - self.w, self.y + 17, 2*self.w, 90)
            translate(self.x, self.y + a)
            rotate(t, 1, 0, 0)
            text(v, 0, 0)
            clip()
            popStyle()
            popMatrix()
        elseif t > -90 and t < 0 then
            pushMatrix()
            pushStyle()
            clip(self.x - self.w, self.y -107, 2*self.w, 90)
            translate(self.x, self.y + a)
            rotate(t, 1, 0, 0)
            text(v, 0, 0)
            clip()
            popStyle()
            popMatrix()
        end
        if t > -20 and t < 20 then
            pushMatrix()
            pushStyle()
            fill(0, 0, 0, 255)
            fontSize(28)
            clip(self.x - self.w, self.y - 17, 2*self.w, 34)
            translate(self.x, self.y + a)
            rotate(t, 1, 0, 0)
            text(v, 0, 0)
            clip()
            popMatrix()
            popStyle()
            if math.abs(a) < 10 then
                self.Selection = v
                self.SelectionK = k
                if self.Selection ~= self.PrevSelection then
                    sound(DATA, "ZgBAIQArQFZBQEBAAAAAAAnUjT0AAAAAGABAf0BHQEBAQEBA")
                end
            end
        end
    end

    strokeWidth(2)
    stroke(127, 127, 127, 255)
    translate(0, 0, 6)
    line(self.x - self.w, self.y + 17, self.x + self.w, self.y + 17)
    line(self.x - self.w, self.y - 17, self.x + self.w, self.y - 17)

    popStyle()
    popMatrix()
    self.PrevSelection = self.Selection
    
    if self.Touching then
        if self.lastDelta then
            local newSpeed = (self.deltaY - self.lastDelta)/ElapsedTime
            self.movingSpeed = 0.5 * newSpeed + 0.5* (self.movingSpeed or newSpeed)
            speed = self.movingSpeed
        end
        self.lastDelta = self.deltaY
    elseif self.movingSpeed then
        self.deltaY = self.deltaY + self.movingSpeed*ElapsedTime
        self.movingSpeed = self.movingSpeed * 0.95
        if math.abs(self.movingSpeed)<0.01 then self.movingSpeed=nil end
    end

end

function RollList:touched(touch)
    if touch.state == BEGAN and math.abs(touch.x - self.x) < self.w and math.abs(touch.y - self.y) < 90 then
        if self.Tween then
            tween.stop(self.Tween)
        end
        self.AvgDeltaY = 0
        self.movingSpeed = nil
        self.lastDelta = nil
        self.Touching = true
        self.ty = touch.y
        self.pty = touch.y

    elseif touch.state == MOVING and self.Touching then
        self.AvgDeltaY = self.AvgDeltaY + touch.deltaY/10
        
        self.deltaY = self.deltaY + touch.deltaY
        self.ty = self.pty - touch.y
        self.pty = touch.y

    elseif touch.state == ENDED and self.Touching then
        if self.movingSpeed and math.abs(self.movingSpeed)> 0.5 then
            if self.movingSpeed>2 then self.movingSpeed=2 end
            if self.movingSpeed<-2 then self.movingSpeed=-2 end
   --         tween(1,self,{movingSpeed=0},"linear",function() self.movingSpeed=nil end)
        else
            self.movingSpeed=nil
        end
        
        self.Touching = false
        self.lastDelta = nil
    end
end



--# Main
--The name of the project must match your Codea project name if dependencies are used. 
--Project: User Interface
--Version: 0.0.1
--Comments:

function setup()
    f = 60
    local t = {}
    t = spriteList("Cargo Bot")
    list = RollList(WIDTH/2, HEIGHT/2, t, #t/2)
    parameter.watch("f")
    parameter.watch("speed")
end

function draw()
    f = .9*f + .1/DeltaTime
    background(255, 255, 255, 255)
    stroke(255, 0, 0, 255)
    strokeWidth(100)
    fill(255, 0, 0, 255)
    --translate(-100, 0)
    list:draw()
    text(list.Selection, WIDTH/2, HEIGHT/2 - 200)
end

function touched(touch)
    list:touched(touch)
end

I noticed that if you go past the beginning or end of the list, it takes time for the text to show again. I think it should stop at the beginning or end of the list.

@dave1707 i noticed that too.

Is this what you need?


--# RollList
RollList = class()

function RollList:init(x, y, list, selection)
    local a, b

    self.x = x
    self.y = y
    self.w = 0

    self.list = {}
    pushStyle()
    font("AmericanTypewriter")
    fontSize(30)
    for k,v in pairs(list) do
        self.list[#self.list + 1] = v
        a,b = textSize(v)
        self.w = math.max(self.w, a)
    end
    popStyle()
    self.w = self.w/2

    local s = selection
    if s == nil then
        s = 1
    end

    self.deltaY = -35*s
    self.Selection = self.list[s]
    self.AvgDeltaY = 0
end

function RollList:draw()
    pushMatrix()
    pushStyle()
    fill(127, 127, 127, 255)
    font("AmericanTypewriter")
    fontSize(25)

    for k,v in pairs(self.list) do
        local t = (self.deltaY + k*35)/90
        local a = 90*math.sin(t)
        t = math.deg(t)
        if t < 90 and t > 0 then
            pushMatrix()
            pushStyle()
            clip(self.x - self.w, self.y + 17, 2*self.w, 90)
            translate(self.x, self.y + a)
            rotate(t, 1, 0, 0)
            text(v, 0, 0)
            clip()
            popStyle()
            popMatrix()
        elseif t > -90 and t < 0 then
            pushMatrix()
            pushStyle()
            clip(self.x - self.w, self.y -107, 2*self.w, 90)
            translate(self.x, self.y + a)
            rotate(t, 1, 0, 0)
            text(v, 0, 0)
            clip()
            popStyle()
            popMatrix()
        end
        if t > -20 and t < 20 then
            pushMatrix()
            pushStyle()
            fill(0, 0, 0, 255)
            fontSize(28)
            clip(self.x - self.w, self.y - 17, 2*self.w, 34)
            translate(self.x, self.y + a)
            rotate(t, 1, 0, 0)
            text(v, 0, 0)
            clip()
            popMatrix()
            popStyle()
            if math.abs(a) < 10 then
                self.Selection = v
                self.SelectionK = k
                if self.Selection ~= self.PrevSelection then
                    sound(DATA, "ZgBAIQArQFZBQEBAAAAAAAnUjT0AAAAAGABAf0BHQEBAQEBA")
                end
            end
        end
    end

    strokeWidth(2)
    stroke(127, 127, 127, 255)
    translate(0, 0, 6)
    line(self.x - self.w, self.y + 17, self.x + self.w, self.y + 17)
    line(self.x - self.w, self.y - 17, self.x + self.w, self.y - 17)

    popStyle()
    popMatrix()
    self.PrevSelection = self.Selection
    if math.abs(self.AvgDeltaY) > 0 then
        self.AvgDeltaY = self.AvgDeltaY*0.9
    end
end

function RollList:touched(touch)
    if touch.state == BEGAN and math.abs(touch.x - self.x) < self.w and math.abs(touch.y - self.y) < 90 then
        if self.Tween then
            tween.stop(self.Tween)
        end
        self.AvgDeltaY = 0
        self.Touching = true
        self.ty = touch.y
        self.pty = touch.y

    elseif touch.state == MOVING and self.Touching then
        self.AvgDeltaY = self.AvgDeltaY + ((touch.deltaY/5)*math.abs(touch.deltaY))*0.05
        self.deltaY = self.deltaY + self.AvgDeltaY --touch.deltaY
        self.ty = self.pty - touch.y
        self.pty = touch.y

    elseif touch.state == ENDED and self.Touching then
        if math.abs(self.ty) > 1 then
            local a,b = math.modf((self.deltaY + self.AvgDeltaY*50)/35)*35
            local t = 3
            if (self.SelectionK < 10 and a > 0) or (self.SelectionK > #self.list - 10 and a < 0) then
                t = 1
            end
            self.Tween = tween(t, self, {deltaY = math.max(math.min(a,-35), -#self.list*35 ) }, 
                                         tween.easing.quartOut)
        else
            a = -self.SelectionK*35
            self.Tween = tween(1, self, {deltaY = math.max(math.min(a,-35), -#self.list*35 ) }, 
                                         tween.easing.quartOut)
        end

        self.Touching = false
    end
end



--# Main
--The name of the project must match your Codea project name if dependencies are used. 
--Project: User Interface
--Version: 0.0.1
--Comments:

function setup()
    f = 60
    local t = {}
    t = spriteList("Cargo Bot")
    list = RollList(WIDTH/2, HEIGHT/2, t, #t/2)
    parameter.watch("f")
end

function draw()
    f = .9*f + .1/DeltaTime
    background(255, 255, 255, 255)
    stroke(255, 0, 0, 255)
    strokeWidth(100)
    fill(255, 0, 0, 255)
    --translate(-100, 0)
    list:draw()
    text(list.Selection, WIDTH/2, HEIGHT/2 - 200)
end

function touched(touch)
    list:touched(touch)
end

I was going to include a ScrollController class in SkyUI, but that won’t come out for a while, so I edited it for standalone use.

local mult = function(i)
    if i > 0 then
        return 1
    else
        return -1
    end
end

ScrollController = class()

function ScrollController:init()
    self.offset = {x = 0, y = 0}
    self.mot = {x = 0, y = 0}
    self.lastSigTouch = vec2(0, 0)
    self.lastSigTouchTime = 0
    self.deltaCache = {}
    self.tweenedX = false
    self.tweenedY = false
    self.tid = 0
    self.lastState = BEGAN
    self.minX, self.minY, self.maxX, self.maxY = 0, 0, 0, 0
end

function ScrollController:setMinX(i)
        self.minX = i
        return self
end

function ScrollController:setMinY(i)
        self.minY = i
        return self
end

function ScrollController:setMaxX(i)
        self.maxX = i
        return self
end

function ScrollController:setMaxY(i)
        self.maxY = i
        return self
end

function ScrollController:update()
        while #self.deltaCache > 5 do
            table.remove(self.deltaCache, 1)
        end
        
        if self.lastState == ENDED or self.lastState == CANCELLED then
            if not self.tweenedX then
                self.mot.x = self.mot.x * 0.96
                
                self.mot.x = tonumber(string.format("%.3f", tostring(self.mot.x)))
                
                self.offset.x = self.offset.x + self.mot.x
            end
            
            if not self.tweenedY then
                self.mot.y = self.mot.y * 0.96
                
                self.mot.y = tonumber(string.format("%.3f", tostring(self.mot.y)))
                
                self.offset.y = self.offset.y + self.mot.y
            end
            
            if not self.tweenedX and self.offset.x < self.minX then
                self.mot.x = 0
                tween(0.5, self.offset, {x = self.minX}, tween.easing.quadOut, function()
                    self.tweenedX = false
                end)
                self.tweenedX = true
            end
            
            if not self.tweenedY and self.offset.y < self.minY then
                self.mot.y = 0
                tween(0.5, self.offset, {y = self.minY}, tween.easing.quadOut, function()
                    self.tweenedY = false
                end)
                self.tweenedY = true
            end
            
            if not self.tweenedX and self.offset.x > self.maxX then
                self.mot.x = 0
                tween(0.5, self.offset, {x = self.maxX}, tween.easing.quadOut, function()
                    self.tweenedX = false
                end)
                self.tweenedX = true
            end
            
            if not self.tweenedY and self.offset.y > self.maxY then
                self.mot.y = 0
                tween(0.5, self.offset, {y = self.maxY}, tween.easing.quadOut, function()
                    self.tweenedY = false
                end)
                self.tweenedY = true
            end
        end
        return self
end

function ScrollController:touched(touch)
        if touch.state == BEGAN and self.tid == 0 then
            self.lastSigTouch = vec2(touch.x, touch.y)
            self.lastSigTouchTime = ElapsedTime
            self.tid = touch.id
        end
        
        if touch.id == self.tid then
            self.lastState = touch.state
            if touch.state ~= ENDED and touch.state ~= CANCELLED then
                if not self.tweenedX then
                    self.offset.x = self.offset.x + touch.deltaX
                end
                
                if not self.tweenedY then
                    self.offset.y = self.offset.y + touch.deltaY
                end
                
                table.insert(self.deltaCache, {x = touch.deltaX, y = touch.deltaY})
                
                if math.abs(touch.x - self.lastSigTouch.x) > 5 or math.abs(touch.y - self.lastSigTouch.y) > 5 then
                    self.lastSigTouch = vec2(touch.x, touch.y)
                    self.lastSigTouchTime = ElapsedTime
                end
            else
                local speed = 0
                
                for k, v in ipairs(self.deltaCache) do
                    if mult(speed) == mult(v.x) then
                        speed = speed + v.x
                    else
                        speed = v.x
                    end
                end
                
                speed = speed + touch.deltaX
                
                speed = speed / (#self.deltaCache + 1)
                
                if not self.tweenedX and ElapsedTime - self.lastSigTouchTime < 0.25 then
                    if mult(self.mot.x) == mult(touch.deltaX) then
                        self.mot.x = self.mot.x + speed
                    else
                        self.mot.x = speed
                    end
                else
                    self.mot.x = 0
                end
                
                speed = 0
                
                for k, v in ipairs(self.deltaCache) do
                    if mult(speed) == mult(v.y) then
                        speed = speed + v.y
                    else
                        speed = v.y
                    end
                end
                
                speed = speed + touch.deltaY
                
                speed = speed / (#self.deltaCache + 1)
                
                self.deltaCache = {}
                
                if not self.tweenedX and ElapsedTime - self.lastSigTouchTime < 0.25 then
                    if mult(self.mot.y) == mult(touch.deltaY) then
                        self.mot.y = self.mot.y + speed
                    else
                        self.mot.y = speed
                    end
                else
                    self.mot.y = 0
                end
                
                self.tid = 0
            end
        end
end

Hope you find it useful. I did. You need to set ScrollController:minX(i), minY, maxX, and maxY.

The indents are a little funny because SkyUI used things like self.draw = function() … end instead of function SkyUI.ScrollController:draw() … end to save time writing function SkyUI.ScrollController.

@SkyTheCoder I tried the following but I can’t understand how I should use it, And I’m also getting an error on line 137 about ‘mult’ I guess it must be your global function!

function setup()
    S = ScrollController()
    S:setMinX(0)
    S:setMaxX(WIDTH)
    S:setMinY(0)
    S:setMaxX(HEIGHT)
end

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

function touched(touch)
    S:touched(touch)
end

ScrollController = class()

function ScrollController:init()
    self.offset = {x = 0, y = 0}
    self.mot = {x = 0, y = 0}
    self.lastSigTouch = vec2(0, 0)
    self.lastSigTouchTime = 0
    self.deltaCache = {}
    self.tweenedX = false
    self.tweenedY = false
    self.tid = 0
    self.lastState = BEGAN
    self.minX, self.minY, self.maxX, self.maxY = 0, 0, 0, 0
end

function ScrollController:setMinX(i)
        self.minX = i
        return self
end

function ScrollController:setMinY(i)
        self.minY = i
        return self
end

function ScrollController:setMaxX(i)
        self.maxX = i
        return self
end

function ScrollController:setMaxY(i)
        self.maxY = i
        return self
end

function ScrollController:update()
        while #self.deltaCache > 5 do
            table.remove(self.deltaCache, 1)
        end

        if self.lastState == ENDED or self.lastState == CANCELLED then
            if not self.tweenedX then
                self.mot.x = self.mot.x * 0.96

                self.mot.x = tonumber(string.format("%.3f", tostring(self.mot.x)))

                self.offset.x = self.offset.x + self.mot.x
            end

            if not self.tweenedY then
                self.mot.y = self.mot.y * 0.96

                self.mot.y = tonumber(string.format("%.3f", tostring(self.mot.y)))

                self.offset.y = self.offset.y + self.mot.y
            end

            if not self.tweenedX and self.offset.x < self.minX then
                self.mot.x = 0
                tween(0.5, self.offset, {x = self.minX}, tween.easing.quadOut, function()
                    self.tweenedX = false
                end)
                self.tweenedX = true
            end

            if not self.tweenedY and self.offset.y < self.minY then
                self.mot.y = 0
                tween(0.5, self.offset, {y = self.minY}, tween.easing.quadOut, function()
                    self.tweenedY = false
                end)
                self.tweenedY = true
            end

            if not self.tweenedX and self.offset.x > self.maxX then
                self.mot.x = 0
                tween(0.5, self.offset, {x = self.maxX}, tween.easing.quadOut, function()
                    self.tweenedX = false
                end)
                self.tweenedX = true
            end

            if not self.tweenedY and self.offset.y > self.maxY then
                self.mot.y = 0
                tween(0.5, self.offset, {y = self.maxY}, tween.easing.quadOut, function()
                    self.tweenedY = false
                end)
                self.tweenedY = true
            end
        end
        return self
end

function ScrollController:touched(touch)
        if touch.state == BEGAN and self.tid == 0 then
            self.lastSigTouch = vec2(touch.x, touch.y)
            self.lastSigTouchTime = ElapsedTime
            self.tid = touch.id
        end

        if touch.id == self.tid then
            self.lastState = touch.state
            if touch.state ~= ENDED and touch.state ~= CANCELLED then
                if not self.tweenedX then
                    self.offset.x = self.offset.x + touch.deltaX
                end

                if not self.tweenedY then
                    self.offset.y = self.offset.y + touch.deltaY
                end

                table.insert(self.deltaCache, {touch.deltaX, touch.deltaY})

                if math.abs(touch.x - self.lastSigTouch.x) > 5 or math.abs(touch.y - self.lastSigTouch.y) > 5 then
                    self.lastSigTouch = vec2(touch.x, touch.y)
                    self.lastSigTouchTime = ElapsedTime
                end
            else
                local speed = 0

                for k, v in ipairs(self.deltaCache) do
                    if mult(speed) == mult(v.x) then
                        speed = speed + v.x
                    else
                        speed = v.x
                    end
                end

                speed = speed + touch.deltaX

                speed = speed / (#self.deltaCache + 1)

                if not self.tweenedX and ElapsedTime - self.lastSigTouchTime < 0.25 then
                    if mult(self.mot.x) == mult(touch.deltaX) then
                        self.mot.x = self.mot.x + speed
                    else
                        self.mot.x = speed
                    end
                else
                    self.mot.x = 0
                end

                speed = 0

                for k, v in ipairs(self.deltaCache) do
                    if mult(speed) == mult(v.y) then
                        speed = speed + v.y
                    else
                        speed = v.y
                    end
                end

                speed = speed + touch.deltaY

                speed = speed / (#self.deltaCache + 1)

                self.deltaCache = {}

                if not self.tweenedX and ElapsedTime - self.lastSigTouchTime < 0.25 then
                    if mult(self.mot.y) == mult(touch.deltaY) then
                        self.mot.y = self.mot.y + speed
                    else
                        self.y.x = speed
                    end
                else
                    self.mot.y = 0
                end

                self.tid = 0
            end
        end
end

Thanks guys i read your codes and came up with this. Used currentTouch instead of the touched function since that happens every draw but touched is called only when the finger slides over the screen so the MOVING state in touched wasn’t being called all the time.


--# RollList
RollList = class()

function RollList:init(x, y, list, selection)
    local a, b
    
    self.x = x
    self.y = y
    self.w = 0
    
    self.list = {}
    pushStyle()
    font("AmericanTypewriter")
    fontSize(30)
    for k,v in pairs(list) do
        self.list[#self.list + 1] = v
        a,b = textSize(v)
        self.w = math.max(self.w, a)
    end
    popStyle()
    self.w = self.w/2 + 2.5
    
    local s = selection
    if s == nil then
        s = 1
    end
    s = math.floor(s)
    self.deltaY = -35*s
    self.Selection = self.list[s]
    self.PrevSelection = self.Selection
    
    self.ty = 0
    self.pty = 0
end

function RollList:draw()
    local a, t
    pushMatrix()
    pushStyle()
    fill(127, 127, 127, 255)
    font("AmericanTypewriter")
    fontSize(25)
    
    for k,v in pairs(self.list) do
        t = (self.deltaY + k*35)/90
        
        if t < math.pi/2 and t > 0 then
            a = 90*math.sin(t)
            t = math.deg(t)
            pushMatrix()
            pushStyle()
            clip(self.x - self.w, self.y + 17, 2*self.w, 90)
            translate(self.x, self.y + a)
            rotate(t, 1, 0, 0)
            text(v, 0, 0)
            clip()
            popStyle()
            popMatrix()
            a = nil
            t = math.rad(t)
        elseif t > -math.pi/2 and t < 0 then
            a = 90*math.sin(t)
            t = math.deg(t)
            pushMatrix()
            pushStyle()
            clip(self.x - self.w, self.y -107, 2*self.w, 90)
            translate(self.x, self.y + a)
            rotate(t, 1, 0, 0)
            text(v, 0, 0)
            clip()
            popStyle()
            popMatrix()
            a = nil
            t = math.rad(t)
        end
        if math.abs(t) < math.pi/9 then
            a = 90*math.sin(t)
            t = math.deg(t)
            pushMatrix()
            pushStyle()
            fill(0, 0, 0, 255)
            fontSize(28)
            clip(self.x - self.w, self.y - 17, 2*self.w, 34)
            translate(self.x, self.y + a)
            rotate(t, 1, 0, 0)
            text(v, 0, 0)
            clip()
            popMatrix()
            popStyle()
            if math.abs(a) < 10 then
                self.Selection = v
                self.SelectionK = k
                if self.Selection ~= self.PrevSelection then
                    sound(DATA, "ZgBAJQAtI0BAQUFAAAAAAAAAAACoG/E9AABAf0BAQEBAQEBA")
                end
            end
        end
    end
    
    strokeWidth(2)
    stroke(127, 127, 127, 255)
    translate(0, 0, 6)
    line(self.x - self.w, self.y + 17, self.x + self.w, self.y + 17)
    line(self.x - self.w, self.y - 17, self.x + self.w, self.y - 17)
    
    popStyle()
    popMatrix()
    
    self.PrevSelection = self.Selection
    
    if self.Touching then
        self.ty = self.pty - CurrentTouch.y
        self.pty = CurrentTouch.y
    end
end

function RollList:touched(touch)
    if touch.state == BEGAN and math.abs(touch.x - self.x) < self.w and math.abs(touch.y - self.y) < 90 then
        if self.Tween then
            tween.stop(self.Tween)
        end
        self.AvgDeltaY = 0
        self.Touching = true
        
    elseif touch.state == MOVING and self.Touching then
        self.AvgDeltaY = self.AvgDeltaY + touch.deltaY/10
        self.deltaY = self.deltaY + touch.deltaY
        
    elseif touch.state == ENDED and self.Touching then
        if math.abs(self.ty) > 1 then
            local a,b = math.modf((self.deltaY + self.AvgDeltaY*30)/35)*35
            local t = 3
            if (self.SelectionK < 10 and a > 0) or (self.SelectionK > #self.list - 10 and a < 0) then
                t = .8
            end
            self.Tween = tween(t, self, {deltaY = math.max(math.min(a,-35), -#self.list*35 ) }, 
                                         tween.easing.quartOut)
        else
            a = -self.SelectionK*35
            self.Tween = tween(1, self, {deltaY = math.max(math.min(a,-35), -#self.list*35 ) }, 
                                         tween.easing.quartOut)
        end
        
        self.Touching = false
    end
end



--# Main
--The name of the project must match your Codea project name if dependencies are used. 
--Project: User Interface
--Version: 0.0.1
--Comments:

--displayMode(FULLSCREEN)
function setup()
    f = 60
    local t = {}
    for i = 1,60 do
        t[i] = i
    end
    list = RollList(WIDTH/2, HEIGHT/2, t, 6)
    parameter.watch("f")
end

function draw()
    f = .9*f + .1/DeltaTime
    background(255, 255, 255, 255)
    list:draw()
    fill(255, 0, 0, 255)
    text(list.Selection, WIDTH/2, HEIGHT/2 - 200)
end

function touched(touch)
    list:touched(touch)
end





@Andrew_Stacey Thanks for the explanation for the function syntax. I like the syntactic sugar version. As for the sign function, all the ones I’ve seen return -1, 0, or 1.

@Saurabh Sorry, I forgot to add function mult(i). Add this:

local mult = function(i)
    if i > 0 then
        return 1
    else
        return -1
    end
end

To use it, in draw you should add something like:

    s:update()
    rect(s.offset.x - 50, s.offset.y - 50, 100, 100)

That should make a rectangle in the screen, that smoothly moves when you drag your fingers. As you set the min and max variables in setup, it won’t be able to move off the screen. If you drag it off, it will smoothly slide back. It continues moving when you lift up your finger, but if you hold it in place for a moment and release, it won’t move. The more you drag the faster is goes.

Edit: Sorry, seems like there were a lot of glitches I missed… This is what the code should be:

function setup()
    S = ScrollController()
    S:setMinX(50)
    S:setMaxX(WIDTH - 50)
    S:setMinY(50)
    S:setMaxY(HEIGHT - 50)
end

function draw()
    background(0, 0, 0, 255)
    S:update()
    rect(S.offset.x - 50, S.offset.y - 50, 100, 100)
end

function touched(touch)
    S:touched(touch)
end

local mult = function(i)
    if i > 0 then
        return 1
    else
        return -1
    end
end

ScrollController = class()

function ScrollController:init()
    self.offset = {x = 0, y = 0}
    self.mot = {x = 0, y = 0}
    self.lastSigTouch = vec2(0, 0)
    self.lastSigTouchTime = 0
    self.deltaCache = {}
    self.tweenedX = false
    self.tweenedY = false
    self.tid = 0
    self.lastState = BEGAN
    self.minX, self.minY, self.maxX, self.maxY = 0, 0, 0, 0
end

function ScrollController:setMinX(i)
        self.minX = i
        return self
end

function ScrollController:setMinY(i)
        self.minY = i
        return self
end

function ScrollController:setMaxX(i)
        self.maxX = i
        return self
end

function ScrollController:setMaxY(i)
        self.maxY = i
        return self
end

function ScrollController:update()
        while #self.deltaCache > 5 do
            table.remove(self.deltaCache, 1)
        end

        if self.lastState == ENDED or self.lastState == CANCELLED then
            if not self.tweenedX then
                self.mot.x = self.mot.x * 0.96

                self.mot.x = tonumber(string.format("%.3f", tostring(self.mot.x)))

                self.offset.x = self.offset.x + self.mot.x
            end

            if not self.tweenedY then
                self.mot.y = self.mot.y * 0.96

                self.mot.y = tonumber(string.format("%.3f", tostring(self.mot.y)))

                self.offset.y = self.offset.y + self.mot.y
            end

            if not self.tweenedX and self.offset.x < self.minX then
                self.mot.x = 0
                tween(0.5, self.offset, {x = self.minX}, tween.easing.quadOut, function()
                    self.tweenedX = false
                end)
                self.tweenedX = true
            end

            if not self.tweenedY and self.offset.y < self.minY then
                self.mot.y = 0
                tween(0.5, self.offset, {y = self.minY}, tween.easing.quadOut, function()
                    self.tweenedY = false
                end)
                self.tweenedY = true
            end

            if not self.tweenedX and self.offset.x > self.maxX then
                self.mot.x = 0
                tween(0.5, self.offset, {x = self.maxX}, tween.easing.quadOut, function()
                    self.tweenedX = false
                end)
                self.tweenedX = true
            end

            if not self.tweenedY and self.offset.y > self.maxY then
                self.mot.y = 0
                tween(0.5, self.offset, {y = self.maxY}, tween.easing.quadOut, function()
                    self.tweenedY = false
                end)
                self.tweenedY = true
            end
        end
        return self
end

function ScrollController:touched(touch)
        if touch.state == BEGAN and self.tid == 0 then
            self.lastSigTouch = vec2(touch.x, touch.y)
            self.lastSigTouchTime = ElapsedTime
            self.tid = touch.id
        end

        if touch.id == self.tid then
            self.lastState = touch.state
            if touch.state ~= ENDED and touch.state ~= CANCELLED then
                if not self.tweenedX then
                    self.offset.x = self.offset.x + touch.deltaX
                end

                if not self.tweenedY then
                    self.offset.y = self.offset.y + touch.deltaY
                end

                table.insert(self.deltaCache, {x = touch.deltaX, y = touch.deltaY})

                if math.abs(touch.x - self.lastSigTouch.x) > 5 or math.abs(touch.y - self.lastSigTouch.y) > 5 then
                    self.lastSigTouch = vec2(touch.x, touch.y)
                    self.lastSigTouchTime = ElapsedTime
                end
            else
                local speed = 0
                
                for k, v in ipairs(self.deltaCache) do
                    if mult(speed) == mult(v.x) then
                        speed = speed + v.x
                    else
                        speed = v.x
                    end
                end

                speed = speed + touch.deltaX
                
                speed = speed / (#self.deltaCache + 1)
                
                if not self.tweenedX and ElapsedTime - self.lastSigTouchTime < 0.25 then
                    if mult(self.mot.x) == mult(touch.deltaX) then
                        self.mot.x = self.mot.x + speed
                    else
                        self.mot.x = speed
                    end
                else
                    self.mot.x = 0
                end

                speed = 0

                for k, v in ipairs(self.deltaCache) do
                    if mult(speed) == mult(v.y) then
                        speed = speed + v.y
                    else
                        speed = v.y
                    end
                end

                speed = speed + touch.deltaY

                speed = speed / (#self.deltaCache + 1)

                self.deltaCache = {}

                if not self.tweenedX and ElapsedTime - self.lastSigTouchTime < 0.25 then
                    if mult(self.mot.y) == mult(touch.deltaY) then
                        self.mot.y = self.mot.y + speed
                    else
                        self.mot.y = speed
                    end
                else
                    self.mot.y = 0
                end

                self.tid = 0
            end
        end
end

Don’t want to release those errors… I got it from another one of my projects, slightly failed to port it to 2D from 1D, forgot to add the mult(i) function… And I forgot to test it. :">

Edit 2: Sorry, Dave, if you’re tagged in this comment. For some reason I thought you posted it… I edited the @ username.

@SkyTheCoder What’s the difference between these 2. I noticed you used the first one. Is one better than the other. They seem to do the same thing.

special function?

mult = function(i)
    if i > 0 then
        return 1
    else
        return -1
    end
end

normal function

function mult(i)
    if i > 0 then
        return 1
    else
        return -1
    end
end

@dave1707 In the top one, I used the prefix “local” so if your game uses a function named “mult” it won’t conflict or overwrite it.

The ScrollController code is designed to be in a separate tab, so nothing is overwritten, like function mult(i).

@SkyTheCoder I noticed the local, and I understand that. What I’m asking about is the difference between “mult = function(i)” and “function mult(i)” . Is there a difference in how they can be used, or is it just another way of doing the same thing.

@dave1707 I don’t believe there is a difference, it’s just a different way of defining a function. I used the = function way because I wasn’t sure how to use local with the standard way.

@dave1707 From Programming in Lua, Section 6:

If functions are values, are there any expressions that create functions? Yes. In fact, the usual way to write a function in Lua, like

function foo (x) return 2*x end

is just an instance of what we call syntactic sugar; in other words, it is just a pretty way to write

foo = function (x) return 2*x end

@SkyTheCoder You may (or may not) be interested to know that your mult function is more commonly known as the sign function since it assigns to a number its sign.

@Andrew_Stacey That’s what I thought. It just looks prettier.

@Andrew_Stacey Hmm, never heard of it. (Well, now I have, but…) Is it used like math.sign?

@SkyTheCoder I’m curious as to the etymology. To me, mult is short for multiply.

(Ah, I think I misread your first of those two comments as being a reply to the sign function comment rather than the function definition one. No matter.)

There’s no math.sign function in the maths library in lua so you’d have to define it anyway. I was talking about the wider world of mathematics: the sign function in mathematics is defined that way, though usually we would say that its value on 0 is 1. So actually we would define:

function sign(i)
    if i >= 0 then
        return 1
    else
        return -1
    end
end