UI Widgets

This week you are going to complete the remainder of the scene graph activity. While working on the previous lab, you may have realized that the scene graph JSON format has two problems. First, it can get very large, especially as you start to design more complex UIs. And second, it is very redundant as you often repeat UI elements with minor differences to them.

This is the purpose of widgets. A widget is a separate JSON file that used to store a reusable UI element. Just like basic JSON format, these are covered in the CUGL Scene Graph Tutorial. By breaking up your UI elements into separate widgets, you can greatly simplify your design process.

This assignment will require that you build off what you completed in the first lab. If you did not finish that lab, contact a TA so that they can get you started. When you finish this lab, you will see the image below.

goal-scene

As a reminder, this assignment is graded in one of two ways. If you are a 4152 student, this is graded entirely on effort (did you legitimately make an attempt), and that effort will go towards you participation grade. If you are a 5152 student, you will get a more detailed numerical score.

Table of Contents


The Scene Tool

This lab requires that you use the scene tool from the previous lab. You do not need to download anything new, though we do have the links below in case you need to get it again.

In both cases you run the application by typing the name SceneTool.exe. On some platforms you may need to add an extra ./ like ./SceneTool.exe.

Remember that using the scene tool on Windows is a bit more involved because you must have Visual Studio involved to get it to run. At this point if you have not gotten it working, please talk to a TA. Remember to run this program using the PowerShell. Navigate to the correct folder and type .\SceneTool.exe.


Widget Overview

A widget is a separate JSON file that defines a template for a UI template. We talked about these in the scene graph lecture. A widget is a separate JSON file that is stored in the widgets folder (not the json folder). It looks a lot like a scene graph with a few differences. Consider the widget below:

{
    "variables" : {
        "scale"   : ["data", "scale"],
        "texture" : ["children", "up", "data", "texture"],
    },
   "contents"  : {
        "type"   : "Button",
        "data"   : {
            "upnode"   : "up",
            "pushable" : [160,280,190,370,280,400,370,370,400,
                          280,370,190,280,160,190,190],
            "visible"  : false,
            "pushsize" : true,
            "anchor"   : [0.5,0.5],
            "scale"    : 0.8
        },
        "children" : {
            "up"       : {
                "type"   : "Image",
                "data"   : {
                    "texture"  : "play"
                }
            }
        },
        "layout" : {
            "x_anchor" : "center",
            "y_anchor" : "middle",
            "y_offset" : -115,
            "absolute" : true
        }
    }
}

The looks sort of like a scene graph JSON. Indeed, the contents of the contents object are a scene graph (in this case a simple button). The only thing that looks unusual is the variables object that comes before it.

To use a widget, you have to do two things. First you load it. Suppose the above widget is in a file call mywidget.json. Then you would need to add the following to the assets.json file, just before scene2s:

"widgets": {
    "widgetname" : "widgets/mywidget.json",
},
"scene2s" : ...

To use this in a scene graph, you would write something like the JSON below:

{
    "scenenam": {
        "type"      : "Node",
        "format"    : {
            "type" : "Anchored"
        },
        "children":  {
            "button": {
                "type": "Widget",
                "data"   : {
                    "key"     : "widgetname"
                }
            }
        }
    }
}

What does this do? The game engine will take the contents object of mywidget.json and make it the contents of the button child. Hence if you have a UI element you are using over and over again, this cuts down on the amount that you need to write.

So what do the variables do? This is a form of customization. Right now the button uses the play texture for its image. What if we wanted to use a different image, called stop? There is a variable called texture, which we can set as follows:

{
    "scenename": {
        "type"      : "Node",
        "format"    : {
            "type" : "Anchored"
        },
        "children":  {
            "button": {
                "type": "Widget",
                "data"   : {
                    "key"     : "widgetname"
                    "variables" : {
                        "texture" : "stop"
                    }
                }
            }
        }
    }
}

This tells CUGL to go into contents, find the value texture, which is inside of data, which is itself inside of up, which is all inside of children, and replace the play with stop. This allows you to do minor reconfigurations like change a color or some text or even the size of a UI element. This is the type of thing you will be doing in this activity.


Instructions

We assume that you have completed the previous lab. In that lab you modified assets.json to produce the image below. In this image the left button is pressed, indicating that the two arrows are active buttons.

task4

If you did not finish that lab, contact a TA so that they can get you started.

1. Add a Nine-Patch

A nine-patch is an arbitrarily resizable image. This might seem strange, as all images are technically resizable. However, resizing can stretch the image in ways that produces distortions or visible artifacts. A nine-patch is always guaranteed to look high resolution no matter its size.

