Another button?

Hey guys, i’m fairly new here, and i made a button class, not to annoy you guys, but just to let you guys check it out, and maybe even getting some feedback? I love it when people tell me how i can do something better, so here’s my first codea class ever

Button = class()

function Button:init(x, y, w, h)
    self.x = x
    self.y = y
    self.w = w
    self.h = h
    
    --button color
    self.clr = color(255, 255, 255, 255)
    self.clrpress = color(35, 88, 220, 255)
    
    --button sprite-icons
    self.img = ''
    self.imgp = self.img
    
    --button shape
    self.ellipse = false
    
    --text color
    self.txtclr = color(0, 0, 0, 255)
    self.txtclrpress = self.txtclr
    
    --text
    self.lbl = ''
    
    --text font
    self.font = "ArialMT"
    self.fontSize = 17
    
    --border
    self.border = 2
    self.borderc = color(0, 0, 0, 255)
    
    --function on press
    self.func = nil
    
    --button pressed?
    self.pressed = false
    
    --set 'temporary' colors
    self.clrc = self.clr
    self.txtclrc = self.txtclr
    self.spritec = self.img

end

function Button:updateVars()
    self.clrc = self.clr
    self.txtclrc = self.txtclr
    self.spritec = self.img
end

function Button:draw()
    
    fill(self.clrc)
    font(self.font)
    fontSize(self.fontSize)
    
    stroke(self.borderc)
    strokeWidth(self.border)
    
    
    if self.ellipse == false then
        rect(self.x, self.y, self.w, self.h)
    else
        ellipse(self.x, self.y, self.w, self.h)
    end
    
    fill(self.txtclrc)
    text(self.lbl, self.x, self.y)
        
    sprite(self.spritec, self.x, self.y)
    
end

function Button:touched(Touch)
    
    if (self.ellipse and math.pow(Touch.x - self.x, 2)/math.pow(self.w/2, 2) + math.pow(Touch.y - self.y, 2)/math.pow(self.h/2, 2) <= 1) or (self.ellipse == false and Touch.x >= self.x - self.w/2 and Touch.x <= self.x + self.w/2 and Touch.y >= self.y - self.h/2 and Touch.y <= self.y + self.h/2) then
        
        if Touch.state == BEGAN then
            self.pressed = 1
            self.spritec = self.imgp
            self.clrc = self.clrpress
            self.txtclrc = self.txtclrpress
        end
        
        if Touch.state == ENDED and self.pressed == 1 then
            if self.func then
            _G[self.func]()
            end
        end
        
    end
    
    
    if Touch.state == ENDED then
        self.pressed = 0
        self.spritec = self.img
        self.clrc = self.clr
        self.txtclrc = self.txtclr
    end
    
end

```


and here's working example (Main class)

-- Control

-- Use this function to perform your initial setup
function setup()
    a = 0
    print("Welcome to Ultimate-Fail-Controls")
    
    ellipseMode(CENTER)
    rectMode(CENTER)
    
    but1 = Button(250, 50, 500, 75)
    but1.clrpress = color(36, 83, 243, 255)
    but1.clr = color(255, 255, 0, 255)
    but1.ellipse = true
    but1.lbl = 'THIS IS AN ELLIPSE SHAPED BUTTON'
    but1.img = "Cargo Bot:Claw Middle"
    but1.func = 'lol'
    
    but1:updateVars()
    
    but2 = Button(WIDTH/2, HEIGHT/2, 130, 35)
    but2.lbl = 'Another button'
    but2.txtclrpress = color(255, 255, 255, 255)
    
    
end

-- This function gets called once every frame
function draw()
    
    background(255, 0, 0, 255)
    
    but1:draw()
    
    but2:draw()
    
end

function touched(t)
    
    but1:touched(t)
    but2:touched(t)
    
end

function lol()
    a = a + 1
    print(a)
end

```


thx in advance guys

@stevon8ter I am currently working on my own button/menu class as well, and one thing that I came across that I also saw in your project was the touch recognition of the buttons. When you touch a button you will notice that if you also touch another part of the screen then the button you first touched will see that as a new touch state and you will lose the shading/touch of the first one. This can become an issue if your trying to map functions to buttons.

You can get past this by mapping each touch state to a table inside your button class, which I generally use as self.touches

You can have something like


function Button:touched(touch)   

    -- Touch Table Setup Values
    if self.touches == nil then self.touches = {} end
    if touch.state == BEGAN or touch.state == MOVING then 
     self.touches[touch.id] = touch

    elseif touch.state == ENDED then 

      local Delay = tween.delay(DeltaTime * 2)
      local End = tween.delay(0,function() self.touches[touch.id] = nil end) 
      tween.sequence(Delay,End) -- This delays the set to nil so draw() can catch the touch end

  end
end

 

