Help a newbie with buttons and understanding touches?

Hello all, I’m rather new to Codea and Lua and haven’t dabbled in programming for over a decade and am learning the ropes (or trying, anyway). Working my way through various tutorials and thinking up little exercises to try and test out skills, one runs into a paucity of input methods for user input as things are, so I said to myself: “let’s make a button!” For easy rapid input for choosing between options. I realize many have come before me and made button classes, but I wish to learn how to do this myself (conceptually speaking) and the buttons that others make are rather advanced for my current level. I have banged my head against this rather hard, and would love to have explained to me how people approach this problem that I’ve found. My problem is that the currentTouch variable stays in the ENDED state at its last touched location and I can’t figure out a good way to handle this. I’ve written a bit of code to demonstrate (that is obviously noob stuff - I’m a noob! It should certainly belong in a class also, but am just demonstrating).

-- buttons experiment

-- Use this function to perform your initial setup
function setup()
    button = {}
    button.x = WIDTH/2
    button.y = HEIGHT/2
    button.diameter = 200
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)
    font("AmericanTypewriter")
    fontSize(40)
    -- Do your drawing here
    fill(88, 89, 108, 255)
    ellipse(button.x, button.y, button.diameter)
    fill(203, 78, 78, 255)
    text("whatev", WIDTH/2, HEIGHT/2)
    if IsPressed(button) then 
        print("hit") 
    end
end

function IsPressed(anyButton)
    local left = anyButton.x - anyButton.diameter/2
    local right = anyButton.x + anyButton.diameter/2
    local bottom = anyButton.y - anyButton.diameter/2
    local top = anyButton.y + anyButton.diameter/2
    
    if CurrentTouch.x > left and CurrentTouch.x < right and 
    CurrentTouch.y > bottom and CurrentTouch.y < top
    and CurrentTouch.state == ENDED
        then
        return true
        else return false
    end
end

Again, sorry for noobishness of this, just want to know a reasonably elegant way to handle it. I saw one person’s class that checked for the length of time that had passed in the ENDED state and I mostly got it, but just kind of wanted to see if that’s the way people really do this. Also, I apologize that there are ample examples included with the (rather complicated) codea programs like the sounds one, I just don’t fully grok that one either. Thanks!

Welcome on the forum ! This is an example of how you can handle touch :

-- buttons experiment

-- Use this function to perform your initial setup
function setup()
    button = {}
    button.x = WIDTH/2
    button.y = HEIGHT/2
    button.diameter = 200
    button.touched = function(self, touch)
        if vec2(touch.x, touch.y):dist(vec2(self.x, self.y)) < self.diameter*.5 then
            if touch.state == BEGAN then
                print("touch BEGAN")
            elseif touch.state == MOVING then
                print("touch MOVING")
            elseif touch.state == ENDED then
                print("touch ENDED")
            end
        end
    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)
    font("AmericanTypewriter")
    fontSize(40)
    -- Do your drawing here
    fill(88, 89, 108, 255)
    ellipse(button.x, button.y, button.diameter)
    fill(203, 78, 78, 255)
    text("whatev", WIDTH/2, HEIGHT/2)
end

-- this function is called when you touch the screen
function touched(touch)
    button:touched(touch) -- : is a sugar syntax for button.touched(button,touch)
end

Here’s a really crummy (but working) button class I made. It isn’t very good, or very well made, actually it really doesn’t have much going for it at all :p. That said, it IS really simple and doesn’t use any fancy techniques so it should be easy to understand for beginners (as I myself am a beginner)
You need to set up the button in setup, call button:draw() in the main class’s draw function, and call button:touched(touch) in the main class’s touched function (also, after calling button:touched(touch) you need to do button:callafterpressed()). I’m aware there are a lot of variables in the init, I did that to make every aspect easier to mess around with. I hope this helps

Button = class()

function Button:init(circ, width, height, xloc, yloc, namestring, namecolour, colour, strokecolour, strokewidth, timedbool, timelen, togglebool, defaultinvis, defaultoff, istag, fontsize)
    self.circle = circ
    if circ == true then
        self.dia = width
        self.rad = width/2
    else
        self.wid = width
        self.hei = height
    end
    self.x = xloc
    self.y = yloc
    self.loc = vec2(self.x,self.y)
    self.name = namestring
    self.namecol = namecolour
    self.col = colour
    self.strokewid = strokewidth
    self.strokecol = strokecolour
    self.timed = timedbool
    if self.timed then
        self.time = timelen
    end
    self.toggle = togglebool
    if defaultoff then
        self.on = false
    else
        self.on = true
    end
    if defaultinvis then
        self.visible = false
    else
        self.visible = true
    end
    self.timer = false
    self.elapsed = 0
    self.pressed = false
    self.originid = nil
    self.halfpressed = false
    self.pressable = true
    self.tag = istag
    self.fsize = fontsize
end

