Touch events for mesh buttons

Hi,

I’m totally new to Codea and until now I only learned markup languages. So I’m trying to get into lua and followed the tutorials in the wiki (wish there’d be more!). I’m trying to create a UI first, as I think it would be the most easy part. I fully understand the mesh concept, but am stuck with TouchEvents. Right now, I would like to initiate an action when pushing a button (actually rectangle meshes with texture).

Here’s the code I’ve come up with:



supportedOrientations(LANDSCAPE_ANY)
-- Use this function to perform your initial setup
function setup()
    --Set the viewer to fullscreen
    displayMode( FULLSCREEN )
    
    c1 = color(31, 57, 84, 255)
    c2 = color(36, 115, 196, 255)
    
    by = 50
    ty = 100
    iw = 120
    i2w = 100
    ih = 30
    
    buttonframe = Frame(865, 50, WIDTH - 5, HEIGHT - 55)
    
    topbar = mesh()
    id1 = topbar:addRect(WIDTH/2,HEIGHT-ty/2, WIDTH,ty)
    topbar:setRectColor(id1, 218, 75, 75, 255)
    
    bottombar = mesh()
    bottombar.vertices = {vec2(0,0), vec2(WIDTH,0), vec2(0,by), vec2(0,by), vec2(WIDTH,by), vec2(WIDTH,0)}
    bottombar.colors = {c1, c1, c2, c2, c2, c1}
    
    menu_img = readImage("Cargo Bot:Menu Button")
    icone = mesh()
    icone.texture = menu_img
    icone:addRect(iw/2+20, by/2, iw, ih)
    icone:setRectTex(1,0,0,1,1)
    
    exit_img = readImage("Cargo Bot:Clear Button")
    ico_exit = mesh()
    ico_exit.texture = exit_img
    ico_exit:addRect(WIDTH - i2w/2 - 20, by/2, i2w, ih)
    ico_exit:setRectTex(1,0,0,1,1)
end

function touched(touch)
    sound(SOUND_HIT, 33733)
    if ico_exit:touched(touch) then
        close()
    end
end

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

    -- This sets the line thickness
    strokeWidth(5)
    
    -- Do your drawing here
    topbar:draw()
    bottombar:draw()
    icone:draw()
    ico_exit:draw()
end

Obviously, TouchEvents are not that easy! I understood that there are two methods discussed on this forum: physics with collide events and touch events depending on coordinates. The later seem to be not so resource friendly as I understood, if there are a lot of tappable shapes/buttons.

I would like to know what you think would be the best practice in my situation to handle touch events. And am I in the right direction for the UI design (using meshes, etc…).

Thanks a lot :slight_smile:

A mesh doesn’t have a touched function, so you have to implement something yourself for that. I would recommend creating a class to hold your button-mesh and implement the touched-function in that class.

Here is an example of a small framework for buttons I played with this weekend. Might be of help.
https://gist.github.com/tnlogy/5024809#file-scene-and-noise-L52

Sorry, was long to respond.

Thanks for pointing that out. I looked at your code, but I find it too hard to understand right now. Could you show me a exemple of a class with button-mesh and where/how to add a touch-function class to it?

Could be helpful :slight_smile: thx

.@Bitsandnumbers, I have refactored your original code to move the button into a class, then you can just instantiate your buttons, passing the possition and image info, and a callback function name.

Hope this helps



--# Main


