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) end 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() -- [[ 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(self.color) -- [[ 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. ]] font("ArialRoundedMTBold") fontSize(22) -- 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, w,h,30) 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. ]] textMode(CENTER) fill(54, 65, 96, 255) text(self.displayName,self.pos.x+2,self.pos.y-2) fill(255, 255, 255, 255) text(self.displayName,self.pos.x,self.pos.y) -- [[ 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. ]] popStyle() end 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 end return false end 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 self.action() end end end
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
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.
drawButton()
end
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()
end
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")
end
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. ]]
button:touched(touch)
end
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.
When I type the code I get an error in Main setup.
ReplyDeleteI edited button.action by removing the word function().
The program now runs but nothing seems to happen. Button Pressed shows up before I press the button. I edited button touched and added an else to print "touch". touch is now displayed each time I press the button.
Did I miss something?
Hi Bruce,
DeleteIt could be a few things. Codea doesn't handle Lua multiline comments very well. The ones which look like --[[ ]].
I suggest you don't use these and replace them with single line comments (-- which just start with two dashes on each line). You will notice in later tutorials that I have stopped using them.
Did you include the roundRect() function from Tutorial 2? It wont work without this.
If you can't find the problem then you can download the file from here: https://www.dropbox.com/s/offa9e57szv3pf5/Tutorial_3_Simple_Button.lua
If you have any other questions let me know.
Same problem here. It's an error in the text above. Change all references to 'button' in the Main code to 'Button' (note the capital 'B').
DeleteHi Anonymous,
DeleteThere is a difference between the Button class (with capital) and the button instance of that class (lower case). This is a common pattern. If you use Button as the class instance it may work but you will only be able to have one button in your program which is probably not what you want.
Regards,
D
I'm having some trouble making a button work in my project
ReplyDeleteI've looked through the program several times the button is supposed to move a sprite right on the screen and it won't work heres right now im just trying to get the button to work so instaed of moving the sprite (which i havent made yet) it should just print "right button pushed" in the output instead im getting errors. can some one tell me what im doing wrong. Thanks in advance!!:):
ButtonRight = class()
function ButtonRight:init(displayName)
-- you can accept and set parameters here
self.displayName = "Right" -- it didnt work when i had this set to displayName
--instead in line 36 the output says says "bad argument #1 to 'text' (string expected, got nil)
--it said it was an error in my Button Right class
--this happened even when i changed it to "Right" which is a string right?
--i put a comment a line 36 to help you find it without counting all the lines
self.pos = vec2(0,0)
self.size = vec2(0,0)
self.action =function (actionright)
print("Right button pressed")
end
self.color = color(113, 66, 190, 255)
end
function ButtonRight:draw()
-- Codea does not automatically call this method
pushStyle()
fill(127, 127, 127, 255)
font("ArialRoundedMTBold")
fontSize(22)
-- use longest sound name for size
local w,h = textSize(self.displayName)
w = w + 20
h = h + 30
roundRect(self.pos.x - w/2,
self.pos.y - h/2,
w,h,30)
self.size = vec2(w,h)
textMode(CENTER)
fill(54, 65, 96, 255)
text(self.displayName,self.pos.x+2, self.pos.y-2)--36
fill(255, 255, 255, 255)
text(self.displayName,self.pos.x+2, self.pos.y-2)
popStyle()
end
function ButtonRight:hit(p)
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
end
return false
end
function ButtonRight:touched(touch)
-- Codea does not automatically call this method
if touch.state == ENDED and
self:hit(vec2(touch.x,touch.y)) then
if self.action then
self.action()
end
end
end
And here's my main if you need it:
supportedOrientations(LANDSCAPE_ANY)
function setup()
print("hold the device in LANDSCAPE mode!!!!")
parameter("posx",1,700)
parameter("posy",1,700)
end
-- This function gets called once every frame
function draw()
-- This sets a dark background color
background(40, 40, 50)
sprite("Documents:background",376.44,385.94,HEIGHT/1,WIDTH/.93)
strokeWidth(5)
drawButtonRight()
drawButtonLeft()
-- Do your drawing here
end
function setup(Buttons)
print("push the right button to move your ship right!")
print("push the left button to move you ship left!")
button=ButtonRight(Right)
button=ButtonLeft(Left)
ButtonRight.action=function()ButtonRightPressed()end
end
function drawButtonRight()
ButtonRight.pos=vec2(posx,posy)
ButtonRight:draw()
end
function ButtonRightPressed()
print("Right button pressed")
end
function touched(touch)
ButtonRight:touched(touch)
ButtonLeft:touched(touch)
end
these tutorial are VERY helpful im am a tenth grader trying to learn how to program
this is probably like the longest comment of all time!!
Hi Anonymous,
DeleteGreat to hear that the tutes are useful and well done on teaching yourself to program. You wont regret it. And yes congratulations on winning the longest comment award. You are on the right track but there are a few problems with your code. Here is what I suggest:
1. There is no need for you to modify the Button class. The point of having a class is that you can make lots of the same object from this class. The problem with having a ButtonRight class is that you will then need a ButtonLeft class, ButtonUp class, etc. So stick with a base Button class - you can download just the Button class code from: https://www.dropbox.com/s/5ckr3u7hs2y0x34/Button.lua
You mix objects and classes up in your code, but this isn't your main problem.
2. In your Main tab you need to define your button object (which is an "instance" of your Button class - don't worry about the terminology too much at this stage). The convention is to start a class name with a capital and objects with a lower case letter. You are doing this in the function setup(Buttons) - which NEVER gets called. Take this code and put it in the setup() function which is called automatically by Codea when the program starts. As per point 1. I would change your code so that it reads:
buttonRight = Button("Right")
buttonRight.action=function()ButtonRightPressed()end
buttonLeft = Button("Left")
buttonLeft.action=function()ButtonLeftPressed()end
You will need to add in the ButtonLeftPressed function obviously.
3. In your drawing and touch functions, make sure that you refer to the object not the base class (e.g. ButtonRight: draw() should be buttonRight:draw(), ButtonLeft:touched(touch) should be buttonLeft:touched(touch).
4. Finally if you want to see how it is done, skip ahead to Interlude 9 - moving a sprite with buttons (http://codeatuts.blogspot.com.au/2012/07/interlude-9-control-object-movement.html) or Tutorial 8 - A simple D-Pad class (http://codeatuts.blogspot.com.au/2012/07/tutorial-8-directional-pad-dpad-class.html). I would suggest that you try and get your code working first, you will learn more that way.
Good luck and feel free to ask more questions if you get stuck.
Cheers,
David
Sorry one last point...
Delete5. This button class needs the roundRect() function from Tutorial 2. It wont work without this.
P.S. You were getting the error "bad argument #1 to 'text' (string expected, got nil) because displayName was never set and hence is nil. You need to pass the display name when you create the object and yes it should be a string. e.g.
DeletebuttonRight = Button("Right")
thanks!!
Deleteim trying to make a game like the bit invader example but with button control and enemies that shoot and go after you
DeleteSounds like fun - I look forward to playing it.
DeleteI'm having some trouble again I got the right and left buttons to do what I want and decided to add a fire button to I added it by copying the right and left buttons exactly except for the location I didnt get any error messages when I ran the program the fire button never came up and the left button stopped working however the right button continued to work perfectly. I am using the single button class from the link you gave me and got the RoundRect from the soundsplus example. heres what i have:
ReplyDelete--spaceteroids
--A game were you shoot down asteroids and aliens before they destroy you
-- Use this function to perform your initial setup
supportedOrientations(LANDSCAPE_ANY)
function setup()
print("hold the device in LANDSCAPE mode!!!!")
iparameter("posx",1,700)
iparameter("posy",1,700)
buttonRight = Button("Right")
buttonRight.action=function()ButtonRightPressed()end
buttonLeft = Button("Left")
buttonLeft.action=function()ButtonLeftPressed()end
buttonFire = Button("Fire")
buttonFire.action =function()ButtonFirePressed()end
end
-- This function gets called once every frame
function draw()
-- This sets a dark background color
background(40, 40, 50)
sprite("Documents:background",376.44,385.94,HEIGHT/1,WIDTH/.93)
strokeWidth(5)
drawButtonRight()
drawButtonLeft()
drawButtonFire()
-- Do your drawing here
end
function drawButtonRight()
buttonRight.pos=vec2(700,40)
buttonRight:draw()
end
function drawButtonLeft()
buttonLeft.pos=vec2(600,40)
buttonLeft:draw()
end
function drawButtonFire()
buttonLeft.pos=vec2(500,500)
buttonFire:draw()
end
function ButtonRightPressed()
print("Right button pressed")
end
function ButtonLeftPressed()
print("Left button pressed")
end
function ButtonFirePressed()
print("Fire button pressed")
end
function touched(touch)
buttonRight:touched(touch)
buttonLeft:touched(touch)
buttonFire:touched(touch)
end
found my mistake :)
ReplyDeleteExcellent - BTW if you arent planning on moving your buttons I would initialise their position (e.g. buttonLeft.pos=vec2(500,500)) in setup() not draw(). draw() gets called up to 60 times per second so you want to minimise the amount of code in that function.
Deleteok thanks, I will do that
DeleteDavid, I graduated from Tut. 2, Thanks.
ReplyDeleteMy Main is what you have:
function setup()
print("Button Test Project")
button = Button("Press Me")
button.action = function() buttonPressed() end
end
function draw()
background(40, 40, 50)
drawButton()
end
function drawButton()
button.pos = vec2(400, HEIGHT/2)
button: draw()
end
function buttonPressed()
print("Button Pressed")
end
function touched(touch)
button:touched(touch)
end
This program draws a purple button with white text. When I touch the button, the "Button Pressed" text print.
1. Is this supposed to be the behavior/purpose of the tutorial?
2. Please explain further the Button fuction Button:touched(touch). How does it work? Hows does the 2nd if work? if self.action then self.action?
3. I don't know why in Main function touched (touch) works or how. Could you further explain?
Thanks, Jose.
Hi Jose,
Delete1. Yes.
2. and 3. Let me answer these together since they are related. In the Main tab, the function touched(touch) gets called automatically by Codea whenever a user taps the screen. When this happens we pass the touch object to the touched function in our Button class.
In the Button class, the first "if" checks if the touch was on the button using the hit() function and the second "if" calls the function that you have assigned to the button "action" variable, if it has been defined.
In Lua, you can assign a function to a variable and we use this to provide a call back function for the button. When you define the button, one of the lines you will see is:
button.action = function() buttonPressed() end
This assigns the function buttonPressed() to the variable action.
In the button touched function we test whether action exists (if it is nil then it hasn't been defined), and if it does we call that function every time the button is tapped. The function buttonPressed() just prints out a message to the console in this simple example.
function buttonPressed()
print("Button Pressed")
end
You can read about what gets returned in the touch object here: http://twolivesleft.com/Codea/Reference/#detail/index/touch
I hope that makes sense.
Regards,
David
David, thanks again. I have several questions.
DeleteMain> button.action = function() buttonPressed() end
Main> function touched(touch) button:touched(touch) end.
Button> Button = class()... function Button: ...
Button> function Button:hit(p)local l = self.pos.x ...
Button> function Button:touched(touch)
if touch.state == ENDED and self.hit(vec2(touch.x,touch.y) ...
1. button.action = function() buttonPressed() end
Why does this function have () at the end?
2. In the Button class, must all subsequent function declarations use "Button"?
3. self.hit(vec2(touch.x, touch.y) invokes function Button:hit(p). Correct?
3a. How are touch.x, touch.y transferred to Button:hit(p)?
3b. When/where is the first time in the code that touch.x, touch.y acquire a value?
3c. How can self:hit(vec2(touch.x,touch.y))have a vector parameter when it is not defined in function Button:hit(p)?
3d. Button class variables self.pos and self.size ARE defined as vectors. But how can a function in the Button class Button:hit(p)use these values if it is not defined in the function?
Thanks ... a little convoluted, but I hope you can help!
Thanks again, Jose.
Jose,
Delete1. That is how you define a function in Lua and assign it to a variable. I'm not sure I can explain it any clearer. That is the correct syntax. Have a look at this, http://lua-users.org/wiki/FunctionsTutorial, and see if that makes it clearer.
2. If you want them associated with the Button class they do.
3. Yes - in this case self = Button.
3a. Inside the touched function of the button, hit gets called with touch.x and touch.y passed as a vector referred to as p in the hit function. I reused hit from some other code, it doesn't have to be a vector that's just the way it was coded.
3b. Whenever someone taps the screen, touch gets populated and the main touched function is called. Did you read the specs on touch?
3c. Lua is a dynamically typed language. This means that variables do not have types; only values do. There are no type definitions in the language. All values carry their own type.
3d. See answer to 3c.
It may be worth you spending some time reading the Lua reference manual. It is available here: http://www.lua.org/manual/5.1/manual.html
Cheers,
D