Nine-patches work by breaking the image into nine quadrants. The corners do not stretch at all. The sides only stretch along one access. And the intertior stretches in all directions. To define a nine-patch, you need only take an image and define its interior; the other quadrants will be computed automatically from that. The file "button.png" in textures is a nine-patch image, and its interior is as shown by the dotted blue box below.

ninepatch

Creating a nine-patch is just like creating an image node. The only difference is that you have a data property for the interior, which is the box show above. The interior for this particular image is defined by

"interior" : [33,40,62,55]

The first two values are the x and y values of the bottom-left corner of the interior. The second two values are the width and height of the interior, respectively. All values are in pixels (because you are referring to a file and not a scene).

When you create a nine-patch you must also define the size. The only exception to this is if the "x_anchor" and "y_anchor" values are both "fill". In that case it expands to fill its entire parent. We want our nine-patch to be 300x75 pixels. So we would specify that with the data property

"size": [300,75]

Given all of this, use the texture "menubutton" to define a 300x75 ninepatch which is centered in the middle of "startmenu" (remember to use "center" for "x_anchor" and "middle" for "y_anchor"). When you are done, the scene should now look like the image below.

task5

2. Attach a Label

A label is a static, uneditable piece of text.n It has type "Label". In addition to traditional data properties like "anchor" and "size", it has the following:

  • "font": The font asset for the label (specified by name like a texture)
  • "text": The text to put in the label
  • "foreground": The color of the text
  • "background": The color of the background behind the text

A color is represented as an array of 4 numbers 0..255. In represents the color in rgba (red-green-blue-alpha). The value [255,255,255,255] is white. The value [255,0,0,255]. The value [0,0,0,255] is black. The value [0,0,0,0] is clear (invisible). By default, the foreground of a label is back and the background is clear (e.g. you do not need to specify either of these properties if you want to keep the defaults).

We want you to add a label to the nine-patch. But it is generally a bad idea to make something a child of a nine-path – that can do unexpected things. Inetead, we want you to

  • Create a new empty node with format "Anchored", that is a child of "startmenu".
  • Move the nine-patch to be a child of this node.
  • Move the layout settings from the nine-patch to its parent
  • Make a label a child of this new node (and a sibling of the nine-patch).
  • Use the font "gyparody" and the text "Click Me"
  • Make the font color white

When you are done, your scene should look like the following.

task6-bad

This seems disappointing. What happened? Well, the nine-patch collapsed to its smallest size because we moved the size value to its parent. We want the two children – the nine-patch and the label – to have the same size as the parent. But that is the purpose of the anchor layout. Give layout values to both the nine-patch and the label and set both "x_anchor" and "y_anchor" to "fill". Once you do that, the scene should look like this.

task6-better

This still is not what we want. The text is not centered. But the problem is that the label is centered. That is guaranteed when you use "fill". The problem is that the text is not centered inside of the label node itself. To solve that problem, we need to set two more data properties in the label

  • "halign": The horizontal alignment of the text
  • "valign": The vertical alignment of the text

These take the same values as "x_anchor" and "y_anchor", respectively, except that they do not support "fill". Set these properties to center the text in the button. When you are done, it should look like the image below.

task6-complete

We now have a scene graph node that combines together a nine-patch and a label. We are going to call the node "patchtext" for the purposes of the next task.

3. Make Another Button

We now want you to turn the nine-patch label combination "patchtext" into a clickable button. You should know how to do this by now. Make a button, and "patchtext" a child of that button. You should copy all of the data properties (e.g. the size and the anchor) from "patchtext" into the button. However, do not move any of the data properties from the nine-patch or the label. You also need to move the layout values from "patchtext" to the button. Finally, set the data property "upnode" to be the name of the button.

There is one last thing to do. Because you moved the size from "patchtext" into the button property, this means that the nine-patch is going to collapse again. To prevent this from happening, you should add a new layout entry to "patchtext" and set all values to "fill". This is very common technique that we use a lot. You set the size in parent node, and then you set the layout properties in all of the children and all of the descendents to "fill" so that they match the parent in size.

When you are done, you should now be able to click on the button as shown below.

task7

We are going to call this button "button1" or the purposes of the next task.

4. Make A Widget

That button involves a lot of scene graph nodes. And we want you to make three more of them! To make this easier, you are going to create a widget. A widget is a separate JSON file that defines a template for a UI template that we described above.

To make a widget, creata new JSON file in the widgets folder (not the json folder) called texbutton.json. The JSON file needs to start and end with curly braces. Inside of the curly braces copy the entire subscene graph for the button you just copy all of "button1", though you need to rename "button1" to be "contents".

Next we want to add some variables to this widget, so that we can customize this widget in the future. To create a variable, you first create a JSON object called "variables" and have an entry for each variable. The value for a variable is the path of JSON keys to value you want to replace. For example, to create a variable to change the label text, you write

