Friday, June 22, 2012

Tutorial 3 - A Simple Button Class (Updated 10/1/16)

Webcomic Courtesy of Ethanol & Entropy

3.0 Creating a Button in Codea

A control that you will use frequently is a button. Now that we have the foundations sorted we can punch out a simple button quite easily. Mostly because a sample button class is provided with Codea. There is a bit of cutting and pasting involved but we will go through each line of the Button() class so you can understand what is what.

Whip back to our old friend the Sounds Plus example project and copy all of the contents of the Button class tab. Back in your Menu project, create a new tab, call the Class Button and paste the code you just copied. Refer to Tutorial 2 if you have forgotten the exact steps.

Your Menu project should now contain 3 tabs: Main, RoundRect and Button. We will have a look at the Button class first. I have inserted a bunch of additional comments (in blue) so you can understand what is happening.

3.1 The Button Class

Button = class()

-- [[ There are no classes in standard Lua however they are handy concepts so Codea includes a global function called class() which provides equivalent functionality. You can read more about Codea classes in the wiki ]]

function Button: init(displayName)

-- [[ The Init function gets called before setup(). This is where you define and initialise your class and its member variables. The class variables are fairly self explanatory but for completeness: displayName: Is the text displayed on your button. The button will scale up and down to fit the text. pos: Defines the x and y - coordinates of the button using a vector. size: Is a vector which contains the width and height of the button, which is set by the display name text, and is used to determine if a button has been hit.  action: Is the function that you want called when the button is tapped. color: Is the color of the button fill. ]]

    -- you can accept and set parameters here

    self.displayName = displayName
    self.pos = vec2(0,0)
    self.size = vec2(0,0)
    self.action = nil
    self.color = color(113, 66, 190, 255)


function Button:draw()

-- [[ Your main code needs to explicitly call this function to draw the button, it won't happen automatically. We will see how this works when we update the main() class. ]]

    -- Codea does not automatically call this method


-- [[ pushStyle() saves the current graphic styles like stroke, width, etc. You can then do your thing and call popStyle at the end to return to this state.]]


-- [[ fill is used initially to set the colour of the button, then the font type and size is set. You could change this in your implementation of the button class if you wish. Click here to see the available fonts. ]]
    -- use display name for size

    local w,h = textSize(self.displayName)
    w = w + 20
    h = h + 30
-- [[ As stated in the code, displayName is used to size the button and then we use the class we looked at in Tutorial 2 to draw a rounded rectangle. ]]

    roundRect(self.pos.x - w/2,
              self.pos.y - h/2,
    self.size = vec2(w,h)

-- [[ Note that class variables are designated using the self keyword. e.g. self.size. The next block of code sets the colour of the button text and its position on the button. ]]
    fill(54, 65, 96, 255)
    fill(255, 255, 255, 255)
-- [[ Return the graphic style to what it was before you entered this function. This is considered polite behaviour for a function because it can be hard to track down if the style is being changed deep within some function and you don't want it to. ]]



function Button:hit(p)

-- [[ This function works out if the last touch (after you lift your finger) was on this button, using the size and pos variables. Returns true if it was and false if it wasn't.  The local keyword defines a local variable. Unlike global variables, local variables have their scope limited to the block where they are declared. A block is the body of a control structure, the body of a function, or a chunk (the file or string with the code where the variable is declared). ]]

    local l = self.pos.x - self.size.x/2
    local r = self.pos.x + self.size.x/2
    local t = self.pos.y + self.size.y/2
    local b = self.pos.y - self.size.y/2

    if p.x > l and p.x < r and
       p.y > b and p.y < t then
        return true
    return false

function Button:touched(touch)

    -- Codea does not automatically call this method

-- [[ As with the draw() function the touched function is also not called automatically by your code. If you don't call this then you won't know if someone has tapped your button. It reminds me of the old joke, "what do you call a boomerang that doesn't come back?" ..."A stick!" The test, if self.action checks whether you have defined a function to call when the button is tapped. If self.action is nil then nothing will happen.]]

    if touch.state == ENDED and
       self:hit(vec2(touch.x,touch.y)) then
        if self.action then

3.2 The Main Class

Now that you are an expert on the Button Class, we can have a look at what is required in your Main Class to instantiate and use a button. It is fairly simple.

-- Use this function to perform your initial setup

function setup()

    print("Button Test Project")
-- [[ Create a new button, it wont be visible until you draw it. The init of the button will also set the displayName. You can change this later if you wish by changing the string assigned to button.displayName. The action variable is assigned the function you want to call when the button is tapped. We haven't attempted to be too ambitious with this first attempt.]]
    button = Button("Press Me")
    button.action = function() buttonPressed() end

-- This function gets called once every frame

function draw()

    -- This sets a dark background color 

    background(40, 40, 50)

    -- Do your drawing here, drawButton is defined below.

function drawButton()

-- [[ Draw the button at some arbitrary spot on the screen and then call the buttons draw() function. You MUST include this step within the Main draw() function. ]]

    button.pos = vec2(400, HEIGHT/2)
    button: draw()


function buttonPressed()
-- [[ This is where the action happens. Whenever the button is tapped, this function will be called. You can call it whatever you want but it must match the function that you assign to the button.action variable. We aren't doing anything too exciting here but it should illustrate the point. ]]

    print("Button Pressed")

function touched(touch)

-- [[ Like the button draw() function this is another one that you MUST call for the button to work. It passes the touch detected in the main class to the button class to see if it needs to do anything with it. If the button detects a hit then the action function gets called. ]]
You can download a copy of the files from here.

3.3 An Alternative Approach

You now know how to implement a button and assign an event handler for when it gets tapped.

There are a number of other approaches that you can take to solve this problem. Over on the Codea forum Bri_G, Maxiking16 and Reldonas have all contributed sample code to help make your buttons look even sexier. 

3.4 Other Alternatives (Mesh or Sprites)

Vega has come up with a button class which uses meshes to generate the buttons. This class includes buttons in the Apple style, Windows style and customised buttons. And ChrisF has come up with another approach which uses sprites.