Carousel

Does anyone have any code for a (preferably infinite scrolling) horizontal carousel they’d be happy to share?

Like this?

--Panorama shader

function setup()
    --create background scenery image
    --make it a little wider than the screen so it doesn't start repeating too soon
    scenery=image(WIDTH*1.2,150)
    --draw some stuff on it 
    setContext(scenery)
    pushStyle()
    strokeWidth(1)
    stroke(75)
    fill(150)
    local x=0
    rectMode(CORNER)
    while x<scenery.width do
        local w=math.random(25,100)
        local h=math.random(50,150)
        rect(x,0,w,h)
        x=x+w
    end
    popStyle()
    setContext()
    --create mesh 
    m=mesh()
    m:addRect(scenery.width/2,scenery.height/2,scenery.width,scenery.height)
    m:setColors(color(255))
    m.texture=scenery
    m.shader=shader(PanoramaShader.vertexShader,PanoramaShader.fragmentShader)
    --initialise offset
    offset=0
    output.clear()
    print("Scroll an image continously")
    print("This is useful for a side scrolling game to create the illusion of movement")
end

function draw()
    background(40, 40, 50)
    offset=offset+1
    m.shader.offset=offset/scenery.width
    m:draw()
    --sprite(scenery,WIDTH/2,100)
end

PanoramaShader = {

vertexShader = [[

uniform mat4 modelViewProjection;
uniform float offset;

attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;

varying lowp vec4 vColor;
varying highp vec2 vTexCoord;

void main()
{
    vColor=color;
    vTexCoord = vec2(texCoord.x+offset,texCoord.y);
    gl_Position = modelViewProjection * position;
}

]],

fragmentShader = [[

precision highp float;

uniform lowp sampler2D texture;

varying lowp vec4 vColor;
varying highp vec2 vTexCoord;

void main()
{
    lowp vec4 col = texture2D( texture, vec2(mod(vTexCoord.x,1.0), mod(vTexCoord.y,1.0)));
    gl_FragColor = col;
}

]]}

I’ll give it a whiz in the morning and let you know - thanks :slight_smile:

Not sure what you mean by Carousel. Here’s a scrolling background.

displayMode(FULLSCREEN)
supportedOrientations(LANDSCAPE_ANY)

function setup()
    tab1={readImage("Small World:Tree 1"),readImage("Small World:Tree 2"),readImage("Small World:Tree 3"),
    readImage("Small World:House White")}
    tab={}
    for z=150,1,-1 do
        table.insert(tab,vec3(math.random(WIDTH),z*4+200,math.random(4)))
    end
end

function draw()
    background(105, 88, 31, 255)
    for a,b in pairs(tab) do
        b.x=b.x+2+(500-b.y)/300
        if b.x>WIDTH+50 then
            b.x=-50
        end
        sprite(tab1[b.z],b.x,b.y,a*.4)
    end
end

What’s a carousel? I’d use translate for scrolling.

Thanks for the examples they’re very useful but that wasn’t exactly what I was referring to.

I want to create a control that will act as a container for a number of “pages”, each page will contain a number of other interactive elements.

You can swipe / scroll left & right (or up / down) to view different pages.

Carousels are the name usually given to these type of controls (especially in the web world) based on the way they go round and round, you often see them used on level select screens in games.

eg.

(the image is nothing to do with me - just some random one I pulled from Google images).

@TechDojo Is this closer to what you want. I didn’t spend a lot of time on this just in case it’s not. I used 1 image, but they could be different. You can scroll each area.

displayMode(FULLSCREEN)
supportedOrientations(LANDSCAPE_ANY)

function setup()
    dx1,dy1=0,0
    dx2,dy2=0,0
    dx3,dy3=0,0
    dx4,dy4=0,0
end

function draw()
    background(40, 40, 50)
    clip(50,300,200,200)
    sprite("Cargo Bot:Startup Screen",150+dx1,450+dy1)
    clip(300,300,200,200)
    sprite("Cargo Bot:Startup Screen",400+dx2,450+dy2)
    clip(550,300,200,200)
    sprite("Cargo Bot:Startup Screen",650+dx3,450+dy3)    
    clip(800,300,200,200)
    sprite("Cargo Bot:Startup Screen",900+dx4,450+dy4)    
end

function touched(t)
    if t.state==MOVING then
        if t.x>50 and t.x<250 and t.y>300 and t.y<500 then
            dx1=dx1+t.deltaX
            dy1=dy1+t.deltaY
        end
        if t.x>300 and t.x<500 and t.y>300 and t.y<500 then
            dx2=dx2+t.deltaX
            dy2=dy2+t.deltaY
        end
        if t.x>550 and t.x<750 and t.y>300 and t.y<500 then
            dx3=dx3+t.deltaX
            dy3=dy3+t.deltaY
        end
        if t.x>800 and t.x<1000 and t.y>300 and t.y<500 then
            dx4=dx4+t.deltaX
            dy4=dy4+t.deltaY
        end
    end
end

Hi Dave,
Thanks, but that’s not quite, actually that’s kind of inverse what I was after. :slight_smile:
I’ll have a bang at it at lunch today and share my attempt later.

Here’s a quick example of what I was thinking of - although this one doesn’t do the infinite wrapping at each end (swipe left / right to change slides)

Carousel = class()

function Carousel:init(options)
    
    print("Carousel:init()")
    
    self.images = options

    for i=1,#self.images do
        self.images[i].baseX = self.images[i].page * WIDTH
    end
    
    self.page = 0
    self.offset = 0
    self.numImages = #self.images
    
    self.tx, self.ty = 0,0      -- position of touch for swipe detection
    self.tid = 0                -- ID (prevent multitouch messing things up)
    self.swiping = false
end

function Carousel:draw()

    local img    
    pushMatrix(); pushStyle();
    
    spriteMode(CENTER)
    
    for i=1,self.numImages do
        img = self.images[i]
        
        resetMatrix()
        translate(img.baseX,0)
        translate(self.offset,0)
        sprite(img.image,img.ox,img.oy)
            
    end
    
    popStyle(); popMatrix()
end

function Carousel:swipe(offset)
    
    local dest = self.offset + offset
    self.swiping = true     
    
    tween(0.333,self,{offset = dest},tween.easing.linear,
        function()
            self.swiping = false
        end)
end

function Carousel:swipeLeft()   
    print("SWIPE LEFT")
    self:swipe(WIDTH)
end

function Carousel:swipeRight()  
    print("SWIPE RIGHT")    
    self:swipe(-WIDTH)
end

function Carousel:touched(t)
    -- Very simple check for horizontal swipes

    if not self.swiping then 
     
        if t.state == BEGAN and self.tid == 0 then
            self.tid,self.tx,self.ty = t.id,t.x,t.y
        end
        
        if t.state == ENDED and self.tid == t.id then
            local dx = t.x - self.tx
            
            if dx > 20 then self:swipeLeft()
            elseif dx < -20 then self:swipeRight()
            end
            
            self.tid = 0
        end
    end
end

-- -----------------------
displayMode(FULLSCREEN)
supportedOrientations(LANDSCAPE_ANY)

local carousel
local CX,CY = WIDTH/2,HEIGHT/2
local OY = CY - 100

function setup()
    
    carousel = Carousel({
        {image = "Cargo Bot:Pack Easy",       ox=CX, oy=OY, page=0 },
        {image = "Cargo Bot:Pack Medium",     ox=CX, oy=OY, page=1 },
        {image = "Cargo Bot:Pack Hard",       ox=CX, oy=OY, page=2 },
        {image = "Cargo Bot:Pack Impossible", ox=CX, oy=OY, page=3 },
        {image = "Cargo Bot:Pack Crazy",      ox=CX, oy=OY, page=4 },    
    })
    
end

function draw()
    spriteMode(CENTER)    
    sprite("Cargo Bot:Opening Background",CX,CY,WIDTH,HEIGHT)
    sprite("Cargo Bot:Cargo Bot Title",CX,HEIGHT-200)
    
    sprite("Cargo Bot:Command Left",100,OY)
    sprite("Cargo Bot:Command Right",WIDTH-100,OY)
    
    carousel:draw()    
end

function touched(t)
    carousel:touched(t)
end

Surely it’s a simple case of testing if you’ve gone past the end and resetting the counter to the start again?

@TechDojo Here’s something I already had. I modified it to allow infinite scrolling left or right and added the table of sprites to display.

displayMode(FULLSCREEN)
supportedOrientations(PORTRAIT_ANY)

