Showing posts with label button. Show all posts
Showing posts with label button. Show all posts

Sunday, July 29, 2012

Tutorial 10 - A Simple Dial Class


10.1 Your Personality and a Little Maths


This tutorial is based on some code that we ported from our iPhone App Personality Profile. In this App you use a dial to answer a number of questions and a profile is provided based on your selection. We used a dial because we wanted a touch friendly solution and this seemed like an intuitive approach. The App is FREE on iTunes so download it and compare the functionality of the Objective C version to our Lua implementation. The embedded video above gives you an idea of how it works but the effect is better understood when used on your iDevice.


The trick to working out how to move the dial is a mathematical one. In essence you need to convert the cartesian co-ordinates of your finger on the screen to equivalent polar co-ordinates.

It is about now that you realise that those maths lessons way back weren't completely useless and perhaps you should have paid more attention. Don't panic, all you need to know is that it is possible to convert from a point (x, y) to polar co-ordinates which consist of an angle and a distance. The angle tells you how much to rotate the dial and you can use the distance portion to work out whether a point is within a circle which is handy for detecting if a tap was on the dial.

To make life easy we created a function which does all the hard work. We have reproduced it below but note that you also  need the math.hypot() function which isn't available in the Codea version of Lua. Both of these functions (and more) are contained in the Math tab of RSLibrary.

We have made use of the new Codea dependency functionality so if you download the entire tutorial code in one file, then you also need to download RSLibrary and link it as a dependency to DialDemo. See Tutorial 9 if you want to understand dependencies better or see what is in RSLibrary. For this tute you only need the Math and Button tabs but you might as well grab the whole thing.

As an aside, a handy trick which we use in the math.polar function is to assign a default value to a parameter using "or". This works because nil evaluates to false in Lua. See the code below to understand how you can use this to overload functions.



10.2 The Main Class

   
In this instance the Main class is just there to demonstrate the Dial Class. Both Landscape and Portrait orientations are supported using the orientationChanged() function.


      

10.3 The Dial Class


The Dial Class is where the magic happens. We only have a licence to use the knob and dial images in our App, so you are going to have to draw or obtain your own version. Strictly speaking you only need a knob image (the bit that moves). The class should handle any differences in size to our sprites.


   

Friday, July 27, 2012

Tutorial 9 - Dependencies & Libraries


Webcomic Courtesy of Ethanol & Entropy


9.1 What is a Dependency and why do I want one?


The latest version (v1.4.3) of Codea introduced the concept of Dependencies. This feature is used to address the problem that over time there are certain functions or classes that you use again and again but you don't want to have to re-type these in each project. 

Prior to version 1.4.3 the answer was to copy and paste the relevant bits from one project to another. This works ok but it is a bit slow, breaks up your workflow and can lead to version problems if you forget where the master copy is located.

A better solution is to have a library which contains all of these functions and classes which you can then "include" in your projects as required. If you select the library as a dependency to your project then all of the code (outside the Main tab) within the library becomes accessible to that project.

You don't have to create a specific Library to use dependencies, any project can be selected as a dependancy, but to address the version control issue it is better to keep this code in a consistent location. We haven't worked out if it is better to have many small libraries or one big one. Our guess is that it won't make a difference as the compiler will just lift out the code it needs and a large library won't create unnecessarily bloated byte-code. 

9.2 How do you create a Library?


A Library is just another project. There are no rules or guidelines as to what a Library should look like so we are just going to do what seems sensible and allow it to evolve over time. To provide something to talk about we created the (Reefwing Software) RSLibrary which is a collection of code which we have found useful but isn't currently available within the Codea framework. 

We have structured the Library with the Main tab containing metadata (contents, version, date, etc.) and which if run, will demonstrate the main functionality of the code in the Library. Each subsequent tab contains the code we want to share grouped in what we think is a logical fashion.

Over time we suspect that people will tailor there own libraries using the best of breed from other shared code. Feel free to adopt some or any of the code in RSLibrary. We have freely borrowed from other contributors on the Codea Forum such as Two Lives Left, Vega and Bri_G.