supportedOrientations(LANDSCAPE_ANY)
-- Use this function to perform your initial setup
function setup()
    --Set the viewer to fullscreen
    displayMode( FULLSCREEN )

    c1 = color(31, 57, 84, 255)
    c2 = color(36, 115, 196, 255)

    by = 50
    ty = 100
    iw = 120
    i2w = 100
    ih = 30

    --buttonframe = frame(865, 50, WIDTH - 5, HEIGHT - 55)

    topbar = mesh()
    id1 = topbar:addRect(WIDTH/2,HEIGHT-ty/2, WIDTH,ty)
    topbar:setRectColor(id1, 218, 75, 75, 255)

    bottombar = mesh()
    bottombar.vertices = {vec2(0,0), vec2(WIDTH,0), vec2(0,by), vec2(0,by), vec2(WIDTH,by), vec2(WIDTH,0)}
    bottombar.colors = {c1, c1, c2, c2, c2, c1}

    buttons = {}
    --menu button
    buttons[#buttons + 1] = Button(iw/2+20, by/2, iw, ih, readImage("Cargo Bot:Menu Button"), menu)
    --exit button
    buttons[#buttons + 1] = Button(WIDTH - i2w/2-20, by/2, i2w, ih, readImage("Cargo Bot:Clear Button"), exit)
end

function menu()
    print("you touched menu")
end

function exit()
    close()
end

function touched(touch)
    sound(SOUND_HIT, 33733)
    for i=1,#buttons do
        buttons[i]:touched(touch)
    end
end

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

    -- This sets the line thickness
    strokeWidth(5)

    -- Do your drawing here
    topbar:draw()
    bottombar:draw()
    
    for i=1,#buttons do
        buttons[i]:draw()
    end
end
--# Button
Button = class()

function Button:init(x, y, width, height, buttonImage, callbackFunction)
    -- you can accept and set parameters here
    self.mesh = mesh()
    self.mesh.texture = buttonImage
    self.mesh:addRect(x,y,width,height)
    self.mesh:setRectTex(1,0,0,1,1)
    self.buttonCorner1 = vec2(x-width/2,y-height/2)
    self.buttonCorner2 = vec2(x+width/2, y+height/2)
    self.callbackFunction = callbackFunction
end

function Button:draw()
    self.mesh:draw()
end

function Button:touched(touch)
    if touch.state == BEGAN then
        if touch.x >= self.buttonCorner1.x and touch.x <= self.buttonCorner2.x and touch.y >= self.buttonCorner1.y and touch.y <= self.buttonCorner2.y then
            self.callbackFunction()
        end
    end    
end

Wow, thanks spacemonkey! It´s really clear like this!

So the callbackFunction calls the “menu” and “exit” functions initiated by the buttons tables ?

And could you elaborate a bit about how you implemented the i=x thing ? And why ?

Anyway, your help is very welcome :slight_smile:

The callbackFunction is exactly as you read it, by passing the function name to the button class, then pressing the button will execute that function back in your main class.

Not sure what you mean by the i=x thing, but the key elements are:

 buttons = {}

Creates a buttons collection which allows you to generically call all buttons in draw and touched.

 buttons[#buttons + 1] = Button(iw/2+20, by/2, iw, ih, readImage("Cargo Bot:Menu Button"), menu)

This adds a new button to the buttons collection, #buttons is the current length of the collection, so adding 1 means a new element. Then the Button(…) executes the Button:init(…) method to create the button.

In draw()

for i=1,#buttons do
        buttons[i]:draw()
    end

Says, iterate through my buttons collection and call draw on the button instances to render them to screen, this effectively means execute the Button:draw() class method which actually does the drawing. This could be rewritten as:

for k,v in ipairs(buttons) do
    v:draw()
end

Which is just a different way of iterating the table, where k would be the button number, and v is the value, so you can use the value directly.

Finally in touched(touch)

for i=1,#buttons do
        buttons[i]:touched(touch)
    end

calls the Button:touched(touch) method for each button (iterating same as in draw). This allows each button to have the opportunity to take an action if it’s clicked. In the Button:touched it is then just doing a if to establish if the touch was within the button itself.

An easier way to add objects to a table is with table.insert. It keeps track of the table size and adds the entry to the end of it.


    table.insert(buttons,Button(iw/2+20, by/2, iw, ih, readImage("Cargo Bot:Menu Button"), menu))
    table.insert(buttons,Button(WIDTH - i2w/2-20, by/2, i2w, ih, readImage("Cargo Bot:Clear Button"), exit))

@ spacemonkey: thanks for the explanation. I still have troubles woth collections/tables though. I wanted to create a variable for the buttonImage to get it´s width and height dynamically to pass it to different sections, but even though I tried a lot of things I didn’t succeed :confused:

Below is what I’ve done and which I think represents best what I try to achieve. I know what´s wrong (buttonImage does not return a correct value for the spriteSize function), but I couldn’t find a way to correct this, even when trying to create a table with my sprites because I don’t understand how to tell spriteSize which sprite to choose dynamically from the table that correspond to the current button.

@Dave1707: thanks, so this would insert datas to the “buttons” table and I could use

for k,v in ipairs(buttons) do
v:draw()
end

To draw the buttons ?

----------------------•

–# Main

buttons = {}
--menu button
buttons[#buttons + 1] = Button(cw/2+20, by/2, cw, ch, readImage("Cargo Bot:Menu Button"), menu)
--exit button
buttons[#buttons + 1] = Button(WIDTH - cw/2-20, by/2, cw, ch, readImage("Cargo Bot:Clear Button"), exit)

end

–# Button
Button = class()

function Button:init(x, y, cw, ch, buttonImage, callbackFunction)

self.mesh.texture = buttonImage
local cw, ch = spriteSize(buttonImage) --trying to get sprite size dynamically

end

I just kept the sections where I changed stuffs.

Ps : what do you to have your code displayed correctly in your posts? Mine are all messes up upon sending comments…

.@Bitsandnumbers . table.insert(buttons,data) is just an easy way to add data to the end of the table buttons. You don’t have to get the table size by using #buttons and then adding 1 to that. All that is done for you with insert.

.@Bitsandnumbers, you are almost there.

However, the reason SpriteSize doesn’t work is you are not using sprites, you’ve read the image into an “image” with readImage(“xxx”). So it’s even simpler, change your line to:

local cw,ch = buttonImage.width, buttonImage.height

As to formatting your code, put three tildes (~ ~ ~ without the spaces) before and after the code block.

[please remove, don’t know what happened]

@Dave1707: ok thanks. I’ll try that once I’m good with the two variables. It´s good to have both your solutions, since it made me better understand how yours work :slight_smile:

@spacemonkey: it did?t work. I still get “attempting to perform arithmetic on global cw (a nil value)”. Are you sure that I put it in the right place?

Bump :wink:

It looks like you don’t have cw and ch defined anywhere. I tried your program with what I think are all the changes you show. I defined cw and ch with values and the program seemed to work. However, I’m not sure I made the exact changes you did.

Thanks Dave1707!

I thought that cw and ch were defined with the ‘local’ line? Here is the full code:

--# Main


supportedOrientations(LANDSCAPE_ANY)
-- Use this function to perform your initial setup
function setup()
    --Set the viewer to fullscreen
    displayMode( FULLSCREEN_NO_BUTTONS )

    c1 = color(31, 57, 84, 255)
    c2 = color(36, 115, 196, 255)

    by = 50
    ty = 100
    iw = 120
    i2w = 100
    ih = 30
    
    --buttonframe = frame(865, 50, WIDTH - 5, HEIGHT - 55)

    topbar = mesh()
    id1 = topbar:addRect(WIDTH/2,HEIGHT-ty/2, WIDTH,ty)
    topbar:setRectColor(id1, 218, 75, 75, 255)

    bottombar = mesh()
    bottombar.vertices = {vec2(0,0), vec2(WIDTH,0), vec2(0,by), vec2(0,by), vec2(WIDTH,by), vec2(WIDTH,0)}
    bottombar.colors = {c1, c1, c2, c2, c2, c1}

    buttons = {}
    --menu button
    buttons[#buttons + 1] = Button(cw/2+20, by/2, cw, ch, readImage("Cargo Bot:Menu Button"), menu)
    --exit button
    buttons[#buttons + 1] = Button(WIDTH - cw/2-20, by/2, cw, ch, readImage("Cargo Bot:Clear Button"), exit)
end

function menu()
    print("you touched menu")
end

function exit()
    close()
end

function touched(touch)
    sound(SOUND_HIT, 33733)
    for i=1,#buttons do
        buttons[i]:touched(touch)
    end
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(100, 120, 160)

    -- This sets the line thickness
    strokeWidth(5)

    -- Do your drawing here
    topbar:draw()
    bottombar:draw()

    for i=1,#buttons do
        buttons[i]:draw()
    end
    
    font("Georgia")
    fill(255)
    fontSize(20)
    textWrapWidth(70)
    text("Hello World!", 500, 500)
end
--# Button
Button = class()

function Button:init(x, y, cw, ch, buttonImage, callbackFunction)
    -- you can accept and set parameters here
    self.mesh = mesh()
    self.mesh.texture = buttonImage
    local cw,ch = buttonImage.width, buttonImage.height
    self.mesh:addRect(x,y,cw,ch)
    self.mesh:setRectTex(1,0,0,1,1)
    self.buttonCorner1 = vec2(x-cw/2,y-ch/2)
    self.buttonCorner2 = vec2(x+cw/2, y+ch/2)
    self.callbackFunction = callbackFunction
end

function Button:draw()
    self.mesh:draw()
end

function Button:touched(touch)
    if touch.state == BEGAN then
        if touch.x >= self.buttonCorner1.x and touch.x <= self.buttonCorner2.x and touch.y >= self.buttonCorner1.y and touch.y <= self.buttonCorner2.y then
            self.callbackFunction()
        end
    end    
end

I’ve put the line in the Button:init section since I think it needs the buttonImage variable defined. But as you can see it doesn’t work… So maybe I didn’t put it in the right place, or maybe I’m not so close as spacemonkey seems to think ? :confused:

Add cw=200 and ch=40 like I show below. I don’t know what the values should be, that’s up to you, but I put 200 and 40 just for starters. Touching Menu doesn’t appear to do anything because it’s printing to the output pane that you’re not allowing to be shown, but it does work. If you want to see the menu button working, see menudraw in the code below and add them to your code.


    by = 50
    ty = 100
    iw = 120
    i2w = 100
    ih = 30
    cw=200
    ch=40


-- the next 2 sets of code is to show something on the screen
-- when the menu button is pressed

function menu()
    print("you touched menu")
    menudraw=not menudraw
end

-- this is at the end of the draw function

    font("Georgia")
    fill(255)
    fontSize(20)
    textWrapWidth(70)
    text("Hello World!", 500, 500)
    if menudraw then
        text("menu pressed",500,300)
    end

Thanks, but for cw and ch, your solution doesn’t get the value from the mesh buttons sizes. Or do I need to set a dummy value so that cw and ch are not empty in the first place, and then the ‘local’ line in the Button:init section should work???

You need to change cw and ch in the lines below and set them to some value. Then remove ch=200 and cw=40 where I showed you before.


    buttons[#buttons + 1] = Button(cw/2+20, by/2, cw, ch, readImage("Cargo Bot:Menu Button"), menu)
    --exit button
    buttons[#buttons + 1] = Button(WIDTH - cw/2-20, by/2, cw, ch, readImage("Cargo Bot:Clear Button"), exit)

I think I know what you’re after. See the code below where I get the cw and ch from the spriteSize.


    buttons = {}
    --menu button

    cw,ch=spriteSize("Cargo Bot:Menu Button")

    buttons[#buttons + 1] = Button(200/2+20, by/2, 200, 40, readImage("Cargo Bot:Menu Button"), menu)

    --exit button

    cw,ch=spriteSize("Cargo Bot:Clear Button")

    buttons[#buttons + 1] = Button(WIDTH - 200/2-20, by/2, 200, 40, readImage("Cargo Bot:Clear Button"), exit)

Yes, that´s pretty much what I want but I wanted to simplify this so that I only have to add a new button and the size of it’s image is automatically parsed and returned to the cw and ch variables. Hence the cw, ch with buttonImage as a reference in my code.

But your exemple gives me an insight on how to hierarchise variables when the same variables are solicited multiple times :slight_smile: