Callback vs. Interface

I mostly developed in C++ in the past, so I try to use the concepts from there in my Lua code. Of course, this could be the wrong way and there are better ways to do this. So I’ll show you how I use an interface as callback for my buttons and maybe somebody could tell me if there is a better way to do this :slight_smile:

I use a simple class for a Button, which gets the image, size and position together with an interface class bIface in the init function:

Button = class()

function Button:init(bIface,id,img,x,y,sizex,sizey)
    self.pos = vec2(x,y)
    self.img = img
    self.selected = false
    self.w = sizex
    self.h = sizey
    self.id = id
    self.bIface = bIface
end

function Button:draw()
    w = self.w
    h = self.h
    if self.selected == true then
        w = w * 1.2
        h = h * 1.2
    end
    sprite(self.img,self.pos.x,self.pos.y,w,h)
end

function Button:touched(touch)
    if touch.state == BEGAN then    
        if math.abs(self.pos.x - touch.x) <= (self.w/2)
        and math.abs(self.pos.y - touch.y) <= (self.h/2) then
            self.selected = true
        else
            self.selected = false
        end
    elseif touch.state == MOVING and self.selected then    
        if math.abs(self.pos.x - touch.x) > (self.w/2)
        or math.abs(self.pos.y - touch.y) > (self.h/2) then
            self.selected = false
        end
    elseif touch.state == ENDED then
        if self.selected == true then
            self.bIface:buttonPressed(self.id)
        end
        self.selected = false
    end
end

In the file, where I use the button, I define the interface class, which has to implement the buttonPressed() function:

MainMenuButtonInterface = class()

function MainMenuButtonInterface:init()
end

function MainMenuButtonInterface:buttonPressed(id)
    if id == 1 then
      -- do start code
    elseif id == 2 then
      -- do quit code
    end
end

And I pass the interface when creating buttons:

self.bIface = MainMenuButtonInterface()

buttonStart = Button(self.bIface, 1, startImg, 200, 300, 70, 70)
buttonQuit = Button(self.bIface, 2, quitImg, 200,400, 70, 70)

The interface can handle multiple buttons, as the buttonPressed callback uses an id. I used this concept, because I can handle multiple callback functions by passing only one interface. E.g. I use this for a level selector class, which creates 20 buttons. I have a getImage() function in the interface, which calculates the image for each button and a isSelectable() function to check if the level is locked. Without an interface I would have to pass all three function pointers.

Well, thats how I would do it in C++ and how it works for me in Lua, but I have seen that there are things which can be solved much easier in Lua like in C++ (e.g. I have seen a tween code which was very simple). So my question is, does this make sense to work with such interfaces?

Making your button class better:

One: Extract something like “function Button:contains(x, y)” to make Button:touched more readable.

Two: The button shouldn’t hold the name of class that offers a buttonPressed(id) function. Way too specialized, you wouldn’t do it in C++ either (except maybe if the member function is static or you use boost::men_fn). Better pass a function (closure) that can be called by the button. E.g.:


function Button:init(...)
    ...
    self.action = nil
end

function Button:touched(...)
    ...
    if self.action then self.action() end
end

buttonStart.action = function() ... do what needs to be done here ... end

See Codea example “Sounds Plus” for a nice example.

One other observation, if you are going to stick with using id’s for each button then instead of a long string of ugly if-elseif-end code, you can use a table to emulate switch functionality. To do this, in your setup or init define your action table, which will look something like:

action = {
    [case1] = function(x) doStuffWithX end,
    [case2] = function(x) doOtherStuff end,
    [case3] = function(x) doMoreStuff end
}

In your situation, case1 = 1, case2 = 2 and case3 = 3, etc. Then the usage would be:

action[id](x)

where x is a parameter you are passing to the associated function. You can pass more than one parameter if you want. In addition to looking better this should also be faster than a linear search using if-elseif-end.

Thanks for your comments. It will help to improve :slight_smile:

@Codeslinger: The button does not hold the name of the class, it just gets an interface class. Same like only passing a function pointer like in your example, but it can keep many function pointers in the class then. It gets handy, when you have more functions to call and not only the pressed function.