function Button:draw()
    fontSize(self.fsize)
    textMode(CENTER)
    ellipseMode(CORNER)
    if self.visible then
        fill(self.col)
        strokeWidth(self.strokewid)
        stroke(self.strokecol)
        if self.circle then
            ellipse(self.x,self.y,self.dia)
            fill(self.namecol)
            text(self.name, self.x+self.rad,self.y+self.rad)
        else
            rect(self.x,self.y,self.wid,self.hei)
            fill(self.namecol)
            text(self.name, self.x+(self.wid/2), self.y+(self.hei/2))
        end
    end
    if self.halfpressed and self.on and self.visible then
        fill(0, 0, 0, 120)
        strokeWidth(0)
        if self.circle then
            ellipse(self.x,self.y,self.dia)
        else
            rect(self.x,self.y,self.wid,self.hei)
        end
    end
    if not self.on or not self.pressable and self.visible then
        fill(0, 0, 0, 120)
        strokeWidth(0)
        if self.circle then
            ellipse(self.x,self.y,self.dia)
        else
            rect(self.x,self.y,self.wid,self.hei)
        end
    end
    if self.timer then
        self:processTimer()
        lineCapMode(PROJECT)
        local vlorp = WIDTH-(((ElapsedTime-self.elapsed)/self.time)*self.wid)
        stroke(255,0,0,255)
        strokeWidth(5)
        if vlorp > self.x+self.wid-5 then
            vlorp= self.x+self.wid-5
        elseif vlorp < self.x+5 then
            vlorp = self.x+5
        end
        if self.timer then
        line(self.x+5, self.y+5,vlorp,self.y+5)
        end
        lineCapMode(ROUND)
    end
end

function Button:ispressed(touch)
    if not self.tag then
        if self.visible and self.pressable then
            t = vec2(touch.x, touch.y)
            if touch.state == BEGAN and self.originid == nil then
                if self.circle then
                    if (self.loc + vec2(self.rad,self.rad)):dist(t) < self.rad then
                        self.originid = touch.id
                        sound("Game Sounds One:Dropzone", .5)
                        self.halfpressed = true
                    end
                else
                    if t.x > self.x and t.x < self.x+self.wid and t.y > self.y and t.y < self.y+self.hei then
                        self.originid = touch.id
                        sound("Game Sounds One:Dropzone", .5)
                        self.halfpressed = true
                    end
                end
            elseif touch.state == ENDED and touch.id == self.originid then
                if self.circle then
                    if (self.loc + vec2(self.rad,self.rad)):dist(t) < self.rad then
                        self.pressed = true
                        sound("Game Sounds One:Menu Select",1)
                        self.halfpressed = false
                    end
                else
                    if t.x > self.x and t.x < self.x+self.wid and t.y > self.y and t.y < self.y+self.hei then
                        self.pressed = true
                        sound("Game Sounds One:Menu Select",1)
                        self.halfpressed = false
                    end
                end
                self.halfpressed = false
                self.originid = nil
            else return end
            
            if self.pressed then
                self:processBehaviour()
            end
        end
        return self.pressed
    end
end

function Button:processTimer()
    if ElapsedTime - self.elapsed > self.time then
        self.timer = false
        if not self.toggle then
            self.on = true
        elseif self.toggle then
            self.pressable = true
        end
    end
end

function Button:togglevisible()
    if self.visible then self.visible = false else self.visible = true end
end

function Button:processBehaviour()
    if self.toggle and self.pressable then
        if not self.on then
            self.on = true
        elseif self.on then
            self.on = false
        end
    end
    if self.timed and self.toggle and self.pressable then
        self.elapsed = ElapsedTime
        self.pressable = false
        self.timer = true
    end
    if self.timed and not self.toggle and self.on then
        self.elapsed = ElapsedTime
        self.on = false
        self.timer = true
    elseif not self.toggle and not self.on then
        self.pressed = false
    end
end

function Button:callafterpressed()
    self.pressed = false
end

Thank you both for your replies! I’m working at present and intermittently trying to parse your suggestions (my day job does not involve programming but does provide the occasional 10 minute burst of downtime). I’ll meditate upon your approaches and report back on progress when I have assimilated (might be a day or two). It looks as if (on my quick scan) you both avoid CurrentTouch, and perhaps that was my main error. Quick clarification question - the individual touch disappears when you stop touching, unlike CurrentTouch? Seems that way. I note you both use the vec2:dist function instead of my rather more clumsy method, so thanks for showing me that (I’ll have to learn vec2 more!). Also I had never heard the term sugar syntax before, funny!

@Therighttoarmbears See this link where I show the use of buttons. Let me know if you end up with a blank screen when you return from a URL call after pressing “done” in the upper left corner of the URL screen. That might be a Codea bug with url’s returns.


http://codea.io/talk/discussion/4664/buttons-on-multiple-screens/p1

CurrentTouch, when you stop touching, will list itself as ended. If you instead track individual touches with touch.id then you can find when the touch of the id you’ve saved is ended then set the saved id to nil. This ensures that multiple touches don’t interfere and you have four states for your touch (nil being the fourth, a very helpful state to have)