function setup()
    tab={"Planet Cute:Character Boy","Planet Cute:Character Cat Girl",
    "Planet Cute:Character Horn Girl","Planet Cute:Character Pink Girl",
    "Planet Cute:Character Princess Girl","Planet Cute:Enemy Bug",
    "Planet Cute:Gem Blue","Planet Cute:Gem Green"}
    dx=0
    page=0
    dir=0
    cc=1
    check(0)    
end

function check(v)
    page=page+v
    dir=30*v
    cc=cc+v
    if cc>#tab then
        cc=1
    end
    if cc<1 then
        cc=#tab
    end
    cc2=cc
    cc1=cc2-1
    if cc1<1 then
        cc1=#tab
    end
    cc3=cc2+1
    if cc3>#tab
    then
        cc3=1
    end
end

function draw()
    background(0)    
    w=WIDTH*(page-1)
    sprite(tab[cc1],WIDTH/2+dx-w,HEIGHT/2)    
    w=WIDTH*page
    sprite(tab[cc2],WIDTH/2+dx-w,HEIGHT/2)
    w=WIDTH*(page+1)
    sprite(tab[cc3],WIDTH/2+dx-w,HEIGHT/2)
    if scroll then
        dx=dx+dir
        if dir>0 and dx>page*WIDTH or dir<0 and dx<page*WIDTH then
            dx=page*WIDTH
            scroll=false
        end
    end
end

function touched(t)
    if t.state==MOVING then
        dx=dx+t.deltaX
        dir=t.deltaX
    end
    if t.state==ENDED then
        scroll=true
        if dir>0 then
            check(1)
        elseif dir<0 then
            check(-1)
        end
    end
end

@TechDojo Here’s another version. You would have different tables for each view. To save time, I just used the same table for each one. Swipe left or right on an object.

displayMode(FULLSCREEN)
supportedOrientations(LANDSCAPE_ANY)

function setup()
    rectMode(CENTER)
    tab={"Planet Cute:Character Boy","Planet Cute:Character Cat Girl",
    "Planet Cute:Character Horn Girl","Planet Cute:Character Pink Girl",
    "Planet Cute:Character Princess Girl","Planet Cute:Enemy Bug",
    "Planet Cute:Gem Blue","Planet Cute:Gem Green"}
    car={}
    table.insert(car,carousel(150,400))
    table.insert(car,carousel(400,400))
    table.insert(car,carousel(650,400))
    table.insert(car,carousel(900,400))
end

function draw()
    background(60, 184, 228, 255)    
    for a,b in pairs(car) do
        b:draw()
    end
end

function touched(t)
    for a,b in pairs(car) do
        b:touched(t)
    end
end

carousel=class()

function carousel:init(x,y)
    self.x=x
    self.y=y
    self.cc=1
    self.dir=0
end

function carousel:draw()
    stroke(255)
    strokeWidth(8)
    fill(222, 186, 140, 255)
    rect(self.x,self.y,200,300)
    sprite(tab[self.cc],self.x,self.y)
end

function carousel:touched(t)
    if t.x>self.x-100 and t.x<self.x+100 and t.y>self.y-150 and t.y<self.y+150 then
        if t.state==BEGAN then
            self.dir=t.x
        end
        if t.state==ENDED then
            if t.x>self.dir then
                self:check(1)
            else
                self:check(-1)
            end
        end
    end    
end

function carousel:check(v)
    self.cc=self.cc+v
    if self.cc>#tab then
        self.cc=1
    elseif self.cc<1 then
        self.cc=#tab
    end    
end

Thanks everyone (especially @dave1707) - I’ll give the code a whirl later (pun intended) :slight_smile:

Hey @TechDojo try this out. I think it’s what you’re going for, based on your picture, and it has infinite scrolling too

displayMode(FULLSCREEN)
supportedOrientations(LANDSCAPE_ANY)
function setup()
    carousel = Carousel()
end

function draw()
    carousel:draw()
end

function touched(touch)
    if touch.state == BEGAN then tchId = touch.id end
    if tchId == touch.id then carousel:touched(touch) end
end

Carousel = class()

