Skip to content

User Interface

dgough edited this page Dec 21, 2012 · 1 revision

The gameplay framework provides a set of user interface (UI) controls that can be used to create menus and HUDs. To add UI elements to your game, you'll create a form from a properties file and call update() and draw() on it. Other than adding listeners to buttons and modifying controls programmatically, there's not much code to write in your game. Most of the work of defining a form's look and feel happens in the .form and .theme files, as well as the texture atlas used by the theme.

Creating a form

To create a form, pass a properties file to Form::create() to instantiate a form. The top-most namespace in the file must be named 'form'. The following properties are available for forms:

form <formID>
{
    // Form properties.
    theme                  = <Path to .theme file>
    layout                 = <Layout::Type constant>
    style                  = <styleID>
    position               = <x, y>
    alignment              = <Control::Alignment constant>
    size                   = <width, height>
    autoWidth              = <bool>
    autoHeight             = <bool>
    width                  = <width>
    height                 = <height>

    // All the Controls within this Form.
    container { }
    label { }
    textBox { }
    button { }
    checkBox { }
    radioButton { }
    slider { }
}

Form property information:

The following is a list of form properties, and their purpose:

theme
See "Creating a theme" below.
layout
Member of Layout::Type enum.
style
A style from the theme.
position
The on-screen position, in pixels.
alignment
Align the form's position within the bounds of the screen. Note the position property will be ignored if the alignment property has been set.
size
The size of the form, in pixels.
autoWidth
Use of this property will result in a form with a width spanning the entire display. Note the width property will be ignored if autoWidth has been set.
autoHeight
Use of this property will result in a form with a height spanning the entire display. The height property will be ignored if autoHeight has been set.
width
Can be used in place of size.
height
Can be used in place of size.

A style determines the look of a control and is defined in the theme file, detailed below. Position and size attributes are determined for controls using the same properties as listed above for forms. Controls can be aligned within their parent container by using the alignment property. Setting autoWidth or autoHeight to true will result in a control the width or height of its parent container. You can add controls to the form by placing namespaces within it. The available controls are:

Container
A container has all the same available properties as a form, except for 'theme'. You can add more controls within a container to group them together, and/or to apply a different layout type to a group of controls.
Label
A simple text label. Available properties: 'style', 'position', 'alignment', 'size', 'autoWidth', 'autoHeight', and 'text'.
TextBox
Editable text label. A TextBox control has the same available properties as label.
Button
A button. A button control has the same available properties as label.
CheckBox
A button that toggles between 'checked' and 'unchecked' states when tapped or clicked. A CheckBox has the same available properties as label, plus 'checked' for its starting state.
RadioButton
RadioButton has the same available properties as CheckBox, with an additional property, 'group'. Only one radio button in a given group can be selected at a time.
Slider
A marker that can slide along a track between its end-caps. A slider makes use of the following properties:
slider <controlID>
{
    style = <styleID>      // A style from the theme.
    position = <x, y>      // Position of the control on-screen, in pixels.
    size = <width, height> // The size of the control, in pixels.
    min = <float>          // The value of the left- / bottom-most point on the slider.
    max = <float>          // The value of the right- / top-most point on the slider.
    value = <float>        // The default position of the marker.
    step = <float>         // If greater than 0, force the marker to snap to discrete
                           // multiples of 'step'.
    text = <string>        // Text to display above, below or alongside the slider
                           // (depending on the style).
}

Creating a theme

A theme contains the information a form needs to determine the look of its controls. A theme has one property, 'texture', which points to a texture atlas containing the images used by the theme. Cursor images, skins, and lists of images used by controls are defined in their own namespaces. The rest of the theme consists of style namespaces. A style describes the border, margin, and padding of a control, what images, skins, and cursors are associated with a control, and font properties to apply to a control's text.

theme <themeID>
{
    texture = <Path to texture>
    cursor <cursorID>
    {
        region = <x, y, width, height>
        color = <#ffffffff>
    }
    imageList <imageID>
    {
        image checked
        {
            region = <x, y, width, height>
        }
        image unchecked
        {
            region = <x, y, width, height>
            color = <#fffffffff>
        }
        color = <#fffffffff>
    }
    skin <skinID>
    {
        border
        {
            top = <int>
            bottom = <int>
            left = <int>
            right = <int>
        }
        region = <x, y, width, height>
        color = <#ffffffff>
    }
    style <styleID>
    {
        margin
        {
            top = <int>
            bottom = <int>
            left = <int>
            right = <int>
        }
        padding
        {
            top = <int>
            bottom = <int>
            left = <int>
            right = <int>
        }
        stateNormal
        {
            skin = <skinID>
            imageList = <imageID>
            cursor = <cursorID>
            font = <Path to font>
            fontSize = <int>
            textColor = <#ffffffff>
            textAlignment = <Control::Alignmentconstant>
            rightToLeft = <bool>
            opacity = <float>
        }
        stateFocus
        {
            skin = <skinID>
            ...
        }
        stateActive
        {
            skin = <skinID>
            ...
        }
        stateDisabled
        {
            skin = <skinID>
            ...
        }
    }
}

Theme property information

The following is a list of theme properties, and their sub-properties if applicable.

texture
The path to the texture atlas used by this theme.
cursor
Describes a single image, to be used as a cursor.

Sub-properties of cursor

