Howdy, Stranger!

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

Touch events for mesh buttons

edited February 2013 in Questions Posts: 11

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 :)

Tagged:

Comments

  • Posts: 502

    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 :) thx

  • Posts: 454

    .@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 :)

  • Posts: 454

    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.

  • dave1707dave1707 Mod
    Posts: 7,683

    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))
  • edited March 2013 Posts: 11

    @ 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 :/

    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...

  • edited March 2013 Posts: 11

    [please remove, don't know what happened]

  • dave1707dave1707 Mod
    Posts: 7,683

    .@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.

  • Posts: 454

    .@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.

  • @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 :)

    @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 ;)

  • dave1707dave1707 Mod
    Posts: 7,683

    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 ? :/

  • dave1707dave1707 Mod
    edited March 2013 Posts: 7,683

    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???

  • dave1707dave1707 Mod
    edited March 2013 Posts: 7,683

    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)
  • dave1707dave1707 Mod
    edited March 2013 Posts: 7,683

    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 :)

  • dave1707dave1707 Mod
    Posts: 7,683

    Then you could do something like what I show below. Where I have 200,50 in buttons, you would change that to whatever x,y value you need. You can do any additional calculations in the Button:init function that you need.


    buttons[#buttons+1]=Button(200,50,readImage("Cargo Bot:Menu Button"),menu) function Button:init(x, y, buttonImage, callbackFunction)     -- you can accept and set parameters here     self.mesh = mesh()     self.mesh.texture = buttonImage     cw,ch=spriteSize(buttonImage)     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
Sign In or Register to comment.