List Scroll class demo

I created a ListScroll class to show a list of items such as Text , color or picture. Here is the demo
http://www.youtube.com/watch?v=UdSu9S3_Kzw

I do not finish it but I want to do how it can work. Thank you @Mark for your inspiration from Lists program.

Here the source code

--List Scroll class
ListScroll = class()

function ListScroll:init(pos,w,h)
    -- you can accept and set parameters here
    self.pos = pos
    self.width= w
    self.height= h
    self.itemHeight = 0
    self.borderColor = color(100,100,100,255)
    self.borderWidth = 2
    self.backColor = color(255,255,255,255)
    self.sepColor = color(100,100,100,255)
    self.rowColor1 =  color (255,255,255,255)
    self.rowColor2 = color(186, 186, 186, 100)    
    self.allowEdit = false
    self.allowDelete = true
    self.allowInsert = false
    self.multiSelected = false
    self.items = {}
    self.posY = self.pos.y+self.height
    self.prevState = nil
    self.selectedItem = nil
end

function ListScroll:add(i)
    self.itemHeight = i.height
    i.width = self.width
    table.insert(self.items,i)
end

function ListScroll:selectedItem()
    return self.selectedItem
end

function ListScroll:selectedValue()
    if self.selectedItem==nil then 
        return nil 
    end 
    return self.items[self.selectedItem].value
end

function ListScroll:itemCount()
    return #self.items
end 

function ListScroll:draw()
    -- Codea does not automatically call this method
    pushStyle()
        strokeWidth(self.borderWidth)
        fill(self.backColor)
        stroke(self.borderColor)
        rect(self.pos.x,self.pos.y,self.width,self.height)
        
        clip(self.pos.x,self.pos.y,self.width,self.height)
        for i=1,#self.items do
            strokeWidth(1)
            if i%2==0 then
                fill(self.rowColor1)
            else
                fill(self.rowColor2)
            end 
            self.items[i]:draw(self.pos.x,self.posY-i*self.itemHeight)
        end 
        noClip()
    popStyle()
end