9.3 Download RSLibrary

  1. Main.lua v1.1 - metadata and demonstration of the library functionality.
  2. Math.lua v1.0 - math.hypot(x, y), math.hypot2(x,y), math.polar(x,y,originX,originY), math.constrain(value,min,max), math.pi(), and math.round(value).
  3. Collisions.lua v1.0 - pointInCircle(pointX, pointY, centreX, centreY, radius), pointInRect(pointX, pointY, x, y, w, h).
  4. Colors v1.0 - Definitions of the 17 predefined colours from UIColor.
  5. Button v1.2 - Modified Vega mesh Button Class.
  6. RoundRect v1.0 - Modified Bri_G Rounded Rectangle Class.
  7. TextBox v1.0 - Two Lives Left TextBox Class (from Spritely example project).

9.4 How do I link a Library to my Project?


So you have created your shiny new Library, how do you make this a dependency of your project. The process is very simple.
  1. With your Project open there is a [+] tab in the top right corner.
  2. Tapping this will pop up a menu with three options:
    • Create New Class;
    • Create Blank File; and
    • Dependencies.
  3. Not surprisingly the section called Dependencies is where you select your library. Tap it to select and it will indicate the available tabs (6 for RSLibrary).
And that's it. You can remove a dependency using the same process.

9.5 What's Next?


As the idea of Libraries and Dependencies is evolving we would welcome thoughts and observations on the best structure or alternate approaches, so that we can share them here.

9.6 The Main Class

Here is a copy of the Main Class so you can see how RSLibrary is structured.
--# Main
-- RSLibrary by Reefwing Software (www.reefwing.com.au)
-- This Library contains functions and definitions which are commonly used
-- but not currently available in the Codea framework.
-- It includes code from: Two Lives Left
--                                        Vega
--                                        Bri_G

--
-- CONTENTS
--
-- Math Extensions:

--     1. math.round(value)
--     2. math.hypot(x, y)
--     3. math.hypot2(x, y)
--     4. math.polar(x, y) or math.polar(x, y, originX, originY)
--     5. math.pi()
--     6. math.constrain(value, min, max)
--
-- Collision Detection Functions:
--     1. pointInCircle(pointX, pointY, centreX, centreY, radius)
--     2. pointInRect(pointX, pointY, x, y, width, height)
--
-- Color Extensions: UIColor Predefined Colors
--     1. blackColor        10. yellowColor
--     2. darkGrayColor 11. magentaColor
--     3. lightGrayColor 12. orangeColor
--     4. whiteColor        13. purpleColor
--     5. grayColor          14. brownColor
--     6. redColor            15. clearColor
--     7. greenColor       16. lightTextColor
--     8. blueColor          17. darkTextColor
--     9. cyanColor
--
-- Classes:

--     1. Vega Modified Mesh Button Class v1.2
--     2. Two Lives Left TextBox Class v1.0
--     3. Bri_G Modified RoundRect Class v1.0
--
-- Note the Main class of the Library is just used to demonstrate the Library
-- functionality. Nothing defined here is available using dependancies.


function setup()
    -- Library Metadata
    version = 1.1
    saveProjectInfo("Description", "Reefwing Software Library v"..version)
    saveProjectInfo("Author", "Reefwing Software")
    saveProjectInfo("Date", "26th July 2012")
    saveProjectInfo("Version", version)
    print("RSLibrary v"..version.."\n")

    -- Parameters to demonstrate the RoundRect Class
    parameter("cr",0,2,1)
    iparameter("border",0,4,2)
    iparameter("x",0,WIDTH - 200,275)
    iparameter("y",0,HEIGHT - 40,350)
    iparameter("w",100,WIDTH,200)
    iparameter("h",0,HEIGHT,40)

    -- Construct the rounded rectangle
    roundRect = RoundRect(x, y, w, h, border, "test", cr)
    -- Construct a TextBox
    textBox = TextBox(WIDTH / 2 - 100, HEIGHT - 120, 200, "Enter your name!")
    showTextBox = false
end