region
Region within the texture, in pixels.
color
Blend color to apply to this cursor.
imageList
A collection of images used by controls.
image
A single image within the list.
region
Region within the texture, in pixels.
color
Optionally override image-list blend color.
color
Default blend color for images that don't specify their own.
skin
Defines the border and background of a control.

Sub-properties of skin

border
The corners and edges of the given region will be used as border sprites.
top
Height of top border, top corners.
bottom
Height of bottom border, bottom corners.
left
Width of left border, left corners.
right
Width of right border, right corners.
region
Total container region including the entire border. A region within the texture, in pixels.
color
The blend color to apply to this skin.

style: A combination of theme attributes that can be applied to any control.

Sub-properties of style

margin
Layouts may make use of a style's margin to put space between adjacent controls.
top
Empty space above a control.
bottom
Empty space below a control.
left
Empty space left of a control.
right
Empty space right of a control.
padding
The space between a control's border and its content.
top
Empty space between the top border and content.
bottom
Empty space between the top border and content.
left
Empty space between the left border and content.
right
Empty space between the right border and content.
stateNormal
Properties used when a control is in the normal state.
skin
Skin to use for border and background sprites.
imageList
Images to use for this state.
cursor
Cursor to use when the mouse is over this control.
font
Font to use for rendering text.
fontSize
Size of text.
textColor
Color of text.
textAlignment
Align text within the control's content area.
rightToLeft
Whether to draw text from right to left.
opacity
Opacity to apply to all text/border/icon colors.
stateFocus
Properties used when a control is in focus.

Same properties as stateNormal. Unspecified properties will inherit from stateNormal.

stateActive
Properties used when a control is active. This is when a touch/mouse is down within the control. If not specified, the normal overlay will be used.

Same properties as stateNormal. Unspecified properties will inherit from stateNormal.

stateDisabled
Properties used when a control is disabled. If not specified, the normal overlay will be used.

Same properties as stateNormal. Unspecified properties will inherit from stateNormal.

The top-level property of a theme is the path to its texture atlas. This is a single image containing all the sprites used by the theme. The skin, cursor, and imageList namespaces within a theme file refer to regions of this image to use to represent their various states.

Skin: A rectangular area representing a border and background. A container namespace will specify the rectangular region and blend color as well as the border sizes. From this, the region will be divided into nine areas: four corners, four borders, and the center background area. Note that the top and bottom borders will be stretched horizontally; the left and right borders will stretch vertically; and the center of the container will stretch in both directions. The corners will never be stretched. It's perfectly valid to set any border size to 0.

Cursor
A single rectangular area representing a mouse cursor.
ImageList
A collection of images used by controls. Images for multiple control types can be combined into one list. Controls use the following images:
CheckBox
'checked', 'unchecked'
RadioButton
'selected, 'unselected'
Slider
'minCap', 'maxCap', 'track', 'marker'
TextBox
'textCaret'

Note that you may specify separate image lists for each state in a style.

Adding a form to your game

Once you have a form, theme, and texture atlas, only a small amount of code is required in order to actually display your UI within your game. There are two options for displaying forms: two-dimensionally, where the form is drawn directly to the display and three-dimensionally, where the form is assigned to a node within the game's scene and displayed on a quad.

The 2-D case is simple. To initialize a form, pass the path to your .form file to Form::create() to be returned a pointer to your form. Now, simply call Form::update() on the form during your game's update() method, and call Form::draw() on the form during render() . See the section below on event handling to learn how to react to player input from within a form.

The 3-D method of drawing forms is somewhat more advanced. For starters, you'll need a scene with at least one node in it. Call Node::setForm() to attach the form to the node. This call will also generate a rectangular model with the dimensions of the form. Scale, rotate, and translate the node as necessary. Now, calling Form::draw() on the form will render the contents of the form into a framebuffer and use that framebuffer to texture the form's model.

Event handling within forms

Controls will trigger events when the user interacts with them. You can handle these events by setting listeners on individual controls.

All controls can trigger the mouse / touch events PRESS , RELEASE , and CLICK . Sliders, check boxes, and radio buttons can also trigger a VALUE_CHANGED event so that you can update your game as a slider is moving or when a radio button becomes unselected. Finally, text boxes trigger a TEXT_CHANGED event any time a character is entered or deleted (but not when the cursor is moved within the text box). Use TEXT_CHANGED along with the getLastKeyPress() method on a TextBox to do things like accepting a player name when the return key is pressed.

To retrieve a control from your form, call Form::getControl() with the ID of the control you're looking for. Cast this to the correct control pointer type and then call addListener() on it. This method takes an object that implements Control::Listener as well as an int representing the events to listen for. You can bitwise-OR together event types. For example, the following code listens for PRESS and RELEASE events on a button:

Button* myButton = static_cast<Button*>(myForm->getControl("myButton");
myButton->addListener(this, Control::Listener::PRESS | Control::Listener::RELEASE);

In this example, the game itself implements Control::Listener. This is easy to do as there's only one method a listener needs to implement, which should look something like this:

void MyGame::controlEvent(Control* control, EventType evt)
{
    switch(evt)
    {
    case Control::Listener::PRESS:
        if (strcmp("myButton", control->getID()) == 0)
        {
            // Do something.
        }
        break;
    case Control::Listener::RELEASE:
        if (strcmp("myButton", control->getID()) == 0)
        {
            // Do something else.
        }
        break;
    }
}

Note that getControl() is also a method on Container. If multiple controls share the same ID but are children of separate containers, the parent container can be retrieved first and then Container::getControl() called to retrieve the specific control needed.