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.

15 comments:

  1. The iPad 3 has the same screen dimensions (not in FULLSCREEN).

    ReplyDelete
  2. Thanks Hillary - good point regarding FULLSCREEN, I will edit the Tute to mention FULLSCREEN mode.

    ReplyDelete
  3. A quick rundown of the rounded rectangle code wouldn't hurt.
    You'd be surprised how skipping a few steps can confuse you later

    ReplyDelete
    Replies
    1. Also it'd be nice to have it because the tutorial is called "creating a rounded rectangle" but I moslty learned how to copy and paste :)

      Delete
    2. Thanks Richard - I will add this over the weekend (and Tutorial 3).

      Delete
    3. Absolutely. I completely support what ur doing :)

      Delete
  4. Hi Richard, I have updated Tutorial 2 with the rounded rectangle code and provided an explanation of how it works. Thanks for the support.

    Cheers,

    D

    ReplyDelete
  5. what is the resolution of STANDARD mode (landscape)on ipad 1 and 2?

    ReplyDelete
    Replies
    1. Hi,

      Have a look at this post: http://codeatuts.blogspot.com.au/2012/11/interlude-13-pixels-vs-points-in-ios.html

      It contains all you ever wanted to know about resolution on the various flavours of iPad.

      Cheers,

      D

      Delete
  6. My iPad 4 Retina (Model A1459) in FULSCREEN only comes up to 768x1024 pixels using text(). Is this a LUA/CODEA issue? - Thx Jose (USA).

    ReplyDelete
    Replies
    1. Hi Jose,

      There is a difference between pixels and points - have a read of this: http://codeatuts.blogspot.com.au/2012/11/interlude-13-pixels-vs-points-in-ios.html

      Cheers,

      D

      Delete
    2. David, thank you! I now know that what I get on the screen for WIDTH and HEIGHT is "points". Simon also explained to me about the ContentScaleFactor = 2.

      Now a new question (THANKS): I like to color the steps in your examples with different colors so that the steps become clearer to me. I tried this:

      fill(red)
      ...
      fA = fill(red)
      fill(green) -- change fill for this drawing
      ... draw
      fill(fA) -- to restore the red fill
      DID NOT WORK
      ==========================================
      fill(red)
      ...
      fill(green) -- change fill for this drawing
      ... draw
      fill(red) -- restore fill
      DID WORK

      Thank you, Jose.

      Delete
    3. Jose - The first option doesn't work because you are passing a function, fA = fill(red) to fill when it is expecting a colour. The function fill(red) returns nothing, if you don't give fill an argument (e.g. fill()) it will return the current fill colour. So you could call fill(fill()), not that this would be much use.

      I think pushStyle() and popStyle() may be more of what you are after. Check out the reference docs on these at: http://twolivesleft.com/Codea/Reference/#functions/index/graphics

      Delete
  7. Is it just me, or are the rounded rectangles uneven? The rounded corners seem to bulge out slightly compared to the sides, as though the radius of the curve is one pixel more than it should be, or alternatively that the flat sides of the rectangle have been 'pushed in' by a pixel. The alternative approach posted to the codea forums has the same problem.

    ReplyDelete
    Replies
    1. Hi Simon,

      You are probably correct. The rounded rectangle approach used in this tutorial is a bit of a hack.

      A better approach (albeit more complicated) is to use a mesh. Have a look at the mesh button class as an example - it uses a rounded rectangle done with a mesh: https://www.dropbox.com/s/5ntio5mq1a2asqe/Button.lua

      Cheers,

      David

      Delete