function draw()

    -- This sets the background color to black
    background(blackColor)
    -- Update our roundRect with the latest parameter values (not required if they
    -- don't change). The larger the width and height the smaller cr needs to be
    -- otherwise you will end up with an ellipse.

    roundRect.x = x
    roundRect.y = y
    roundRect.w = w
    roundRect.h = h
    roundRect.cr = cr
    roundRect.border = border

    -- Set the rectangle fill and border colours. Text colour is also available.

    roundRect.fillColor = blueColor
    roundRect.borderColor = redColor

    -- call the roundRect draw() function
    roundRect:draw()
    -- If tap is not on the rectangle then draw the textBox
    if showTextBox then
         textBox:draw()
    end
end

function touched(touch)

-- Use this method to demonstrate the pointInRect() collision detection function
    if touch.state == BEGAN and pointInRect(touch.x, touch.y, x, y, w, h) then
         showTextBox = false
         print("Rounded Rectangle Tapped.")
    elseif touch.state == ENDED and not pointInRect(touch.x, touch.y, x, y, w, h) then
         showTextBox = true
         showKeyboard()
    end
end


-- KeyBoard handling function
-- Used to enter name if touch is not on the rounded rectangle


function keyboard(key)
    if key ~= nil then
        if string.byte(key) == 10 then
-- <RETURN> Key pressed
            print("Hello "..textBox.text)
            showTextBox = false
            hideKeyboard()
        elseif string.byte(key) ~= 44 then
-- e.g. filter out commas
            textBox:acceptKey(key)
        end
    end
end

Thursday, July 19, 2012

Interlude 9 - Control Object Movement with Buttons (Updated 6/4/16)



Interlude 9.1 Introduction


One of the most useful design patterns in game design is being able to move an object on the screen using buttons (up, down, left and right).


Interlude 9.2 Solution 1 - One Tap per Move


Our first solution will move an object on the screen by a defined amount (shipSpeed) each time a button is tapped. We have defined the shipSpeed variable as a parameter so that you can tweak the distance each tap moves the ship. Once again we have used @Vega's Mesh Button class for the four directional buttons. Note that this code only works properly with a landscape orientation.

Let's have a look at the main class first.

--# Main

-- Use this function to perform your initial setup

function setup()

   -- This block of code is optional. We just include it in 
   -- every project so that we have a method of version control.

   version = 1.0

   saveProjectInfo("Description", "Move Object Demonstration")
   saveProjectInfo("Author", "Reefwing Software")
   saveProjectInfo("Date", "11th July 2012")
   saveProjectInfo("Version", version)

   print("MoveShip v"..version.."\n")

   -- Initialise the co-ordinates of your "Ship"
   -- These will be updated by the action methods 
   -- associated with each directional button.

   shipPosition = vec2(WIDTH/2, HEIGHT/2)

   -- This slider parameter will control the ship "speed"
   -- The larger the number the further the ship will 
   -- move with each button tap.
   --
   -- The format of this function is: 
   -- parameter("name", min value, max value, init value)

   parameter("shipSpeed", 1, 10, 4)

   -- Define the four Buttons used to move the ship
   -- They wont be visible until you draw() them.
   -- Note that 50 pixels is the minimum height for the default
   -- button font size.

   local mButtonSize = vec2(100, 50)
   local mLocX = WIDTH - 250
   local mLocY = 100

   leftButton = Button("Left", vec2(mLocX, mLocY), mButtonSize.x, mButtonSize.y)
   leftButton.action = function() leftButtonTapped() end

   mLocX = mLocX + 150
   rightButton = Button("Right", vec2(mLocX, mLocY), mButtonSize.x, mButtonSize.y)
   rightButton.action = function() rightButtonTapped() end

   mLocX = mLocX - 75
   mLocY = mLocY + 60
   upButton = Button("Up", vec2(mLocX, mLocY), mButtonSize.x, mButtonSize.y)
   upButton.action = function() upButtonTapped() end

   mLocY = mLocY - 120
   downButton = Button("Down", vec2(mLocX, mLocY), mButtonSize.x, mButtonSize.y)
   downButton.action = function() downButtonTapped() end

   -- Assign the colours that you want to use in your game.

   blackColour = color(0,0,0)

end

-- And now for the draw() function which is quite simple:
--
-- This function gets called once every frame
-- Codea will attempt to call draw() 60 times per second, it
-- can be much less than this if you have a lot going on in your code.

function draw()

   -- This sets a black background color 

   background(blackColour)

   -- Do your drawing here
   -- Draw your ship at the current co-ordinates stored in shipPosition

   sprite("Tyrian Remastered:Boss D", shipPosition.x, shipPosition.y)

   -- Draw the four directional buttons

   leftButton:draw()
   rightButton:draw()
   upButton:draw()
   downButton:draw()

end

-- Most of the action happens in the button call back functions...
--
-- Button action methods

function leftButtonTapped()

   -- Update ship position as long as the ship isnt off the screen.
   -- math.max() returns the maximum value of the arguments, so
   -- if shipPosition < 0 it will set it to 0.

   shipPosition.x = math.max(shipPosition.x - shipSpeed, 0)

end

function rightButtonTapped()

   -- Update ship position as long as the ship isnt off the screen.
   -- math.min() returns the minimum value of the arguments, so
   -- if shipPosition > WIDTH it will set it to WIDTH.

   shipPosition.x = math.min(shipPosition.x + shipSpeed, WIDTH)

end

function upButtonTapped()
   shipPosition.y = math.min(shipPosition.y + shipSpeed, HEIGHT)
end

function downButtonTapped()
   shipPosition.y = math.max(shipPosition.y - shipSpeed, 0)
end

-- Handle screen touches

-- Note that you need to pass any touches through
-- to your button touch handlers.

function touched(touch)
   leftButton:touched(touch)
   rightButton:touched(touch)
   upButton:touched(touch)
   downButton:touched(touch)
end


Interlude 9.3 Solution 2 - Tap and Hold Continuous Move


In some cases you want your object to move for as long as you hold down the directional button. This turns out to be an easy change due to the functionality built into the Button class. We can detect whether a user is holding down a button by querying its state and seeing if it is "pressing". In this instance you don't need the button action methods (leftButtonTapped, rightButtonTapped, etc) so you can delete them if you aren't using them for something else.

The new draw() function is shown below. While a directional button is being held down its position will be updated by shipSpeed once per frame (roughly 60 times per second).

You can download the entire moveShipCode here.

function draw()

   -- This sets a black background color 

   background(blackColour)

   -- Do your drawing here
   -- Draw your ship at the current co-ordinates stored in shipPosition

   if leftButton.state == "pressing" then
       shipPosition.x = math.max(shipPosition.x - shipSpeed, 0)
   elseif rightButton.state == "pressing" then
       shipPosition.x = math.min(shipPosition.x + shipSpeed, WIDTH)
   elseif upButton.state == "pressing" then
       shipPosition.y = math.min(shipPosition.y + shipSpeed, HEIGHT)
   elseif downButton.state == "pressing" then
       shipPosition.y = math.max(shipPosition.y - shipSpeed, 0)
   end

   sprite("Tyrian Remastered:Boss D", shipPosition.x, shipPosition.y)

   -- Draw the four directional buttons

   leftButton:draw()
   rightButton:draw()
   upButton:draw()
   downButton:draw()

end

Interlude 9.4 Solution 3 - Adding Flames and Direction




This final version adds sprites to represent the ship engine flames (as shown above) and will point the ship in the right direction using rotation which is based on the directional buttons. You can download the complete program including the Button class.

--# Main
-- Use this function to perform your initial setup

function setup()

  -- This block of code is optional. We just include it in 
  -- every project so that we have a method of version control.
  --
  -- Version 3.0 adds rocket flames and points the ship in the
  -- direction of movement.

  version = 3.0

  saveProjectInfo("Description", "Move Object Demonstration")
  saveProjectInfo("Author", "Reefwing Software")
  saveProjectInfo("Date", "11th July 2012")
  saveProjectInfo("Version", version)

  print("MoveShip v"..version.."\n")

  -- Initialise the co-ordinates of your "Ship"
  -- These will be updated in the draw() function to allow for 
  -- continuous pressing of the directional buttons.

  shipPosition = vec2(WIDTH/2, HEIGHT/2)

  -- The ship sprite used faces down. Initially we want it facing up so 
  -- we need to rotate the sprite 180 degrees when we draw() it. For those new to rotation,
  -- note that there are 360 degrees in a circle, 180 degrees is half a circle.
  -- We will then use this variable to rotate the ship so that it is
  -- facing in the right direction when the up, down, left and right
  -- buttons are tapped.

  rotationDegrees = 180

  -- This slider parameter will control the ship "speed"
  -- The larger the number the further the ship will 
  -- move with each button tap.
  --
  -- The format of this function is: 
  -- parameter("name", min value, max value, init value)

  parameter("shipSpeed", 1, 10, 4)

  -- Define the four Buttons used to move the ship
  -- They wont be visible until you draw() them.
  -- Note that 50 pixels is the minimum height for the default
  -- button font size.

  local mButtonSize = vec2(100, 50)
  local mLocX = WIDTH - 250
  local mLocY = 100

  leftButton = Button("Left", vec2(mLocX, mLocY), mButtonSize.x, mButtonSize.y)

  mLocX = mLocX + 150
  rightButton = Button("Right", vec2(mLocX, mLocY), mButtonSize.x, mButtonSize.y)

  mLocX = mLocX - 75
  mLocY = mLocY + 60
  upButton = Button("Up", vec2(mLocX, mLocY), mButtonSize.x, mButtonSize.y)

  mLocY = mLocY - 120
  downButton = Button("Down", vec2(mLocX, mLocY), mButtonSize.x, mButtonSize.y)

  -- Assign the colours that you want to use in your game.

  blackColour = color(0,0,0)

end

-- This function gets called once every frame
-- Codea will attempt to call draw() 60 times per second, it
-- can be much less than this if you have a lot going on in your code.

function draw()

  -- This sets a black background color 

  background(blackColour)

  -- Do your drawing here
  -- Draw your ship at the current co-ordinates stored in shipPosition
  --
  -- pushMatrix() saves any transformations (rotate, translate or scale) that have been made.
  -- popMatrix() returns to these saved transformations.
  -- If you are going to perform transformations it is a good idea to encapsulate your code
  -- with these so that you don't have unexpected effects elsewhere.

  pushMatrix()

  local buttonDown = false

  if leftButton.state == "pressing" then
      shipPosition.x = math.max(shipPosition.x - shipSpeed, 0)
      buttonDown = true
      rotationDegrees = 270
  elseif rightButton.state == "pressing" then
      shipPosition.x = math.min(shipPosition.x + shipSpeed, WIDTH)
      buttonDown = true
      rotationDegrees = 90
  elseif upButton.state == "pressing" then
      shipPosition.y = math.min(shipPosition.y + shipSpeed, HEIGHT)
      buttonDown = true
      rotationDegrees = 180
  elseif downButton.state == "pressing" then
      shipPosition.y = math.max(shipPosition.y - shipSpeed, 0)
      buttonDown = true
      rotationDegrees = 0
  end

  translate(shipPosition.x, shipPosition.y)
  rotate(rotationDegrees)
  sprite("Tyrian Remastered:Boss D", 0, 0)
  if buttonDown then
      -- draw the engine flames
      sprite("Tyrian Remastered:Flame 1", 17, 100)
      sprite("Tyrian Remastered:Flame 1", -19, 100)
  end

  popMatrix()

  -- Draw the four directional buttons

  leftButton:draw()
  rightButton:draw()
  upButton:draw()
  downButton:draw()

end

-- Handle screen touches
-- Note that you need to pass any touches through
-- to your button touch handlers.

function touched(touch)
  leftButton:touched(touch)
  rightButton:touched(touch)
  upButton:touched(touch)
  downButton:touched(touch)
end

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)

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

