Button class (Be excited!)

Hey all! (especially @Andrew_Stacey)
I have been thinking about this problem, and I see no other solution, except for my ugly code. I have made a button class that automatically hooks onto the touched function, so it doesn’t need to have a myButton:touched().

button = class()

_buttons = {}

function button:init(x, y, w, h, f)
    if _touched == nil then
        if touched == nil then
            touched = function () return end
        end
        _touched = touched
        touched = function (touch)
            for k, v in pairs(_buttons) do
                v:touched(touch)
            end
            _touched(touch)
        end
    end
    self.x, self.y, self.w, self.h = x, y, w, h
    self.f = f or function () return end
    self.touched = function (self, touch)
        if math.abs(touch.x - self.x) < self.w 
        and math.abs(touch.y - self.y) < self.h 
        and touch.state == ENDED then
            self.f()
        end
    end
    self.index = #_buttons + 1
    table.insert(_buttons, self.index, self)
end

function button:delete()
    _buttons[self.index] = nil
end

I do have a few problems though…

  1. The button:delete(). I think it’s messy, and I couldn’t think of another name for it. (P.S. I was looking at the __gc() metamethod, but I couldn’t figure it out.
  2. The _buttons table… Eugh
  3. (not really a problem, just an unfinished feature) I have it so it works by rectMode(RADIUS), I want to make it so it corrects itself by each mode

interesting approach - I haven’t had a chance to try it out but where does the button draw itself?

I haven’t made it visual yet, I was not sure what to do for that. Right now it is just a invisible button. I was thinking about it, and I like @Vega 's mesh approach, but honestly, I have no clue how it works. I was thinking about letting the user do that themselves, as when this is compatible with rectMode (+10 or less lines), you could do something like this

myButton = button()
rect(x, y, w, h)

But it would be quite easy to implement, I could just edit draw() to do it, but then what about the order of it? Or I could make it wipe every frame, so you would call button() in draw, and would that be memory intensive? Thanks for the comment @Reefwing. :slight_smile:

What’s wrong with having a container class that holds an arbitrary number of buttons (as an array)? Then the container can have addButton and deleteButton methods.

One problem with this approach is that it doen’t play nicely with other things that try to do the same. Without a central controller, who decides which thing gets the touch? It’s also hard for someone using your code to see exactly ehat’s going on with the touched function.

OK, I don’t ask why you want to highjack the touched function (you shouldn’t), I just take it as an academic quest.

Why don’t you like the _buttons table? You need to keep track of your buttons. Or is it that it is a global table? Then write “button.buttons = {}” instead.

You made a self.touched closure, but nothing is there that needs to be enclosed. Make it easy for other readers and simply introduce a “function button:touched(touch)”.

“math.abs(touch.x - self.x) < self.w” makes the button twice as wide for touch sensitivity, you include plus width and minus width, sloppily said. Am I right?

The __gc metamethod only works for userdata in Lua5.1, you need Lua5.2 for a table destructor.

Ah, and yes, what Andrew said while I’m writing this.

.@Codeslinger, THANK YOU FOR YOUR COMMENT!!! =D> ^:)^ In the order of your questions my answers are:
I “hijack” the touched function because I can’t stand putting in myButton:touched for EVERY LAST BUTTON! I find it annoying and unnecessary.
You are a genius! I should have thought of that X( (Am I correct in thinking that all the instances could access the table by self.buttons? Or is that stretching it?)
No idea why I did that, I don’t normally, and I try to keep my code as neat and readable as possible.
I am using those variables from the perspective of rectMode(RADIUS), I will add in code to convert it from the current rectMode later
Awwww :frowning: (Any suggestions for me?)
Thank you soooooooo much. It means a lot to me :slight_smile:

When you write “button = class()” then button is a just table and you can add things like class variables like “buttons” to it. However, make sure to address it by always naming it “button.buttons”, not “self.buttons”. Every instance of button has its private copy of the buttons variable that you must ignore then.

To keep things flexible you should however start with a plain button class and write add-ons or controllers like Andrew suggested.

What about


b1 = button()
addTouchable(b1)
. . .
deleteTouchable(b1)

If you like the OO way, then perhaps this:


tc = TouchableController()

b1 = button()
tc.add(b1)

You have to write a class “TouchableController” that accepts any class that comes with a “touched” method. Hey, that’s an advantage by itself. OK, you have to write one extra line after each instantiation of button, but it’s worth the effort.

After you’ve done all this, read Andrew’s Touch Tutorial.

After you’ve done all this, read Andrew’s other library components.

After you’ve done all this, read the Cargo Bot source code.

After you’ve done all this, you’ll never write things like your first idea again.

.@Codeslinger, I still am not sure about the one extra line, could I not implement that with a one line call. I am thinking something like a function you put in draw() and it acts like a touchable rect… Maybe even draws itself! (Any tips on how to implement that (I have made a roundRect class, no meshes))

Okay, here is my code (I am including my entire library that I am working on minus my roundRect code, as that has been deleted. X()



--# button
-- button class

button = class()

button.buttons = {}

function button:init(x, y, w, h, f)
    if not button.Draw then
        button.Draw = draw or function () return end
        draw = function ()
            button.buttons = {}
            button.Draw()
        end
    end
    if not button.Touched then
        button.Touched = touched or function () return end
        touched = function (touch)
            button.Touched(touch)
            for k, v in pairs(button.buttons) do
                v:touched(touch)
            end
        end
    end
    if rectMode() == CORNER then
        w = w / 2
        h = h / 2
        x = x + w
        y = y + h
    elseif rectMode() == CORNERS then
        w = (w - x) / 2
        h = (h - y) / 2
        x = x + w
        y = y + h
    elseif rectMode() == CENTER then
        w = w / 2
        h = h / 2
    end
    self.x = x
    self.y = y
    self.w = w
    self.h = h
    self.f = f or function () return end
    table.insert(button.buttons, self)
end

function button:touched(touch)
    if math.abs(touch.x - self.x) <= self.w 
    and math.abs(touch.y - self.y) <= self.h 
    and touch.state == ENDED then
        self.f()
    end  
end

--# ClipUpgrade
-- New clip function, affected by translate() and scale()

_clip = clip -- Save the old clip()
function clip(x, y, w, h) -- Redefine the clip() function, erasing the old one
    if x ~= nil then -- if there is a value passed to the first parameter (clip(val, ...))
        local m = modelMatrix() -- Thanks to @gunnar_z for this solution to my problems
        
        -- m[1] is the x value of scale
        -- m[6] is the y value of scale
        -- m[13] is the x value of transform
        -- m[14] is the y value of transform
        
        x = x * m[1] + m[13]
        y = y * m[6] + m[14]
        w = w * m[1]
        h = h * m[6]
        
        _clip(x, y, w, h) -- Clip with the new parameters
    else
        _clip() -- Else, reset the clipping bounds
    end
end
--# fps
_FPS = {}

function fps(x, y, r)
    table.insert(_FPS, 1 / DeltaTime)
    local f = 0
    if _FPS[61] then
        table.remove(_FPS,  1)
    end
    for k, v in ipairs(_FPS) do
        f = f + v
    end
    f = f / 60
    local r = r or 75
    pushMatrix()
        pushStyle()
            translate(x, y)
            ellipseMode(RADIUS)
            lineCapMode(SQUARE)
            clip(-r, -r / 2, r * 2, r * 1.5)
            fill(255, 255, 255, 255)
            stroke(0, 0, 0, 255)
            strokeWidth(r / 10)
            ellipse(0, 0, r)
            
            for i = 0, 16 do
                pushMatrix()
                    rotate(210 - 15 * i)
                    line(r * .8, 0, r * .9, 0)
                popMatrix()
            end
            pushMatrix()
                translate(0, (-r) * .475)
                line(-r * .85, 0, r * .85, 0)
            popMatrix()
            rotate(210 - f * 4)
            stroke(255, 0, 0, 255)
            line(0, 0, r * .85, 0)
            clip()
        popStyle()
    popMatrix()
end
--# Main
-- Module by Jordan Arenstein

function setup()
    watch("bool")
    bool = false
    rectMode(CORNER)
    r = 50
end

function draw()
    background(bool and 255 or 0,not bool and 255 or 0, 255, 255)
    button(WIDTH / 2, HEIGHT / 2, 200, 100, function ()
        bool = not bool
        r = r + 1
    end)
    rect(WIDTH / 2, HEIGHT / 2, 200, 100)
    fps(100, 100, r)
end

function touched(touch)
    
end

I “hijack” the touched function because I can’t stand putting in myButton:touched for EVERY LAST BUTTON!

So put it in just for the controller. And if you make it a general controller, you only need to put it in once for buttons, menus, number wheels, colour pickers, keyboads, keypads, sliders, …

My “basic” code looks like this:

function setup()
    touches = Touches()
    ui = UI(touches)
end

function draw()
    touches:draw()
    -- all other stuff goes here
    ui:draw()
end

function touched(touch)
    touches:addTouch(touch)
end
```


Now, you could argue that I could modify the `draw` function to include those extra lines automatically, but I'd rather not actually.  I tend to forget that I do things like that and go hunting for bugs in the wrong place.  I don't see that there's any difference in functionality by hacking into the functions like that either.

Here's where hacking could get complicated: I've found that it's a good idea to do any recording changes right at the end of the draw function.  So I have defined a hook, `AtEndOfDraw()` which gets called ... surprise, surprise ... at the end of the draw.  When I use this, my `draw()` function looks like:

function draw()
    touches:draw()
    -- other stuff
    ui:draw()
    AtEndOfDraw()
end
```


Now, the recording stuff is handled in the UI usually (there are menu buttons for it).  So if I were hacking the draw function, the sensible thing to do would be to have the UI code hack the `draw` function to add the `AtEndOfDraw()` code in.  But sometimes I also use a debugging module and that works by putting a transparent sheet over the top of *everything* on which it can write messages.  Clearly, this needs to be done last.  But last of the *drawing* stuff, not last of everything.  So when I use this, I need to do:

function draw()
    touches:draw()
    -- other stuff
    ui:draw()
    debug:draw()
    AtEndOfDraw()
end
```


This would be complicated to implement if I were hacking the function, but is simplicity itself if I do it explicitly.

So the moral is that hacking the functions sacrifices flexibility and hides what you're doing from the most important person: yourself.  There's always ways to collapse stuff to make sure that you only ever see the important bits, but you shouldn't hide it completely unless you know that you *never* want to know it is there!

Thank you for the detailed reply. :slight_smile: and I know this might sting a bit, but please don’t use the <pre lang="lua"> format for usable code, it is harder to copy, amd when you do, you have to erase all the number lines.

Don’t spoil my fun! I just learnt about that shiny new toy!

I just tried it and it copied just fine (but that’s on a laptop with fine precision - I guess you’re talking about the iPad).

@Andrew I played with adding a syntax highlighter to the forums, but found that it made copying and pasting impossible on iPad, so I never announced it. Feel free to use it — especially for code that is likely not going to be copied/pasted.

EDIT: Hmm, I just tried disabling the line gutter (not sure why I didn’t try this before), and copying+pasting seems possible on iPad. It’s not quite as easy as a plain pre-block, but it’s much better than getting line numbers.

@Simeon: Are you playing around with adjusting the highlighting? The line numbers have disappeared.

Yes. I edited my above post as you posted.

Sorry to re-open this discussion (actually, I’m not). There have been a few queries as to why I would want to “hijack” the certain functions, my answer is because I have been looking at the physics library, it is lightweight, fast, and requires the absolute minimum amount of code. One instantiation, and one line to delete it. That simple. I think it would be a hassle free way to do it.