Howdy, Stranger!

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

Button class (Be excited!)

edited August 2012 in General Posts: 371

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
        _touched = touched
        touched = function (touch)
            for k, v in pairs(_buttons) do
    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.index = #_buttons + 1
    table.insert(_buttons, self.index, self)

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

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



  • Posts: 563

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

  • Posts: 371

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

  • Posts: 2,161

    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.

  • Posts: 371

    .@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 :( (Any suggestions for me?)
    Thank you soooooooo much. It means a lot to me :-)

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

  • Posts: 371

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

  • Posts: 371

    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
  • edited August 2012 Posts: 2,161

    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)
    function draw()
        -- all other stuff goes here
    function touched(touch)

    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()
        -- other stuff

    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()
        -- other stuff

    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!

  • Posts: 371

    Thank you for the detailed reply. :-) 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.

  • Posts: 2,161

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

  • SimeonSimeon Admin Mod
    edited August 2012 Posts: 5,613

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

  • Posts: 2,161

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

  • SimeonSimeon Admin Mod
    Posts: 5,613

    Yes. I edited my above post as you posted.

  • Posts: 371

    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.

Sign In or Register to comment.