2
0
Эх сурвалжийг харах

Adding a bunch of unfinished UI stuff along with handling of inheritance in Properties files.

Adam Blake 14 жил өмнө
parent
commit
55187aac27

+ 75 - 0
gameplay/src/Button.cpp

@@ -0,0 +1,75 @@
+#include "Base.h"
+#include "Button.h"
+
+namespace gameplay
+{
+    static std::vector<Button*> __buttons;
+
+    Button::Button() : _callback(NULL)
+    {
+    }
+
+    Button::~Button()
+    {
+    }
+
+    Button* Button::create(Theme::Style* style, Properties* properties)
+    {
+        Button* button = new Button();
+        button->_style = style;
+        button->_id = properties->getId();
+        properties->getVector2("position", &button->_position);
+        properties->getVector2("size", &button->_size);
+        button->_text = properties->getString("text");
+
+        __buttons.push_back(button);
+
+        return button;
+    }
+
+    Button* Button::create(const char* id, unsigned int x, unsigned int y, unsigned int width, unsigned int height)
+    {
+        Button* button = new Button();
+        button->_id = id;
+        button->_position.set(x, y);
+        button->_size.set(width, height);
+
+        __buttons.push_back(button);
+
+        return button;
+    }
+
+    Button* Button::getButton(const char* id)
+    {
+        std::vector<Button*>::const_iterator it;
+        for (it = __buttons.begin(); it < __buttons.end(); it++)
+        {
+            Button* b = *it;
+            if (strcmp(id, b->getID()) == 0)
+            {
+                return b;
+            }
+        }
+
+        return NULL;
+    }
+
+    void Button::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+    {
+        switch (evt)
+        {
+        case Touch::TOUCH_PRESS:
+            _state = Control::STATE_ACTIVE;
+            break;
+        case Touch::TOUCH_RELEASE:
+            if (_callback &&
+                x > 0 && x <= _size.x &&
+                y > 0 && y <= _size.y)
+            {
+                _callback->trigger(this);
+            }
+            setState(Control::STATE_NORMAL);
+            break;
+        }
+    }
+}

+ 75 - 0
gameplay/src/Button.h

@@ -0,0 +1,75 @@
+#ifndef BUTTON_H_
+#define BUTTON_H_
+
+#include "Label.h"
+#include "Touch.h"
+#include "Theme.h"
+#include "Properties.h"
+
+namespace gameplay
+{
+
+class Button : public Label
+{
+    class Callback;
+
+public:
+    Button();
+    virtual ~Button();
+
+    static Button* create(Theme::Style* style, Properties* properties);
+    static Button* create(const char* id, unsigned int x, unsigned int y, unsigned int width, unsigned int height);
+    static Button* getButton(const char* id);
+
+    template <class ClassType>
+    void setCallback(ClassType* instance, void (ClassType::*callbackMethod)(Button*));
+
+    void touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
+
+protected:
+    Callback* _callback;
+
+private:
+    Button(const Button& copy);
+
+    class Callback
+    {
+    public:
+        virtual ~Callback() { }
+        virtual void trigger(Button* button) = 0;
+    };
+
+    template <class ClassType>
+    class CallbackImpl : public Callback
+    {
+        typedef void (ClassType::*CallbackMethod)(Button*);
+    public:
+        CallbackImpl(ClassType* instance, CallbackMethod method);
+        void trigger(Button* button);
+    private:
+        ClassType* _instance;
+        CallbackMethod _method;
+    };
+};
+
+template <class ClassType>
+Button::CallbackImpl<ClassType>::CallbackImpl(ClassType* instance, CallbackMethod method)
+    : _instance(instance), _method(method)
+{
+}
+
+template <class ClassType>
+void Button::setCallback(ClassType* instance, void (ClassType::*callbackMethod)(Button*))
+{
+    _callback = new CallbackImpl<ClassType>(instance, callbackMethod);
+}
+
+template <class ClassType>
+void Button::CallbackImpl<ClassType>::trigger(Button* button)
+{
+    (_instance->*_method)(button);
+}
+
+}
+
+#endif

+ 134 - 0
gameplay/src/CheckBox.cpp