Monday, June 18, 2012

Tutorial 2 - Creating a Rounded Rectangle (Updated 2/9/15)


Webcomic Courtesy of Ethanol & Entropy

Version Information


This tutorial has been updated for Codea version 2.3.1(47). You can check your version of Codea by tapping on the Two Lives Left logo in the bottom middle of the Codea start screen (Tutorial 1, Figure 1).

2.0 Drawing a Rounded Rectangle


Ok so our aim in these tutorials is to create code and classes that can be reused. We are working up to a standard menu class which can be used in your programs. But we need to build some foundations first, and the good people at Two Lives Left have provided most of the sample code we need. Curious?

Now menus need buttons and buttons need a rounded rectangle, so let's start there. Open up the Codea example program called Sounds Plus (Figure 7 - you will need to scroll down a bit) by tapping on it. 


Figure 7. Tap on Sounds Plus Example

One of the tabs in this project is called RoundRect, which is exactly what we need. Tap on this tab. We want to copy this entire class and use it in our new Menu project. To do this, tap and hold on any of the text in this class to bring up the Selection pop up, then tap on Select All, and then Copy. Dismiss the keyboard and then tap the back button (<) in the top left corner.

Back at the Codea launch page, tap on Add New Project and call it Menu (or whatever else you want). You will have one class called Main which you would have seen in Tutorial 1. Tap on the + in the top right corner and then tap on Create New Class (Figure 8). 