function Carousel:init()
    tab = {
    "Cargo Bot:Pack Tutorial","Cargo Bot:Pack Tutorial","Cargo Bot:Pack Tutorial",
    "Cargo Bot:Pack Easy","Cargo Bot:Pack Easy","Cargo Bot:Pack Easy",
    "Cargo Bot:Pack Medium","Cargo Bot:Pack Medium","Cargo Bot:Pack Medium",
    "Cargo Bot:Pack Hard","Cargo Bot:Pack Hard","Cargo Bot:Pack Hard",
    "Cargo Bot:Pack Crazy","Cargo Bot:Pack Crazy","Cargo Bot:Pack Crazy",
    "Cargo Bot:Pack Impossible","Cargo Bot:Pack Impossible","Cargo Bot:Pack Impossible"
    }
    local n = #tab
    self.scroll = 0
    self.limit = 0
    self.perPage = 3
    self.pageNum = math.ceil(n/self.perPage)
    self.page = 1
    self.vel = 0
    while #tab % self.perPage ~= 0 do
        tab[#tab+1] = image(0,0)
    end
end

function Carousel:draw()
    background(40,40,50)
    
    if self.page > self.pageNum then self.page = 1 end
    if self.page < 1 then self.page = self.pageNum end
    self.limit = (self.page-1)*WIDTH
    
    if self.swipe == true then
        if self.scroll ~= self.limit then 
            self.scroll = self.scroll - (self.scroll-self.limit)/5
        end
        self.vel = 0
        self.scroll = self.scroll + self.vel
    end
    
    pushMatrix()
    translate(-self.scroll,0)
    for i,v in pairs(tab) do
        sprite(v,i*WIDTH/self.perPage-WIDTH/self.perPage/2,HEIGHT/2)
    end
    for i = 1,self.perPage do
        local x = self.pageNum*WIDTH
        local v = tab[i]
        sprite(v,x+i*WIDTH/self.perPage-WIDTH/self.perPage/2,HEIGHT/2)
    end
    for i = 1,self.perPage do
        local x = -WIDTH
        local v = tab[#tab+i-self.perPage]
        sprite(v,x+i*WIDTH/self.perPage-WIDTH/self.perPage/2,HEIGHT/2)
    end
    popMatrix()
    pushStyle()
    for i = 1,self.pageNum do
        local s = self.pageNum*50/2+25
        noStroke()
        if i == self.page then
            fill(200,255)
            ellipse(WIDTH/2-s+i*50,75,40)
        else
            fill(100,255)
            ellipse(WIDTH/2-s+i*50,75,30)
        end
    end
    popStyle()
end

function Carousel:touched(touch)
    if touch.state == BEGAN then
        self.vel = nil
        self.temp1 = nil
        self.temp2 = nil
    end
    
    if touch.state == ENDED and touch.tapCount > 0 then
        for i = 1,self.pageNum do
            local s = self.pageNum*50/2+25
            local x,y = WIDTH/2-s+i*50,75
            local v = vec2(touch.x,touch.y)-vec2(x,y)
            if v:len() < 20 then self.page = i end
        end
    end
    
    if touch.state ~= ENDED then
        self.swipe = false
        self.scroll = self.scroll - touch.deltaX
        if self.scroll < self.limit - WIDTH then self.scroll = self.limit - WIDTH end
        if self.scroll > self.limit + WIDTH then self.scroll = self.limit + WIDTH end
    else
        self.swipe = false
    end
    if touch.state == ENDED then 
        self.swipe = true
        if self.vel > 5 or self.scroll > self.limit + 200 then self.page = self.page + 1 end
        if self.vel < -5 or self.scroll < self.limit - 200 then self.page = self.page - 1 end
        if self.page > self.pageNum then 
            self.page = 1 
            self.scroll = self.scroll - self.pageNum*WIDTH
        end
        if self.page < 1 then 
            self.page = self.pageNum 
            self.scroll = self.scroll + self.pageNum*WIDTH
        end
        self.limit = (self.page-1)*WIDTH
    end
    self.vel = self.temp1 or -touch.deltaX
    self.temp1 = self.temp2 or -touch.deltaX
    self.temp2 = -touch.deltaX
end

EDIT: I changed some of the code so you can switch pages by tapping the circles at the bottom
EDIT 2: I added a new variable self.perPage that lets you change the number of items on each screen. 1, 2, 3, and 4 all look pretty good, but try anything higher than that and they start to overlap

@Dwins - That seems to work a treat - thanks :smile:

No problem! Now that I’ve written the code for it, I’ll probably use it in a lot of my other projects as well, so thanks for giving me the idea :smiley:

@Dwins very nice tool!

@Dwins - really great!