@@ -0,0 +1,134 @@
+#include "Base.h"
+#include "CheckBox.h"
+
+namespace gameplay
+{
+
+static std::vector<CheckBox*> __checkBoxes;
+
+CheckBox::CheckBox()
+{
+}
+
+CheckBox::CheckBox(const CheckBox& copy)
+{
+    // Hidden.
+}
+
+CheckBox::~CheckBox()
+{
+
+}
+
+CheckBox* CheckBox::create(Theme::Style* style, Properties* properties)
+{
+    CheckBox* checkbox = new CheckBox();
+    checkbox->_style = style;
+    checkbox->_id = properties->getId();
+    properties->getVector2("position", &checkbox->_position);
+    properties->getVector2("size", &checkbox->_size);
+    checkbox->_text = properties->getString("text");
+
+    __checkBoxes.push_back(checkbox);
+
+    return checkbox;
+}
+
+CheckBox* CheckBox::getCheckBox(const char* id)
+{
+    std::vector<CheckBox*>::const_iterator it;
+    for (it = __checkBoxes.begin(); it < __checkBoxes.end(); it++)
+    {
+        CheckBox* checkbox = *it;
+        if (strcmp(id, checkbox->getID()) == 0)
+        {
+            return checkbox;
+        }
+    }
+
+    return NULL;
+}
+
+void CheckBox::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+{
+
+    switch (evt)
+    {
+    case Touch::TOUCH_PRESS:
+        {
+            _state = Control::STATE_ACTIVE;
+        }
+        break;
+    case Touch::TOUCH_RELEASE:
+        {
+            if (_state == Control::STATE_ACTIVE)
+            {
+                if (_callback)
+                {
+                    _callback->trigger(this);
+                }
+                _checked = !_checked;
+                setState(Control::STATE_NORMAL);
+            }
+        }
+        break;
+    }
+}
+
+void CheckBox::drawSprites(SpriteBatch* spriteBatch, const Vector2& position)
+{
+    // Left, v-center.
+    // TODO: Set an alignment for icons.
+    Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
+    Theme::CheckBoxIcon* icon = overlay->getCheckBoxIcon();
+    if (icon)
+    {
+        Theme::Border border = _style->getBorder();
+        Theme::Padding padding = _style->getPadding();
+
+        Vector2 pos(position.x + _position.x + border.left + padding.left,
+            position.y + _position.y + (_size.y - border.bottom - padding.bottom) / 2.0f - icon->size.y / 2.0f);
+
+        if (_checked)
+        {
+            spriteBatch->draw(pos.x, pos.y, icon->size.x, icon->size.y, icon->on.u1, icon->on.v1, icon->on.u2, icon->on.v2, overlay->getBorderColor());
+        }
+        else
+        {
+            spriteBatch->draw(pos.x, pos.y, icon->size.x, icon->size.y, icon->off.u1, icon->off.v1, icon->off.u2, icon->off.v2, overlay->getBorderColor());
+        }
+    }
+}
+
+void CheckBox::drawText(const Vector2& position)
+{
+    // TODO: Batch all labels that use the same font.
+    Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
+    Theme::CheckBoxIcon* icon = overlay->getCheckBoxIcon();
+    Theme::Border border = _style->getBorder();
+    Theme::Padding padding = _style->getPadding();
+
+    // Set up the text viewport.
+    float iconWidth = 0.0f;
+    if (icon)
+    {
+        iconWidth = icon->size.x;
+    }
+
+    Font* font = overlay->getFont();
+    Vector2 pos(position.x + _position.x + border.left + padding.left + iconWidth,
+            position.y + _position.y + border.top + padding.top);
+
+    Rectangle viewport(pos.x, pos.y,
+        _size.x - border.left - padding.left - border.right - padding.right - iconWidth,
+        _size.y - border.top - padding.top - border.bottom - padding.bottom - font->getSize());
+    
+    // Draw the text.
+    font->begin();
+    font->drawText(_text.c_str(), viewport, overlay->getTextColor(), overlay->getFontSize(), overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
+    font->end();
+
+    _dirty = false;
+}
+
+}

+ 34 - 0
gameplay/src/CheckBox.h

@@ -0,0 +1,34 @@
+#ifndef CHECKBOX_H_
+#define CHECKBOX_H_
+
+#include "Theme.h"
+#include "Properties.h"
+#include "Touch.h"
+#include "Button.h"
+
+namespace gameplay
+{
+
+class CheckBox : public Button
+{
+public:
+    CheckBox();
+    ~CheckBox();
+
+    static CheckBox* create(Theme::Style* style, Properties* properties);
+    static CheckBox* getCheckBox(const char* id);
+
+    void touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
+
+    void drawSprites(SpriteBatch* spriteBatch, const Vector2& position);
+    void drawText(const Vector2& position);
+
+private:
+    CheckBox(const CheckBox& copy);
+
+    bool _checked;
+};
+
+}
+
+#endif

+ 215 - 0
gameplay/src/Container.cpp

@@ -0,0 +1,215 @@
+#include "Base.h"
+#include "Container.h"
+#include "Layout.h"
+#include "AbsoluteLayout.h"
+
+namespace gameplay
+{
+    static std::vector<Container*> __containers;
+
+    Container::Container() : _layout(NULL)
+    {
+    }
+
+    Container::Container(const Container& copy)
+    {
+    }
+
+    Container::~Container()
+    {
+    }
+
+    Container* Container::create(const char* id, Layout::Type type)
+    {
+        Layout* layout;
+        switch(type)
+        {
+        case Layout::LAYOUT_ABSOLUTE:
+            layout = AbsoluteLayout::create();
+            break;
+        case Layout::LAYOUT_FLOW:
+            break;
+        case Layout::LAYOUT_VERTICAL:
+            break;
+        }
+
+        Container* container = new Container();
+        container->_id = id;
+        container->_layout = layout;
+
+        __containers.push_back(container);
+
+        return container;
+    }
+
+    Container* Container::getContainer(const char* id)
+    {
+        std::vector<Container*>::const_iterator it;
+        for (it = __containers.begin(); it < __containers.end(); it++)
+        {
+            Container* c = *it;
+            if (strcmp(id, c->getID()) == 0)
+            {
+                return c;
+            }
+        }
+
+        return NULL;
+    }
+
+    Layout* Container::getLayout()
+    {
+        return _layout;
+    }
+
+    unsigned int Container::addControl(Control* control)
+    {
+        _controls.push_back(control);
+        control->addRef();
+
+        return _controls.size() - 1;
+    }
+
+    void Container::insertControl(Control* control, unsigned int index)
+    {
+        std::vector<Control*>::iterator it = _controls.begin() + index;
+        _controls.insert(it, control);
+    }
+
+    void Container::removeControl(unsigned int index)
+    {
+        std::vector<Control*>::iterator it = _controls.begin() + index;
+        _controls.erase(it);
+    }
+
+    void Container::removeControl(const char* id)
+    {
+        std::vector<Control*>::iterator it;
+        for (it = _controls.begin(); it < _controls.end(); it++)
+        {
+            Control* c = *it;
+            if (strcmp(id, c->getID()) == 0)
+            {
+                _controls.erase(it);
+            }
+        }
+    }
+
+    void Container::removeControl(Control* control)
+    {
+        std::vector<Control*>::iterator it;
+        for (it = _controls.begin(); it < _controls.end(); it++)
+        {
+            if (*it == control)
+            {
+                _controls.erase(it);
+            }
+        }
+    }
+
+    Control* Container::getControl(unsigned int index) const
+    {
+        std::vector<Control*>::const_iterator it = _controls.begin() + index;
+        return *it;
+    }
+
+    Control* Container::getControl(const char* id) const
+    {
+        std::vector<Control*>::const_iterator it;
+        for (it = _controls.begin(); it < _controls.end(); it++)
+        {
+            Control* c = *it;
+            if (strcmp(id, c->getID()) == 0)
+            {
+                return c;
+            }
+        }
+
+        return NULL;
+    }
+
+    void Container::update()
+    {
+        // Should probably have sizeChanged() for this.
+        //if (isDirty())
+        {
+            _layout->update(_controls, _size);
+        }
+    }
+
+    void Container::drawSprites(SpriteBatch* spriteBatch, const Vector2& position)
+    {
+        std::vector<Control*>::const_iterator it;
+        for (it = _controls.begin(); it < _controls.end(); it++)
+        {
+            Control* control = *it;
+            control->drawSprites(spriteBatch, position);
+        }
+
+        _dirty = false;
+    }
+
+    void Container::drawText(const Vector2& position)
+    {
+        std::vector<Control*>::const_iterator it;
+        for (it = _controls.begin(); it < _controls.end(); it++)
+        {
+            Control* control = *it;
+            control->drawText(position);
+        }
+
+        _dirty = false;
+    }
+
+    bool Container::isDirty()
+    {
+        if (_dirty)
+        {
+            return true;
+        }
+        else
+        {
+            std::vector<Control*>::const_iterator it;
+            for (it = _controls.begin(); it < _controls.end(); it++)
+            {
+                if ((*it)->isDirty())
+                {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    void Container::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+    {
+        std::vector<Control*>::const_iterator it;
+        for (it = _controls.begin(); it < _controls.end(); it++)
+        {
+            Control* control = *it;
+            const Vector2& size = control->getSize();
+            const Vector2& position = control->getPosition();
+            
+            if (control->getState() == Control::STATE_ACTIVE ||
+                (x >= position.x &&
+                 x <= position.x + size.x &&
+                 y >= position.y &&
+                 y <= position.y + size.y))
+            {
+                // Pass on the event's position relative to the control.
+                control->touchEvent(evt, x - position.x, y - position.y, contactIndex);
+            }
+        }
+
+        switch (evt)
+        {
+        case Touch::TOUCH_PRESS:
+            setState(Control::STATE_ACTIVE);
+            break;
+        case Touch::TOUCH_RELEASE:
+            setState(Control::STATE_NORMAL);
+            break;
+        }
+    }
+}

+ 84 - 0
gameplay/src/Container.h

@@ -0,0 +1,84 @@
+#ifndef CONTAINER_H_
+#define CONTAINER_H_
+
+#include "Control.h"
+#include "Layout.h"
+
+namespace gameplay
+{
+
+class Container : public Control
+{
+public:
+    /**
+     * A Container's layout type must be specified at creation time.
+     */
+    static Container* create(const char* id, Layout::Type type);
+    static Container* getContainer(const char* id);
+
+    Layout* getLayout();
+
+    /**
+     * Add a control to this layout.
+     * The control will be assigned the next available index.
+     *
+     * @param control The Control to add.
+     *
+     * @return The index assigned to the added Control.
+     */
+    unsigned int addControl(Control* control);
+
+    /**
+     * Insert a Control at a specific index.
+     *
+     * @param control The Control to add.
+     * @param index The index at which to insert the Control.
+     */
+    void insertControl(Control* control, unsigned int index);
+
+    void removeControl(unsigned int index);
+    void removeControl(const char* id);
+    void removeControl(Control* control);
+
+    /**
+     * Get the Control at a specific index.
+     *
+     * @param index The index at which to retrieve the Control.
+     *
+     * @return The Control at the given index.
+     */
+    Control* getControl(unsigned int index) const;
+
+    /**
+     * Get a Control with a specific ID that belongs to this Layout.
+     *
+     * @param id The ID of the Control to search for.
+     */
+    Control* getControl(const char* id) const;
+
+    /**
+     * Updates the position of each Control within this Container
+     * according to the Container's Layout.
+     */
+    void update();
+
+    //void draw(Theme* theme, const Vector2& position);
+    void drawSprites(SpriteBatch* spriteBatch, const Vector2& position);
+    void drawText(const Vector2& position);
+
+    void touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
+
+protected:
+    Container();
+    Container(const Container& copy);
+    virtual ~Container();
+
+    bool isDirty();
+
+    Layout* _layout;
+    std::vector<Control*> _controls;
+};
+
+}
+
+#endif

+ 191 - 0
gameplay/src/Control.cpp

@@ -0,0 +1,191 @@
+#include "Base.h"
+#include "Control.h"
+
+namespace gameplay
+{
+    Control::Control()
+        : _id(""), _state(Control::STATE_NORMAL), _size(Vector2::zero()), _position(Vector2::zero()), _border(Vector2::zero()), _padding(Vector2::zero()),
+          _autoWidth(true), _autoHeight(true), _dirty(true)
+    {
+    }
+
+    Control::Control(const Control& copy)
+    {
+    }
+
+    Control::~Control()
+    {
+    }
+
+    const char* Control::getID()
+    {
+        return _id.c_str();
+    }
+
+    const Rectangle& Control::getBounds(bool includePadding) const
+    {
+        return Rectangle();
+    }
+
+    void Control::setPosition(float x, float y)
+    {
+        _position.set(x, y);
+    }
+
+    const Vector2& Control::getPosition() const
+    {
+        return _position;
+    }
+
+    void Control::setSize(float width, float height)
+    {
+        _size.set(width, height);
+    }
+
+    const Vector2& Control::getSize() const
+    {
+        return _size;
+    }
+
+    void Control::setAutoSize(bool width, bool height)
+    {
+        _autoWidth = width;
+        _autoHeight = height;
+    }
+
+    void Control::setBorder(float horizontal, float vertical)
+    {
+        _border.set(horizontal, vertical);
+    }
+
+    const Vector2& Control::getBorder() const
+    {
+        return _border;
+    }
+
+    void Control::setPadding(float horizontal, float vertical)
+    {
+        _padding.set(horizontal, vertical);
+    }
+
+    const Vector2& Control::getPadding() const
+    {
+        return _padding;
+    }
+
+    void Control::setStyle(Theme::Style* style)
+    {
+        _style = style;
+    }
+
+    Theme::Style* Control::getStyle() const
+    {
+        return _style;
+    }
+
+    void Control::setState(State state)
+    {
+        _state = state;
+    }
+
+    Control::State Control::getState()
+    {
+        return _state;
+    }
+
+    Theme::Style::OverlayType Control::getOverlayType()
+    {
+        switch (_state)
+        {
+        case Control::STATE_NORMAL:
+            return Theme::Style::OVERLAY_NORMAL;
+        case Control::STATE_FOCUS:
+            return Theme::Style::OVERLAY_FOCUS;
+        case Control::STATE_ACTIVE:
+            return Theme::Style::OVERLAY_ACTIVE;
+        default:
+            return Theme::Style::OVERLAY_NORMAL;
+        }
+    }
+
+    void Control::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+    {
+        // Empty stub to be implemented by Button and its descendents.
+    }
+
+    void Control::drawBorder(SpriteBatch* spriteBatch, const Vector2& position)
+    {
+        Vector2 pos(position.x + _position.x, position.y + _position.y);
+
+        // Get the overlay for this control's current state.
+        Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
+
+        if (overlay && !(overlay->getRegion().isEmpty()))
+        {
+            // Get the UVs.
+            Theme::UVs topLeft, top, topRight, left, center, right, bottomLeft, bottom, bottomRight;
+            topLeft = overlay->getUVs(Theme::Style::Overlay::TOP_LEFT);
+            top = overlay->getUVs(Theme::Style::Overlay::TOP);
+            topRight = overlay->getUVs(Theme::Style::Overlay::TOP_RIGHT);
+            left = overlay->getUVs(Theme::Style::Overlay::LEFT);
+            center = overlay->getUVs(Theme::Style::Overlay::CENTER);
+            right = overlay->getUVs(Theme::Style::Overlay::RIGHT);
+            bottomLeft = overlay->getUVs(Theme::Style::Overlay::BOTTOM_LEFT);
+            bottom = overlay->getUVs(Theme::Style::Overlay::BOTTOM);
+            bottomRight = overlay->getUVs(Theme::Style::Overlay::BOTTOM_RIGHT);
+
+            // Calculate screen-space positions.
+            Theme::Border border = _style->getBorder();
+            Theme::Padding padding = _style->getPadding();
+            Vector4 borderColor = overlay->getBorderColor();
+
+            float midWidth = _size.x - border.left - border.right;
+            float midHeight = _size.y - border.top - border.bottom;
+            float midX = pos.x + border.left;
+            float midY = pos.y + border.top;
+            float rightX = pos.x + _size.x - border.right;
+            float bottomY = pos.y + _size.y - border.bottom;
+
+            // Draw themed border sprites.
+            if (border.left && border.top)
+                spriteBatch->draw(pos.x, pos.y, border.left, border.top, topLeft.u1, topLeft.v1, topLeft.u2, topLeft.v2, borderColor);
+            if (border.top)
+                spriteBatch->draw(pos.x + border.left, pos.y, midWidth, border.top, top.u1, top.v1, top.u2, top.v2, borderColor);
+            if (border.right && border.top)
+                spriteBatch->draw(rightX, pos.y, border.right, border.top, topRight.u1, topRight.v1, topRight.u2, topRight.v2, borderColor);
+            if (border.left)
+                spriteBatch->draw(pos.x, midY, border.left, midHeight, left.u1, left.v1, left.u2, left.v2, borderColor);
+            if (border.left && border.right && border.top && border.bottom)
+                spriteBatch->draw(pos.x + border.left, pos.y + border.top, _size.x - border.left - border.right, _size.y - border.top - border.bottom,
+                              center.u1, center.v1, center.u2, center.v2, borderColor);
+            if (border.right)
+                spriteBatch->draw(rightX, midY, border.right, midHeight,
+                              right.u1, right.v1, right.u2, right.v2, borderColor);
+            if (border.bottom && border.left)
+                spriteBatch->draw(pos.x, bottomY, border.left, border.bottom, bottomLeft.u1, bottomLeft.v1, bottomLeft.u2, bottomLeft.v2, borderColor);
+            if (border.bottom)
+                spriteBatch->draw(midX, bottomY, midWidth, border.bottom, bottom.u1, bottom.v1, bottom.u2, bottom.v2, borderColor);
+            if (border.bottom && border.right)
+                spriteBatch->draw(rightX, bottomY, border.right, border.bottom, bottomRight.u1, bottomRight.v1, bottomRight.u2, bottomRight.v2, borderColor);
+        }
+    }
+
+    void Control::drawSprites(SpriteBatch* spriteBatch, const Vector2& position)
+    {
+    }
+
+    void Control::drawText(const Vector2& position)
+    {
+    }
+
+    bool Control::isDirty()
+    {
+        if (_dirty)
+        {
+            _dirty = false;
+            return true;
+        }
+
+        return false;
+    }
+}

+ 116 - 0
gameplay/src/Control.h

@@ -0,0 +1,116 @@
+#ifndef CONTROL_H_
+#define CONTROL_H_
+
+#include "Ref.h"
+#include "Rectangle.h"
+#include "Vector2.h"
+#include "SpriteBatch.h"
+#include "Theme.h"
+#include "Touch.h"
+
+namespace gameplay
+{
+
+class Control : public Ref
+{
+public:
+
+    enum State
+    {
+        STATE_NORMAL,
+        STATE_FOCUS,
+        STATE_ACTIVE
+    };
+
+    const char* getID();
+
+    /**
+     * Get the actual bounding box of this Control, local to its Container,
+     * after any calculations performed due to the Container's Layout or settings of auto-size.
+     * Always includes the Control's border.
+     * Can optionally include the Control's padding.
+     * Query getPosition() and getSize() to learn the bounds without border or padding.
+     */
+    const Rectangle& getBounds(bool includePadding) const;
+
+    /**
+     * Position of this Control relative to its parent Container.
+     */
+    void setPosition(float x, float y);
+    const Vector2& getPosition() const;
+
+    void setSize(float width, float height);
+    const Vector2& getSize() const;
+
+    /**
+     * Set width and/or height to auto-size to size a Control to tightly fit
+     * its text and themed visual elements (CheckBox / RadioButton toggle etc.).
+     *
+     * Similarly set this on the width and/or height of a Container to tightly fit
+     * the Container around all its children.
+     */
+    void setAutoSize(bool width, bool height);
+
+    /**
+     * 
+     * When auto-size is set on width and/or height:
+     * Space added to the calculated (tightly bound) width and height.
+     */
+    void setBorder(float horizontal, float vertical);
+    const Vector2& getBorder() const;
+
+    /**
+     * In a layout of any type other than Absolute,
+     * guarantee this much empty space surrounding this Control.
+     */
+    void setPadding(float horizontal, float vertical);
+    const Vector2& getPadding() const;
+
+    void setState(State state);
+    State getState();
+
+    Theme::Style::OverlayType getOverlayType();
+
+    /**
+     * Defaults to empty stub.
+     */
+    virtual void touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
+
+    /**
+     * Draws the themed border and background of a control.
+     */
+    void drawBorder(SpriteBatch* spriteBatch, const Vector2& position);
+    virtual void drawSprites(SpriteBatch* spriteBatch, const Vector2& position);
+    virtual void drawText(const Vector2& position);
+
+    /**
+     * Returns whether this Control has been modified since the last time
+     * isDirty() was called, and resets its dirty flag.
+     */
+    virtual bool isDirty();
+
+    void setStyle(Theme::Style* Style);
+    Theme::Style* getStyle() const;
+
+    void themeChanged();
+
+protected:
+    Control();
+    Control(const Control& copy);
+    virtual ~Control();
+
+    std::string _id;
+    State _state;           // Determines overlay used during draw().
+    Vector2 _size;
+    Vector2 _position;
+    Vector2 _border;
+    Vector2 _padding;
+    bool _autoWidth;
+    bool _autoHeight;
+    bool _dirty;
+    Theme::Style* _style;
+};
+
+}
+
+#endif

+ 24 - 0
gameplay/src/FlowLayout.h

@@ -0,0 +1,24 @@
+#ifndef FLOWLAYOUT_H_
+#define FLOWLAYOUT_H_
+
+#include "Layout.h"
+
+namespace gameplay
+{
+
+class FlowLayout : public Layout
+{
+public:
+    static FlowLayout* create();
+
+    void setRightToLeft(bool rightToLeft);
+
+private:
+    FlowLayout();
+    FlowLayout(const FlowLayout& copy);
+    virtual ~FlowLayout();
+};
+
+}
+
+#endif

+ 72 - 0
gameplay/src/Font.cpp

@@ -1117,4 +1117,76 @@ void Font::addLineInfo(const Rectangle& area, int lineWidth, int lineLength, Jus
     }
 }
 
+SpriteBatch* Font::getSpriteBatch()
+{
+    return _batch;
+}
+
+Font::Justify Font::getJustifyFromString(const char* justify)
+{
+    if (strcmp(justify, "ALIGN_LEFT") == 0)
+    {
+        return Font::ALIGN_LEFT;
+    }
+    else if (strcmp(justify, "ALIGN_HCENTER") == 0)
+    {
+        return Font::ALIGN_HCENTER;
+    }
+    else if (strcmp(justify, "ALIGN_RIGHT") == 0)
+    {
+        return Font::ALIGN_RIGHT;
+    }
+    else if (strcmp(justify, "ALIGN_TOP") == 0)
+    {
+        return Font::ALIGN_TOP;
+    }
+    else if (strcmp(justify, "ALIGN_VCENTER") == 0)
+    {
+        return Font::ALIGN_VCENTER;
+    }
+    else if (strcmp(justify, "ALIGN_BOTTOM") == 0)
+    {
+        return Font::ALIGN_BOTTOM;
+    }
+    else if (strcmp(justify, "ALIGN_TOP_LEFT") == 0)
+    {
+        return Font::ALIGN_TOP_LEFT;
+    }
+    else if (strcmp(justify, "ALIGN_VCENTER_LEFT") == 0)
+    {
+        return Font::ALIGN_VCENTER_LEFT;
+    }
+    else if (strcmp(justify, "ALIGN_BOTTOM_LEFT") == 0)
+    {
+        return Font::ALIGN_BOTTOM_LEFT;
+    }
+    else if (strcmp(justify, "ALIGN_TOP_HCENTER") == 0)
+    {
+        return Font::ALIGN_TOP_HCENTER;
+    }
+    else if (strcmp(justify, "ALIGN_VCENTER_HCENTER") == 0)
+    {
+        return Font::ALIGN_VCENTER_HCENTER;
+    }
+    else if (strcmp(justify, "ALIGN_BOTTOM_HCENTER") == 0)
+    {
+        return Font::ALIGN_BOTTOM_HCENTER;
+    }
+    else if (strcmp(justify, "ALIGN_TOP_RIGHT") == 0)
+    {
+        return Font::ALIGN_TOP_RIGHT;
+    }
+    else if (strcmp(justify, "ALIGN_VCENTER_RIGHT") == 0)
+    {
+        return Font::ALIGN_VCENTER_RIGHT;
+    }
+    else if (strcmp(justify, "ALIGN_BOTTOM_RIGHT") == 0)
+    {
+        return Font::ALIGN_BOTTOM_RIGHT;
+    }
+
+    // Default.
+    return Font::ALIGN_TOP_LEFT;
+}
+
 }

+ 4 - 0
gameplay/src/Font.h

@@ -175,6 +175,10 @@ public:
     void measureText(const char* text, const Rectangle& clip, unsigned int size, Rectangle* out,
                      Justify justify = ALIGN_TOP_LEFT, bool wrap = true, bool ignoreClip = false);
 
+    SpriteBatch* getSpriteBatch();
+
+    static Justify getJustifyFromString(const char* justify);
+
 
 private:
 

+ 376 - 0
gameplay/src/Form.cpp

@@ -0,0 +1,376 @@
+#include "Base.h"
+#include "Form.h"
+#include "AbsoluteLayout.h"
+#include "VerticalLayout.h"
+#include "Game.h"
+#include "Theme.h"
+#include "Label.h"
+#include "Button.h"
+#include "CheckBox.h"
+
+namespace gameplay
+{
+    static std::vector<Form*> __forms;
+
+    Form::Form() : _theme(NULL), _quad(NULL), _node(NULL), _frameBuffer(NULL)
+    {
+    }
+
+    Form::Form(const Form& copy)
+    {
+    }
+
+    Form::~Form()
+    {
+        SAFE_RELEASE(_quad);
+        SAFE_RELEASE(_node);
+        SAFE_RELEASE(_frameBuffer);
+        SAFE_RELEASE(_theme);
+        SAFE_DELETE(_viewport);
+
+        // Remove this Form from the global list.
+        std::vector<Form*>::iterator it = std::find(__forms.begin(), __forms.end(), this);
+        if (it != __forms.end())
+        {
+            __forms.erase(it);
+        }
+    }
+
+    Form* Form::create(const char* path)
+    {
+        // Load Form from .form file.
+        assert(path);
+
+        Properties* properties = Properties::create(path);
+        assert(properties);
+        if (properties == NULL)
+        {
+            return NULL;
+        }
+
+        // Check if the Properties is valid and has a valid namespace.
+        Properties* formProperties = properties->getNextNamespace();
+        assert(formProperties);
+        if (!formProperties || !(strcmp(formProperties->getNamespace(), "form") == 0))
+        {
+            SAFE_DELETE(properties);
+            return NULL;
+        }
+
+        // Create new form with given ID, theme and layout.
+        const char* id = formProperties->getId();
+        const char* themeFile = formProperties->getString("theme");
+        const char* layoutString = formProperties->getString("layout");
+        Form* form = Form::create(id, themeFile, getLayoutType(layoutString));
+
+        // Set form's position and dimensions.
+        formProperties->getVector2("position", &form->_position);
+        formProperties->getVector2("size", &form->_size);
+
+        // Set style from theme.
+        const char* styleName = formProperties->getString("style");
+        form->setStyle(form->getTheme()->getStyle(styleName));
+
+        // Add all the controls to the form.
+        Properties* controlSpace = formProperties->getNextNamespace();
+        while (controlSpace != NULL)
+        {
+            Control* control = NULL;
+
+            const char* controlStyleName = controlSpace->getString("style");
+            Theme::Style* controlStyle = NULL;
+            if (controlStyleName)
+            {
+                 controlStyle = form->getTheme()->getStyle(controlStyleName);
+            }
+
+            const char* controlName = controlSpace->getNamespace();
+            if (strcmp(controlName, "label") == 0)
+            {
+                control = Label::create(controlStyle, controlSpace);
+            }
+            else if (strcmp(controlName, "button") == 0)
+            {
+                control = Button::create(controlStyle, controlSpace);
+            }
+            else if (strcmp(controlName, "checkbox") == 0)
+            {
+                control = CheckBox::create(controlStyle, controlSpace);
+            }
+            else if (strcmp(controlName, "radioGroup") == 0)
+            {
+                //control = RadioGroup::create(controlStyle, controlSpace);
+            }
+
+            // Add the new control to the form.
+            if (control)
+            {
+                form->addControl(control);
+            }
+
+            // Get the next control.
+            controlSpace = formProperties->getNextNamespace();
+        }
+
+        return form;
+    }
+
+    Form* Form::create(const char* id, const char* themeFile, Layout::Type type)
+    {
+        Layout* layout;
+        switch(type)
+        {
+        case Layout::LAYOUT_ABSOLUTE:
+            layout = AbsoluteLayout::create();
+            break;
+        case Layout::LAYOUT_FLOW:
+            break;
+        case Layout::LAYOUT_VERTICAL:
+            layout = VerticalLayout::create();
+            break;
+        }
+
+        assert(themeFile);
+        Theme* theme = Theme::create(themeFile);
+        assert(theme);
+
+        Form* form = new Form();
+        form->_id = id;
+        form->_layout = layout;
+        form->_frameBuffer = FrameBuffer::create(id);
+        form->_theme = theme;
+
+        __forms.push_back(form);
+
+        return form;
+    }
+
+    Form* Form::getForm(const char* id)
+    {
+        std::vector<Form*>::const_iterator it;
+        for (it = __forms.begin(); it < __forms.end(); it++)
+        {
+            Form* f = *it;
+            if (strcmp(id, f->getID()) == 0)
+            {
+                return f;
+            }
+        }
+        
+        return NULL;
+    }
+
+    void Form::setTheme(Theme* theme)
+    {
+        _theme = theme;
+    }
+
+    Theme* Form::getTheme() const
+    {
+        return _theme;
+    }
+
+    void Form::setQuad(const Vector3& p1, const Vector3& p2, const Vector3& p3, const Vector3& p4)
+    {
+        Mesh* mesh = Mesh::createQuad(p1, p2, p3, p4);
+        initQuad(mesh);
+        SAFE_RELEASE(mesh);
+    }
+
+    void Form::setQuad(float x, float y, float width, float height)
+    {
+        Mesh* mesh = Mesh::createQuad(x, y, width, height);
+        initQuad(mesh);
+        SAFE_RELEASE(mesh);
+    }
+
+    void Form::setNode(Node* node)
+    {
+        // Connect the new node.
+        _node = node;
+        if (_node)
+        {
+            _node->setModel(_quad);
+        }
+
+        // Set this Form up to be 3D by initializing a quad, projection matrix and viewport.
+        setQuad(0.0f, 0.0f, _size.x, _size.y);
+        Matrix::createOrthographicOffCenter(0, _size.x, _size.y, 0, 0, 1, &_projectionMatrix);
+        _viewport = new Viewport(0, 0, _size.x, _size.y);
+    }
+
+    void Form::draw()
+    {
+        // If this Form has a Node then it's a 3D Form.  The contents will be rendered
+        // into a FrameBuffer which will be used to texture a quad.  The quad will be
+        // given the same dimensions as the form and must be transformed appropriately
+        // by the user (or they can call setQuad() themselves).
+        // "Billboard mode" should also be allowed for 3D Forms.
+
+        // On the other hand, if this Form has not been set on a Node it will render
+        // directly to the display.
+
+
+        // Check whether this Form has changed since the last call to draw()
+        // and if so, render into the FrameBuffer.
+        if (_node)
+        {
+            if (isDirty())
+            {
+                _theme->getSpriteBatch()->setProjectionMatrix(_projectionMatrix);
+                _frameBuffer->bind();
+                _viewport->bind();
+
+                // Clear form background color.
+                Game* game = Game::getInstance();
+                game->clear(Game::CLEAR_COLOR, _style->getOverlay(getOverlayType())->getBorderColor(), 1.0f, 0);
+
+                //draw(_theme, _position);
+                draw(_theme->getSpriteBatch(), _position);
+                FrameBuffer::bindDefault();
+
+                // Rebind the game viewport.
+                GL_ASSERT( glViewport(0, 0, game->getWidth(), game->getHeight()) );
+            }
+
+            _quad->draw();
+        }
+        else
+        {
+            draw(_theme->getSpriteBatch(), _position);
+        }
+    }
+
+    //void Form::draw(Theme* theme, const Vector2& position)
+    void Form::draw(SpriteBatch* spriteBatch, const Vector2& position)
+    {
+        std::vector<Control*>::const_iterator it;
+
+        // Batch all themed border and background sprites.
+        spriteBatch->begin();
+
+        // Draw the form's border and background.
+        // We don't pass the form's position to itself or it will be applied twice!
+        drawBorder(spriteBatch, Vector2::zero());
+
+        // Draw each control's border and background.
+        for (it = _controls.begin(); it < _controls.end(); it++)
+        {
+            Control* control = *it;
+
+            //if ((*it)->isDirty())
+            {
+                control->drawBorder(spriteBatch, position);
+
+                // Add all themed foreground sprites (checkboxes etc.) to the same batch.
+                control->drawSprites(spriteBatch, position);
+            }
+        }
+        spriteBatch->end();
+
+        // Draw all control foregrounds / text.
+        for (it = _controls.begin(); it < _controls.end(); it++)
+        {
+            Control* control = *it;
+
+            //if ((*it)->isDirty())
+            {
+                control->drawText(position);
+            }
+        }
+
+        _dirty = false;
+    }
+
+    void Form::initQuad(Mesh* mesh)
+    {
+        // Release current model.
+        SAFE_RELEASE(_quad);
+
+        // Create the model
+        _quad = Model::create(mesh);
+
+        // Create the material
+        Material* material = _quad->setMaterial("res/shaders/textured.vsh", "res/shaders/textured.fsh");
+
+        // Set the common render state block for the material
+        material->setStateBlock(_theme->getSpriteBatch()->getStateBlock());
+
+        // Bind the WorldViewProjection matrix
+        material->setParameterAutoBinding("u_worldViewProjectionMatrix", RenderState::WORLD_VIEW_PROJECTION_MATRIX);
+
+        // Use the FrameBuffer to texture the quad.
+        if (_frameBuffer->getRenderTarget() == NULL)
+        {
+            RenderTarget* rt = RenderTarget::create(_id.c_str(), _size.x, _size.y);
+            _frameBuffer->setRenderTarget(rt);
+            SAFE_RELEASE(rt);
+        }
+
+        Texture::Sampler* sampler = Texture::Sampler::create(_frameBuffer->getRenderTarget()->getTexture());
+        sampler->setWrapMode(Texture::CLAMP, Texture::CLAMP);
+        material->getParameter("u_texture")->setValue(sampler);
+
+        material->getParameter("u_textureRepeat")->setValue(Vector2::one());
+        material->getParameter("u_textureTransform")->setValue(Vector2::zero());
+
+        SAFE_RELEASE(sampler);
+    }
+
+    Layout::Type Form::getLayoutType(const char* layoutString)
+    {
+        if (strcmp(layoutString, "LAYOUT_ABSOLUTE") == 0)
+        {
+            return Layout::LAYOUT_ABSOLUTE;
+        }
+        else if (strcmp(layoutString, "LAYOUT_VERTICAL") == 0)
+        {
+            return Layout::LAYOUT_VERTICAL;
+        }
+        else if (strcmp(layoutString, "LAYOUT_FLOW") == 0)
+        {
+            return Layout::LAYOUT_FLOW;
+        }
+        else
+        {
+            // Default.
+            return Layout::LAYOUT_ABSOLUTE;
+        }
+    }
+
+    void Form::touchEventInternal(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+    {
+        // Check for a collision with each Form in __forms.
+        // Pass the event on.
+        std::vector<Form*>::const_iterator it;
+        for (it = __forms.begin(); it < __forms.end(); it++)
+        {
+            Form* form = *it;
+
+            if (form->_node)
+            {
+                // Project point into world space.
+
+                // Check for collision with form.
+
+                // Unproject point into form's space.
+            }
+            else
+            {
+                // Simply compare with the form's bounds.
+                const Vector2& size = form->getSize();
+                const Vector2& position = form->getPosition();
+
+                if (form->getState() == Control::STATE_ACTIVE ||
+                    (x >= position.x &&
+                     x <= position.x + size.x &&
+                     y >= position.y &&
+                     y <= position.y + size.y))
+                {
+                    // Pass on the event's position relative to the form.
+                    form->touchEvent(evt, x - position.x, y - position.y, contactIndex);
+                }
+            }
+        }
+    }
+}

+ 68 - 0
gameplay/src/Form.h

@@ -0,0 +1,68 @@
+#ifndef FORM_H_
+#define FORM_H_
+
+#include "Ref.h"
+#include "Container.h"
+#include "Mesh.h"
+#include "Node.h"
+#include "FrameBuffer.h"
+#include "Touch.h"
+
+namespace gameplay
+{
+
+class Theme;
+
+class Form : public Container
+{
+    friend class Platform;
+
+public:
+    /**
+     * Create from .form file.
+     */
+    static Form* create(const char* path);
+    static Form* create(const char* id, const char* textureFile, Layout::Type type);
+    static Form* getForm(const char* id);
+
+    void setTheme(Theme* theme);
+    Theme* getTheme() const;
+
+    /**
+     * Create a 3D quad to texture with this Form.
+     */
+    void setQuad(const Vector3& p1, const Vector3& p2, const Vector3& p3, const Vector3& p4);
+
+    /**
+     * Create a 2D quad to texture with this Form.
+     */
+    void setQuad(float x, float y, float width, float height);
+
+    void setNode(Node* node);
+
+    void draw();
+
+private:
+    Form();
+    Form(const Form& copy);
+    virtual ~Form();
+
+    void initQuad(Mesh* mesh);
+    void draw(SpriteBatch* spriteBatch);
+    void draw(SpriteBatch* spriteBatch, const Vector2& position);
+
+    static void touchEventInternal(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
+    
+    static Layout::Type getLayoutType(const char* layoutString);
+
+    Theme* _theme;              // The Theme applied to this Form.
+    Model* _quad;               // Quad for rendering this Form in world-space.
+    Node* _node;                // Node for transforming this Form in world-space.
+    FrameBuffer* _frameBuffer;  // FBO the Form is rendered into for texturing the quad.
+    Matrix _projectionMatrix;   // Orthographic projection matrix to be set on SpriteBatch objects when rendering into the FBO.
+    Viewport* _viewport;        // Viewport for setting before rendering into the FBO.
+};
+
+}
+
+#endif

+ 86 - 0
gameplay/src/Label.cpp

@@ -0,0 +1,86 @@
+#include "Base.h"
+#include "Label.h"
+
+namespace gameplay
+{
+    static std::vector<Label*> __labels;
+
+    Label::Label() : _text("")
+    {
+
+    }
+
+    Label::Label(const Label& copy)
+    {
+    }
+
+    Label::~Label()
+    {
+    }
+
+    Label* Label::create(Theme::Style* style, Properties* properties)
+    {
+        Label* label = new Label();
+        label->_style = style;
+        label->_id = properties->getId();
+        properties->getVector2("position", &label->_position);
+        properties->getVector2("size", &label->_size);
+        label->_text = properties->getString("text");
+
+        __labels.push_back(label);
+
+        return label;
+    }
+
+    Label* Label::getLabel(const char* id)
+    {
+        std::vector<Label*>::const_iterator it;
+        for (it = __labels.begin(); it < __labels.end(); it++)
+        {
+            Label* l = *it;
+            if (strcmp(id, l->getID()) == 0)
+            {
+                return l;
+            }
+        }
+
+        return NULL;
+    }
+    
+    void Label::setText(const char* text)
+    {
+        if (text)
+        {
+            _text = text;
+        }
+    }
+
+    const char* Label::getText()
+    {
+        return _text.c_str();
+    }
+
+    void Label::drawText(const Vector2& position)
+    {
+        // TODO: Batch all labels that use the same font.
+        Vector2 pos(position.x + _position.x, position.y + _position.y);
+        Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
+        Theme::Border border = _style->getBorder();
+        Theme::Padding padding = _style->getPadding();
+
+        // Set up the text viewport.
+        Font* font = overlay->getFont();
+        Rectangle viewport(pos.x + border.left + padding.left,
+                           pos.y + border.top + padding.top,
+                           _size.x - border.left - padding.left - border.right - padding.right,
+                           _size.y - border.top - padding.top - border.bottom - padding.bottom - font->getSize());
+
+        // Draw the text.
+        
+        font->begin();
+        font->drawText(_text.c_str(), viewport, overlay->getTextColor(), overlay->getFontSize(), overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
+        font->end();
+
+        _dirty = false;
+    }
+}

+ 33 - 0
gameplay/src/Label.h

@@ -0,0 +1,33 @@
+#ifndef LABEL_H_
+#define LABEL_H_
+
+#include "Control.h"
+#include "Theme.h"
+
+namespace gameplay
+{
+
+    class Theme;
+
+class Label : public Control
+{
+public:
+    static Label* create(Theme::Style* style, Properties* properties);
+    static Label* getLabel(const char* id);
+    
+    void setText(const char* text);
+    const char* getText();
+
+    void drawText(const Vector2& position);
+
+protected:
+    Label();
+    Label(const Label& copy);
+    virtual ~Label();
+
+    std::string _text;
+};
+
+}
+
+#endif

+ 32 - 0
gameplay/src/Layout.h

@@ -0,0 +1,32 @@
+#ifndef LAYOUT_H_
+#define LAYOUT_H_
+
+#include "Ref.h"
+#include "Control.h"
+
+namespace gameplay
+{
+
+class Layout : public Ref
+{
+public:
+    enum Type
+    {
+        LAYOUT_FLOW,
+        LAYOUT_VERTICAL,
+        LAYOUT_ABSOLUTE
+    };
+
+    virtual Type getType() = 0;
+
+    virtual void update(std::vector<Control*> controls, const Vector2& size) = 0;
+
+protected:
+    //Layout();
+    //Layout(const Layout& copy);
+    //virtual ~Layout();
+};
+
+}
+
+#endif

+ 26 - 1
gameplay/src/Node.cpp

@@ -12,7 +12,7 @@ namespace gameplay
 
 Node::Node(const char* id)
     : _scene(NULL), _firstChild(NULL), _nextSibling(NULL), _prevSibling(NULL), _parent(NULL), _childCount(NULL),
-    _camera(NULL), _light(NULL), _model(NULL), _audioSource(NULL), _particleEmitter(NULL), _physicsRigidBody(NULL), 
+    _camera(NULL), _light(NULL), _model(NULL), _form(NULL), _audioSource(NULL), _particleEmitter(NULL), _physicsRigidBody(NULL), 
     _dirtyBits(NODE_DIRTY_ALL), _notifyHierarchyChanged(true)
 {
     if (id)
@@ -567,6 +567,31 @@ Model* Node::getModel() const
     return _model;
 }
 
+void Node::setForm(Form* form)
+{
+    if (_form != form)
+    {
+        if (_form)
+        {
+            _form->setNode(NULL);
+            SAFE_RELEASE(_form);
+        }
+
+        _form = form;
+
+        if (_form)
+        {
+            _form->addRef();
+            _form->setNode(this);
+        }
+    }
+}
+
+Form* Node::getForm() const
+{
+    return _form;
+}
+
 const BoundingSphere& Node::getBoundingSphere() const
 {
     if (_dirtyBits & NODE_DIRTY_BOUNDS)

+ 6 - 0
gameplay/src/Node.h

@@ -5,6 +5,7 @@
 #include "Camera.h"
 #include "Light.h"
 #include "Model.h"
+#include "Form.h"
 #include "AudioSource.h"
 #include "ParticleEmitter.h"
 #include "PhysicsRigidBody.h"
@@ -15,6 +16,7 @@ namespace gameplay
 
 class Package;
 class Scene;
+class Form;
 
 /**
  * Defines a basic hierachial structure of transformation spaces.
@@ -310,6 +312,9 @@ public:
      */
     void setModel(Model* model);
 
+    Form* getForm() const;
+    void setForm(Form* form);
+
     /**
      * Returns the pointer to this node's audio source.
      *
@@ -448,6 +453,7 @@ protected:
     Camera* _camera;
     Light* _light;
     Model* _model;
+    Form* _form;
     AudioSource* _audioSource;
     ParticleEmitter* _particleEmitter;
     PhysicsRigidBody* _physicsRigidBody;

+ 4 - 0
gameplay/src/Platform.h

@@ -1,6 +1,8 @@
 #ifndef PLATFORM_H_
 #define PLATFORM_H_
 
+#include "Touch.h"
+
 namespace gameplay
 {
 
@@ -95,6 +97,8 @@ public:
      */
     static void swapBuffers();
 
+    static void touchEventInternal(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
+
 private:
 
     /**

+ 9 - 3
gameplay/src/PlatformMacOS.mm

@@ -178,14 +178,14 @@ static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTime
 {
     NSPoint point = [event locationInWindow];
     __leftMouseDown = true;
-    _game->touchEvent(Touch::TOUCH_PRESS, point.x, WINDOW_HEIGHT - point.y, 0);
+    gameplay::Platform::touchEventInternal(Touch::TOUCH_PRESS, point.x, WINDOW_HEIGHT - point.y, 0);
 }
 
 - (void) mouseUp: (NSEvent*) event
 {
     NSPoint point = [event locationInWindow];
     __leftMouseDown = false;
-    _game->touchEvent(Touch::TOUCH_RELEASE, point.x, WINDOW_HEIGHT - point.y, 0);
+    gameplay::Platform::touchEventInternal(Touch::TOUCH_RELEASE, point.x, WINDOW_HEIGHT - point.y, 0);
 }
 
 - (void) mouseDragged: (NSEvent*) event
@@ -193,7 +193,7 @@ static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTime
     NSPoint point = [event locationInWindow];
     if (__leftMouseDown)
     {
-        _game->touchEvent(Touch::TOUCH_MOVE, point.x, WINDOW_HEIGHT - point.y, 0);
+        gameplay::Platform::touchEventInternal(Touch::TOUCH_MOVE, point.x, WINDOW_HEIGHT - point.y, 0);
     }
 }
 
@@ -594,6 +594,12 @@ void Platform::swapBuffers()
         CGLFlushDrawable((CGLContextObj)[[__view openGLContext] CGLContextObj]);
 }
 
+void Platform::touchEventInternal(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+{
+    Game::getInstance()->touchEvent(evt, x, y, contactIndex);
+    Form::touchEventInternal(evt, x, y, contactIndex);
+}
+
 }
 
 #endif

+ 12 - 6
gameplay/src/PlatformQNX.cpp

@@ -710,12 +710,12 @@ int Platform::enterMessagePump()
                         if (!__multiTouch)
                         {
                             screen_get_event_property_iv(__screenEvent, SCREEN_PROPERTY_POSITION, position);
-                           Game::getInstance()->touchEvent(Touch::TOUCH_PRESS, position[0], position[1], 0);
+                           gameplay::Platform::touchEventInternal(Touch::TOUCH_PRESS, position[0], position[1], 0);
                         }
                         else
                         {
                             screen_get_mtouch_event(__screenEvent, &touchEvent, 0);
-                            Game::getInstance()->touchEvent(Touch::TOUCH_PRESS, touchEvent.x, touchEvent.y, touchEvent.contact_id);
+                            gameplay::Platform::touchEventInternal(Touch::TOUCH_PRESS, touchEvent.x, touchEvent.y, touchEvent.contact_id);
                         }
                         break;
                     }
@@ -725,12 +725,12 @@ int Platform::enterMessagePump()
                         if (!__multiTouch)
                         {
                             screen_get_event_property_iv(__screenEvent, SCREEN_PROPERTY_POSITION, position);
-                           Game::getInstance()->touchEvent(Touch::TOUCH_RELEASE, position[0], position[1], 0);
+                           gameplay::Platform::touchEventInternal(Touch::TOUCH_RELEASE, position[0], position[1], 0);
                         }
                         else
                         {
                             screen_get_mtouch_event(__screenEvent, &touchEvent, 0);
-                            Game::getInstance()->touchEvent(Touch::TOUCH_RELEASE, touchEvent.x, touchEvent.y, touchEvent.contact_id);
+                            gameplay::Platform::touchEventInternal(Touch::TOUCH_RELEASE, touchEvent.x, touchEvent.y, touchEvent.contact_id);
                         }
                         break;
                     }
@@ -740,12 +740,12 @@ int Platform::enterMessagePump()
                         if (!__multiTouch)
                         {
                             screen_get_event_property_iv(__screenEvent, SCREEN_PROPERTY_POSITION, position);
-                           Game::getInstance()->touchEvent(Touch::TOUCH_MOVE, position[0], position[1], 0);
+                           gameplay::Platform::touchEventInternal(Touch::TOUCH_MOVE, position[0], position[1], 0);
                         }
                         else
                         {
                             screen_get_mtouch_event(__screenEvent, &touchEvent, 0);
-                            Game::getInstance()->touchEvent(Touch::TOUCH_MOVE, touchEvent.x, touchEvent.y, touchEvent.contact_id);
+                            gameplay::Platform::touchEventInternal(Touch::TOUCH_MOVE, touchEvent.x, touchEvent.y, touchEvent.contact_id);
                         }
                         break;
                         break;
@@ -880,6 +880,12 @@ void Platform::swapBuffers()
         eglSwapBuffers(__eglDisplay, __eglSurface);
 }
 
+void Platform::touchEventInternal(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+{
+    Game::getInstance()->touchEvent(evt, x, y, contactIndex);
+    Form::touchEventInternal(evt, x, y, contactIndex);
+}
+
 }
 
 #endif

+ 9 - 3
gameplay/src/PlatformWin32.cpp

@@ -267,13 +267,13 @@ LRESULT CALLBACK __WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
         return 0;
 
     case WM_LBUTTONDOWN:
-        gameplay::Game::getInstance()->touchEvent(gameplay::Touch::TOUCH_PRESS, LOWORD(lParam), HIWORD(lParam), 0);
+        gameplay::Platform::touchEventInternal(gameplay::Touch::TOUCH_PRESS, LOWORD(lParam), HIWORD(lParam), 0);
         lMouseDown = true;
         return 0;
 
     case WM_LBUTTONUP:
         lMouseDown = false;
-        gameplay::Game::getInstance()->touchEvent(gameplay::Touch::TOUCH_RELEASE, LOWORD(lParam), HIWORD(lParam), 0);
+        gameplay::Platform::touchEventInternal(gameplay::Touch::TOUCH_RELEASE, LOWORD(lParam), HIWORD(lParam), 0);
         return 0;
 
     case WM_RBUTTONDOWN:
@@ -301,7 +301,7 @@ LRESULT CALLBACK __WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
 
         if (lMouseDown)
         {
-            gameplay::Game::getInstance()->touchEvent(gameplay::Touch::TOUCH_MOVE, LOWORD(lParam), HIWORD(lParam), 0);
+            gameplay::Platform::touchEventInternal(gameplay::Touch::TOUCH_MOVE, LOWORD(lParam), HIWORD(lParam), 0);
             return 0;
         }
         else if (rMouseDown)
@@ -586,6 +586,12 @@ void Platform::swapBuffers()
         SwapBuffers(__hdc);
 }
 
+void Platform::touchEventInternal(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+{
+    Game::getInstance()->touchEvent(evt, x, y, contactIndex);
+    Form::touchEventInternal(evt, x, y, contactIndex);
+}
+
 }
 
 #endif

+ 138 - 7
gameplay/src/Properties.cpp

@@ -13,12 +13,16 @@ Properties::Properties(FILE* file)
     _namespacesItr = _namespaces.end();
 }
 
-Properties::Properties(FILE* file, const char* name, const char* id) : _namespace(name)
+Properties::Properties(FILE* file, const char* name, const char* id, const char* parentID) : _namespace(name)
 {
     if (id)
     {
         _id = id;
     }
+    if (parentID)
+    {
+        _parentID = parentID;
+    }
     readProperties(file);
     _propertiesItr = _properties.end();
     _namespacesItr = _namespaces.end();
@@ -36,6 +40,8 @@ Properties* Properties::create(const char* filePath)
 
     Properties* properties = new Properties(file);
 
+    properties->resolveInheritance();
+
     fclose(file);
 
     return properties;
@@ -47,7 +53,9 @@ void Properties::readProperties(FILE* file)
     int c;
     char* name;
     char* value;
+    char* parentID;
     char* rc;
+    char* rcc;
 
     while (true)
     {
@@ -104,11 +112,16 @@ void Properties::readProperties(FILE* file)
             }
             else
             {
+                parentID = NULL;
+
                 // This line might begin or end a namespace,
                 // or it might be a key/value pair without '='.
 
                 // Check for '{' on same line.
                 rc = strchr(line, '{');
+
+                // Check for inheritance: ':'
+                rcc = strchr(line, ':');
             
                 // Get the name of the namespace.
                 name = strtok(line, " \t\n{");
@@ -124,12 +137,18 @@ void Properties::readProperties(FILE* file)
                 }
 
                 // Get its ID if it has one.
-                value = strtok(NULL, "{");
+                value = strtok(NULL, ":{");
                 value = trimWhiteSpace(value);
+                if (rcc != NULL)
+                {
+                    parentID = strtok(NULL, "{");
+                    parentID = trimWhiteSpace(parentID);
+                }
+
                 if (value != NULL && value[0] == '{')
                 {
                     // New namespace without an ID.
-                    Properties* space = new Properties(file, name, NULL);
+                    Properties* space = new Properties(file, name, NULL, parentID);
                     _namespaces.push_back(space);
                 }
                 else
@@ -138,7 +157,7 @@ void Properties::readProperties(FILE* file)
                     if (rc != NULL)
                     {
                         // Create new namespace.
-                        Properties* space = new Properties(file, name, value);
+                        Properties* space = new Properties(file, name, value, parentID);
                         _namespaces.push_back(space);
                     }
                     else
@@ -149,7 +168,7 @@ void Properties::readProperties(FILE* file)
                         if (c == '{')
                         {
                             // Create new namespace.
-                            Properties* space = new Properties(file, name, value);
+                            Properties* space = new Properties(file, name, value, parentID);
                             _namespaces.push_back(space);
                         }
                         else
@@ -228,7 +247,119 @@ char* Properties::trimWhiteSpace(char *str)
     return str;
 }
 
-const char* Properties::getNextProperty(const char** value)
+void Properties::resolveInheritance(const char* id)
+{
+    // Namespaces can be defined like so:
+    // "name id : parentID { }"
+    // This method merges data from the parent namespace into the child.
+
+    // Get a top-level namespace.
+    Properties* derived;
+    if (id)
+    {
+        derived = getNamespace(id);
+    }
+    else
+    {
+        derived = getNextNamespace();
+    }
+    while (derived)
+    {
+        // If the namespace has a parent ID, find the parent.
+        if (!derived->_parentID.empty())
+        {
+            Properties* parent = getNamespace(derived->_parentID.c_str());
+            if (parent)
+            {
+                resolveInheritance(parent->getId());
+
+                // Copy the child.
+                Properties* overrides = new Properties(*derived);
+                overrides->_propertiesItr = overrides->_properties.end();
+                overrides->_namespacesItr = overrides->_namespaces.end();
+
+                // Copy data from the parent into the child.
+                derived->_properties = parent->_properties;
+                derived->_propertiesItr = derived->_properties.end();
+                derived->_namespaces = std::vector<Properties*>();
+                std::vector<Properties*>::const_iterator it;
+                for (it = parent->_namespaces.begin(); it < parent->_namespaces.end(); it++)
+                {
+                    derived->_namespaces.push_back(new Properties(**it));
+                }
+                derived->_namespacesItr = derived->_namespaces.end();
+
+                // Take the original copy of the child and override the data copied from the parent.
+                derived->mergeWith(overrides);
+
+                // Delete the child copy.
+                delete overrides;
+            }
+        }
+
+        // Resolve inheritance within this namespace.
+        derived->resolveInheritance();
+
+        // Get the next top-level namespace and check again.
+        if (!id)
+        {
+            derived = getNextNamespace();
+        }
+        else
+        {
+            derived = NULL;
+        }
+    }
+}
+
+void Properties::mergeWith(Properties* overrides)
+{
+    // Overwrite or add each property found in child.
+    char* value = new char[255];
+    const char* name = overrides->getNextProperty(&value);
+    while (name)
+    {
+        this->_properties[name] = value;
+        name = overrides->getNextProperty(&value);
+    }
+    this->_propertiesItr = this->_properties.end();
+
+    // Merge all common nested namespaces, add new ones.
+    Properties* overridesNamespace = overrides->getNextNamespace();
+    while (overridesNamespace)
+    {
+        bool merged = false;
+
+        rewind();
+        Properties* derivedNamespace = getNextNamespace();
+        while (derivedNamespace)
+        {
+            if (strcmp(derivedNamespace->getNamespace(), overridesNamespace->getNamespace()) == 0 &&
+                strcmp(derivedNamespace->getId(), overridesNamespace->getId()) == 0)
+            {
+                derivedNamespace->mergeWith(overridesNamespace);
+                merged = true;
+            }
+
+            derivedNamespace = getNextNamespace();
+        }
+
+        if (!merged)
+        {
+            // Add this new namespace.
+            Properties* newNamespace = new Properties(*overridesNamespace);
+            newNamespace->_propertiesItr = newNamespace->_properties.end();
+            newNamespace->_namespacesItr = newNamespace->_namespaces.end();
+
+            this->_namespaces.push_back(newNamespace);
+            this->_namespacesItr = this->_namespaces.end();
+        }
+
+        overridesNamespace = overrides->getNextNamespace();
+    }
+}
+
+const char* Properties::getNextProperty(char** value)
 {
     if (_propertiesItr == _properties.end())
     {
@@ -248,7 +379,7 @@ const char* Properties::getNextProperty(const char** value)
         {
             if (value)
             {
-                *value = _propertiesItr->second.c_str();
+                strcpy(*value, _propertiesItr->second.c_str());
             }
             return name.c_str();
         }

+ 12 - 3
gameplay/src/Properties.h

@@ -97,7 +97,7 @@ void printProperties(Properties* properties)
 {
     // Print the name and ID of the current namespace.
     const char* spacename = properties->getNamespace();
-    const char* id = properties->getID();
+    const char* id = properties->getId();
     WARN_VARG("Namespace: %s  ID: %s\n{", spacename, id);
  
     // Print all properties in this namespace.
@@ -163,7 +163,7 @@ public:
      * 
      * @return The name of the next property, or NULL if there are no properties remaining.
      */
-    const char* getNextProperty(const char** value = NULL);
+    const char* getNextProperty(char** value = NULL);
 
     /**
      * Get the next namespace.
@@ -378,7 +378,9 @@ private:
     /**
      * Constructor. Read from the beginning of namespace specified
      */
-    Properties(FILE* file, const char* name, const char* id = NULL);
+    Properties(FILE* file, const char* name, const char* id = NULL, const char* parentID = NULL);
+
+    //Properties(const Properties& copy);
 
     void readProperties(FILE* file);
 
@@ -386,8 +388,15 @@ private:
 
     char* trimWhiteSpace(char* str);
 
+    // Called after create(); copies info from parents into derived namespaces.
+    void resolveInheritance(const char* id = NULL);
+
+    // Called by resolveInheritance().
+    void mergeWith(Properties* overrides);
+
     std::string _namespace;
     std::string _id;
+    std::string _parentID;
     std::map<std::string, std::string> _properties;
     std::map<std::string, std::string>::const_iterator _propertiesItr;
     std::vector<Properties*> _namespaces;

+ 694 - 0
gameplay/src/Theme.cpp

@@ -0,0 +1,694 @@
+/*
+ * Theme.cpp
+ */
+
+#include "Base.h"
+#include "Theme.h"
+
+namespace gameplay
+{
+    static std::vector<Theme*> __themeCache;
+
+    Theme::Theme()
+        //: _texture(NULL)
+    {
+    }
+
+    Theme::Theme(const Theme* theme)
+    {
+    }
+
+    Theme::~Theme()
+    {
+        // Destroy all the cursors, styles and , fonts.
+        for (unsigned int i = 0, count = _cursors.size(); i < count; ++i)
+        {
+            Cursor* cursor = _cursors[i];
+            if (cursor)
+            {
+                delete cursor;
+            }
+        }
+
+        for (unsigned int i = 0, count = _styles.size(); i < count; ++i)
+        {
+            Style* style = _styles[i];
+            if (style)
+            {
+                delete style;
+            }
+        }
+
+        for (unsigned int i = 0, count = _fonts.size(); i < count; ++i)
+        {
+            Font* font = _fonts[i];
+            if (font)
+            {
+                SAFE_RELEASE(font);
+            }
+        }
+
+        SAFE_DELETE(_spriteBatch);
+
+        // Remove ourself from the theme cache.
+        std::vector<Theme*>::iterator itr = find(__themeCache.begin(), __themeCache.end(), this);
+        if (itr != __themeCache.end())
+        {
+            __themeCache.erase(itr);
+        }
+    }
+
+    Theme::Style::Overlay::~Overlay()
+    {
+        SAFE_RELEASE(_font);
+    }
+
+    Theme::Style::~Style()
+    {
+        for (int i = 0; i < MAX_OVERLAYS; i++)
+        {
+            SAFE_DELETE(_overlays[i]);
+        }
+    }
+
+    Theme* Theme::create(const char* path)
+    {
+        assert(path);
+
+        // Search theme cache first.
+        for (unsigned int i = 0, count = __themeCache.size(); i < count; ++i)
+        {
+            Theme* t = __themeCache[i];
+            if (t->_path == path)
+            {
+                // Found a match.
+                t->addRef();
+
+                return t;
+            }
+        }
+
+        // Load theme properties from file path.
+        Properties* properties = Properties::create(path);
+        assert(properties);
+        if (properties == NULL)
+        {
+            return NULL;
+        }
+
+        // Check if the Properties is valid and has a valid namespace.
+        Properties* themeProperties = properties->getNextNamespace();
+        assert(themeProperties);
+        if (!themeProperties || !(strcmp(themeProperties->getNamespace(), "theme") == 0))
+        {
+            SAFE_DELETE(properties);
+            return NULL;
+        }
+
+        // Create a new theme.
+        Theme* theme = new Theme();
+        theme->_path = path;
+        
+        // Parse the Properties object and set up the theme.
+        const char* textureFile = themeProperties->getString("texture");
+        theme->_texture = Texture::create(textureFile, true);
+        theme->_spriteBatch = SpriteBatch::create(theme->_texture);
+
+        Properties* space = themeProperties->getNextNamespace();
+        while (space != NULL)
+        {
+            // First load all cursors, checkboxes etc. that are referred to be styles.
+            const char* spacename = space->getNamespace();
+            if (strcmp(spacename, "cursor") == 0)
+            {
+                Vector4 regionVector;                
+                space->getVector4("region", &regionVector);
+                const Rectangle region(regionVector.x, regionVector.y, regionVector.z, regionVector.w);
+
+                Vector4 color;
+                space->getColor("color", &color);
+
+                Theme::Cursor* c = new Theme::Cursor(space->getId(), region, color);
+                theme->_cursors.push_back(c);
+            }
+            else if (strcmp(spacename, "checkBox") == 0)
+            {
+                Vector2 unchecked;
+                space->getVector2("uncheckedPosition", &unchecked);
+                Vector2 checked;
+                space->getVector2("checkedPosition", &checked);
+                Vector2 size;
+                space->getVector2("size", &size);
+
+                float tw = 1.0f / theme->_texture->getWidth();
+                float th = 1.0f / theme->_texture->getHeight();
+
+                UVs on;
+                on.u1 = checked.x * tw;
+                on.u2 = (checked.x + size.x) * tw;
+                on.v1 = 1.0f - (checked.y * tw);
+                on.v2 = 1.0f - ((checked.y + size.y) * tw);
+
+                UVs off;
+                off.u1 = unchecked.x * tw;
+                off.u2 = (unchecked.x + size.x) * tw;
+                off.v1 = 1.0f - (unchecked.y * tw);
+                off.v2 = 1.0f - ((unchecked.y + size.y) * tw);
+
+                CheckBoxIcon* icon = new CheckBoxIcon();
+                icon->id = space->getId();
+                icon->on = on;
+                icon->off = off;
+                icon->size = size;
+
+                theme->_checkBoxIcons.push_back(icon);
+            }
+            else if (strcmp(spacename, "radiobutton") == 0)
+            {
+
+            }
+
+            space = themeProperties->getNextNamespace();
+        }
+
+        themeProperties->rewind();
+        space = themeProperties->getNextNamespace();
+        while (space != NULL)
+        {
+            const char* spacename = space->getNamespace();
+            if (strcmp(spacename, "style") == 0)
+            {
+                // Each style contains up to MAX_OVERLAYS overlays,
+                // as well as Border and Padding namespaces.
+                Theme::Margin margin;
+                Theme::Border border;
+                Theme::Padding padding;
+                Theme::Style::Overlay* normal = NULL;
+                Theme::Style::Overlay* focus = NULL;
+                Theme::Style::Overlay* active = NULL;
+
+                // Need to load OVERLAY_NORMAL first so that the other overlays can inherit from it.
+                Properties* innerSpace = space->getNextNamespace();
+                while (innerSpace != NULL)
+                {
+                    const char* innerSpacename = innerSpace->getNamespace();
+                    if (strcmp(innerSpacename, "normal") == 0)
+                    {
+                        Vector4 regionVector;
+                        innerSpace->getVector4("region", &regionVector);
+                        const Rectangle region = Rectangle(regionVector.x, regionVector.y, regionVector.z, regionVector.w);
+
+                        Vector4 textColor;
+                        Vector4 borderColor;
+                        innerSpace->getColor("textColor", &textColor);
+                        innerSpace->getColor("borderColor", &borderColor);
+
+                        // Font info.
+                        const char* fontPath = innerSpace->getString("font");
+                        Font* font = NULL;
+                        if (fontPath)
+                        {
+                            font = Font::create(fontPath);
+                        }
+                        unsigned int fontSize = innerSpace->getInt("fontSize");
+                        const char* alignmentString = innerSpace->getString("alignment");
+                        Font::Justify alignment = Font::ALIGN_TOP_LEFT;
+                        if (alignmentString)
+                        {
+                            alignment = Font::getJustifyFromString(alignmentString);
+                        }
+                        bool rightToLeft = innerSpace->getBool("rightToLeft");
+
+                        const char* checkBoxString = innerSpace->getString("checkBox");
+                        CheckBoxIcon* icon = NULL;
+                        if (checkBoxString)
+                        {
+                            for (unsigned int i = 0; i < theme->_checkBoxIcons.size(); i++)
+                            {
+                                if (strcmp(theme->_checkBoxIcons[i]->id.c_str(), checkBoxString) == 0)
+                                {
+                                    icon = theme->_checkBoxIcons[i];
+                                    break;
+                                }
+                            }
+                        }
+
+                        // TODO: Cursor.
+
+                        normal = new Theme::Style::Overlay(Theme::Style::OVERLAY_NORMAL);
+                        normal->setRegion(region);
+                        normal->setTextColor(textColor);
+                        normal->setBorderColor(borderColor);
+                        normal->setFont(font);
+                        normal->setFontSize(fontSize);
+                        normal->setTextAlignment(alignment);
+                        normal->setTextRightToLeft(rightToLeft);
+                        normal->setCheckBoxIcon(icon);
+
+                        // Done with this pass.
+                        break;
+                    }
+
+                    innerSpace = space->getNextNamespace();
+                }
+
+                // At least the OVERLAY_NORMAL is required.
+                assert(normal);
+
+                space->rewind();
+                innerSpace = space->getNextNamespace();
+                while (innerSpace != NULL)
+                {
+                    const char* innerSpacename = innerSpace->getNamespace();
+                    if (strcmp(innerSpacename, "margin") == 0)
+                    {
+                        margin.top = innerSpace->getFloat("top");
+                        margin.bottom = innerSpace->getFloat("bottom");
+                        margin.left = innerSpace->getFloat("left");
+                        margin.right = innerSpace->getFloat("right");
+                    }
+                    else if (strcmp(innerSpacename, "border") == 0)
+                    {
+                        border.top = innerSpace->getFloat("top");
+                        border.bottom = innerSpace->getFloat("bottom");
+                        border.left = innerSpace->getFloat("left");
+                        border.right = innerSpace->getFloat("right");
+                    }
+                    else if (strcmp(innerSpacename, "padding") == 0)
+                    {
+                        padding.top = innerSpace->getFloat("top");
+                        padding.bottom = innerSpace->getFloat("bottom");
+                        padding.left = innerSpace->getFloat("left");
+                        padding.right = innerSpace->getFloat("right");
+                    }
+                    else if (strcmp(innerSpacename, "normal") != 0)
+                    {
+                        // Either OVERLAY_FOCUS or OVERLAY_ACTIVE.
+                        // If a property isn't specified, it inherits from OVERLAY_NORMAL.
+                        Vector4 regionVector;
+                        Rectangle region;
+                        if (!innerSpace->getVector4("region", &regionVector))
+                        {
+                            region.set(normal->getRegion());
+                        }
+                        else
+                        {
+                            region = Rectangle(regionVector.x, regionVector.y, regionVector.z, regionVector.w);
+                        }
+
+                        Vector4 textColor;
+                        if (!innerSpace->getColor("textColor", &textColor))
+                        {
+                            textColor.set(normal->getTextColor());
+                        }
+
+                        Vector4 borderColor;
+                        if (!innerSpace->getColor("borderColor", &borderColor))
+                        {
+                            borderColor.set(normal->getBorderColor());
+                        }
+
+                        const char* fontPath = innerSpace->getString("font");
+                        Font* font = NULL;
+                        if (fontPath)
+                        {
+                            font = Font::create(fontPath);
+                        }
+                        if (!font)
+                        {
+                            font = normal->getFont();
+                        }
+
+                        unsigned int fontSize;
+                        if (innerSpace->exists("fontSize"))
+                        {
+                            fontSize = innerSpace->getInt("fontSize");
+                        }
+                        else
+                        {
+                            fontSize = normal->getFontSize();
+                        }
+
+                        const char* alignmentString = innerSpace->getString("alignment");
+                        Font::Justify alignment;
+                        if (alignmentString)
+                        {
+                            alignment = Font::getJustifyFromString(alignmentString);
+                        }
+                        else
+                        {
+                            alignment = normal->getTextAlignment();
+                        }
+
+                        bool rightToLeft;
+                        if (innerSpace->exists("rightToLeft"))
+                        {
+                            rightToLeft = innerSpace->getBool("rightToLeft");
+                        }
+                        else
+                        {
+                            rightToLeft = normal->getTextRightToLeft();
+                        }
+
+                        const char* checkBoxString = innerSpace->getString("checkBox");
+                        CheckBoxIcon* checkBoxIcon = NULL;
+                        if (checkBoxString)
+                        {
+                            for (unsigned int i = 0; i < theme->_checkBoxIcons.size(); i++)
+                            {
+                                if (strcmp(theme->_checkBoxIcons[i]->id.c_str(), checkBoxString) == 0)
+                                {
+                                    checkBoxIcon = theme->_checkBoxIcons[i];
+                                    break;
+                                }
+                            }
+                        }
+                        if (!checkBoxIcon)
+                        {
+                            checkBoxIcon = normal->getCheckBoxIcon();
+                        }
+
+                        // TODO: Cursor.
+                        
+                        if (strcmp(innerSpacename, "focus") == 0)
+                        {
+                            focus = new Theme::Style::Overlay(Theme::Style::OVERLAY_FOCUS);
+                            focus->setRegion(region);
+                            focus->setTextColor(textColor);
+                            focus->setBorderColor(borderColor);
+                            focus->setFont(font);
+                            focus->setFontSize(fontSize);
+                            focus->setTextAlignment(alignment);
+                            focus->setTextRightToLeft(rightToLeft);
+                            focus->setCheckBoxIcon(checkBoxIcon);
+                        }
+                        else if (strcmp(innerSpacename, "active") == 0)
+                        {
+                            active = new Theme::Style::Overlay(Theme::Style::OVERLAY_ACTIVE);
+                            active->setRegion(region);
+                            active->setTextColor(textColor);
+                            active->setBorderColor(borderColor);
+                            active->setFont(font);
+                            active->setFontSize(fontSize);
+                            active->setTextAlignment(alignment);
+                            active->setTextRightToLeft(rightToLeft);
+                            active->setCheckBoxIcon(checkBoxIcon);
+                        }
+                    }
+
+                    innerSpace = space->getNextNamespace();
+                }
+
+                unsigned int textureWidth = theme->_texture->getWidth();
+                unsigned int textureHeight = theme->_texture->getHeight();
+
+                normal->calculateUVs(border, textureWidth, textureHeight);
+                
+                if (focus)
+                {
+                    focus->calculateUVs(border, textureWidth, textureHeight);
+                }
+                else
+                {
+                    focus = normal;
+                }
+                if (active)
+                {
+                    active->calculateUVs(border, textureWidth, textureHeight);
+                }
+                else
+                {
+                    active = normal;
+                }
+
+                Theme::Style* s = new Theme::Style(space->getId(), margin, border, padding, normal, focus, active);
+                theme->_styles.push_back(s);
+            }
+
+            space = themeProperties->getNextNamespace();
+        }
+
+        // Add this theme to the cache.
+        __themeCache.push_back(theme);
+
+        return theme;
+    }
+
+    Theme::Style* Theme::getStyle(const char* name) const
+    {
+        for (unsigned int i = 0, count = _styles.size(); i < count; ++i)
+        {
+            if (strcmp(name, _styles[i]->getId()) == 0)
+            {
+                return _styles[i];
+            }
+        }
+
+        return NULL;
+    }
+
+    SpriteBatch* Theme::getSpriteBatch() const
+    {
+        return _spriteBatch;
+    }
+
+    Theme::Style::Style(const char* id, const Theme::Margin& margin, const Theme::Border& border, const Theme::Padding& padding,
+            Theme::Style::Overlay* normal, Theme::Style::Overlay* focus, Theme::Style::Overlay* active)
+        : _id(id), _margin(margin), _border(border), _padding(padding)
+    {
+        // 
+
+        _overlays[OVERLAY_NORMAL] = normal;
+        _overlays[OVERLAY_FOCUS] = focus;
+        _overlays[OVERLAY_ACTIVE] = active;
+    }
+
+    Theme::Cursor::Cursor(const char* id, const Rectangle& region, const Vector4& color)
+        : _id(id), _region(region), _color(color)
+    {
+    }
+
+    const char* Theme::Cursor::getId() const
+    {
+        return _id.data();
+    }
+
+    const Rectangle& Theme::Cursor::getRegion() const
+    {
+        return _region;
+    }
+
+    const Theme::UVs& Theme::Cursor::getUVs() const
+    {
+        return _uvs;
+    }
+    
+    const char* Theme::Style::getId() const
+    {
+        return _id.data();
+    }
+
+    Theme::Style::Overlay* Theme::Style::getOverlay(OverlayType overlayType) const
+    {
+        return _overlays[overlayType];
+    }
+
+    const Theme::Margin& Theme::Style::getMargin() const
+    {
+        return _margin;
+    }
+
+    const Theme::Border& Theme::Style::getBorder() const
+    {
+        return _border;
+    }
+
+    const Theme::Padding& Theme::Style::getPadding() const
+    {
+        return _padding;
+    }
+
+    Theme::Style::Overlay::Overlay()
+    {
+    }
+
+    Theme::Style::Overlay::Overlay(Theme::Style::OverlayType type)
+        : _type(type)
+    {
+    }
+
+    Theme::Style::OverlayType Theme::Style::Overlay::getType()
+    {
+        return _type;
+    }
+    
+    const Rectangle& Theme::Style::Overlay::getRegion() const
+    {
+        return _region;
+    }
+
+    void Theme::Style::Overlay::setRegion(const Rectangle& region)
+    {
+        _region = region;
+    }
+
+    void Theme::Style::Overlay::calculateUVs(const Theme::Border& border, unsigned int textureWidth, unsigned int textureHeight)
+    {
+        // Need to convert pixel coords to unit space by dividing by texture size.
+        float tw = 1.0f / (float)textureWidth;
+        float th = 1.0f / (float)textureHeight;
+
+        // Can calculate all measurements in advance.
+        float leftEdge = _region.x * tw;
+        float rightEdge = (_region.x + _region.width) * tw;
+        float leftBorder = (_region.x + border.left) * tw;
+        float rightBorder = (_region.x + _region.width - border.right) * tw;
+
+        float topEdge = 1.0f - (_region.y * th);
+        float bottomEdge = 1.0f - ((_region.y + _region.height) * th);
+        float topBorder = 1.0f - ((_region.y + border.top) * th);
+        float bottomBorder = 1.0f - ((_region.y + _region.height - border.bottom) * th);
+
+        // There are 9 sets of UVs to set.
+        _uvs[TOP_LEFT].u1 = leftEdge;
+        _uvs[TOP_LEFT].v1 = topEdge;
+        _uvs[TOP_LEFT].u2 = leftBorder;
+        _uvs[TOP_LEFT].v2 = topBorder;
+
+        _uvs[TOP].u1 = leftBorder;
+        _uvs[TOP].v1 = topEdge;
+        _uvs[TOP].u2 = rightBorder;
+        _uvs[TOP].v2 = topBorder;
+
+        _uvs[TOP_RIGHT].u1 = rightBorder;
+        _uvs[TOP_RIGHT].v1 = topEdge;
+        _uvs[TOP_RIGHT].u2 = rightEdge;
+        _uvs[TOP_RIGHT].v2 = topBorder;
+
+        _uvs[LEFT].u1 = leftEdge;
+        _uvs[LEFT].v1 = topBorder;
+        _uvs[LEFT].u2 = leftBorder;
+        _uvs[LEFT].v2 = bottomBorder;
+
+        _uvs[CENTER].u1 = leftBorder;
+        _uvs[CENTER].v1 = topBorder;
+        _uvs[CENTER].u2 = rightBorder;
+        _uvs[CENTER].v2 = bottomBorder;
+
+        _uvs[RIGHT].u1 = rightBorder;
+        _uvs[RIGHT].v1 = topBorder;
+        _uvs[RIGHT].u2 = rightEdge;
+        _uvs[RIGHT].v2 = bottomBorder;
+
+        _uvs[BOTTOM_LEFT].u1 = leftEdge;
+        _uvs[BOTTOM_LEFT].v1 = bottomBorder;
+        _uvs[BOTTOM_LEFT].u2 = leftBorder;
+        _uvs[BOTTOM_LEFT].v2 = bottomEdge;
+
+        _uvs[BOTTOM].u1 = leftBorder;
+        _uvs[BOTTOM].v1 = bottomBorder;
+        _uvs[BOTTOM].u2 = rightBorder;
+        _uvs[BOTTOM].v2 = bottomEdge;
+
+        _uvs[BOTTOM_RIGHT].u1 = rightBorder;
+        _uvs[BOTTOM_RIGHT].v1 = bottomBorder;
+        _uvs[BOTTOM_RIGHT].u2 = rightEdge;
+        _uvs[BOTTOM_RIGHT].v2 = bottomEdge;
+    }
+
+    const Theme::UVs& Theme::Style::Overlay::getUVs(OverlayArea area)
+    {
+        return _uvs[area];
+    }
+
+    void Theme::Style::Overlay::setUVs(OverlayArea area, const Theme::UVs& uvs)
+    {
+        _uvs[area] = uvs;
+    }
+
+    Font* Theme::Style::Overlay::getFont() const
+    {
+        return _font;
+    }
+
+    void Theme::Style::Overlay::setFont(Font* font)
+    {
+        _font = font;
+
+        if (_font)
+        {
+            font->addRef();
+        }
+    }
+
+    unsigned int Theme::Style::Overlay::getFontSize() const
+    {
+        return _fontSize;
+    }
+
+    void Theme::Style::Overlay::setFontSize(unsigned int fontSize)
+    {
+        _fontSize = fontSize;
+    }
+
+    Font::Justify Theme::Style::Overlay::getTextAlignment() const
+    {
+        return _alignment;
+    }
+
+    void Theme::Style::Overlay::setTextAlignment(Font::Justify alignment)
+    {
+        _alignment = alignment;
+    }
+            
+    // Text direction.
+    bool Theme::Style::Overlay::getTextRightToLeft() const
+    {
+        return _textRightToLeft;
+    }
+
+    void Theme::Style::Overlay::setTextRightToLeft(bool rightToLeft)
+    {
+        _textRightToLeft = rightToLeft;
+    }
+
+    Theme::Cursor* Theme::Style::Overlay::getCursor() const
+    {
+        return _cursor;
+    }
+
+    void Theme::Style::Overlay::setCursor(Theme::Cursor* cursor)
+    {
+        _cursor = cursor;
+    }
+
+    const Vector4& Theme::Style::Overlay::getTextColor() const
+    {
+        return _textColor;
+    }
+
+    void Theme::Style::Overlay::setTextColor(const Vector4& color)
+    {
+        _textColor = color;
+    }
+
+    const Vector4& Theme::Style::Overlay::getBorderColor() const
+    {
+        return _borderColor;
+    }
+
+    void Theme::Style::Overlay::setBorderColor(const Vector4& color)
+    {
+        _borderColor = color;
+    }
+
+    void Theme::Style::Overlay::setCheckBoxIcon(CheckBoxIcon* icon)
+    {
+        _checkBoxIcon = icon;
+    }
+
+    Theme::CheckBoxIcon* Theme::Style::Overlay::getCheckBoxIcon()
+    {
+        return _checkBoxIcon;
+    }
+}

+ 282 - 0
gameplay/src/Theme.h

@@ -0,0 +1,282 @@
+/*
+ * Theme.h
+ */
+
+# ifndef THEME_H_
+# define THEME_H_
+
+#include "Base.h"
+#include "Ref.h"
+#include "Font.h"
+#include "Rectangle.h"
+#include "Texture.h"
+#include "Properties.h"
+
+namespace gameplay
+{
+
+#define MAX_OVERLAYS 3
+#define MAX_OVERLAY_REGIONS 9
+/**
+ * This class represents the apperance of an UI form described in a 
+ * theme file. A theme file, at its simplest, contains a source texture atlas,
+ * the texture coordinates of each control in its different mode. Once loaded,
+ * the appearance properties can be retrieved using a style id and set on a UI control.
+ */
+class Theme: public Ref
+{
+public:
+    class Style;
+    class Cursor;
+
+    typedef struct UVs
+    {
+        float u1;
+        float v1;
+        float u2;
+        float v2;
+    } UVs;
+
+    typedef struct padding
+    {
+        float top;
+        float bottom;
+        float left;
+        float right;
+    } Margin, Border, Padding;
+
+    typedef struct icon
+    {
+        std::string id;
+        UVs off;
+        UVs on;
+        Vector2 size;
+    } CheckBoxIcon, RadioButtonIcon;
+
+    /**
+     * Creates an instance of a Theme from a theme file.
+     *
+     * @param path path to a theme file.
+     *
+     * @return A new Theme.
+     */
+    static Theme* create(const char* path);
+
+    /**
+     * Returns style with the given name.
+     *
+     * @param name Name of the style (as specified in the Theme file).
+     *
+     * @return instance of the Style.
+     */
+    Theme::Style* getStyle(const char* styleName) const;
+
+    SpriteBatch* getSpriteBatch() const;
+
+    /**
+     * This class represents the apperance of a cursor.
+     */
+    class Cursor
+    {
+    public:
+        Cursor(const char* id, const Rectangle& region, const Vector4& color);
+
+       /**
+        * Returns the Id of this Cursor.
+        */
+        const char* getId() const;
+
+       /**
+        * Gets a texture region in the texture atlas for a cursor.
+        */
+        const Rectangle& getRegion() const;
+
+       /**
+        * Gets a UV coordinates computed from the texture region.
+        */
+        const Theme::UVs& getUVs() const;
+    
+    private:
+
+        std::string _id;
+        Rectangle _region;
+        UVs _uvs;
+        Vector4 _color;
+    };
+    
+    /**
+     * This class represents the apperance of a control's style.
+     */
+    class Style
+    {
+    public:
+        class Overlay;
+
+        enum OverlayType
+        {
+            OVERLAY_NORMAL,
+            OVERLAY_FOCUS,
+            OVERLAY_ACTIVE
+        };
+
+        Style(const char* id, const Theme::Margin& margin, const Theme::Border& border, const Theme::Padding& padding,
+            Theme::Style::Overlay* normal, Theme::Style::Overlay* focus, Theme::Style::Overlay* active);
+
+        ~Style();
+
+        /**
+         * Returns the Id of this Style.
+         */
+        const char* getId() const;
+
+        /**
+         * Gets an overlay from the overlay type.
+         */
+        Theme::Style::Overlay* getOverlay(OverlayType overlayType) const;
+
+        /**
+         * Gets the Border region of this overlay.
+         */
+        const Theme::Border& getBorder() const;
+
+        /**
+         * Gets the Padding region of this overlay.
+         */
+        const Theme::Padding& getPadding() const;
+
+        /**
+         * Gets the Margin region of this overlay.
+         */
+        const Theme::Margin& getMargin() const;
+       
+        /**
+         * This class represents a control's overlay for one of the 3 modes: normal, focussed or active.
+         */
+        class Overlay
+        {
+        public:
+            Overlay();
+            Overlay(OverlayType type);
+
+            enum OverlayArea
+            {
+                TOP_LEFT, TOP, TOP_RIGHT,
+                LEFT, CENTER, RIGHT,
+                BOTTOM_LEFT, BOTTOM, BOTTOM_RIGHT
+            };
+
+           /**
+            * Destructor.
+            */
+            ~Overlay();
+
+           /**
+            * Returns the Overlay type.
+            */
+            OverlayType getType();
+            
+            /**
+            * Gets a texture region in the texture atlas that the overlay uses.
+            */
+            const Rectangle& getRegion() const;
+
+            void setRegion(const Rectangle& region);
+
+            // Calculates and sets UV coordinates based on the current region and given border and texture size.
+            void calculateUVs(const Theme::Border& border, unsigned int textureWidth, unsigned int textureHeight);
+
+            const Theme::UVs& getUVs(OverlayArea area);
+
+            void setUVs(OverlayArea area, const Theme::UVs& uvs);
+
+           /**
+            * Gets a font associated with this overlay.
+            */
+            Font* getFont() const;
+
+            void setFont(Font* font);
+
+            // Font size.
+            unsigned int getFontSize() const;
+            void setFontSize(unsigned int fontSize);
+
+            // Alignment.
+            Font::Justify getTextAlignment() const;
+            void setTextAlignment(Font::Justify alignment);
+            
+            // Text direction.
+            bool getTextRightToLeft() const;
+            void setTextRightToLeft(bool rightToLeft);
+
+           /**
+            * Gets a cursor associated with this overlay.
+            */
+            Cursor* getCursor() const;
+
+            void setCursor(Cursor* cursor);
+
+            const Vector4& getTextColor() const;
+
+            void setTextColor(const Vector4& color);
+
+            const Vector4& getBorderColor() const;
+
+            void setBorderColor(const Vector4& color);
+            
+            void setCheckBoxIcon(CheckBoxIcon* icon);
+
+            CheckBoxIcon* getCheckBoxIcon();
+        
+        private:
+           
+            OverlayType _type;
+            Rectangle _region;
+            UVs _uvs[MAX_OVERLAY_REGIONS];
+            Cursor* _cursor;
+            CheckBoxIcon* _checkBoxIcon;
+            RadioButtonIcon* _radioButtonIcon;
+            Font* _font;
+            unsigned int _fontSize;
+            Font::Justify _alignment;
+            bool _textRightToLeft;
+            Vector4 _textColor;
+            Vector4 _borderColor;
+        };
+
+    private:
+        
+        std::string _id;
+        Border _border;
+        Padding _padding;
+        Margin _margin;
+        Overlay* _overlays[MAX_OVERLAYS];
+    };
+
+private:
+    /**
+     * Constructor.
+     */
+    Theme();
+
+    /**
+     * Copy Constructor.
+     */
+    Theme(const Theme* theme);
+
+    /**
+     * Destructor.
+     */
+    ~Theme();
+
+    std::string _path;
+    Texture* _texture;
+    SpriteBatch* _spriteBatch;
+    std::vector<Cursor *> _cursors;
+    std::vector<Style *> _styles;
+    std::vector<Font *> _fonts;
+    std::vector<CheckBoxIcon *> _checkBoxIcons;
+};
+
+}
+
+#endif

+ 67 - 0
gameplay/src/VerticalLayout.cpp

@@ -0,0 +1,67 @@
+#include "Base.h"
+#include "VerticalLayout.h"
+
+namespace gameplay
+{
+    VerticalLayout::VerticalLayout()
+    {
+    }
+
+    VerticalLayout::VerticalLayout(const VerticalLayout& copy)
+    {
+    }
+
+    VerticalLayout::~VerticalLayout()
+    {
+    }
+
+    VerticalLayout* VerticalLayout::create()
+    {
+        VerticalLayout* al = new VerticalLayout();
+        return al;
+    }
+
+    void VerticalLayout::setBottomToTop(bool bottomToTop)
+    {
+        _bottomToTop = bottomToTop;
+    }
+
+    Layout::Type VerticalLayout::getType()
+    {
+        return Layout::LAYOUT_VERTICAL;
+    }
+
+    void VerticalLayout::update(std::vector<Control*> controls, const Vector2& size)
+    {
+        float yPosition = 0.0f;
+
+        unsigned int i, end, iter;
+        if (_bottomToTop)
+        {
+            i = controls.size() - 1;
+            end = -1;
+            iter = -1;
+        }
+        else
+        {
+            i = 0;
+            end = controls.size();
+            iter = 1;
+        }
+
+        while (i != end)
+        {
+            Control* control = controls.at(i);
+            const Vector2& size = control->getSize();
+            const Theme::Margin& margin = control->getStyle()->getMargin();
+
+            yPosition += margin.top;
+
+            control->setPosition(0.0f, yPosition);
+
+            yPosition += size.y + margin.bottom;
+
+            i += iter;
+        }
+    }
+}

+ 30 - 0
gameplay/src/VerticalLayout.h

@@ -0,0 +1,30 @@
+#ifndef VERTICALLAYOUT_H_
+#define VERTICALLAYOUT_H_
+
+#include "Layout.h"
+
+namespace gameplay
+{
+
+class VerticalLayout : public Layout
+{
+public:
+    static VerticalLayout* create();
+
+    void setBottomToTop(bool bottomToTop);
+
+    Layout::Type getType();
+
+    void update(std::vector<Control*> controls, const Vector2& size);
+
+private:
+    VerticalLayout();
+    VerticalLayout(const VerticalLayout& copy);
+    virtual ~VerticalLayout();
+
+    bool _bottomToTop;
+};
+
+}
+
+#endif

+ 39 - 0
gameplay/src/gameplay.dox

@@ -0,0 +1,39 @@
+/** \mainpage GamePlay - 3D Game Framework
+ *
+ * \section intro Introduction
+ *
+ * GamePlay is a cross-platform, C++, 3D gaming framework that includes a runtime library, tools,
+ * and learning content that allows developers to write games for leading-edge mobile and desktop
+ * environments without worrying about platform details.
+ *
+ * \section License
+ *
+ * The project is open sourced under the Apache 2.0 license.
+ *
+ * \section Project Details
+ *
+ * The GamePlay C++ runtime library offers a simple, well-defined, 3D gaming framework that's
+ * designed to get the most out of today's mobile and desktop platforms. Its purpose is to help you
+ * create stunning, world-class, games that harness the power and performance of the platform without
+ * being concerned about the platform details. This is accomplished through a set of C++ classes that
+ * are built on top of the OS, providing access to the hardware, graphics, and audio libraries
+ * (including EGL, OpenGL ES 2.0, and OpenAL 1.1). These classes allow you to:
+ *
+ * <b>Build your game without worrying about platform details</b>
+ * \li Drive your game with application initialization, update, and render callbacks.
+ * \li Control your game with touch screen, keyboard and accelerometer handlers.
+ * \li Manage your game with control over the device's file system.
+ *
+ * <b>Create game components with ease</b>
+ * \li Add visuals with scene, camera, light, model, mesh, texture, sprite, materials, animation, and physics classes.
+ * \li Control and position the game using the viewport, camera, and audio listener.
+ * \li Manage fundamental elements such as fonts, colors, and curves.
+ * \li Update game data with built-in math classes for vectors, matrices, rays, planes, and their associated operations.
+ *
+ * <b>Improve your game and learn</b>
+ * \li Supports TrueType fonts and the Khronos COLLADA and FBX interchange formats to import and binary encode your 3D game assets.
+ * \li Built-in materials and shaders to enhance your game's objects.
+ * \li Both mobile and desktop platforms are supported for ease of portability.
+ * \li Documentation, tutorials, and code samples are provided.
+ *
+ */

+ 9 - 1
gameplay/src/gameplay.h

@@ -68,4 +68,12 @@
 #include "PhysicsRigidBody.h"
 
 
-
+// UI
+#include "Theme.h"
+#include "Control.h"
+#include "Container.h"
+#include "Form.h"
+#include "Layout.h"
+#include "AbsoluteLayout.h"
+#include "Label.h"
+#include "Button.h"