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.
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.
- macOS: SceneToolMac.zip
- Windows: SceneToolWin.zip
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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