"variables" : {
    "text"   : ["children","patchtext","children","label","data","text"]
}

Add this variable to the widget, as well as variables for "size" and "font".

Next, you need to load this widget in the engine. To do that, go in assets.json and put the following right before "scene2s".

"widgets": {
    "textbutton" : "widgets/textbutton.json"
},
"scene2s" : {

Now it is time to use the widget. Replace "button1" with the following

"button1": {
    "type"   : "Widget",
    "data"   : {
        "key" : "textbutton",
        "variables" : {
            "text": "Option 1"
        }
    }
}

This tells CUGL to use the widget "textbutton" with all of the default values except for the text, which is replaced with “Option 1”. Run the program and you will see the following.

task8

5. Add a Solid Color Box

We are going to add mutliple buttons together, but we want to organize them vertically. This organization step is going to be hard to see because it will involve another one of our invisible scene graph nodes that we use to organize things. So in this task we are going to show you how to make a node with a solid color.

The purpose of this is debugging. By using a solid color instead of invisible node, you can see exactly the bounds of the content area, and this makes it easier to use the layout managers. And when you are done, you can remove the solid color (which you will do in a later task).

For now remove the child "button1"; you can add it back later. In its place you are going to add a node called "center". It has type "Solid" (for solid color). Are almost the same as simple nodes except that they have a data property called "color" which specifies their color. You define the color with four numbers, just like we did with font colors.

Make "center" have size [400, 350]. In addition, do not center it in the menu (despite the name). Instead, anchor it to the bottom center of the menu with an offset of 10% higher. When you are done, it will look like this.

task9

6. Use a Flow Layout

You are now going to add three buttons as children to "center". All of these will use the widget "textbutton", so that will be relatively easy. The interesting part is how you will add them. This time we will not use an anchor layout. We will use a flow layout instead.

A flow layout allows you arrange children vertically or horizontally in regular intervals. We are going to stack the buttons vertically, centered in the blue area. To do that, set the format of "center" to be

"format"    : {
   "type"        : "Float",
   "orientation" : "vertical",
   "x_alignment" : "center",
   "y_alignment" : "middle"
}

Add the three buttons as children to "center". Use the variables to change the text of the buttons to "Option 1", "Option 2", and "Option 3", in that order. But the big difference is the layout values. Float layout does not have "x_anchor" and "y_anchor". Instead, it has the following two layout properties

  • "priority": The order of this child in the flow
  • "padding": The extra space to put around this child

Priorities should be non-negative and there should be no ties. The easiest thing to do is give you buttons priorities 1, 2, and 3 in that order. Padding is another array of four values. It represents the blank space to the left, the bottom, the right, and top, in that order. While anchor offsets are measured in percents, padding is currently measured in raw pixel units (we have not decided if this is a good idea or not, but this will not change until a future release). So a padding of [10,0,0,20] puts 10 pixels of space to the left of the node, and 20 pixels of space above the node.

Put a padding of 30 pixels below the first two buttons, but not the last button. When your run the project, it should look like the image below.

task10

7. Remove the Solid Color

The solid color background has served its purpose. So now you should remove it. We do not want you to remove the node – it is organizing the flow layout. Just change its type from "Solid" to "Node". Oh, and also remove the color property, unless you want to tint all your buttons blue.

When you are done, run the project. It should look like the image below.

task11

8. Convert the Menu into a Widget

Congratulations! You have made a functional menu on top of an aspect ration independent background. It seems to good to let that work go to waste for just one screen. So let’s turn the menu into a widget!

Make a new file in the widgets folder called mainmenu.json. Copy all of "startmenu" into this JSON (but do not forget to rename it "contents"). Create variables for each of the three text values. This may seem counterintuitive. Aren’t the text values already variables? They are variables in textbutton.json. We want them to be variables in this widget as well. For example, to make the text of the first button into a variable, we would do something like

"variables" : {
    "option1" : ["children","center","children","button1","data","variables","text"]
}

Add variables for the other two text values. Make sure all of the variable names are different.

Now you can replace the "startmenu" in assets.json with a widget node. Use the variables to reassign the button text values to "First", "Second", and "Third". When you run the project, you should see the following.

task12

This completes the lab.

Submission

Due: Mon, Feb 20 at 11:59 PM

This lab is definitely more work than the other two. That is because we had to build up your skills to get here. But this lab should be very doable for anyone who completed the previous lab. But unlike the last lab, this time you modified more files than just assets.json. So this time we want you a zip file containing the files:

  • assets.json
  • mainmenu.json
  • textbutton.json

Submit this zip file as lab3design.zip to CMS