Figure 8. Create a New Class.

Call the new class RoundRect and then tap done. This will generate three boiler plate functions (init, draw and touched) which we don't need. Tap and hold on any of the text in the class, Select All and tap the delete key on the keyboard. Tap and hold on the empty screen to bring up the Paste pop up and tap that. Make sure you don't tap Cut to remove the old text otherwise you will overwrite the code you copied and will paste back what you just tried to delete.

You should now have the RoundRect class (Figure 9) which you copied from the Sounds Plus example code. 

Figure 9. RoundRect Class

Let's have a bit of a look at the rounded rectangle class. I have inserted comments in green to help you understand what is happening.

The roundRect function has 5 variables. These are: roundRect(x, y, w, h, r)

x - the x co-ordinate of the lower left corner of the rounded rectangle
y - the y co-ordinate of the lower left corner of the rounded rectangle

w - width of the rounded rectangle
h - height of the rounded rectangle

r - radius of the corners

function roundRect(x,y,w,h,r)

-- [[ 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.]]

    pushStyle()

-- [[ insetPos and insetSize contain the co-ordinates for the internal "fill" rectangle. InsetPos.x = x, insetPos.y = y, insetSize.x = w and insetSize.y = h. In effect this creates a rectangle that is smaller than a factor of "r" within the rectangle co-ordinates specified in roundRect. ]]
    
    insetPos = vec2(x+r,y+r)
    insetSize = vec2(w-2*r,h-2*r)
    