function ListScroll:touched(touch)
    -- Codea does not automatically call this method
    if not (touch.x>=self.pos.x and touch.x<=self.pos.x+self.width and 
           touch.y>=self.pos.y and touch.y<=self.pos.y+self.height) then 
        return 
    end 
    if touch.state==MOVING then
        self.prevState=MOVING
        self.posY = self.posY + touch.deltaY 
        if self.posY>=self.pos.y+self.height+self.itemHeight*(#self.items-1) then
            self.posY=self.pos.y+self.height+self.itemHeight*(#self.items-1)
        end 
    elseif touch.state==ENDED then
        if self.prevState==MOVING then
             if self.posY<self.pos.y+self.height then
                self.posY=self.pos.y+self.height
             end
            self.prevState=nil
        else
            local i = math.ceil((self.posY-touch.y)/self.itemHeight)
            if i<=#self.items then
                self.selectedItem = i
                self.items[i]:touched(touch)
                if not self.multiSelected then
                    for r=1,#self.items do
                        if r ~= i then
                            self.items[r].selected=false
                        end 
                    end 
                end 
            else
                self.selectedItem = nil
            end 
        end 
    end
end

--Color Item
ColorItem = class()

function ColorItem:init(id,c,w,h)
    -- you can accept and set parameters here
    self.pos = vec2(0,0)
    self.id = id
    self.selected = false
    self.selectedColor = color(0,0,0, 100)
    self.width = w
    self.height = h
    self.value = c
end

function ColorItem:draw(x,y)
    -- Codea does not automatically call this method
    self.pos = vec2(x,y)
    pushStyle()
        fill(self.value)
        rect(self.pos.x,self.pos.y,self.width,self.height)
        if self.selected then
            noFill()
            strokeWidth(5)
            stroke(self.selectedColor)
            rect(self.pos.x,self.pos.y,self.width,self.height)
        end 
    popStyle()
end

function ColorItem:touched(touch)
    -- Codea does not automatically call this method
    if self.selected then
        self.selected=false
    else
        self.selected=true
    end
end

--Text Item class
TextItem = class()

function TextItem:init(id,value)
    -- you can accept and set parameters here
    self.pos = vec2(0,0)
    self.id = id
    self.value=value
    self.selected=false 
    self.selectedColor = color(223, 218, 150, 255)
    self.width = 0
    self.height = 0
    self.textColor = color(0,0,0,255)
    self.textAlign=LEFT
    self.fontName = "Courier"
    self.fontSize=24
    font(self.fontName)
    fontSize(self.fontSize)
    _,self.height = textSize(self.fontName)
end

function TextItem:draw(x,y)
    -- Codea does not automatically call this method
    self.pos = vec2(x,y)
    pushStyle()
        strokeWidth(1)
        stroke(stroke())
        if self.selected then
            fill(self.selectedColor)
        end 
        rect(self.pos.x,self.pos.y,self.width,self.height)
        font(self.fontName)
        fontSize(self.fontSize)
        textMode(CORNER)
        textAlign(self.textAlign)
        textWrapWidth(self.width)
        fill(self.textColor)
        text(self.value,self.pos.x+5,self.pos.y)
    popStyle()
end

function TextItem:touched(touch)
    -- Codea does not automatically call this method
    if self.selected then
        self.selected=false
    else
        self.selected=true
    end
end

--Main
-- Use this function to perform your initial setup
function setup()
    print("Hello World!")
    --displayMode(FULLSCREEN)
    
    listtxt = ListScroll(vec2(50,100),300,600)
    vocab = {"cat","bird","desk","pen","car","dog","rubber","ruler","book","school","bike"}
    for i=1,#vocab do
        itm = TextItem(i,vocab[i])
        listtxt:add(itm)
    end
    
    cols = {color(255,0,0,255),color(0,0,0,255),color(0,255,0,255),color(0,0,255,255),
            color(255,255,255,255),color(255,255,0,255),color(255,0,255,255),color(0,255,255,255)}
    listcolor = ListScroll(vec2(400,100),300,600)
    for i=1,#cols do
        col = ColorItem(i,cols[i],300,50)
        listcolor:add(col)
    end 
    
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(0)

    -- This sets the line thickness
    --strokeWidth(5)

    -- Do your drawing here
    listtxt:draw()
    listcolor:draw()
end

function touched(touch)
    listtxt:touched(touch)
    listcolor:touched(touch)
    --[[
    if touch.state==ENDED then
        print(listtxt:itemCount())
        print(listtxt:selectedValue())
    end 
    --]]
end

Cool. I’ll try it when I get a chance :slight_smile:

Great use of clip(), @sanit. I like the bounce scrolling too.

Hey, that’s nice. I really like it, @sanit.

I hadn’t written a line toward solving this issue, and now I don’t have to. Thanks!

@sanit, I like your ideas. I’ll test your example, quickly. =;

I am commenting just so I can refer back to this thread easily latter. B-)

EDIT:
Wow - I must be slow. I never noticed the Star on the right to bookmark threads.

May I have permission to use this for my Codea Style UI (Yes, I’m still working on it)

Please feel free to modify the code. One idea can create another idea. That is the way that sharing code works. I don’t think I can finish the full functionality of the list in the near future.



Here is the list of missing functionality for the ListScroll class as follows

  1. Swiping to the left for deleting an item (show a delete button)
  2. a Bug when swiping down through the outside of the list Scroll area
  3. Double clicking for editing the item (show keyboard)
  4. Smooth scrolling
  5. Add new item in the List
  6. Create a Base class called Item class. All of other item types should subclass from the Item class

The below code is another sample of ImageItem class

-- Image Item class
ImageItem = class()

function ImageItem:init(id,n,w,h)
    -- you can accept and set parameters here
    self.pos = vec2(0,0)
    self.id = id
    self.value = n
    self.selected = false
    self.selectedColor = color(0,0,0, 100)
    self.width = w
    self.height = h
end

function ImageItem:draw(x,y)
    -- Codea does not automatically call this method
    self.pos = vec2(x,y)
    pushStyle()
        if self.selected then
            noFill()
            strokeWidth(5)
            stroke(self.selectedColor)
            rect(self.pos.x,self.pos.y,self.width,self.height)
        end 
        spriteMode(CORNER)
        sprite(self.value,self.pos.x,self.pos.y,self.width,self.height)
    popStyle()
end

function ImageItem:touched(touch)
    -- Codea does not automatically call this method
    if self.selected then
        self.selected=false
    else
        self.selected=true
    end
end

-- Main class using all items such as Text, Color and Image class

-- Use this function to perform your initial setup
function setup()
    print("Hello World!")
    displayMode(FULLSCREEN)
    
    listtxt = ListScroll(vec2(50,100),300,600)
    --listtxt.multiSelected=true
    vocab = {"cat","bird","desk","pen","car","dog","rubber","ruler","book","school","bike"}
    for i=1,#vocab do
        itm = TextItem(i,vocab[i])
        listtxt:add(itm)
    end
    
    cols = {color(255,0,0,255),color(0,0,0,255),color(0,255,0,255),color(0,0,255,255),
            color(255,255,255,255),color(255,255,0,255),color(255,0,255,255),color(0,255,255,255)}
    listcolor = ListScroll(vec2(400,100),300,600)
    for i=1,#cols do
        col = ColorItem(i,cols[i],300,50)
        listcolor:add(col)
    end 
    
    imgs = {"Planet Cute:Character Boy","Planet Cute:Character Horn Girl",
             "Planet Cute:Character Princess Girl","Planet Cute:Character Cat Girl"}
 
    listimg = ListScroll (vec2(750,100),200,600)
    for i= 1,#imgs do
        img = ImageItem(i,imgs[i],200,200)
        listimg:add(img)
    end
    
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(0)

    -- This sets the line thickness
    --strokeWidth(5)

    -- Do your drawing here
    listtxt:draw()
    listimg:draw()
    listcolor:draw()
end

function touched(touch)
    listtxt:touched(touch)
    listimg:touched(touch)
    listcolor:touched(touch)
    --[[
    if touch.state==ENDED then
        print(listtxt:itemCount())
        print(listtxt:selectedValue())
    end 
    --]]
end

hi, I have a project similar to the List Scroll, and I get a chance to try it.
The missing functionality (from item1 to item4) works.(item5 - it can’t add a item to the table?
The modified code is below

--List Scroll class
ListScroll = class()

function ListScroll:init(pos,w,h)
    -- you can accept and set parameters here
    self.pos = pos
    self.width= w
    self.height= h
    self.itemHeight = 0
    self.borderColor = color(100,100,100,255)
    self.borderWidth = 2
    self.backColor = color(255,255,255,255)
    self.sepColor = color(100,100,100,255)
    self.rowColor1 =  color (255,255,255,255)
    self.rowColor2 = color(186, 186, 186, 100)    
    self.allowEdit = false
    self.allowDelete = true
    self.allowInsert = false
    self.multiSelected = false
    self.items = {}
    self.posY = self.pos.y+self.height
    self.prevState = nil
    self.selectedItem = nil
    
    self.scrollBackTarget = nil
    self.cap = {"Sure","Cancel"}
    self.msgMenu = {}
    for i=1,#self.cap do
        table.insert(self.msgMenu, TextItem(i, self.cap[i]))
        self.msgMenu[i].width = self.width*.5       
        self.msgMenu[i].pos.y = self.posY + self.msgMenu[i].height*.5
        self.msgMenu[i].pos.x = pos.x + self.width*.5*(i-1)
        --self.msgMenu[i].textColor = color(254, 255, 0, 255)
    end
    self.msgMenu["indicate"] = TextItem(#self.msgMenu+1, "indicate")
    self.msgMenu["indicate"].width = self.width      
    self.msgMenu["indicate"].pos.y = self.posY - self.height - self.msgMenu[1].height
    self.msgMenu["indicate"].pos.x = pos.x    
end

function ListScroll:add(i)
    self.itemHeight = i.height
    i.width = self.width
    table.insert(self.items,i)
end

function ListScroll:remove(i)
    self.items[i] = self.items[#self.items] 
    self.items[#self.items] = nil
end

function ListScroll:selectedItem()
    return self.selectedItem
end

function ListScroll:selectedValue()
    if self.selectedItem==nil then 
        return nil 
    end 
    return self.items[self.selectedItem].value
end

function ListScroll:itemCount()
    return #self.items
end 

function ListScroll:draw()
    -- Codea does not automatically call this method
    pushStyle()
        strokeWidth(self.borderWidth)
        fill(self.backColor)
        stroke(self.borderColor)
        rect(self.pos.x,self.pos.y,self.width,self.height)

        clip(self.pos.x,self.pos.y,self.width,self.height)
        for i=1,#self.items do
            strokeWidth(1)
            if i%2==0 then
                fill(self.rowColor1)
            else
                fill(self.rowColor2)
            end 
            self.items[i]:draw(self.pos.x,self.posY-i*self.itemHeight)
        end 
        noClip()
        
        self:ShowSureBtn()
        self:ScrollSmooth()
        
    popStyle()
end

function ListScroll:touched(touch)
    if touch.tapCount == 1 then hideKeyboard() end
    if (not isTouched(self.pos, self.width, self.height) 
        or self.scrollBackTarget or self.activedID) then 
        if self.prevState==nil  then
            return 
        end
    end 

    local i = math.ceil((self.posY-touch.y)/self.itemHeight)    
    
    if touch.state==MOVING then
        self.prevState=MOVING
        self.posY = self.posY + touch.deltaY 

        if self.posY>=self.pos.y+self.height+self.itemHeight*(#self.items-1) then
            self.posY=self.pos.y+self.height+self.itemHeight*(#self.items-1)
        end
        
        if i<=#self.items then
            if touch.deltaX < -self.width/8 then
                self.activedID = i
                self.msgMenu["indicate"].value = "????" ..tostring(self.items[i].value).."?"
            end 
        end      
    elseif touch.state==ENDED then

        if self.prevState==MOVING then
            --if self.itemHeight*#self.items <= self.height then
                --self.posY = self.pos.y + self.height             
            if self.posY < self.pos.y+self.height then
                self.scrollBackTarget = self.pos.y+self.height 
            end
            
            self.prevState=nil

        else
            if i<=#self.items then
                self.selectedItem = i
                self.items[i]:touched(touch)
                if not self.multiSelected then
                    for r=1,#self.items do
                        if r ~= i then
                            self.items[r].selected=false
                        end 
                    end 
                end 
                if touch.tapCount > 1 then showKeyboard() end
            else
                self.selectedItem = nil
            end 
        end 
    end
end





function ListScroll:ScrollSmooth()
    if self.scrollBackTarget then 
        self.posY = self.posY + 7
        if self.posY >= self.scrollBackTarget then
            self.posY = self.scrollBackTarget 
            self.scrollBackTarget  = nil
        end
    end
end
        
function ListScroll:ShowSureBtn()
    if self.activedID then
        for k,item in pairs(self.msgMenu) do            
            self:HndlBtn(item) 
            fill(239, 97, 53, 255)               
            item:draw(item.pos.x,item.pos.y)
        end
        
    end
end  

function ListScroll:HndlBtn(btn)
    if isTouched(btn.pos, btn.width, btn.height ) then
        if btn.value == "Sure" then
            self:remove(self.activedID)
        end
        self.activedID = nil 
    end                    
end

function isTouched(pos, width, height )
    return CurrentTouch.x>pos.x and CurrentTouch.x<pos.x+width 
        and CurrentTouch.y>pos.y and CurrentTouch.y<pos.y+height
end