Then after you get the touches for the individual buttons you can have an action set that is in a draw function either in the class or in your main (I try to keep everything within the class though to keep things ordered. Each buttons touch will correspond to the unique touch ID and not the global touch state beginning or ending


    -- Button Multiple Touch Recognition --
    if self.touches then for _,touchV in pairs(self.touches) do 
    
    if touchV.state == BEGAN and (Touched) then -- Determines if Button Touched
     Button.TouchID = touchV.id -- Sets Button ID so that it knows it's touched
     (The things the button does)
      
    elseif touchV.state == ENDED then -- Clears Touch status info 
     if Button.TouchID == touchV.id then (End Touch State for Button) end

end

This is sort of a rough showing of how you could get your buttons to have multiple touches. I’m working on a timer class right now as well so that delays won’t necessarily have to use tween.delay, but that will work to delay the table entry being set to nil. Let me know if that doesn’t really make sense, because I can expand on it if needed.

Good job so far though with the class so far though!

Edit : You can also get past the need for a delay if you put the touch part in the actual Button:Touched function. I’m working on a better way of showing this.

@stevon8ter The only problem I have is with the “if” statement in the function Button:touched(Touch). I had a hard time trying to copy the code because the “if” statement was so long. I changed it a little and broke it down into this format. Of course that’s my own preference, others my differ.


function Button:touched(Touch)
    if Touch.state == BEGAN then
        if  self.ellipse and
                (Touch.x - self.x)^2 / (self.w/2)^2 + 
                (Touch.y - self.y)^2 / (self.h/2)^2 <= 1
            or not self.ellipse and
                Touch.x >= self.x - self.w/2 and 
                Touch.x <= self.x + self.w/2 and 
                Touch.y >= self.y - self.h/2 and 
                Touch.y <= self.y + self.h/2 
            then 
                self.pressed = 1
                self.spritec = self.imgp
                self.clrc = self.clrpress
                self.txtclrc = self.txtclrpress
            end
 
        if Touch.state == ENDED and self.pressed == 1 then
            if self.func then
                _G[self.func]()
            end
        end 
    end
 
    if Touch.state == ENDED then
        self.pressed = 0
        self.spritec = self.img
        self.clrc = self.clr
        self.txtclrc = self.txtclr
    end 
end

Well thx guys
@Beckett2000, thx for the advice, i always keep waiting with going multi touch, but i’ll look into your code when i get home

Hey @Stevon8ter, nice to see you coding up a storm! I would like to add my 2 cents and offer a few suggestions… Feel free to use and not use any of them :slight_smile:

  1. Defining a Button:
    I am not a big fan of specifying the multiple variables for an object over separate lines of code, I find I always manage to forget something or mistype some variable. Personally, I prefer defining all variables in the initialisation of an object (e.g. obj = Object(x, y, w, h, text, func, color)) and allowing some variety using if/elseif statements and self.var = var or 10 to specify default variables in :init

  2. Touching, Touching, and more Touching:
    Your touch logic is fine, but a bit confusing to read. Usually spacing out a function can make it better for you and for others. For checking if a rectangle is touched and you use centre (center) co-ordinates then a very nifty way to do it is if math.abs(self.x - touch.x) < self.w / 2 and math.abs(self.y - touch.y) < self.h / 2 then.
    I have a method of retaining multi-touch capability without using a clunky touches table. I define a variable self.selected in :init to be nil. (This is irrelevant, but helps onlookers understand). In :touched I check if self.selected == touch.id then and then add my touch handling code, but then I add a sneaky little elseif touch.state == BEGAN at the bottom. In this elseif clause I set self.selected to touch.id if the button is touched.
    I also think that if you drag your finger off a button, the colour (color) should revert back to the regular colour and cancel the button being selected.

  3. Functions are Variables!
    Your method of defining a function as text is VERY unnecessary. A variable can be set to a function! but1.func = lol will work! Defining a function as text can limit your class, if I had a function in a table, I would not be able to use it with your class, as _G["table[function]"]() wouldn’t work.

  4. Push and Pop:
    Please, please, please use pushStyle and popStyle in your :draw. It can cause unexpected colours, fonts, and/or text for the user. Oh, and if my code uses rectMode(CORNER) it completely messes up your button, so be sure to include that call in your :draw

  5. Tables help!
    For storing your colours you should use a table, it’s very easy to implement and it can make your program run just that much faster. You can use a self.currentColor variable to access your table, and just swap around the self.currentColor to the current colour.

  6. Graphical Stuff:
    Just some slight fixes and adjustments:
    a. You draw your sprite above your text, can be a bit distracting.
    b. Sudden changes in colour are so 2012, tweens are all the rage now! It is just my preference to have smooth transitions, but its your choice, not mine. (P.S. @Simeon, we can’t tween colors unless we tween them individually! I think the problem is in tween.lua where it attempts to perform arithmetic on a userdata value, so this is a double shout-out for math with colors (scalars and addition/subtraction) and the ability to tween them!)

Thx for the comments

i’ll be sure to look into this

especially in the tween thing (i haven’t quite looked at all functions :o )

but for now, i need to start another project, school related, but i’m still gonna do it with codea as that gives me some more exercise