-- Copy fill into stroke

-- [[ Since Codea 1.3 you can retrieve the style information from all style functions by calling them without arguments. This way you only have to set the fill style once as you would for the normal rectangle function. You can read all about how this rounded rectangle function evolved on the Codea Forums.]]

    local red,green,blue,a = fill()
    stroke(red,green,blue,a)
    
-- [[noSmooth() will disable smooth (unaliased) line drawing. It is useful for drawing thin lines. This initial rectangle is used to fill in the centre of your rounded rectangle, it has the usual 90 degree corners. Four lines are then drawn around this to give the rounded corner look. You can see this yourself by commenting out the 4 lines drawn below. ]]

    noSmooth()
    rectMode(CORNER)
    rect(insetPos.x,insetPos.y,insetSize.x,insetSize.y)
    
    if r > 0 then

-- [[ You have to use smooth() if you want to use the ROUND option for lineCapMode. Four lines are now drawn around the filler rectangle. One on each edge. Because the lines have rounded ends when you overlap them it makes the corners look rounded, albeit a bit like a ball if you get the proportions wrong. Each of the lines are twice the width of the corner radius. ]]

        smooth()
        lineCapMode(ROUND)
        strokeWidth(r*2)

        line(insetPos.x, insetPos.y, 
             insetPos.x + insetSize.x, insetPos.y)
        line(insetPos.x, insetPos.y,
             insetPos.x, insetPos.y + insetSize.y)
        line(insetPos.x, insetPos.y + insetSize.y,
             insetPos.x + insetSize.x, insetPos.y + insetSize.y)
        line(insetPos.x + insetSize.x, insetPos.y,
             insetPos.x + insetSize.x, insetPos.y + insetSize.y)            
    end

    popStyle()

end

So let's take our copied class for a spin. On my iPad AIR, the dimensions of the drawing screen is:

Width - 749 pixels
Height - 768 pixels

Sidebar: You can find out your screen dimensions by adding the following two lines to the Hello World project that you created in Tutorial 1. Stick these in the setup() function (Figure 10).


Figure 10. Determine the WIDTH & HEIGHT of your screen.

print("Screen Width: "..WIDTH)

print("Screen Height: "..HEIGHT)

One of the fab things about Lua is its ability to simply concatenate a string and a number using .. as shown in the print statement above. There is no need to convert the number to a string, it happens automagically.

Sidebar Update: As Hillary mentions in the comment below the iPad 3 has the same screen dimensions in the STANDARD display mode as the iPad 1 and 2. I should have mentioned that Codea lets you set one of three display modes using the function displayMode() - obvious huh. The three display modes available are:

1. STANDARD;
2. FULLSCREEN; and
3. FULLSCREEN_NO_BUTTONS (which hides the back, pause and play buttons).

In FULLSCREEN landscape mode the iPad 1 & 2 has screen dimensions of:

Height: 768 pixels
Width: 1024 pixels

You can check this out yourself by sticking displayMode(FULLSCREEN) in the setup() function of the Hello World project that we did in Tutorial 1 and using text() in the draw() function to display the HEIGHT and WIDTH constants - note the output from print() is not visible in FULLSCREEN mode.

The iPad 3 has double the resolution of the earlier versions, i.e. in FULLSCREEN landscape mode -

Height: 1536 pixels
Width: 2048 pixels 


In Interlude 13 we will discuss this subject in a lot more depth, skip ahead and have a read if you are interested.


Tap on the Main tab in your Menu class and below the comment -- Do your drawing here add the following line of code. Note that the variables chosen aren't anything special, we are just trying to draw the rectangle near the centre of the screen. Try modifying the variables yourself to see the effect. See Figure 11.

roundRect(WIDTH/2 - 150, HEIGHT/2, 300, 20, 30)


Figure 11. Using the RoundRect Function.

Run the program and you should end up with a gray rounded rectangle on your drawing screen (Figure 12). In the next tutorial we will turn this rectangle into a button.

Figure 12. A Rectangle with rounded corners!

Happy Coding.

SURPRISE SPECIAL BONUS SECTION:

Well not really that special - I should have mentioned that Codea is designed for fast prototyping and comes with some groovy functions to assist with this, namely parameter.

parameter.number(name, min, max) will populate your run screen with a visual slider which adjusts the value of the global variable called name between the min and max values specified, starting at the initial value. The difference between parameter.number and parameter.integer is that parameter.number gives you a float variable while parameter.integer will give you an integer. You usually stick these in the setup() function of your Main class.

Figure 13. RoundRect with Parameters.

There are a couple of example projects provided with Codea which illustrate the use of parameters but let's give them a spin on our rounded rectangle class. Update the Main class in our menu project to look like the following (Figure 13).

function setup()

    print("Rounded Rectangle Fast Prototype")
    
    -- [[parameter.number provides a float parameter(name, min, max, initial, callback). Don't worry about callback at the moment, we will use this later to respond to changes in the parameter.]]

    parameter.number("x", 0, WIDTH, WIDTH/2 - 150)
    parameter.number("y", 0, HEIGHT,  HEIGHT/2) 
    parameter.number("w", 0, WIDTH, 300)
    parameter.number("h", 0, HEIGHT, 21)
    parameter.number("r", 0,  200,  30)

end

-- This function gets called once every frame

function draw()

    -- This sets a dark background color 
    background(40, 40, 50)

    -- Do your drawing here
    
    roundRect(x, y, w, h, r)
    
end

Now run the program and note how moving the sliders changes the variables which effect your rounded rectangle. You can see how easily you could play with this to get exactly the shape you were after. You could then note down the variables required to reproduce that shape in your code. You will need to scroll up the parameter list to see r (Figure 14).


Figure 14. RoundRect with Parameters.


2.1 An Alternative Approach


@Jordan has come up with an alternative approach to drawing rounded rectangles using an ellipse for the corners. His code is available over at the Codea Forums.