Explorar o código

Merge pull request #155 from blackberry-gaming/next-ablake

First commit of UI Forms API
Sean Paul Taylor %!s(int64=14) %!d(string=hai) anos
pai
achega
38112fdf3f

+ 1 - 1
gameplay.doxyfile

@@ -548,7 +548,7 @@ SHOW_FILES             = YES
 # Namespaces page.  This will remove the Namespaces entry from the Quick Index 
 # and from the Folder Tree View (if specified). The default is YES.
 
-SHOW_NAMESPACES        = YES
+SHOW_NAMESPACES        = NO
 
 # The FILE_VERSION_FILTER tag can be used to specify a program or script that 
 # doxygen should invoke to get the current version for each file (typically from 

+ 35 - 0
gameplay.sln

@@ -25,6 +25,23 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sample03-character", "gamep
 EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gameplay-encoder", "gameplay-encoder\gameplay-encoder.vcxproj", "{9D69B743-4872-4DD1-8E30-0087C64298D7}"
 EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D3CA8AE2-3ED6-458B-963A-EDA671E6F51C}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sample05-particles", "gameplay-samples\sample05-particles\sample05-particles.vcxproj", "{217A796A-1B0F-4F09-ABF1-BC6E029D5C7D}"
+	ProjectSection(ProjectDependencies) = postProject
+		{1032BA4B-57EB-4348-9E03-29DD63E80E4A} = {1032BA4B-57EB-4348-9E03-29DD63E80E4A}
+	EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test-text", "..\test-text\test-text.vcxproj", "{635ECFF5-9E27-4C0A-9FCF-A7A4FE161541}"
+	ProjectSection(ProjectDependencies) = postProject
+		{1032BA4B-57EB-4348-9E03-29DD63E80E4A} = {1032BA4B-57EB-4348-9E03-29DD63E80E4A}
+	EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test-forms", "..\test-forms\test-forms.vcxproj", "{F0CF2418-536D-4C4F-ACB5-8BBC37B208A9}"
+	ProjectSection(ProjectDependencies) = postProject
+		{1032BA4B-57EB-4348-9E03-29DD63E80E4A} = {1032BA4B-57EB-4348-9E03-29DD63E80E4A}
+	EndProjectSection
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Win32 = Debug|Win32
@@ -68,6 +85,24 @@ Global
 		{9D69B743-4872-4DD1-8E30-0087C64298D7}.DebugMem|Win32.Build.0 = Debug|Win32
 		{9D69B743-4872-4DD1-8E30-0087C64298D7}.Release|Win32.ActiveCfg = Release|Win32
 		{9D69B743-4872-4DD1-8E30-0087C64298D7}.Release|Win32.Build.0 = Release|Win32
+		{217A796A-1B0F-4F09-ABF1-BC6E029D5C7D}.Debug|Win32.ActiveCfg = Debug|Win32
+		{217A796A-1B0F-4F09-ABF1-BC6E029D5C7D}.Debug|Win32.Build.0 = Debug|Win32
+		{217A796A-1B0F-4F09-ABF1-BC6E029D5C7D}.DebugMem|Win32.ActiveCfg = DebugMem|Win32
+		{217A796A-1B0F-4F09-ABF1-BC6E029D5C7D}.DebugMem|Win32.Build.0 = DebugMem|Win32
+		{217A796A-1B0F-4F09-ABF1-BC6E029D5C7D}.Release|Win32.ActiveCfg = Release|Win32
+		{217A796A-1B0F-4F09-ABF1-BC6E029D5C7D}.Release|Win32.Build.0 = Release|Win32
+		{635ECFF5-9E27-4C0A-9FCF-A7A4FE161541}.Debug|Win32.ActiveCfg = Debug|Win32
+		{635ECFF5-9E27-4C0A-9FCF-A7A4FE161541}.Debug|Win32.Build.0 = Debug|Win32
+		{635ECFF5-9E27-4C0A-9FCF-A7A4FE161541}.DebugMem|Win32.ActiveCfg = DebugMem|Win32
+		{635ECFF5-9E27-4C0A-9FCF-A7A4FE161541}.DebugMem|Win32.Build.0 = DebugMem|Win32
+		{635ECFF5-9E27-4C0A-9FCF-A7A4FE161541}.Release|Win32.ActiveCfg = Release|Win32
+		{635ECFF5-9E27-4C0A-9FCF-A7A4FE161541}.Release|Win32.Build.0 = Release|Win32
+		{F0CF2418-536D-4C4F-ACB5-8BBC37B208A9}.Debug|Win32.ActiveCfg = Debug|Win32
+		{F0CF2418-536D-4C4F-ACB5-8BBC37B208A9}.Debug|Win32.Build.0 = Debug|Win32
+		{F0CF2418-536D-4C4F-ACB5-8BBC37B208A9}.DebugMem|Win32.ActiveCfg = DebugMem|Win32
+		{F0CF2418-536D-4C4F-ACB5-8BBC37B208A9}.DebugMem|Win32.Build.0 = DebugMem|Win32
+		{F0CF2418-536D-4C4F-ACB5-8BBC37B208A9}.Release|Win32.ActiveCfg = Release|Win32
+		{F0CF2418-536D-4C4F-ACB5-8BBC37B208A9}.Release|Win32.Build.0 = Release|Win32
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 25 - 0
gameplay/gameplay.vcxproj

@@ -15,6 +15,7 @@
     </ProjectConfiguration>
   </ItemGroup>
   <ItemGroup>
+    <ClCompile Include="src\AbsoluteLayout.cpp" />
     <ClCompile Include="src\Animation.cpp" />
     <ClCompile Include="src\AnimationClip.cpp" />
     <ClCompile Include="src\AnimationController.cpp" />
@@ -26,13 +27,18 @@
     <ClCompile Include="src\AudioSource.cpp" />
     <ClCompile Include="src\BoundingBox.cpp" />
     <ClCompile Include="src\BoundingSphere.cpp" />
+    <ClCompile Include="src\Button.cpp" />
     <ClCompile Include="src\Camera.cpp" />
+    <ClCompile Include="src\CheckBox.cpp" />
+    <ClCompile Include="src\Container.cpp" />
+    <ClCompile Include="src\Control.cpp" />
     <ClCompile Include="src\Curve.cpp" />
     <ClCompile Include="src\DebugNew.cpp" />
     <ClCompile Include="src\DepthStencilTarget.cpp" />
     <ClCompile Include="src\Effect.cpp" />
     <ClCompile Include="src\FileSystem.cpp" />
     <ClCompile Include="src\Font.cpp" />
+    <ClCompile Include="src\Form.cpp" />
     <ClCompile Include="src\FrameBuffer.cpp" />
     <ClCompile Include="src\Frustum.cpp" />
     <ClCompile Include="src\Game.cpp" />
@@ -41,6 +47,7 @@
     <ClCompile Include="src\gameplay-main-win32.cpp" />
     <ClCompile Include="src\Image.cpp" />
     <ClCompile Include="src\Joint.cpp" />
+    <ClCompile Include="src\Label.cpp" />
     <ClCompile Include="src\Light.cpp" />
     <ClCompile Include="src\Material.cpp" />
     <ClCompile Include="src\MeshBatch.cpp" />
@@ -69,6 +76,7 @@
     <ClCompile Include="src\PlatformWin32.cpp" />
     <ClCompile Include="src\Properties.cpp" />
     <ClCompile Include="src\Quaternion.cpp" />
+    <ClCompile Include="src\RadioButton.cpp" />
     <ClCompile Include="src\Ray.cpp" />
     <ClCompile Include="src\Rectangle.cpp" />
     <ClCompile Include="src\Ref.cpp" />
@@ -76,18 +84,23 @@
     <ClCompile Include="src\RenderTarget.cpp" />
     <ClCompile Include="src\Scene.cpp" />
     <ClCompile Include="src\SceneLoader.cpp" />
+    <ClCompile Include="src\Slider.cpp" />
     <ClCompile Include="src\SpriteBatch.cpp" />
     <ClCompile Include="src\Technique.cpp" />
+    <ClCompile Include="src\TextBox.cpp" />
     <ClCompile Include="src\Texture.cpp" />
+    <ClCompile Include="src\Theme.cpp" />
     <ClCompile Include="src\Transform.cpp" />
     <ClCompile Include="src\Vector2.cpp" />
     <ClCompile Include="src\Vector3.cpp" />
     <ClCompile Include="src\Vector4.cpp" />
     <ClCompile Include="src\VertexAttributeBinding.cpp" />
     <ClCompile Include="src\VertexFormat.cpp" />
+    <ClCompile Include="src\VerticalLayout.cpp" />
     <ClCompile Include="src\Viewport.cpp" />
   </ItemGroup>
   <ItemGroup>
+    <ClInclude Include="src\AbsoluteLayout.h" />
     <ClInclude Include="src\Animation.h" />
     <ClInclude Include="src\AnimationClip.h" />
     <ClInclude Include="src\AnimationController.h" />
@@ -100,13 +113,18 @@
     <ClInclude Include="src\Base.h" />
     <ClInclude Include="src\BoundingBox.h" />
     <ClInclude Include="src\BoundingSphere.h" />
+    <ClInclude Include="src\Button.h" />
     <ClInclude Include="src\Camera.h" />
+    <ClInclude Include="src\CheckBox.h" />
+    <ClInclude Include="src\Container.h" />
+    <ClInclude Include="src\Control.h" />
     <ClInclude Include="src\Curve.h" />
     <ClInclude Include="src\DebugNew.h" />
     <ClInclude Include="src\DepthStencilTarget.h" />
     <ClInclude Include="src\Effect.h" />
     <ClInclude Include="src\FileSystem.h" />
     <ClInclude Include="src\Font.h" />
+    <ClInclude Include="src\Form.h" />
     <ClInclude Include="src\FrameBuffer.h" />
     <ClInclude Include="src\Frustum.h" />
     <ClInclude Include="src\Game.h" />
@@ -114,6 +132,8 @@
     <ClInclude Include="src\Image.h" />
     <ClInclude Include="src\Joint.h" />
     <ClInclude Include="src\Keyboard.h" />
+    <ClInclude Include="src\Label.h" />
+    <ClInclude Include="src\Layout.h" />
     <ClInclude Include="src\Light.h" />
     <ClInclude Include="src\Material.h" />
     <ClInclude Include="src\MeshBatch.h" />
@@ -141,6 +161,7 @@
     <ClInclude Include="src\Platform.h" />
     <ClInclude Include="src\Properties.h" />
     <ClInclude Include="src\Quaternion.h" />
+    <ClInclude Include="src\RadioButton.h" />
     <ClInclude Include="src\Ray.h" />
     <ClInclude Include="src\Rectangle.h" />
     <ClInclude Include="src\Ref.h" />
@@ -148,9 +169,12 @@
     <ClInclude Include="src\RenderTarget.h" />
     <ClInclude Include="src\Scene.h" />
     <ClInclude Include="src\SceneLoader.h" />
+    <ClInclude Include="src\Slider.h" />
     <ClInclude Include="src\SpriteBatch.h" />
     <ClInclude Include="src\Technique.h" />
+    <ClInclude Include="src\TextBox.h" />
     <ClInclude Include="src\Texture.h" />
+    <ClInclude Include="src\Theme.h" />
     <ClInclude Include="src\Touch.h" />
     <ClInclude Include="src\Transform.h" />
     <ClInclude Include="src\Vector2.h" />
@@ -158,6 +182,7 @@
     <ClInclude Include="src\Vector4.h" />
     <ClInclude Include="src\VertexAttributeBinding.h" />
     <ClInclude Include="src\VertexFormat.h" />
+    <ClInclude Include="src\VerticalLayout.h" />
     <ClInclude Include="src\Viewport.h" />
   </ItemGroup>
   <ItemGroup>

+ 75 - 0
gameplay/gameplay.vcxproj.filters

@@ -228,6 +228,42 @@
     <ClCompile Include="src\PlatformAndroid.cpp">
       <Filter>src</Filter>
     </ClCompile>
+    <ClCompile Include="src\AbsoluteLayout.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="src\Button.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="src\CheckBox.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="src\Container.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="src\Control.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="src\Label.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="src\RadioButton.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="src\Slider.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="src\VerticalLayout.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="src\Form.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="src\Theme.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="src\TextBox.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="src\Animation.h">
@@ -443,6 +479,45 @@
     <ClInclude Include="src\Mouse.h">
       <Filter>src</Filter>
     </ClInclude>
+    <ClInclude Include="src\AbsoluteLayout.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="src\Button.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="src\CheckBox.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="src\Container.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="src\Control.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="src\Label.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="src\Layout.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="src\RadioButton.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="src\Slider.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="src\VerticalLayout.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="src\Form.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="src\Theme.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="src\TextBox.h">
+      <Filter>src</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <None Include="res\shaders\bumped-specular.vsh">

+ 44 - 0
gameplay/src/AbsoluteLayout.cpp

@@ -0,0 +1,44 @@
+#include "Base.h"
+#include "Control.h"
+#include "AbsoluteLayout.h"
+#include "Container.h"
+
+namespace gameplay
+{
+    AbsoluteLayout::AbsoluteLayout()
+    {
+    }
+
+    AbsoluteLayout::AbsoluteLayout(const AbsoluteLayout& copy)
+    {
+    }
+
+    AbsoluteLayout::~AbsoluteLayout()
+    {
+    }
+
+    AbsoluteLayout* AbsoluteLayout::create()
+    {
+        AbsoluteLayout* al = new AbsoluteLayout();
+        return al;
+    }
+
+    Layout::Type AbsoluteLayout::getType()
+    {
+        return Layout::LAYOUT_ABSOLUTE;
+    }
+
+    void AbsoluteLayout::update(const Container* container)
+    {
+        // An AbsoluteLayout does nothing to modify the layout of Controls.
+        std::vector<Control*> controls = container->getControls();
+        unsigned int controlsCount = controls.size();
+        for (unsigned int i = 0; i < controlsCount; i++)
+        {
+            if (controls[i]->isDirty())
+            {
+                controls[i]->update(container->getPosition());
+            }
+        }
+    }
+}

+ 26 - 0
gameplay/src/AbsoluteLayout.h

@@ -0,0 +1,26 @@
+#ifndef ABSOLUTELAYOUT_H_
+#define ABSOLUTELAYOUT_H_
+
+#include "Layout.h"
+
+namespace gameplay
+{
+
+class AbsoluteLayout : public Layout
+{
+public:
+    static AbsoluteLayout* create();
+
+    Layout::Type getType();
+
+    void update(const Container* container);
+
+private:
+    AbsoluteLayout();
+    AbsoluteLayout(const AbsoluteLayout& copy);
+    virtual ~AbsoluteLayout();
+};
+
+}
+
+#endif

+ 5 - 0
gameplay/src/Base.h

@@ -151,6 +151,11 @@ extern void printError(const char* format, ...);
 #define M_1_PI                      0.31830988618379067154
 #endif
 
+inline float round(float r)
+{
+    return (r > 0.0f) ? floor(r + 0.5f) : ceil(r - 0.5f);
+}
+
 // NOMINMAX makes sure that windef.h doesn't add macros min and max
 #ifdef WIN32
     #define NOMINMAX

+ 82 - 0
gameplay/src/Button.cpp

@@ -0,0 +1,82 @@
+#include "Base.h"
+#include "Button.h"
+
+namespace gameplay
+{
+    static std::vector<Button*> __buttons;
+
+    Button::Button() : _callback(NULL)
+    {
+    }
+
+    Button::~Button()
+    {
+        SAFE_DELETE(_callback);
+    }
+
+    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)
+    {
+        if (_state != STATE_DISABLED)
+        {
+            switch (evt)
+            {
+            case Touch::TOUCH_PRESS:
+                // TODO: button-down callback.
+
+                _state = Control::STATE_ACTIVE;
+                break;
+            case Touch::TOUCH_RELEASE:
+                if (_callback &&
+                    x > 0 && x <= _size.x &&
+                    y > 0 && y <= _size.y)
+                {
+                    // Button-clicked callback.
+                    _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

+ 144 - 0
gameplay/src/CheckBox.cpp

@@ -0,0 +1,144 @@
+#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_RELEASE:
+        {
+            if (_state == Control::STATE_ACTIVE)
+            {
+                if (x > 0 && x <= _size.x &&
+                    y > 0 && y <= _size.y)
+                {
+                    _checked = !_checked;
+                }
+            }
+        }
+        break;
+    }
+
+    Button::touchEvent(evt, x, y, contactIndex);
+}
+
+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::Icon* icon = overlay->getCheckBoxIcon();
+    if (icon)
+    {
+        const Theme::ContainerRegion* containerRegion = overlay->getContainerRegion();
+        Theme::Border border;
+        if (containerRegion)
+        {
+                border = containerRegion->getBorder();
+        }
+        const Theme::Padding padding = _style->getPadding();
+
+        const Vector2 size = icon->getSize();
+        const Vector4 color = icon->getColor();
+
+        Vector2 pos(position.x + _position.x + border.left + padding.left,
+            position.y + _position.y + (_size.y - border.bottom - padding.bottom) / 2.0f - size.y / 2.0f);
+
+        if (_checked)
+        {
+            const Theme::UVs on = icon->getOnUVs();
+            spriteBatch->draw(pos.x, pos.y, size.x, size.y, on.u1, on.v1, on.u2, on.v2, color);
+        }
+        else
+        {
+            const Theme::UVs off = icon->getOffUVs();
+            spriteBatch->draw(pos.x, pos.y, size.x, size.y, off.u1, off.v1, off.u2, off.v2, color);
+        }
+    }
+}
+
+void CheckBox::drawText(const Vector2& position)
+{
+    // TODO: Batch all labels that use the same font.
+    Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
+    Theme::Icon* icon = overlay->getCheckBoxIcon();
+    Theme::Border border;
+    Theme::ContainerRegion* containerRegion = overlay->getContainerRegion();
+    if (containerRegion)
+    {
+        border = overlay->getContainerRegion()->getBorder();
+    }
+    Theme::Padding padding = _style->getPadding();
+
+    // Set up the text viewport.
+    float iconWidth = 0.0f;
+    if (icon)
+    {
+        iconWidth = icon->getSize().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

+ 343 - 0
gameplay/src/Container.cpp

@@ -0,0 +1,343 @@
+#include "Base.h"
+#include "Container.h"
+#include "Layout.h"
+#include "AbsoluteLayout.h"
+#include "VerticalLayout.h"
+#include "Label.h"
+#include "Button.h"
+#include "CheckBox.h"
+#include "RadioButton.h"
+#include "Slider.h"
+#include "TextBox.h"
+
+namespace gameplay
+{
+    static std::vector<Container*> __containers;
+
+    Container::Container() : _layout(NULL)
+    {
+    }
+
+    Container::Container(const Container& copy)
+    {
+    }
+
+    Container::~Container()
+    {
+        std::vector<Control*>::iterator it;
+        for (it = _controls.begin(); it < _controls.end(); it++)
+        {
+            SAFE_RELEASE((*it));
+        }
+
+        SAFE_RELEASE(_layout);
+    }
+
+    Container* Container::create(const char* id, Layout::Type type)
+    {
+        Layout* layout = NULL;
+        switch(type)
+        {
+        case Layout::LAYOUT_ABSOLUTE:
+            layout = AbsoluteLayout::create();
+            break;
+        case Layout::LAYOUT_FLOW:
+            break;
+        case Layout::LAYOUT_VERTICAL:
+            layout = VerticalLayout::create();
+            break;
+        }
+
+        Container* container = new Container();
+        container->_id = id;
+        container->_layout = layout;
+
+        __containers.push_back(container);
+
+        return container;
+    }
+
+    Container* Container::create(Theme::Style* style, Properties* properties, Theme* theme)
+    {
+        const char* id = properties->getId();
+        const char* layoutString = properties->getString("layout");
+        Container* container = Container::create(id, getLayoutType(layoutString));
+
+        container->_style = style;
+        properties->getVector2("position", &container->_position);
+        properties->getVector2("size", &container->_size);
+
+        container->addControls(theme, properties);
+
+        return container;
+    }
+
+    void Container::addControls(Theme* theme, Properties* properties)
+    {
+        // Add all the controls to this container.
+        Properties* controlSpace = properties->getNextNamespace();
+        while (controlSpace != NULL)
+        {
+            Control* control = NULL;
+
+            const char* controlStyleName = controlSpace->getString("style");
+            Theme::Style* controlStyle = NULL;
+            if (controlStyleName)
+            {
+                 controlStyle = theme->getStyle(controlStyleName);
+            }
+
+            std::string controlName(controlSpace->getNamespace());
+            std::transform(controlName.begin(), controlName.end(), controlName.begin(), (int(*)(int))toupper);
+            if (controlName == "LABEL")
+            {
+                control = Label::create(controlStyle, controlSpace);
+            }
+            else if (controlName == "BUTTON")
+            {
+                control = Button::create(controlStyle, controlSpace);
+            }
+            else if (controlName == "CHECKBOX")
+            {
+                control = CheckBox::create(controlStyle, controlSpace);
+            }
+            else if (controlName == "RADIOBUTTON")
+            {
+                control = RadioButton::create(controlStyle, controlSpace);
+            }
+            else if (controlName == "CONTAINER")
+            {
+                control = Container::create(controlStyle, controlSpace, theme);
+            }
+            else if (controlName == "SLIDER")
+            {
+                control = Slider::create(controlStyle, controlSpace);
+            }
+            else if (controlName == "TEXTBOX")
+            {
+                control = TextBox::create(controlStyle, controlSpace);
+            }
+
+            // Add the new control to the form.
+            if (control)
+            {
+                addControl(control);
+            }
+
+            // Get the next control.
+            controlSpace = properties->getNextNamespace();
+        }
+    }
+
+    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);
+
+        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;
+    }
+
+    std::vector<Control*> Container::getControls() const
+    {
+        return _controls;
+    }
+
+    void Container::update(const Vector2& position)
+    {
+        // Should probably have sizeChanged() for this.
+        if (isDirty())
+        {
+            _layout->update(this);
+        }
+
+        _dirty = false;
+    }
+
+    void Container::drawSprites(SpriteBatch* spriteBatch, const Vector2& position)
+    {
+        Vector2 pos(position.x + _position.x, position.y + _position.y);
+        std::vector<Control*>::const_iterator it;
+        for (it = _controls.begin(); it < _controls.end(); it++)
+        {
+            Control* control = *it;
+            control->drawSprites(spriteBatch, pos);
+        }
+
+        _dirty = false;
+    }
+
+    void Container::drawText(const Vector2& position)
+    {
+        Vector2 pos(position.x + _position.x, position.y + _position.y);
+        std::vector<Control*>::const_iterator it;
+        for (it = _controls.begin(); it < _controls.end(); it++)
+        {
+            Control* control = *it;
+            control->drawText(pos);
+        }
+
+        _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_NORMAL ||
+                (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;
+        }
+    }
+
+    void Container::keyEvent(Keyboard::KeyEvent evt, int key)
+    {
+        std::vector<Control*>::const_iterator it;
+        for (it = _controls.begin(); it < _controls.end(); it++)
+        {
+            Control* control = *it;
+            control->keyEvent(evt, key);
+        }
+    }
+
+    Layout::Type Container::getLayoutType(const char* layoutString)
+    {
+        std::string layoutName(layoutString);
+        std::transform(layoutName.begin(), layoutName.end(), layoutName.begin(), (int(*)(int))toupper);
+        if (layoutName == "LAYOUT_ABSOLUTE")
+        {
+            return Layout::LAYOUT_ABSOLUTE;
+        }
+        else if (layoutName == "LAYOUT_VERTICAL")
+        {
+            return Layout::LAYOUT_VERTICAL;
+        }
+        else if (layoutName == "LAYOUT_FLOW")
+        {
+            return Layout::LAYOUT_FLOW;
+        }
+        else
+        {
+            // Default.
+            return Layout::LAYOUT_ABSOLUTE;
+        }
+    }
+}

+ 92 - 0
gameplay/src/Container.h

@@ -0,0 +1,92 @@
+#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* create(Theme::Style* style, Properties* properties, Theme* theme);
+    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;
+
+    std::vector<Control*> getControls() const;
+
+    /**
+     * Updates the position of each Control within this Container
+     * according to the Container's Layout.
+     */
+    virtual void update(const Vector2& position);
+
+    //void draw(Theme* theme, const Vector2& position);
+    virtual void drawSprites(SpriteBatch* spriteBatch, const Vector2& position);
+    virtual void drawText(const Vector2& position);
+
+    virtual void touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
+
+    virtual void keyEvent(Keyboard::KeyEvent evt, int key);
+
+protected:
+    Container();
+    Container(const Container& copy);
+    virtual ~Container();
+
+    static Layout::Type getLayoutType(const char* layoutString);
+
+    bool isDirty();
+    void addControls(Theme* theme, Properties* properties);
+
+    Layout* _layout;
+    std::vector<Control*> _controls;
+};
+
+}
+
+#endif

+ 182 - 0
gameplay/src/Control.cpp

@@ -0,0 +1,182 @@
+#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::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;
+    }
+
+    void Control::disable()
+    {
+        _state = STATE_DISABLED;
+    }
+
+    void Control::enable()
+    {
+        _state = STATE_NORMAL;
+    }
+
+    Theme::Style::OverlayType Control::getOverlayType() const
+    {
+        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::keyEvent(Keyboard::KeyEvent evt, int key)
+    {
+    }
+
+    void Control::update(const Vector2& position)
+    {
+    }
+
+    void Control::drawBorder(SpriteBatch* spriteBatch, const Vector2& position)
+    {
+        Vector2 pos(position.x + _position.x, position.y + _position.y);
+
+        // Get the border and background images for this control's current state.
+        Theme::ContainerRegion* containerRegion = _style->getOverlay(getOverlayType())->getContainerRegion();
+        if (containerRegion)
+        {
+            // Get the UVs.
+            Theme::UVs topLeft, top, topRight, left, center, right, bottomLeft, bottom, bottomRight;
+            topLeft = containerRegion->getUVs(Theme::ContainerRegion::TOP_LEFT);
+            top = containerRegion->getUVs(Theme::ContainerRegion::TOP);
+            topRight = containerRegion->getUVs(Theme::ContainerRegion::TOP_RIGHT);
+            left = containerRegion->getUVs(Theme::ContainerRegion::LEFT);
+            center = containerRegion->getUVs(Theme::ContainerRegion::CENTER);
+            right = containerRegion->getUVs(Theme::ContainerRegion::RIGHT);
+            bottomLeft = containerRegion->getUVs(Theme::ContainerRegion::BOTTOM_LEFT);
+            bottom = containerRegion->getUVs(Theme::ContainerRegion::BOTTOM);
+            bottomRight = containerRegion->getUVs(Theme::ContainerRegion::BOTTOM_RIGHT);
+
+            // Calculate screen-space positions.
+            Theme::Border border = containerRegion->getBorder();
+            Theme::Padding padding = _style->getPadding();
+            Vector4 borderColor = containerRegion->getColor();
+
+            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()
+    {
+        return _dirty;
+    }
+}

+ 134 - 0
gameplay/src/Control.h

@@ -0,0 +1,134 @@
+#ifndef CONTROL_H_
+#define CONTROL_H_
+
+#include "Ref.h"
+#include "Rectangle.h"
+#include "Vector2.h"
+#include "SpriteBatch.h"
+#include "Theme.h"
+#include "Touch.h"
+#include "Keyboard.h"
+
+namespace gameplay
+{
+
+class Control : public Ref
+{
+public:
+
+    enum State
+    {
+        STATE_NORMAL,
+        STATE_FOCUS,
+        STATE_ACTIVE,
+        STATE_DISABLED
+    };
+
+    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;
+
+    /**
+     * Set the position of this Control relative to its parent Container.
+     *
+     * @param x The x coordinate.
+     * @param y The y coordinate.
+     */
+    void setPosition(float x, float y);
+
+    /**
+     * Get the position of this Control relative to its parent Container.
+     *
+     * @return The position vector.
+     */
+    const Vector2& getPosition() const;
+
+    /**
+     * Set the size of this Control, including its border and padding.
+     *
+     * @param width The width.
+     * @param height The height.
+     */
+    void setSize(float width, float height);
+
+    /**
+     * Get the size of this Control, including its border and padding.
+     *
+     * @return The size vector.
+     */
+    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.
+     *
+     * @param width Whether to automatically determine this Control's width.
+     * @param height Whether to automatically determine this Control's height.
+     */
+    void setAutoSize(bool width, bool height);
+
+    void setState(State state);
+    State getState();
+
+    void disable();
+    void enable();
+
+    Theme::Style::OverlayType getOverlayType() const;
+
+    /**
+     * Defaults to empty stub.
+     */
+    virtual void touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
+
+    virtual void keyEvent(Keyboard::KeyEvent evt, int key);
+
+    virtual void update(const Vector2& position);
+
+    /**
+     * 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

+ 523 - 19
gameplay/src/Font.cpp

@@ -178,6 +178,7 @@ void Font::drawText(const char* text, int x, int y, const Vector4& color, unsign
 {
     float scale = (float)size / _size;
     const char* cursor = NULL;
+
     if (rightToLeft)
     {
         cursor = text;
@@ -204,7 +205,7 @@ void Font::drawText(const char* text, int x, int y, const Vector4& color, unsign
                 switch (delimiter)
                 {
                 case ' ':
-                    xPos += size>>1;
+                    xPos += (float)size*0.5f;
                     break;
                 case '\r':
                 case '\n':
@@ -212,7 +213,7 @@ void Font::drawText(const char* text, int x, int y, const Vector4& color, unsign
                     xPos = x;
                     break;
                 case '\t':
-                    xPos += (size>>1)*4;
+                    xPos += ((float)size*0.5f)*4;
                     break;
                 case 0:
                     done = true;
@@ -253,7 +254,7 @@ void Font::drawText(const char* text, int x, int y, const Vector4& color, unsign
             switch (c)
             {
             case ' ':
-                xPos += size>>1;
+                xPos += (float)size*0.5f;
                 break;
             case '\r':
             case '\n':
@@ -261,7 +262,7 @@ void Font::drawText(const char* text, int x, int y, const Vector4& color, unsign
                 xPos = x;
                 break;
             case '\t':
-                xPos += (size>>1)*4;
+                xPos += ((float)size*0.5f)*4;
                 break;
             default:
                 int index = c - 32; // HACK for ASCII
@@ -269,7 +270,7 @@ void Font::drawText(const char* text, int x, int y, const Vector4& color, unsign
                 {
                     Glyph& g = _glyphs[index];
                     _batch->draw(xPos, yPos, g.width * scale, size, g.uvs[0], g.uvs[1], g.uvs[2], g.uvs[3], color);
-                    xPos += g.width * scale + (size>>3);
+                    xPos += g.width * scale + ((float)size*0.125f);
                     break;
                 }
             }
@@ -337,7 +338,7 @@ void Font::drawText(const char* text, const Rectangle& area, const Vector4& colo
                     switch (delimiter)
                     {
                         case ' ':
-                            delimWidth += size>>1;
+                            delimWidth += (float)size*0.5f;
                             lineLength++;
                             break;
                         case '\r':
@@ -354,7 +355,7 @@ void Font::drawText(const char* text, const Rectangle& area, const Vector4& colo
                             delimWidth = 0;
                             break;
                         case '\t':
-                            delimWidth += (size>>1)*4;
+                            delimWidth += ((float)size*0.5f)*4;
                             lineLength++;
                             break;
                         case 0:
@@ -574,7 +575,7 @@ void Font::drawText(const char* text, const Rectangle& area, const Vector4& colo
                         _batch->draw(xPos, yPos, g.width * scale, size, g.uvs[0], g.uvs[1], g.uvs[2], g.uvs[3], color);
                     }
                 }
-                xPos += g.width*scale + (size>>3);
+                xPos += g.width*scale + ((float)size*0.125f);
             }
         }
 
@@ -715,7 +716,7 @@ void Font::measureText(const char* text, const Rectangle& viewport, unsigned int
                 switch (delimiter)
                 {
                     case ' ':
-                        delimWidth += size>>1;
+                        delimWidth += (float)size*0.5f;
                         break;
                     case '\r':
                     case '\n':
@@ -751,7 +752,7 @@ void Font::measureText(const char* text, const Rectangle& viewport, unsigned int
                         delimWidth = 0;
                         break;
                     case '\t':
-                        delimWidth += (size>>1)*4;
+                        delimWidth += ((float)size*0.5f)*4;
                         break;
                     case 0:
                         reachedEOF = true;
@@ -996,6 +997,413 @@ void Font::measureText(const char* text, const Rectangle& viewport, unsigned int
     }
 }
 
+unsigned int Font::getIndexAtLocation(const char* text, const Rectangle& area, unsigned int size, const Vector2& inLocation, Vector2* outLocation,
+                                      Justify justify, bool wrap, bool rightToLeft)
+{
+    return getIndexOrLocation(text, area, size, inLocation, outLocation, -1, justify, wrap, rightToLeft);
+}
+
+void Font::getLocationAtIndex(const char* text, const Rectangle& clip, unsigned int size, Vector2* outLocation, const unsigned int destIndex,
+                              Justify justify, bool wrap, bool rightToLeft)
+{
+    getIndexOrLocation(text, clip, size, *outLocation, outLocation, (const int)destIndex, justify, wrap, rightToLeft);
+}
+
+unsigned int Font::getIndexOrLocation(const char* text, const Rectangle& area, unsigned int size, const Vector2& inLocation, Vector2* outLocation,
+                                      const int destIndex, Justify justify, bool wrap, bool rightToLeft)
+{
+    unsigned int charIndex = 0;
+
+    // Essentially need to measure text until we reach inLocation.
+    float scale = (float)size / _size;
+    const char* token = text;
+    const int length = strlen(text);
+    int yPos = area.y;
+
+    Justify vAlign = static_cast<Justify>(justify & 0xF0);
+    if (vAlign == 0)
+    {
+        vAlign = ALIGN_TOP;
+    }
+
+    Justify hAlign = static_cast<Justify>(justify & 0x0F);
+    if (hAlign == 0)
+    {
+        hAlign = ALIGN_LEFT;
+    }
+
+    token = text;
+
+    // For alignments other than top-left, need to calculate the y position to begin drawing from
+    // and the starting x position of each line.  For right-to-left text, need to determine
+    // the number of characters on each line.
+    std::vector<int> xPositions;
+    std::vector<unsigned int> lineLengths;
+    if (vAlign != ALIGN_TOP || hAlign != ALIGN_LEFT || rightToLeft)
+    {
+        int lineWidth = 0;
+        int delimWidth = 0;
+
+        if (wrap)
+        {
+            // Go a word at a time.
+            bool reachedEOF = false;
+            unsigned int lineLength = 0;
+            while (token[0] != 0)
+            {
+                unsigned int tokenWidth = 0;
+
+                // Handle delimiters until next token.
+                char delimiter = token[0];
+                while (delimiter == ' ' ||
+                       delimiter == '\t' ||
+                       delimiter == '\r' ||
+                       delimiter == '\n' ||
+                       delimiter == 0)
+                {
+                    switch (delimiter)
+                    {
+                        case ' ':
+                            delimWidth += (float)size*0.5f;
+                            lineLength++;
+                            break;
+                        case '\r':
+                        case '\n':
+                            yPos += size;
+
+                            if (lineWidth > 0)
+                            {
+                                addLineInfo(area, lineWidth, lineLength, hAlign, &xPositions, &lineLengths, rightToLeft);
+                            }
+
+                            lineWidth = 0;
+                            lineLength = 0;
+                            delimWidth = 0;
+                            break;
+                        case '\t':
+                            delimWidth += ((float)size*0.5f)*4;
+                            lineLength++;
+                            break;
+                        case 0:
+                            reachedEOF = true;
+                            break;
+                    }
+
+                    if (reachedEOF)
+                    {
+                        break;
+                    }
+
+                    token++;
+                    delimiter = token[0];
+                }
+
+                if (reachedEOF || token == NULL)
+                {
+                    break;
+                }
+
+                unsigned int tokenLength = strcspn(token, " \r\n\t");
+                tokenWidth += getTokenWidth(token, tokenLength, size, scale);
+
+                // Wrap if necessary.
+                if (lineWidth + tokenWidth + delimWidth > area.width)
+                {
+                    yPos += size;
+
+                    // Push position of current line.
+                    if (lineLength)
+                    {
+                        addLineInfo(area, lineWidth, lineLength-1, hAlign, &xPositions, &lineLengths, rightToLeft);
+                    }
+                    else
+                    {
+                        addLineInfo(area, lineWidth, tokenLength, hAlign, &xPositions, &lineLengths, rightToLeft);
+                    }
+
+                    // Move token to the next line.
+                    lineWidth = 0;
+                    lineLength = 0;
+                    delimWidth = 0;
+                }
+                else
+                {
+                    lineWidth += delimWidth;
+                    delimWidth = 0;
+                }
+
+                lineWidth += tokenWidth;
+                lineLength += tokenLength;
+                token += tokenLength;
+            }
+
+            // Final calculation of vertical position.
+            int textHeight = yPos - area.y;
+            int vWhiteSpace = area.height - textHeight;
+            if (vAlign == ALIGN_VCENTER)
+            {
+                yPos = area.y + vWhiteSpace / 2;
+            }
+            else if (vAlign == ALIGN_BOTTOM)
+            {
+                yPos = area.y + vWhiteSpace;
+            }
+
+            // Calculation of final horizontal position.
+            addLineInfo(area, lineWidth, lineLength, hAlign, &xPositions, &lineLengths, rightToLeft);
+        }
+        else
+        {
+            // Go a line at a time.
+            while (token[0] != 0)
+            {
+                char delimiter = token[0];
+                while (delimiter == '\n')
+                {
+                    yPos += size;
+                    ++token;
+                    delimiter = token[0];
+                }
+
+                unsigned int tokenLength = strcspn(token, "\n");
+                if (tokenLength == 0)
+                {
+                    tokenLength = strlen(token);
+                }
+
+                int lineWidth = getTokenWidth(token, tokenLength, size, scale);
+                addLineInfo(area, lineWidth, tokenLength, hAlign, &xPositions, &lineLengths, rightToLeft);
+
+                token += tokenLength;
+            }
+
+            int textHeight = yPos - area.y;
+            int vWhiteSpace = area.height - textHeight;
+            if (vAlign == ALIGN_VCENTER)
+            {
+                yPos = area.y + vWhiteSpace / 2;
+            }
+            else if (vAlign == ALIGN_BOTTOM)
+            {
+                yPos = area.y + vWhiteSpace;
+            }
+        }
+
+        if (vAlign == ALIGN_TOP)
+        {
+            yPos = area.y;
+        }
+    }
+
+    // Now we have the info we need in order to render.
+    int xPos = area.x;
+    std::vector<int>::const_iterator xPositionsIt = xPositions.begin();
+    if (xPositionsIt != xPositions.end())
+    {
+        xPos = *xPositionsIt++;
+    }
+
+    token = text;
+    
+    int iteration = 1;
+    unsigned int lineLength;
+    unsigned int currentLineLength = 0;
+    const char* lineStart;
+    std::vector<unsigned int>::const_iterator lineLengthsIt;
+    if (rightToLeft)
+    {
+        lineStart = token;
+        lineLengthsIt = lineLengths.begin();
+        lineLength = *lineLengthsIt++;
+        token += lineLength - 1;
+        iteration = -1;
+    }
+
+    while (token[0] != 0)
+    {
+        // Handle delimiters until next token.
+        unsigned int delimLength = 0;
+        int result;
+        if (destIndex == -1)
+        {
+            result = handleDelimiters(&token, size, iteration, area.x, &xPos, &yPos, &delimLength, &xPositionsIt, xPositions.end(), &charIndex, &inLocation);
+        }
+        else
+        {
+            result = handleDelimiters(&token, size, iteration, area.x, &xPos, &yPos, &delimLength, &xPositionsIt, xPositions.end(), &charIndex, NULL, charIndex, destIndex);
+        }
+
+        currentLineLength += delimLength;
+        if (result == 0)
+        {
+            break;
+        }
+        else if (result == 2)
+        {
+            outLocation->x = xPos;
+            outLocation->y = yPos;
+            return charIndex;
+        }
+
+        if (destIndex == charIndex ||
+            (destIndex == -1 &&
+             inLocation.x >= xPos && inLocation.x < floor(xPos + ((float)size*0.125f)) &&
+             inLocation.y >= yPos && inLocation.y < yPos + size))
+        {
+            outLocation->x = xPos;
+            outLocation->y = yPos;
+            return charIndex;
+        }
+
+        bool truncated = false;
+        unsigned int tokenLength;
+        unsigned int tokenWidth;
+        unsigned int startIndex;
+        if (rightToLeft)
+        {
+            tokenLength = getReversedTokenLength(token, text);
+            currentLineLength += tokenLength;
+            charIndex += tokenLength;
+            token -= (tokenLength - 1);
+            tokenWidth = getTokenWidth(token, tokenLength, size, scale);
+            iteration = -1;
+            startIndex = tokenLength - 1;
+        }
+        else
+        {
+            tokenLength = strcspn(token, " \r\n\t");
+            tokenWidth = getTokenWidth(token, tokenLength, size, scale);
+            iteration = 1;
+            startIndex = 0;
+        }
+
+        // Wrap if necessary.
+        if (wrap &&
+            xPos + tokenWidth > area.x + area.width ||
+            (rightToLeft && currentLineLength > lineLength))
+        {
+            yPos += size;
+            currentLineLength = tokenLength;
+
+            if (xPositionsIt != xPositions.end())
+            {
+                xPos = *xPositionsIt++;
+            }
+            else
+            {
+                xPos = area.x;
+            }
+        }
+
+        if (yPos > area.y + area.height)
+        {
+            // Truncate below area's vertical limit.
+            break;
+        }
+
+        for (int i = startIndex; i < (int)tokenLength && i >= 0; i += iteration)
+        {
+            char c = token[i];
+            int glyphIndex = c - 32; // HACK for ASCII
+        
+            if (glyphIndex >= 0 && glyphIndex < (int)_glyphCount)
+            {
+                Glyph& g = _glyphs[glyphIndex];
+
+                if (xPos + (int)(g.width*scale) > area.x + area.width)
+                {
+                    // Truncate this line and go on to the next one.
+                    truncated = true;
+                    break;
+                }
+
+                // Check against inLocation.
+                if (destIndex == charIndex ||
+                    (destIndex == -1 &&
+                    inLocation.x >= xPos && inLocation.x < floor(xPos + g.width*scale + ((float)size*0.125f)) &&
+                    inLocation.y >= yPos && inLocation.y < yPos + size))
+                {
+                    outLocation->x = xPos;
+                    outLocation->y = yPos;
+                    return charIndex;
+                }
+
+                xPos += g.width*scale + ((float)size*0.125f);
+                charIndex++;
+            }
+        }
+
+        if (!truncated)
+        {
+            if (rightToLeft)
+            {
+                if (token == lineStart)
+                {
+                    token += lineLength;
+                    
+                    // Now handle delimiters going forwards.
+                    if (!handleDelimiters(&token, size, 1, area.x, &xPos, &yPos, &currentLineLength, &xPositionsIt, xPositions.end()))
+                    {
+                        break;
+                    }
+                    charIndex += currentLineLength;
+
+                    if (lineLengthsIt != lineLengths.end())
+                    {
+                        lineLength = *lineLengthsIt++;
+                    }
+                    lineStart = token;
+                    token += lineLength-1;
+                    charIndex += tokenLength;
+                }
+                else
+                {
+                    token--;
+                }
+            }
+            else
+            {
+                token += tokenLength;
+            }
+        }
+        else
+        {
+            if (rightToLeft)
+            {
+                token = lineStart + lineLength;
+                
+                if (!handleDelimiters(&token, size, 1, area.x, &xPos, &yPos, &currentLineLength, &xPositionsIt, xPositions.end()))
+                {
+                    break;
+                }
+
+                if (lineLengthsIt != lineLengths.end())
+                {
+                    lineLength = *lineLengthsIt++;
+                }
+                lineStart = token;
+                token += lineLength-1;
+            }
+            else
+            {
+                // Skip the rest of this line.
+                unsigned int tokenLength = strcspn(token, "\n");
+
+                if (tokenLength > 0)
+                {                
+                    // Get first token of next line.
+                    token += tokenLength;
+                    charIndex += tokenLength;
+                }
+            }
+        }
+    }
+
+    outLocation->x = xPos;
+    outLocation->y = yPos;
+    return charIndex;
+}
+
 unsigned int Font::getTokenWidth(const char* token, unsigned int length, unsigned int size, float scale)
 {
     // Calculate width of word or line.
@@ -1006,17 +1414,17 @@ unsigned int Font::getTokenWidth(const char* token, unsigned int length, unsigne
         switch (c)
         {
         case ' ':
-            tokenWidth += size>>1;
+            tokenWidth += (float)size*0.5f;
             break;
         case '\t':
-            tokenWidth += (size>>1)*4;
+            tokenWidth += ((float)size*0.5f)*4;
             break;
         default:
             int glyphIndex = c - 32;
             if (glyphIndex >= 0 && glyphIndex < (int)_glyphCount)
             {
                 Glyph& g = _glyphs[glyphIndex];
-                tokenWidth += g.width * scale + (size>>3);
+                tokenWidth += g.width * scale + ((float)size*0.125f);
             }
             break;
         }
@@ -1046,8 +1454,9 @@ unsigned int Font::getReversedTokenLength(const char* token, const char* bufStar
     return length;
 }
 
-bool Font::handleDelimiters(const char** token, const unsigned int size, const int iteration, const int areaX, int* xPos, int* yPos, unsigned int* lineLength,
-                      std::vector<int>::const_iterator* xPositionsIt, std::vector<int>::const_iterator xPositionsEnd)
+int Font::handleDelimiters(const char** token, const unsigned int size, const int iteration, const int areaX, int* xPos, int* yPos, unsigned int* lineLength,
+                          std::vector<int>::const_iterator* xPositionsIt, std::vector<int>::const_iterator xPositionsEnd, unsigned int* charIndex,
+                          const Vector2* stopAtPosition, const int currentIndex, const int destIndex)
 {
     char delimiter = *token[0];
     bool nextLine = true;
@@ -1057,11 +1466,24 @@ bool Font::handleDelimiters(const char** token, const unsigned int size, const i
             delimiter == '\n' ||
             delimiter == 0)
     {
+        if ((stopAtPosition &&
+            stopAtPosition->x >= *xPos && stopAtPosition->x < floor(*xPos + ((float)size*0.5f)) &&
+            stopAtPosition->y >= *yPos && stopAtPosition->y < *yPos + size) ||
+            (currentIndex >= 0 && destIndex >= 0 && currentIndex + *lineLength == destIndex))
+        {
+            // Success + stopAtPosition was reached.
+            return 2;
+        }
+
         switch (delimiter)
         {
             case ' ':
-                *xPos += size>>1;
+                *xPos += (float)size*0.5f;
                 (*lineLength)++;
+                if (charIndex)
+                {
+                    (*charIndex)++;
+                }
                 break;
             case '\r':
             case '\n':
@@ -1081,21 +1503,31 @@ bool Font::handleDelimiters(const char** token, const unsigned int size, const i
                     }
                     nextLine = false;
                     *lineLength = 0;
+                    if (charIndex)
+                    {
+                        (*charIndex)++;
+                    }
                 }
                 break;
             case '\t':
-                *xPos += (size>>1)*4;
+                *xPos += ((float)size*0.5f)*4;
                 (*lineLength)++;
+                if (charIndex)
+                {
+                    (*charIndex)++;
+                }
                 break;
             case 0:
-                return false;
+                // EOF reached.
+                return 0;
         }
 
         *token += iteration;
         delimiter = *token[0];
     }
 
-    return true;
+    // Success.
+    return 1;
 }
 
 void Font::addLineInfo(const Rectangle& area, int lineWidth, int lineLength, Justify hAlign,
@@ -1117,4 +1549,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;
+}
+
 }

+ 21 - 3
gameplay/src/Font.h

@@ -12,6 +12,7 @@ namespace gameplay
 class Font : public Ref
 {
     friend class Package;
+    friend class TextBox;
 
 public:
 
@@ -175,6 +176,18 @@ 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);
 
+    // Get an index into a string corresponding to the character nearest the given location within the clip region.
+    unsigned int getIndexAtLocation(const char* text, const Rectangle& clip, unsigned int size, const Vector2& inLocation, Vector2* outLocation,
+                                    Justify justify = ALIGN_TOP_LEFT, bool wrap = true, bool rightToLeft = false);
+
+    // Get the location of the character at the given index.
+    void getLocationAtIndex(const char* text, const Rectangle& clip, unsigned int size, Vector2* outLocation, const unsigned int destIndex,
+                            Justify justify = ALIGN_TOP_LEFT, bool wrap = true, bool rightToLeft = false);
+
+    SpriteBatch* getSpriteBatch();
+
+    static Justify getJustifyFromString(const char* justify);
+
 
 private:
 
@@ -193,13 +206,18 @@ private:
      */
     ~Font();
 
+    // Used by both getIndexAtLocation and getLocationAtIndex.
+    unsigned int getIndexOrLocation(const char* text, const Rectangle& clip, unsigned int size, const Vector2& inLocation, Vector2* outLocation,
+                                    const int destIndex = -1, Justify justify = ALIGN_TOP_LEFT, bool wrap = true, bool rightToLeft = false);
+
     // Utilities
     unsigned int getTokenWidth(const char* token, unsigned int length, unsigned int size, float scale);
     unsigned int getReversedTokenLength(const char* token, const char* bufStart);
 
-    // Returns false if EOF was reached, true otherwise.
-    bool handleDelimiters(const char** token, const unsigned int size, const int iteration, const int areaX, int* xPos, int* yPos, unsigned int* lineLength,
-                          std::vector<int>::const_iterator* xPositionsIt, std::vector<int>::const_iterator xPositionsEnd);
+    // Returns 0 if EOF was reached, 1 if delimiters were handles correctly, and 2 if the stopAtPosition was reached while handling delimiters.
+    int handleDelimiters(const char** token, const unsigned int size, const int iteration, const int areaX, int* xPos, int* yPos, unsigned int* lineLength,
+                          std::vector<int>::const_iterator* xPositionsIt, std::vector<int>::const_iterator xPositionsEnd, unsigned int* charIndex = NULL,
+                          const Vector2* stopAtPosition = NULL, const int currentIndex = -1, const int destIndex = -1);
     void addLineInfo(const Rectangle& area, int lineWidth, int lineLength, Justify hAlign,
                      std::vector<int>* xPositions, std::vector<unsigned int>* lineLengths, bool rightToLeft);
 

+ 385 - 0
gameplay/src/Form.cpp

@@ -0,0 +1,385 @@
+#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"
+#include "Scene.h"
+
+namespace gameplay
+{
+    static std::vector<Form*> __forms;
+
+    Form::Form() : _theme(NULL), _quad(NULL), _node(NULL), _frameBuffer(NULL), _viewport(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.
+        form->addControls(form->getTheme(), formProperties);
+
+        SAFE_DELETE(properties);
+
+        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)
+    {
+        // 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);
+        _theme->setProjectionMatrix(_projectionMatrix);
+        _viewport = new Viewport(0, 0, _size.x, _size.y);
+
+        // Connect the new node.
+        _node = node;
+        if (_node)
+        {
+            _node->setModel(_quad);
+        }
+    }
+
+    void Form::update()
+    {
+        Container::update(Vector2::zero());
+    }
+
+    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())
+            {
+                _frameBuffer->bind();
+                _viewport->bind();
+
+                Game* game = Game::getInstance();
+
+                // Clear form background color.
+                //game->clear(Game::CLEAR_COLOR, _style->getOverlay(getOverlayType())->getBorderColor(), 1.0f, 0);
+
+                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(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 (!_node || (*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 (!_node || (*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
+        RenderState::StateBlock* stateBlock = _theme->getSpriteBatch()->getStateBlock();
+        stateBlock->setDepthWrite(true);
+        //material->setStateBlock(_theme->getSpriteBatch()->getStateBlock());
+        material->setStateBlock(stateBlock);
+
+        // 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);
+    }
+
+    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;
+
+            Node* node = form->_node;
+            if (node)
+            {
+                Scene* scene = node->getScene();
+                Camera* camera = scene->getActiveCamera();
+
+                if (camera)
+                {
+                    // Get info about the form's position.
+                    Matrix m = node->getMatrix();
+                    const Vector2& size = form->getSize();
+                    Vector3 min(0, 0, 0);
+                    m.transformPoint(&min);
+
+                    // Unproject point into world space.
+                    Ray ray;
+                    camera->pickRay(NULL, x, y, &ray);
+
+                    // Find the quad's plane.
+                    // We know its normal is the quad's forward vector.
+                    Vector3 normal = node->getForwardVectorWorld();
+
+                    // To get the plane's distance from the origin,
+                    // we'll find the distance from the plane defined
+                    // by the quad's forward vector and one of its points
+                    // to the plane defined by the same vector and the origin.
+                    const float& a = normal.x; const float& b = normal.y; const float& c = normal.z;
+                    const float d = -(a*min.x) - (b*min.y) - (c*min.z);
+                    const float distance = abs(d) /  sqrt(a*a + b*b + c*c);
+                    Plane plane(normal, distance);
+
+                    // Check for collision with plane.
+                    float collides = ray.intersects(plane);
+                    if (collides != Ray::INTERSECTS_NONE)
+                    {
+                        // Check for collision with form.
+                        // Multiply the ray's direction vector by collision distance
+                        // and add that to the ray's origin.
+                        Vector3 rayOrigin = ray.getOrigin();
+                        Vector3 rayDirection = ray.getDirection();
+                        float alpha = (distance - normal.dot(rayOrigin)) / normal.dot(rayDirection);
+                        Vector3 point = rayOrigin + alpha*rayDirection;
+
+                        // Project this point into the plane.
+                        m.invert();
+                        m.transformPoint(&point);
+
+                        // If this point lies within the form, pass the touch event on.
+                        if (point.x >= 0 && point.x <= size.x &&
+                            point.y >= 0 && point.y <= size.y)
+                        {
+                            form->touchEvent(evt, point.x, size.y - point.y, contactIndex);
+                        }
+                    }
+                }
+            }
+            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);
+                }
+            }
+        }
+    }
+
+    void Form::keyEventInternal(Keyboard::KeyEvent evt, int key)
+    {
+        std::vector<Form*>::const_iterator it;
+        for (it = __forms.begin(); it < __forms.end(); it++)
+        {
+            Form* form = *it;
+            form->keyEvent(evt, key);
+        }
+    }
+}

+ 70 - 0
gameplay/src/Form.h

@@ -0,0 +1,70 @@
+#ifndef FORM_H_
+#define FORM_H_
+
+#include "Ref.h"
+#include "Container.h"
+#include "Mesh.h"
+#include "Node.h"
+#include "FrameBuffer.h"
+#include "Touch.h"
+#include "Keyboard.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 update();
+    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 void keyEventInternal(Keyboard::KeyEvent evt, int key);
+
+    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

+ 96 - 0
gameplay/src/Label.cpp

@@ -0,0 +1,96 @@
+#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::update(const Vector2& position)
+    {
+        Vector2 pos(position.x + _position.x, position.y + _position.y);
+        Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
+        
+        Theme::Border border;
+        Theme::ContainerRegion* containerRegion = overlay->getContainerRegion();
+        if (containerRegion)
+        {
+            border = overlay->getContainerRegion()->getBorder();
+        }
+        Theme::Padding padding = _style->getPadding();
+
+        // Set up the text viewport.
+        Font* font = overlay->getFont();
+        _viewport.set(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 - overlay->getFontSize());
+    }
+
+    void Label::drawText(const Vector2& position)
+    {
+        // TODO: Batch all labels that use the same font.
+        Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
+        Font* font = overlay->getFont();
+
+        // Draw the text.
+        font->begin();
+        font->drawText(_text.c_str(), _viewport, overlay->getTextColor(), overlay->getFontSize(), overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
+        font->end();
+
+        _dirty = false;
+    }
+}

+ 35 - 0
gameplay/src/Label.h

@@ -0,0 +1,35 @@
+#ifndef LABEL_H_
+#define LABEL_H_
+
+#include "Control.h"
+#include "Theme.h"
+
+namespace gameplay
+{
+
+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 update(const Vector2& position);
+
+    void drawText(const Vector2& position);
+
+protected:
+
+    Label();
+    Label(const Label& copy);
+    virtual ~Label();
+
+    std::string _text;
+    Rectangle _viewport;
+};
+
+}
+
+#endif

+ 33 - 0
gameplay/src/Layout.h

@@ -0,0 +1,33 @@
+#ifndef LAYOUT_H_
+#define LAYOUT_H_
+
+#include "Ref.h"
+
+namespace gameplay
+{
+
+class Container;
+
+class Layout : public Ref
+{
+public:
+    enum Type
+    {
+        LAYOUT_FLOW,
+        LAYOUT_VERTICAL,
+        LAYOUT_ABSOLUTE
+    };
+
+    virtual Type getType() = 0;
+
+    virtual void update(const Container* container) = 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;

+ 7 - 0
gameplay/src/Platform.h

@@ -1,6 +1,9 @@
 #ifndef PLATFORM_H_
 #define PLATFORM_H_
 
+#include "Touch.h"
+#include "Keyboard.h"
+
 namespace gameplay
 {
 
@@ -116,6 +119,10 @@ public:
      */
      static void displayKeyboard(bool display);
 
+    static void touchEventInternal(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
+
+    static void keyEventInternal(Keyboard::KeyEvent evt, int key);
+
 private:
 
     /**

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 554 - 554
gameplay/src/PlatformMacOS.mm


+ 18 - 6
gameplay/src/PlatformQNX.cpp

@@ -805,12 +805,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;
                     }
@@ -820,12 +820,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;
                     }
@@ -835,12 +835,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;
@@ -1089,6 +1089,18 @@ void Platform::displayKeyboard(bool display)
         virtualkeyboard_hide();
 }
 
+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);
+}
+
+void Platform::keyEventInternal(Keyboard::KeyEvent evt, int key)
+{
+    gameplay::Game::getInstance()->keyEvent(evt, key);
+    Form::keyEventInternal(evt, key);
+}
+
 }
 
 #endif

+ 37 - 9
gameplay/src/PlatformWin32.cpp

@@ -57,6 +57,18 @@ static gameplay::Keyboard::Key getKey(WPARAM win32KeyCode, bool shiftDown)
         return gameplay::Keyboard::KEY_ALT;
     case VK_APPS:
         return gameplay::Keyboard::KEY_MENU;
+    case VK_LSHIFT:
+        return gameplay::Keyboard::KEY_SHIFT;
+    case VK_RSHIFT:
+        return gameplay::Keyboard::KEY_SHIFT;
+    case VK_LCONTROL:
+        return gameplay::Keyboard::KEY_CTRL;
+    case VK_RCONTROL:
+        return gameplay::Keyboard::KEY_CTRL;
+    case VK_LMENU:
+        return gameplay::Keyboard::KEY_ALT;
+    case VK_RMENU:
+        return gameplay::Keyboard::KEY_ALT;
     case VK_LWIN:
     case VK_RWIN:
         return gameplay::Keyboard::KEY_HYPER;
@@ -251,6 +263,7 @@ LRESULT CALLBACK __WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
     static int lx, ly;
 
     static bool shiftDown = false;
+    static bool capsOn = false;
 
     switch (msg)
     {
@@ -270,7 +283,7 @@ LRESULT CALLBACK __WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
     case WM_LBUTTONDOWN:
         if (!gameplay::Game::getInstance()->mouseEvent(gameplay::Mouse::MOUSE_PRESS_LEFT_BUTTON, LOWORD(lParam), HIWORD(lParam), 0))
         {
-            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;
@@ -279,7 +292,7 @@ LRESULT CALLBACK __WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
         lMouseDown = false;
         if (!gameplay::Game::getInstance()->mouseEvent(gameplay::Mouse::MOUSE_RELEASE_LEFT_BUTTON, LOWORD(lParam), HIWORD(lParam), 0))
         {
-            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;
 
@@ -321,7 +334,7 @@ LRESULT CALLBACK __WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
         if (lMouseDown && !consumed)
         {
             // Mouse move events should be interpreted as touch move only if left mouse is held and the game did not consume the mouse event.
-            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)
@@ -352,31 +365,34 @@ LRESULT CALLBACK __WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
         break;
 
     case WM_KEYDOWN:
-        if (wParam == VK_SHIFT)
+        if (wParam == VK_SHIFT || wParam == VK_LSHIFT || wParam == VK_RSHIFT)
             shiftDown = true;
 
+        if (wParam == VK_CAPITAL)
+            capsOn = !capsOn;
+
         // Suppress key repeats
         if ((lParam & 0x40000000) == 0)
-            gameplay::Game::getInstance()->keyEvent(gameplay::Keyboard::KEY_PRESS, getKey(wParam, shiftDown));
+	        gameplay::Platform::keyEventInternal(gameplay::Keyboard::KEY_PRESS, getKey(wParam, shiftDown ^ capsOn));
         break;
         
     case WM_KEYUP:
-        if (wParam == VK_SHIFT)
+        if (wParam == VK_SHIFT || wParam == VK_LSHIFT || wParam == VK_RSHIFT)
             shiftDown = false;
 
-        gameplay::Game::getInstance()->keyEvent(gameplay::Keyboard::KEY_RELEASE, getKey(wParam, shiftDown));
+        gameplay::Platform::keyEventInternal(gameplay::Keyboard::KEY_RELEASE, getKey(wParam, shiftDown ^ capsOn));
         break;
 
     case WM_CHAR:
         // Suppress key repeats
         if ((lParam & 0x40000000) == 0)
-            gameplay::Game::getInstance()->keyEvent(gameplay::Keyboard::KEY_CHAR, wParam);
+            gameplay::Platform::keyEventInternal(gameplay::Keyboard::KEY_CHAR, wParam);
         break;
 
     case WM_UNICHAR:
         // Suppress key repeats
         if ((lParam & 0x40000000) == 0)
-            gameplay::Game::getInstance()->keyEvent(gameplay::Keyboard::KEY_CHAR, wParam);
+            gameplay::Platform::keyEventInternal(gameplay::Keyboard::KEY_CHAR, wParam);
         break;
 
     case WM_SETFOCUS:
@@ -645,6 +661,18 @@ void Platform::displayKeyboard(bool display)
     // Do nothing.
 }
 
+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);
+}
+
+void Platform::keyEventInternal(Keyboard::KeyEvent evt, int key)
+{
+    gameplay::Game::getInstance()->keyEvent(evt, key);
+    Form::keyEventInternal(evt, key);
+}
+
 }
 
 #endif

+ 174 - 0
gameplay/src/RadioButton.cpp

@@ -0,0 +1,174 @@
+#include "Base.h"
+#include "RadioButton.h"
+
+namespace gameplay
+{
+
+static std::vector<RadioButton*> __radioButtons;
+
+RadioButton::RadioButton() : _selected(false)
+{
+}
+
+RadioButton::RadioButton(const RadioButton& copy)
+{
+    // Hidden.
+}
+
+RadioButton::~RadioButton()
+{
+
+}
+
+RadioButton* RadioButton::create(Theme::Style* style, Properties* properties)
+{
+    RadioButton* radioButton = new RadioButton();
+    radioButton->_style = style;
+    radioButton->_id = properties->getId();
+    properties->getVector2("position", &radioButton->_position);
+    properties->getVector2("size", &radioButton->_size);
+    radioButton->_text = properties->getString("text");
+    radioButton->_groupId = properties->getString("group");
+    if (properties->getBool("selected"))
+    {
+        RadioButton::clearSelected(radioButton->_groupId);
+        radioButton->_selected = true;
+    }
+
+    __radioButtons.push_back(radioButton);
+
+    return radioButton;
+}
+
+RadioButton* RadioButton::getRadioButton(const char* id)
+{
+    std::vector<RadioButton*>::const_iterator it;
+    for (it = __radioButtons.begin(); it < __radioButtons.end(); it++)
+    {
+        RadioButton* radioButton = *it;
+        if (strcmp(id, radioButton->getID()) == 0)
+        {
+            return radioButton;
+        }
+    }
+
+    return NULL;
+}
+
+void RadioButton::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+{
+    switch (evt)
+    {
+    case Touch::TOUCH_PRESS:
+        {
+            _state = Control::STATE_ACTIVE;
+            _dirty = true;
+        }
+        break;
+    case Touch::TOUCH_RELEASE:
+        {
+            if (_state == Control::STATE_ACTIVE)
+            {
+                if (x > 0 && x <= _size.x &&
+                    y > 0 && y <= _size.y)
+                {
+                    if (_callback)
+                    {
+                        _callback->trigger(this);
+                    }
+                    RadioButton::clearSelected(_groupId);
+                    _selected = true;
+                }
+                setState(Control::STATE_NORMAL);
+
+            }
+        }
+        break;
+    }
+}
+
+void RadioButton::clearSelected(const std::string& groupId)
+{
+    std::vector<RadioButton*>::const_iterator it;
+    for (it = __radioButtons.begin(); it < __radioButtons.end(); it++)
+    {
+        RadioButton* radioButton = *it;
+        if (groupId == radioButton->_groupId)
+        {
+            radioButton->_selected = false;
+        }
+    }
+}
+
+void RadioButton::drawSprites(SpriteBatch* spriteBatch, const Vector2& position)
+{
+    // Left, v-center.
+    // TODO: Set an alignment for icons.
+    Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
+    Theme::Icon* icon = overlay->getRadioButtonIcon();
+    if (icon)
+    {
+        Theme::Border border;
+        Theme::ContainerRegion* containerRegion = overlay->getContainerRegion();
+        if (containerRegion)
+        {
+            border = containerRegion->getBorder();
+        }
+        const Theme::Padding padding = _style->getPadding();
+
+        const Vector2 size = icon->getSize();
+        const Vector4 color = icon->getColor();
+
+        Vector2 pos(position.x + _position.x + border.left + padding.left,
+            position.y + _position.y + (_size.y - border.bottom - padding.bottom) / 2.0f - size.y / 2.0f);
+
+        if (_selected)
+        {
+            const Theme::UVs on = icon->getOnUVs();
+            spriteBatch->draw(pos.x, pos.y, size.x, size.y, on.u1, on.v1, on.u2, on.v2, color);
+        }
+        else
+        {
+            const Theme::UVs off = icon->getOffUVs();
+            spriteBatch->draw(pos.x, pos.y, size.x, size.y, off.u1, off.v1, off.u2, off.v2, color);
+        }
+    }
+}
+
+void RadioButton::drawText(const Vector2& position)
+{
+    // TODO: Batch all labels that use the same font.
+    Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
+    Theme::Icon* icon = overlay->getRadioButtonIcon();
+    Theme::Border border;
+    Theme::ContainerRegion* containerRegion = overlay->getContainerRegion();
+    if (containerRegion)
+    {
+        border = containerRegion->getBorder();
+    }
+    Theme::Padding padding = _style->getPadding();
+
+    // Set up the text viewport.
+    float iconWidth = 0.0f;
+    if (icon)
+    {
+        iconWidth = icon->getSize().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;
+}
+
+}

+ 37 - 0
gameplay/src/RadioButton.h

@@ -0,0 +1,37 @@
+#ifndef RADIOBUTTON_H_
+#define RADIOBUTTON_H_
+
+#include "Button.h"
+#include "Theme.h"
+#include "Properties.h"
+
+namespace gameplay
+{
+
+class RadioButton : public Button
+{
+public:
+    RadioButton();
+    virtual ~RadioButton();
+
+    static RadioButton* create(Theme::Style* style, Properties* properties);
+    static RadioButton* getRadioButton(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:
+    RadioButton(const RadioButton& copy);
+
+    // Clear the _selected flag of all RadioButtons in the given group.
+    static void clearSelected(const std::string& groupId);
+
+    std::string _groupId;
+    bool _selected;
+};
+
+}
+
+#endif

+ 158 - 0
gameplay/src/Slider.cpp

@@ -0,0 +1,158 @@
+#include "Slider.h"
+
+namespace gameplay
+{
+
+static std::vector<Slider*> __sliders;
+
+Slider::Slider()
+{
+}
+
+Slider::~Slider()
+{
+}
+
+Slider* Slider::create(Theme::Style* style, Properties* properties)
+{
+    Slider* slider = new Slider();
+
+    slider->_style = style;
+    slider->_id = properties->getId();
+    properties->getVector2("position", &slider->_position);
+    properties->getVector2("size", &slider->_size);
+    slider->_min = properties->getFloat("min");
+    slider->_max = properties->getFloat("max");
+    slider->_value = properties->getFloat("value");
+    slider->_step = properties->getFloat("step");
+    slider->_text = properties->getString("text");
+
+    __sliders.push_back(slider);
+
+    return slider;
+}
+
+Slider* Slider::create(const char* id, float min, float max, float default, float step)
+{
+    Slider* slider = new Slider();
+
+    return slider;
+}
+
+Slider* Slider::getSlider(const char* id)
+{
+    std::vector<Slider*>::const_iterator it;
+    for (it = __sliders.begin(); it < __sliders.end(); it++)
+    {
+        Slider* slider = *it;
+        if (strcmp(id, slider->getID()) == 0)
+        {
+            return slider;
+        }
+    }
+
+    return NULL;
+}
+
+void Slider::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+{
+    Button::touchEvent(evt, x, y, contactIndex);
+
+    if (_state == STATE_ACTIVE &&
+        x > 0 && x <= _size.x &&
+        y > 0 && y <= _size.y)
+    {
+        // Horizontal case.
+        Theme::Border border;
+        Theme::ContainerRegion* containerRegion = _style->getOverlay(getOverlayType())->getContainerRegion();
+        if (containerRegion)
+        {
+            border = containerRegion->getBorder();
+        }
+        Theme::Padding padding = _style->getPadding();
+
+        const Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
+        const Theme::SliderIcon* icon = overlay->getSliderIcon();
+
+        const Vector2 minCapSize = icon->getMinCapSize();
+        const Vector2 maxCapSize = icon->getMaxCapSize();
+
+        float markerPosition = ((float)x - maxCapSize.x - border.left - padding.left) /
+                               (_size.x - border.left - border.right - padding.left - padding.right - minCapSize.x - maxCapSize.x);
+        if (markerPosition > 1.0f)
+        {
+            markerPosition = 1.0f;
+        }
+        else if (markerPosition < 0.0f)
+        {
+            markerPosition = 0.0f;
+        }
+
+        _value = markerPosition * _max;
+        if (_step > 0.0f)
+        {
+            float stepDistance = _step / (_max - _min);
+            
+            int numSteps = round(_value / _step);
+            _value = _step * numSteps;
+        }
+    }
+}
+
+void Slider::drawSprites(SpriteBatch* spriteBatch, const Vector2& position)
+{
+    // TODO: Vertical slider.
+
+    // The slider is drawn in the center of the control (perpendicular to orientation).
+    // The track is stretched according to orientation.
+    // Caps and marker are not stretched.
+    Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
+    Theme::SliderIcon* icon = overlay->getSliderIcon();
+    if (icon)
+    {
+        Theme::Border border;
+        Theme::ContainerRegion* containerRegion = overlay->getContainerRegion();
+        if (containerRegion)
+        {
+            border = containerRegion->getBorder();
+        }
+        Theme::Padding padding = _style->getPadding();
+
+        const Vector2 minCapSize = icon->getMinCapSize();
+        const Vector2 maxCapSize = icon->getMaxCapSize();
+        const Vector2 markerSize = icon->getMarkerSize();
+        const Vector2 trackSize = icon->getTrackSize();
+
+        const Theme::UVs minCap = icon->getMinCapUVs();
+        const Theme::UVs maxCap = icon->getMaxCapUVs();
+        const Theme::UVs marker = icon->getMarkerUVs();
+        const Theme::UVs track = icon->getTrackUVs();
+
+        const Vector4 color = icon->getColor();
+
+        // Draw order: track, caps, marker.
+        float midY = position.y + _position.y + (_size.y - border.bottom - padding.bottom) / 2.0f;
+        Vector2 pos(position.x + _position.x + border.left + padding.left, midY - trackSize.y / 2.0f);
+        spriteBatch->draw(pos.x, pos.y, _size.x, trackSize.y, track.u1, track.v1, track.u2, track.v2, color);
+
+        pos.y = midY - minCapSize.y / 2.0f;
+        spriteBatch->draw(pos.x, pos.y, minCapSize.x, minCapSize.y, minCap.u1, minCap.v1, minCap.u2, minCap.v2, color);
+        
+        pos.x = position.x + _position.x + _size.x - border.left - padding.left - maxCapSize.x;
+        spriteBatch->draw(pos.x, pos.y, maxCapSize.x, maxCapSize.y, maxCap.u1, maxCap.v1, maxCap.u2, maxCap.v2, color);
+
+        // Percent across.
+        float markerPosition = _value / (_max - _min);
+        markerPosition *= _size.x - border.left - padding.left - border.right - padding.right - minCapSize.x - maxCapSize.x - markerSize.x;
+        pos.x = position.x + _position.x + border.left + padding.left +  minCapSize.x + markerPosition;
+        pos.y = midY - markerSize.y / 2.0f;
+        spriteBatch->draw(pos.x, pos.y, markerSize.x, markerSize.y, marker.u1, marker.v1, marker.u2, marker.v2, color);
+    }
+}
+
+void Slider::drawText(const Vector2& position)
+{
+
+}
+
+}

+ 42 - 0
gameplay/src/Slider.h

@@ -0,0 +1,42 @@
+#ifndef SLIDER_H_
+#define SLIDER_H_
+
+#include "Base.h"
+#include "Theme.h"
+#include "Properties.h"
+#include "Button.h"
+#include "Touch.h"
+
+namespace gameplay
+{
+
+class Slider : public Button
+{
+public:
+
+    Slider();
+    ~Slider();
+
+    static Slider* create(Theme::Style* style, Properties* properties);
+    static Slider* create(const char* id, float min, float max, float default = 0.0f, float step = 1.0f);
+    static Slider* getSlider(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);
+
+    void setValue(float value);
+    float getValue();
+
+private:
+
+    float _min;
+    float _max;
+    float _step;
+    float _value;
+};
+
+}
+
+#endif

+ 1 - 0
gameplay/src/SpriteBatch.cpp

@@ -101,6 +101,7 @@ SpriteBatch* SpriteBatch::create(Texture* texture, Effect* effect, unsigned int
         else
         {
             effect = __spriteEffect;
+            __spriteEffect->addRef();
         }
     }
 

+ 294 - 0
gameplay/src/TextBox.cpp

@@ -0,0 +1,294 @@
+#include "TextBox.h"
+
+namespace gameplay
+{
+
+static std::vector<TextBox*> __textBoxes;
+
+TextBox::TextBox()
+{
+}
+
+TextBox::TextBox(const TextBox& copy)
+{
+}
+
+TextBox::~TextBox()
+{
+}
+
+TextBox* TextBox::create(Theme::Style* style, Properties* properties)
+{
+    TextBox* textBox = new TextBox();
+    textBox->_style = style;
+    textBox->_id = properties->getId();
+    properties->getVector2("position", &textBox->_position);
+    properties->getVector2("size", &textBox->_size);
+    textBox->_text = properties->getString("text");
+
+    __textBoxes.push_back(textBox);
+    return textBox;
+}
+
+TextBox* TextBox::getTextBox(const char* id)
+{
+    std::vector<TextBox*>::const_iterator it;
+    for (it = __textBoxes.begin(); it < __textBoxes.end(); it++)
+    {
+        TextBox* t = *it;
+        if (strcmp(id, t->getID()) == 0)
+        {
+            return t;
+        }
+    }
+
+    return NULL;
+}
+
+void TextBox::setCursorLocation(int x, int y)
+{
+    Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
+    Theme::ContainerRegion* containerRegion = overlay->getContainerRegion();
+    Theme::Border border;
+    if (containerRegion)
+    {
+        border = containerRegion->getBorder();
+    }
+    Theme::Padding padding = _style->getPadding();
+
+    _cursorLocation.set(x - border.left - padding.left + _viewport.x,
+                       y - border.top - padding.top + _viewport.y);
+}
+
+void TextBox::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+{   
+    if (_state != STATE_DISABLED)
+    {
+        switch (evt)
+        {
+        case Touch::TOUCH_PRESS: 
+            if (_state == STATE_NORMAL)
+            {
+                _state = STATE_ACTIVE;
+            }
+            break;
+        case Touch::TOUCH_MOVE:
+            if (_state == STATE_FOCUS &&
+                x > 0 && x <= _size.x &&
+                y > 0 && y <= _size.y)
+            {
+                setCursorLocation(x, y);
+                _dirty = true;
+            }
+            break;
+        case Touch::TOUCH_RELEASE:
+            if (x > 0 && x <= _size.x &&
+                y > 0 && y <= _size.y)
+            {
+                setCursorLocation(x, y);
+                _state = STATE_FOCUS;
+                _dirty = true;
+            }
+            else
+            {
+                _state = STATE_NORMAL;
+            }
+            break;
+        }
+    }
+}
+
+void TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
+{
+    if (_state == STATE_FOCUS)
+    {
+        switch (evt)
+        {
+            case Keyboard::KEY_PRESS:
+            {
+                switch (key)
+                {
+                    case Keyboard::KEY_HOME:
+                    {
+                        // TODO: Move cursor to beginning of line.
+                        // This only works for left alignment...
+                        
+                        //_cursorLocation.x = _viewport.x;
+                        //_dirty = true;
+                        break;
+                    }
+                    case Keyboard::KEY_END:
+                    {
+                        // TODO: Move cursor to end of line.
+                        break;
+                    }
+                    case Keyboard::KEY_DELETE:
+                    {
+                        Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
+                        Font* font = overlay->getFont();
+                        unsigned int fontSize = overlay->getFontSize();
+                        unsigned int textIndex = font->getIndexAtLocation(_text.c_str(), _viewport, fontSize, _cursorLocation, &_cursorLocation,
+                            overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
+
+                        _text.erase(textIndex, 1);
+                        font->getLocationAtIndex(_text.c_str(), _viewport, fontSize, &_cursorLocation, textIndex,
+                            overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
+                        _dirty = true;
+                        break;
+                    }
+                    case Keyboard::KEY_LEFT_ARROW:
+                    {
+                        Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
+                        Font* font = overlay->getFont();
+                        unsigned int fontSize = overlay->getFontSize();
+                        unsigned int textIndex = font->getIndexAtLocation(_text.c_str(), _viewport, fontSize, _cursorLocation, &_cursorLocation,
+                            overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
+
+                        font->getLocationAtIndex(_text.c_str(), _viewport, fontSize, &_cursorLocation, textIndex - 1,
+                            overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
+                        _dirty = true;
+                        break;
+                    }
+                    case Keyboard::KEY_RIGHT_ARROW:
+                    {
+                        Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
+                        Font* font = overlay->getFont();
+                        unsigned int fontSize = overlay->getFontSize();
+                        unsigned int textIndex = font->getIndexAtLocation(_text.c_str(), _viewport, fontSize, _cursorLocation, &_cursorLocation,
+                            overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
+
+                        font->getLocationAtIndex(_text.c_str(), _viewport, fontSize, &_cursorLocation, textIndex + 1,
+                            overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
+                        _dirty = true;
+                        break;
+                    }
+                    case Keyboard::KEY_UP_ARROW:
+                    {
+                        Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
+                        Font* font = overlay->getFont();
+                        unsigned int fontSize = overlay->getFontSize();
+
+                        _cursorLocation.y -= fontSize;
+                        font->getIndexAtLocation(_text.c_str(), _viewport, fontSize, _cursorLocation, &_cursorLocation,
+                            overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
+                        _dirty = true;
+                        break;
+                    }
+                    case Keyboard::KEY_DOWN_ARROW:
+                    {
+                        Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
+                        Font* font = overlay->getFont();
+                        unsigned int fontSize = overlay->getFontSize();
+
+                        _cursorLocation.y += fontSize;
+                        font->getIndexAtLocation(_text.c_str(), _viewport, fontSize, _cursorLocation, &_cursorLocation,
+                            overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
+                        _dirty = true;
+                        break;
+                    }
+                }
+                break;
+            }
+
+            case Keyboard::KEY_CHAR:
+            {
+                Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
+                Font* font = overlay->getFont();
+                unsigned int fontSize = overlay->getFontSize();
+                unsigned int textIndex = font->getIndexAtLocation(_text.c_str(), _viewport, fontSize, _cursorLocation, &_cursorLocation,
+                    overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
+
+                switch (key)
+                {
+                    case Keyboard::KEY_BACKSPACE:
+                    {
+                        if (textIndex > 0)
+                        {
+                            --textIndex;
+                            _text.erase(textIndex, 1);
+                            font->getLocationAtIndex(_text.c_str(), _viewport, fontSize, &_cursorLocation, textIndex,
+                                overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
+
+                            _dirty = true;
+                        }
+                        break;
+                    }
+                    case Keyboard::KEY_RETURN:
+                        // TODO: Handle line-break insertion correctly.
+                        break;
+                    default:
+                    {
+                        // Insert character into string.
+                        _text.insert(textIndex, 1, (char)key);
+
+                        // Get new location of cursor.
+                        font->getLocationAtIndex(_text.c_str(), _viewport, fontSize, &_cursorLocation, textIndex + 1,
+                            overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
+                
+                        _dirty = true;
+                        break;
+                    }
+            
+                    break;
+                }
+            }
+        }
+    }
+}
+
+void TextBox::update(const Vector2& position)
+{
+    Vector2 pos(position.x + _position.x, position.y + _position.y);
+    Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
+    Theme::Border border;
+    Theme::ContainerRegion* containerRegion = overlay->getContainerRegion();
+    if (containerRegion)
+    {
+        border = containerRegion->getBorder();
+    }
+    Theme::Padding padding = _style->getPadding();
+
+    // Set up the text viewport.
+    Font* font = overlay->getFont();
+    _viewport.set(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 - overlay->getFontSize());
+
+    // Get index into string and cursor location from the last recorded touch location.
+    if (_state == STATE_FOCUS)
+    {
+        font->getIndexAtLocation(_text.c_str(), _viewport, overlay->getFontSize(), _cursorLocation, &_cursorLocation,
+            overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
+    }
+
+    _dirty = false;
+}
+
+void TextBox::drawSprites(SpriteBatch* spriteBatch, const Vector2& position)
+{
+    if (_state == STATE_FOCUS)
+    {
+        // Draw the cursor at its current location.
+        Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
+        Theme::Cursor* cursor = overlay->getTextCursor();
+        if (cursor)
+        {
+            Theme::Border border;
+            Theme::ContainerRegion* containerRegion = overlay->getContainerRegion();
+            if (containerRegion)
+            {
+                border = containerRegion->getBorder();
+            }
+            const Theme::Padding padding = _style->getPadding();
+            const Vector2 size = cursor->getSize();
+            const Vector4 color = cursor->getColor();
+            const Theme::UVs uvs = cursor->getUVs();
+            unsigned int fontSize = overlay->getFontSize();
+
+            spriteBatch->draw(_cursorLocation.x - (size.x / 2.0f), _cursorLocation.y, size.x, fontSize, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color);
+        }
+    }
+}
+
+}

+ 40 - 0
gameplay/src/TextBox.h

@@ -0,0 +1,40 @@
+#ifndef TEXTBOX_H_
+#define TEXTBOX_H_
+
+#include "Base.h"
+#include "Label.h"
+#include "Theme.h"
+#include "Keyboard.h"
+
+namespace gameplay
+{
+
+class TextBox : public Label
+{
+public:
+    static TextBox* create(Theme::Style* style, Properties* properties);
+    static TextBox* getTextBox(const char* id);
+
+    void touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
+
+    void keyEvent(Keyboard::KeyEvent evt, int key);
+
+    void update(const Vector2& position);
+
+    // Draw the cursor.
+    void drawSprites(SpriteBatch* spriteBatch, const Vector2& position);
+
+private:
+    TextBox();
+    TextBox(const TextBox& copy);
+    ~TextBox();
+
+    void setCursorLocation(int x, int y);
+
+    Vector2 _cursorLocation;
+    unsigned int textIndex;
+};
+
+}
+
+#endif

+ 1124 - 0
gameplay/src/Theme.cpp

@@ -0,0 +1,1124 @@
+/*
+ * Theme.cpp
+ */
+
+#include "Base.h"
+#include "Theme.h"
+
+namespace gameplay
+{
+    static std::vector<Theme*> __themeCache;
+
+    Theme::Theme()
+    {
+    }
+
+    Theme::Theme(const Theme* theme)
+    {
+    }
+
+    Theme::~Theme()
+    {
+        // Destroy all the cursors, styles and , fonts.
+        for (unsigned int i = 0, count = _styles.size(); i < count; ++i)
+        {
+            Style* style = _styles[i];
+            SAFE_DELETE(style);
+        }
+
+        for (unsigned int i = 0, count = _cursors.size(); i < count; ++i)
+        {
+            Cursor* cursor = _cursors[i];
+            SAFE_RELEASE(cursor);
+        }
+
+        for (unsigned int i = 0, count = _icons.size(); i < count; ++i)
+        {
+            Icon* icon = _icons[i];
+            SAFE_RELEASE(icon);
+        }
+
+        for (unsigned int i = 0, count = _sliders.size(); i < count; ++i)
+        {
+            SliderIcon* slider = _sliders[i];
+            SAFE_RELEASE(slider);
+        }
+
+        for (unsigned int i = 0, count = _containers.size(); i < count; ++i)
+        {
+            ContainerRegion* container = _containers[i];
+            SAFE_RELEASE(container);
+        }
+
+        SAFE_DELETE(_spriteBatch);
+        SAFE_RELEASE(_texture);
+
+        // 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* 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 can be referred to by 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(1, 1, 1, 1);
+                if (space->exists("color"))
+                {
+                    space->getColor("color", &color);
+                }
+
+                Theme::Cursor* c = Theme::Cursor::create(space->getId(), *theme->_texture, region, color);
+                theme->_cursors.push_back(c);
+            }
+            else if (strcmp(spacename, "icon") == 0)
+            {
+                Vector2 offVec;
+                Vector2 onVec;
+                Vector2 size;
+                space->getVector2("offPosition", &offVec);
+                space->getVector2("onPosition", &onVec);
+                space->getVector2("size", &size);
+                
+                Vector4 color(1, 1, 1, 1);
+                if (space->exists("color"))
+                {
+                    space->getColor("color", &color);
+                }
+
+                Icon* icon = Icon::create(space->getId(), *theme->_texture, size, offVec, onVec, color);
+                theme->_icons.push_back(icon);
+            }
+            else if (strcmp(spacename, "slider") == 0)
+            {
+                Vector4 minCapRegion;
+                Vector4 maxCapRegion;
+                Vector4 trackRegion;
+                Vector4 markerRegion;
+                space->getVector4("leftCapRegion", &minCapRegion);
+                space->getVector4("rightCapRegion", &maxCapRegion);
+                space->getVector4("trackRegion", &trackRegion);
+                space->getVector4("markerRegion", &markerRegion);
+                
+                Vector4 color(1, 1, 1, 1);
+                if (space->exists("color"))
+                {
+                    space->getColor("color", &color);
+                }
+
+                SliderIcon* sliderIcon = SliderIcon::create(space->getId(), *theme->_texture, minCapRegion, maxCapRegion, markerRegion, trackRegion, color);
+                theme->_sliders.push_back(sliderIcon);
+            }
+            else if (strcmp(spacename, "cursor") == 0)
+            {
+                Vector4 regionVector;
+                space->getVector4("region", &regionVector);
+                const Rectangle region(regionVector.x, regionVector.y, regionVector.z, regionVector.w);
+
+                Vector4 color(1, 1, 1, 1);
+                if (space->exists("color"))
+                {
+                    space->getColor("color", &color);
+                }
+
+                Cursor* cursor = Cursor::create(space->getId(), *theme->_texture, region, color);
+                theme->_cursors.push_back(cursor);
+            }
+            else if (strcmp(spacename, "container") == 0)
+            {
+                Theme::Border border;
+                Properties* innerSpace = space->getNextNamespace();
+                if (innerSpace)
+                {
+                    const char* innerSpacename = innerSpace->getNamespace();
+                    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");
+                    }
+                }
+
+                Vector4 regionVector;
+                space->getVector4("region", &regionVector);
+                const Rectangle region(regionVector.x, regionVector.y, regionVector.z, regionVector.w);
+
+                Vector4 color(1, 1, 1, 1);
+                if (space->exists("color"))
+                {
+                    space->getColor("color", &color);
+                }
+
+                ContainerRegion* container = ContainerRegion::create(space->getId(), *theme->_texture, region, border, color);
+                theme->_containers.push_back(container);
+            }
+
+            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::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 textColor(0, 0, 0, 1);
+                        if (innerSpace->exists("textColor"))
+                        {
+                            innerSpace->getColor("textColor", &textColor);
+                        }
+
+                        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");
+
+                        Icon* checkBoxIcon = NULL;
+                        Icon* radioButtonIcon = NULL;
+                        SliderIcon* sliderIcon = NULL;
+                        Cursor* textCursor = NULL;
+                        Cursor* mouseCursor = NULL;
+                        ContainerRegion* containerRegion = NULL;
+                        theme->lookUpSprites(innerSpace, &checkBoxIcon, &radioButtonIcon, &sliderIcon, &textCursor, &mouseCursor, &containerRegion);
+
+                        normal = Theme::Style::Overlay::create();
+                        normal->setContainerRegion(containerRegion);
+                        normal->setTextCursor(textCursor);
+                        normal->setMouseCursor(mouseCursor);
+                        normal->setCheckBoxIcon(checkBoxIcon);
+                        normal->setRadioButtonIcon(radioButtonIcon);
+                        normal->setSliderIcon(sliderIcon);
+                        normal->setTextColor(textColor);
+                        normal->setFont(font);
+                        normal->setFontSize(fontSize);
+                        normal->setTextAlignment(alignment);
+                        normal->setTextRightToLeft(rightToLeft);
+
+                        theme->_fonts.insert(font);
+
+                        if (font) font->release();
+
+                        // 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, "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 textColor;
+                        if (!innerSpace->getColor("textColor", &textColor))
+                        {
+                            textColor.set(normal->getTextColor());
+                        }
+
+                        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();
+                        }
+
+
+                        Icon* checkBoxIcon = NULL;
+                        Icon* radioButtonIcon = NULL;
+                        SliderIcon* sliderIcon = NULL;
+                        Cursor* textCursor = NULL;
+                        Cursor* mouseCursor = NULL;
+                        ContainerRegion* containerRegion = NULL;
+                        theme->lookUpSprites(innerSpace, &checkBoxIcon, &radioButtonIcon, &sliderIcon, &textCursor, &mouseCursor, &containerRegion);
+
+                        if (!checkBoxIcon)
+                        {
+                            checkBoxIcon = normal->getCheckBoxIcon();
+                        }
+                        
+                        if (!radioButtonIcon)
+                        {
+                            radioButtonIcon = normal->getRadioButtonIcon();
+                        }
+                        
+                        if (!sliderIcon)
+                        {
+                            sliderIcon = normal->getSliderIcon();
+                        }
+
+                        if (!textCursor)
+                        {
+                            textCursor = normal->getTextCursor();
+                        }
+
+                        if (!mouseCursor)
+                        {
+                            mouseCursor = normal->getMouseCursor();
+                        }
+                        
+                        if (!containerRegion)
+                        {
+                            containerRegion = normal->getContainerRegion();
+                        }
+
+                        
+                        if (strcmp(innerSpacename, "focus") == 0)
+                        {
+                            focus = Theme::Style::Overlay::create();
+                            focus->setContainerRegion(containerRegion);
+                            focus->setCheckBoxIcon(checkBoxIcon);
+                            focus->setTextCursor(textCursor);
+                            focus->setMouseCursor(mouseCursor);
+                            focus->setCheckBoxIcon(checkBoxIcon);
+                            focus->setRadioButtonIcon(radioButtonIcon);
+                            focus->setSliderIcon(sliderIcon);
+                            focus->setTextColor(textColor);
+                            focus->setFont(font);
+                            focus->setFontSize(fontSize);
+                            focus->setTextAlignment(alignment);
+                            focus->setTextRightToLeft(rightToLeft);
+
+                            theme->_fonts.insert(font);
+                        }
+                        else if (strcmp(innerSpacename, "active") == 0)
+                        {
+                            active = Theme::Style::Overlay::create();
+                            active->setContainerRegion(containerRegion);
+                            active->setTextCursor(textCursor);
+                            active->setMouseCursor(mouseCursor);
+                            active->setCheckBoxIcon(checkBoxIcon);
+                            active->setRadioButtonIcon(radioButtonIcon);
+                            active->setSliderIcon(sliderIcon);
+                            active->setTextColor(textColor);
+                            active->setFont(font);
+                            active->setFontSize(fontSize);
+                            active->setTextAlignment(alignment);
+                            active->setTextRightToLeft(rightToLeft);
+
+                            theme->_fonts.insert(font);
+                        }
+                    }
+
+                    innerSpace = space->getNextNamespace();
+                }
+                
+                if (!focus)
+                {
+                    focus = normal;
+                    focus->addRef();
+                }
+
+                if (!active)
+                {
+                    active = normal;
+                    active->addRef();
+                }
+
+                Theme::Style* s = new Theme::Style(space->getId(), margin, padding, normal, focus, active);
+                theme->_styles.push_back(s);
+            }
+
+            space = themeProperties->getNextNamespace();
+        }
+
+        // Add this theme to the cache.
+        __themeCache.push_back(theme);
+
+        SAFE_DELETE(properties);
+
+        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;
+    }
+
+    void Theme::setProjectionMatrix(const Matrix& matrix)
+    {
+        _spriteBatch->setProjectionMatrix(matrix);
+
+        // Set the matrix on each Font used by the style.
+        std::set<Font*>::const_iterator it;
+        for (it = _fonts.begin(); it != _fonts.end(); ++it)
+        {
+            (*it)->getSpriteBatch()->setProjectionMatrix(matrix);
+        }
+    }
+
+    SpriteBatch* Theme::getSpriteBatch() const
+    {
+        return _spriteBatch;
+    }
+
+    /***************
+     * Theme::Icon *
+     ***************/
+    Theme::Icon* Theme::Icon::create(const char* id, const Texture& texture, const Vector2& size,
+                                     const Vector2& offPosition, const Vector2& onPosition, const Vector4& color)
+    {
+        Icon* icon = new Icon(texture, size, offPosition, onPosition, color);
+
+        if (id)
+        {
+            icon->_id = id;
+        }
+
+        return icon;
+    }
+
+    Theme::Icon::Icon(const Texture& texture, const Vector2& size,
+                      const Vector2& offPosition, const Vector2& onPosition, const Vector4& color)
+                      : _size(size), _color(color)
+    {
+        generateUVs(texture, offPosition.x, offPosition.y, size.x, size.y, &_off);
+        generateUVs(texture, onPosition.x, onPosition.y, size.x, size.y, &_on);
+    }
+
+    Theme::Icon::~Icon()
+    {
+    }
+
+    const char* Theme::Icon::getId() const
+    {
+        return _id.c_str();
+    }
+
+    const Vector2& Theme::Icon::getSize() const
+    {
+        return _size;
+    }
+
+    const Vector4& Theme::Icon::getColor() const
+    {
+        return _color;
+    }
+
+    const Theme::UVs& Theme::Icon::getOffUVs() const
+    {
+        return _off;
+    }
+
+    const Theme::UVs& Theme::Icon::getOnUVs() const
+    {
+        return _on;
+    }
+
+
+    /*********************
+     * Theme::SliderIcon *
+     *********************/
+    Theme::SliderIcon* Theme::SliderIcon::create(const char* id, const Texture& texture, const Vector4& minCapRegion,
+            const Vector4& maxCapRegion, const Vector4& markerRegion, const Vector4& trackRegion, const Vector4& color)
+    {
+        SliderIcon* sliderIcon = new SliderIcon(texture, minCapRegion, maxCapRegion, markerRegion, trackRegion, color);
+
+        if (id)
+        {
+            sliderIcon->_id = id;
+        }
+
+        return sliderIcon;
+    }
+
+    Theme::SliderIcon::SliderIcon(const Texture& texture, const Vector4& minCapRegion, const Vector4& maxCapRegion,
+                                  const Vector4& markerRegion, const Vector4& trackRegion, const Vector4& color)
+                                  : _color(color)
+    {
+        _minCapSize.set(minCapRegion.z, minCapRegion.w);
+        _maxCapSize.set(maxCapRegion.z, maxCapRegion.w);
+        _markerSize.set(markerRegion.z, markerRegion.w);
+        _trackSize.set(trackRegion.z, trackRegion.w);
+
+        generateUVs(texture, minCapRegion.x, minCapRegion.y, minCapRegion.z, minCapRegion.w, &_minCap);
+        generateUVs(texture, maxCapRegion.x, maxCapRegion.y, maxCapRegion.z, maxCapRegion.w, &_maxCap);
+        generateUVs(texture, markerRegion.x, markerRegion.y, markerRegion.z, markerRegion.w, &_marker);
+        generateUVs(texture, trackRegion.x, trackRegion.y, trackRegion.z, trackRegion.w, &_track);
+    }
+
+    Theme::SliderIcon::~SliderIcon()
+    {
+    }
+
+    const char* Theme::SliderIcon::getId() const
+    {
+        return _id.c_str();
+    }
+
+    const Theme::UVs& Theme::SliderIcon::getMinCapUVs() const
+    {
+        return _minCap;
+    }
+
+    const Theme::UVs& Theme::SliderIcon::getMaxCapUVs() const
+    {
+        return _maxCap;
+    }
+    
+    const Theme::UVs& Theme::SliderIcon::getMarkerUVs() const
+    {
+        return _marker;
+    }
+    
+    const Theme::UVs& Theme::SliderIcon::getTrackUVs() const
+    {
+        return _track;
+    }
+
+    const Vector2& Theme::SliderIcon::getMinCapSize() const
+    {
+        return _minCapSize;
+    }
+
+    const Vector2& Theme::SliderIcon::getMaxCapSize() const
+    {
+        return _maxCapSize;
+    }
+
+    const Vector2& Theme::SliderIcon::getMarkerSize() const
+    {
+        return _markerSize;
+    }
+
+    const Vector2& Theme::SliderIcon::getTrackSize() const
+    {
+        return _trackSize;
+    }
+
+    const Vector4& Theme::SliderIcon::getColor() const
+    {
+        return _color;
+    }
+
+    /*****************
+     * Theme::Cursor *
+     *****************/
+    Theme::Cursor* Theme::Cursor::create(const char* id, const Texture& texture, const Rectangle& region, const Vector4& color)
+    {
+        Cursor* cursor = new Cursor(texture, region, color);
+        
+        if (id)
+        {
+            cursor->_id = id;
+        }
+
+        return cursor;
+    }
+
+    Theme::Cursor::Cursor(const Texture& texture, const Rectangle& region, const Vector4& color)
+        : _color(color)
+    {
+        _size.set(region.width, region.height);
+        generateUVs(texture, region.x, region.y, region.width, region.height, &_uvs);
+    }
+
+    Theme::Cursor::~Cursor()
+    {
+    }
+
+    const char* Theme::Cursor::getId() const
+    {
+        return _id.data();
+    }
+
+    const Theme::UVs& Theme::Cursor::getUVs() const
+    {
+        return _uvs;
+    }
+
+    const Vector2& Theme::Cursor::getSize() const
+    {
+        return _size;
+    }
+
+    const Vector4& Theme::Cursor::getColor() const
+    {
+        return _color;
+    }
+
+    /**************************
+     * Theme::ContainerRegion *
+     **************************/
+    Theme::ContainerRegion* Theme::ContainerRegion::create(const char* id, const Texture& texture, const Rectangle& region, const Theme::Border& border, const Vector4& color)
+    {
+        ContainerRegion* containerRegion = new ContainerRegion(texture, region, border, color);
+
+        if (id)
+        {
+            containerRegion->_id = id;
+        }
+
+        return containerRegion;
+    }
+
+    Theme::ContainerRegion::ContainerRegion(const Texture& texture, const Rectangle& region, const Theme::Border& border, const Vector4& color)
+        : _border(border), _color(color)
+    {
+        // Need to convert pixel coords to unit space by dividing by texture size.
+        float tw = 1.0f / (float)texture.getWidth();
+        float th = 1.0f / (float)texture.getHeight();
+
+        // 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;
+    }
+
+    Theme::ContainerRegion::~ContainerRegion()
+    {
+    }
+
+    const char* Theme::ContainerRegion::getId() const
+    {
+        return _id.c_str();
+    }
+
+    const Theme::Border& Theme::ContainerRegion::getBorder() const
+    {
+        return _border;
+    }
+
+    const Theme::UVs& Theme::ContainerRegion::getUVs(ContainerArea area) const
+    {
+        return _uvs[area];
+    }
+
+    const Vector4& Theme::ContainerRegion::getColor() const
+    {
+        return _color;
+    }
+
+    /****************
+     * Theme::Style *
+     ****************/
+    Theme::Style::Style(const char* id, const Theme::Margin& margin, const Theme::Padding& padding,
+            Theme::Style::Overlay* normal, Theme::Style::Overlay* focus, Theme::Style::Overlay* active)
+        : _id(id), _margin(margin), _padding(padding)
+    {
+        _overlays[OVERLAY_NORMAL] = normal;
+        _overlays[OVERLAY_FOCUS] = focus;
+        _overlays[OVERLAY_ACTIVE] = active;
+    }
+
+    Theme::Style::~Style()
+    {
+        for (int i = 0; i < MAX_OVERLAYS; i++)
+        {
+            SAFE_RELEASE(_overlays[i]);
+        }
+    }
+    
+    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::Padding& Theme::Style::getPadding() const
+    {
+        return _padding;
+    }
+
+    /*************************
+     * Theme::Style::Overlay *
+     *************************/
+    Theme::Style::Overlay* Theme::Style::Overlay::create()
+    {
+        Overlay* overlay = new Overlay();
+        return overlay;
+    }
+
+    Theme::Style::Overlay::Overlay() : _container(NULL), _textCursor(NULL), _mouseCursor(NULL), _checkBoxIcon(NULL), _radioButtonIcon(NULL), _sliderIcon(NULL), _font(NULL)
+    {
+    }
+
+    Theme::Style::Overlay::~Overlay()
+    {
+        SAFE_RELEASE(_container);
+        SAFE_RELEASE(_checkBoxIcon);
+        SAFE_RELEASE(_radioButtonIcon);
+        SAFE_RELEASE(_sliderIcon);
+        SAFE_RELEASE(_mouseCursor);
+        SAFE_RELEASE(_textCursor);
+        SAFE_RELEASE(_font);
+    }
+
+    Font* Theme::Style::Overlay::getFont() const
+    {
+        return _font;
+    }
+
+    void Theme::Style::Overlay::setFont(Font* font)
+    {
+        if (_font != font)
+        {
+            SAFE_RELEASE(_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;
+    }
+
+    bool Theme::Style::Overlay::getTextRightToLeft() const
+    {
+        return _textRightToLeft;
+    }
+
+    void Theme::Style::Overlay::setTextRightToLeft(bool rightToLeft)
+    {
+        _textRightToLeft = rightToLeft;
+    }
+
+    const Vector4& Theme::Style::Overlay::getTextColor() const
+    {
+        return _textColor;
+    }
+
+    void Theme::Style::Overlay::setTextColor(const Vector4& color)
+    {
+        _textColor = color;
+    }
+
+    Theme::Cursor* Theme::Style::Overlay::getTextCursor() const
+    {
+        return _textCursor;
+    }
+
+    void Theme::Style::Overlay::setTextCursor(Theme::Cursor* cursor)
+    {
+        if (_textCursor != cursor)
+        {
+            SAFE_RELEASE(_textCursor);
+
+            _textCursor = cursor;
+            
+            if (cursor)
+            {
+                cursor->addRef();
+            }
+        }
+    }
+
+    Theme::Cursor* Theme::Style::Overlay::getMouseCursor() const
+    {
+        return _mouseCursor;
+    }
+
+    void Theme::Style::Overlay::setMouseCursor(Theme::Cursor* cursor)
+    {
+        if (_mouseCursor != cursor)
+        {
+            SAFE_RELEASE(_mouseCursor);
+
+            _mouseCursor = cursor;
+            
+            if (cursor)
+            {
+                cursor->addRef();
+            }
+        }
+    }
+
+    void Theme::Style::Overlay::setCheckBoxIcon(Icon* icon)
+    {
+        if (_checkBoxIcon != icon)
+        {
+            SAFE_RELEASE(_checkBoxIcon);
+
+            _checkBoxIcon = icon;
+            
+            if (icon)
+            {
+                icon->addRef();
+            }
+        }
+    }
+
+    Theme::Icon* Theme::Style::Overlay::getCheckBoxIcon() const
+    {
+        return _checkBoxIcon;
+    }
+
+    void Theme::Style::Overlay::setRadioButtonIcon(Icon* icon)
+    {
+        if (_radioButtonIcon != icon)
+        {
+            SAFE_RELEASE(_radioButtonIcon);
+
+            _radioButtonIcon = icon;
+
+            if (icon)
+            {
+                icon->addRef();
+            }
+        }
+    }
+
+    Theme::Icon* Theme::Style::Overlay::getRadioButtonIcon() const
+    {
+        return _radioButtonIcon;
+    }
+
+    void Theme::Style::Overlay::setSliderIcon(SliderIcon* slider)
+    {
+        if (_sliderIcon != slider)
+        {
+            SAFE_RELEASE(_sliderIcon);
+
+            _sliderIcon = slider;
+
+            if (slider)
+            {
+                slider->addRef();
+            }
+        }
+    }
+
+    Theme::SliderIcon* Theme::Style::Overlay::getSliderIcon() const
+    {
+        return _sliderIcon;
+    }
+
+    void Theme::Style::Overlay::setContainerRegion(ContainerRegion* container)
+    {
+        if (_container != container)
+        {
+            SAFE_RELEASE(_container);
+
+            _container = container;
+
+            if (container)
+            {
+                container->addRef();
+            }
+        }
+    }
+
+    Theme::ContainerRegion* Theme::Style::Overlay::getContainerRegion() const
+    {
+        return _container;
+    }
+
+    void Theme::generateUVs(const Texture& texture, float x, float y, float width, float height, UVs* uvs)
+    {
+        float tw = 1.0f / texture.getWidth();
+        float th = 1.0f / texture.getHeight();
+
+        uvs->u1 = x * tw;
+        uvs->u2 = (x + width) * tw;
+        uvs->v1 = 1.0f - (y * th);
+        uvs->v2 = 1.0f - ((y + height) * th);
+    }
+
+    void Theme::lookUpSprites(const Properties* overlaySpace, Icon** checkBoxIcon, Icon** radioButtonIcon, SliderIcon** sliderIcon,
+                              Cursor** textCursor, Cursor** mouseCursor, ContainerRegion** containerRegion)
+    {
+        const char* checkBoxString = overlaySpace->getString("checkBox");
+        if (checkBoxString)
+        {
+            for (unsigned int i = 0; i < _icons.size(); i++)
+            {
+                if (strcmp(_icons[i]->getId(), checkBoxString) == 0)
+                {
+                    *checkBoxIcon = _icons[i];
+                    break;
+                }
+            }
+        }
+
+        const char* radioButtonString = overlaySpace->getString("radioButton");
+        if (radioButtonString)
+        {
+            for (unsigned int i = 0; i < _icons.size(); i++)
+            {
+                if (strcmp(_icons[i]->getId(), radioButtonString) == 0)
+                {
+                    *radioButtonIcon = _icons[i];
+                    break;
+                }
+            }
+        }
+
+        const char* sliderString = overlaySpace->getString("slider");
+        if (sliderString)
+        {
+            for (unsigned int i = 0; i < _sliders.size(); ++i)
+            {
+                if (strcmp(_sliders[i]->getId(), sliderString) == 0)
+                {
+                    *sliderIcon = _sliders[i];
+                    break;
+                }
+            }
+        }
+
+        const char* textCursorString = overlaySpace->getString("textCursor");
+        if (textCursorString)
+        {
+            for (unsigned int i = 0; i < _cursors.size(); ++i)
+            {
+                if (strcmp(_cursors[i]->getId(), textCursorString) == 0)
+                {
+                    *textCursor = _cursors[i];
+                    break;
+                }
+            }
+        }
+
+        const char* mouseCursorString = overlaySpace->getString("mouseCursor");
+        if (mouseCursorString)
+        {
+            for (unsigned int i = 0; i < _cursors.size(); ++i)
+            {
+                if (strcmp(_cursors[i]->getId(), mouseCursorString) == 0)
+                {
+                    *mouseCursor = _cursors[i];
+                    break;
+                }
+            }
+        }
+
+        const char* containerString = overlaySpace->getString("container");
+        if (containerString)
+        {
+            for (unsigned int i = 0; i < _containers.size(); ++i)
+            {
+                if (strcmp(_containers[i]->getId(), containerString) == 0)
+                {
+                    *containerRegion = _containers[i];
+                    break;
+                }
+            }
+        }
+    }
+}

+ 372 - 0
gameplay/src/Theme.h

@@ -0,0 +1,372 @@
+/*
+ * 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 appearance 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;
+
+        padding() : top(0), bottom(0), left(0), right(0) {}
+    } Margin, Border, Padding;
+
+    class Icon : public Ref
+    {
+    public:
+        static Icon* create(const char* id, const Texture& texture, const Vector2& size,
+                            const Vector2& offPosition, const Vector2& onPosition, const Vector4& color);
+
+        const char* getId() const;
+        const Vector2& getSize() const;
+        const Vector4& getColor() const;
+        const Theme::UVs& getOffUVs() const;
+        const Theme::UVs& getOnUVs() const;
+
+    private:
+        Icon(const Texture& texture, const Vector2& size, const Vector2& offPosition, const Vector2& onPosition, const Vector4& color);
+        Icon(const Icon& copy);
+        ~Icon();
+
+        std::string _id;
+        Vector2 _size;
+        Vector4 _color;
+        UVs _off;
+        UVs _on;
+    };
+
+    class SliderIcon : public Ref
+    {
+    public:
+        static SliderIcon* create(const char* id, const Texture& texture, const Vector4& minCapRegion, const Vector4& maxCapRegion,
+                                  const Vector4& markerRegion, const Vector4& trackRegion, const Vector4& color);
+
+        const char* getId() const;
+        const Theme::UVs& getMinCapUVs() const;
+        const Theme::UVs& getMaxCapUVs() const;
+        const Theme::UVs& getMarkerUVs() const;
+        const Theme::UVs& getTrackUVs() const;
+        const Vector2& getMinCapSize() const;
+        const Vector2& getMaxCapSize() const;
+        const Vector2& getMarkerSize() const;
+        const Vector2& getTrackSize() const;
+        const Vector4& getColor() const;
+
+    private:
+        SliderIcon(const Texture& texture, const Vector4& minCapRegion, const Vector4& maxCapRegion,
+                   const Vector4& markerRegion, const Vector4& trackRegion, const Vector4& color);
+        SliderIcon(const SliderIcon& copy);
+        ~SliderIcon();
+
+        std::string _id;
+        UVs _minCap;
+        UVs _maxCap;
+        UVs _marker;
+        UVs _track;
+        Vector2 _minCapSize;
+        Vector2 _maxCapSize;
+        Vector2 _markerSize;
+        Vector2 _trackSize;
+        Vector4 _color;
+    };
+    
+    /**
+     * This class represents the appearance of a cursor.
+     */
+    class Cursor : public Ref
+    {
+    public:
+        static Theme::Cursor* create(const char* id, const Texture& texture, const Rectangle& region, const Vector4& color);
+
+       /**
+        * Returns the Id of this Cursor.
+        */
+        const char* getId() const;
+
+       /**
+        * Gets a UV coordinates computed from the texture region.
+        */
+        const Theme::UVs& getUVs() const;
+
+        const Vector2& getSize() const;
+
+        const Vector4& getColor() const;
+    
+    private:
+        Cursor(const Texture& texture, const Rectangle& region, const Vector4& color);
+        Cursor(const Cursor& copy);
+        ~Cursor();
+
+        std::string _id;
+        UVs _uvs;
+        Vector2 _size;
+        Vector4 _color;
+    };
+
+    class ContainerRegion : public Ref
+    {
+    public:
+        static ContainerRegion* create(const char* id, const Texture& texture, const Rectangle& region, const Theme::Border& border, const Vector4& color);
+
+        enum ContainerArea
+        {
+            TOP_LEFT, TOP, TOP_RIGHT,
+            LEFT, CENTER, RIGHT,
+            BOTTOM_LEFT, BOTTOM, BOTTOM_RIGHT
+        };
+
+        const char* getId() const;
+        const Theme::Border& getBorder() const;
+        const Theme::UVs& getUVs(ContainerArea area) const;
+        const Vector4& getColor() const;
+        
+        /**
+         * Set the size of this ContainerRegion's border.
+         *
+         * When auto-size is set on width and/or height:
+         * Space added to the calculated (tightly bound) width and height.
+         *
+         */
+        //void setBorder(float top, float bottom, float left, float right);
+
+    private:
+        ContainerRegion(const Texture& texture, const Rectangle& region, const Theme::Border& border, const Vector4& color);
+        ContainerRegion(const ContainerRegion& copy);
+        ~ContainerRegion();
+    
+        std::string _id;
+        Theme::Border _border;
+        UVs _uvs[MAX_OVERLAY_REGIONS];
+        Vector4 _color;
+    };
+
+    /**
+     * 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 id ID of the style (as specified in the Theme file).
+     *
+     * @return Instance of the Style.
+     */
+    Theme::Style* getStyle(const char* id) const;
+
+    void setProjectionMatrix(const Matrix& matrix);
+
+    SpriteBatch* getSpriteBatch() const;
+    
+    /**
+     * 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::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 Padding region of this style.
+         */
+        const Theme::Padding& getPadding() const;
+
+        /**
+         * Gets the Margin region of this style.
+         */
+        const Theme::Margin& getMargin() const;
+
+        /**
+         * Set this size of this Style's padding.
+         *
+         * Padding is the space between a Control's content (all icons and text) and its border.
+         */
+        void setPadding(float top, float bottom, float left, float right);
+
+        /**
+         * Set the size of this Style's margin.
+         *
+         * The margin is used by Layouts other than AbsoluteLayout to put space between Controls.
+         */
+        void setMargin(float top, float bottom, float left, float right);
+       
+        /**
+         * This class represents a control's overlay for one of the 3 modes: normal, focussed or active.
+         */
+        class Overlay : public Ref
+        {
+        public:
+            static Overlay* create();
+
+           /**
+            * Returns the Overlay type.
+            */
+            OverlayType getType();
+
+           /**
+            * 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);
+
+            const Vector4& getTextColor() const;
+            void setTextColor(const Vector4& color);
+            
+           /**
+            * Gets a cursor associated with this overlay.
+            */
+            void setTextCursor(Cursor* cursor);
+            Cursor* getTextCursor() const;
+            
+            void setMouseCursor(Cursor* cursor);
+            Cursor* getMouseCursor() const;
+            
+            void setCheckBoxIcon(Icon* icon);
+            Icon* getCheckBoxIcon() const;
+
+            void setRadioButtonIcon(Icon* icon);
+            Icon* getRadioButtonIcon() const;
+
+            void setSliderIcon(SliderIcon* slider);
+            SliderIcon* getSliderIcon() const;
+            
+            void setContainerRegion(ContainerRegion* container);
+            ContainerRegion* getContainerRegion() const;
+        
+        private:
+            Overlay();
+            Overlay(const Overlay& copy);
+            ~Overlay();
+
+            ContainerRegion* _container;
+            Cursor* _textCursor;
+            Cursor* _mouseCursor;
+            Icon* _checkBoxIcon;
+            Icon* _radioButtonIcon;
+            SliderIcon* _sliderIcon;
+            Font* _font;
+            unsigned int _fontSize;
+            Font::Justify _alignment;
+            bool _textRightToLeft;
+            Vector4 _textColor;
+        };
+
+    private:
+        Style(const Style& style);
+        
+        std::string _id;
+        Padding _padding;
+        Margin _margin;
+        Overlay* _overlays[MAX_OVERLAYS];
+    };
+
+private:
+    /**
+     * Constructor.
+     */
+    Theme();
+
+    /**
+     * Copy Constructor.
+     */
+    Theme(const Theme* theme);
+
+    /**
+     * Destructor.
+     */
+    ~Theme();
+
+    static void generateUVs(const Texture& texture, float x, float y, float width, float height, UVs* uvs);
+    void lookUpSprites(const Properties* overlaySpace, Icon** checkBoxIcon, Icon** radioButtonIcon, SliderIcon** sliderIcon,
+                              Cursor** textCursor, Cursor** mouseCursor, ContainerRegion** containerRegion);
+
+    std::string _path;
+    Texture* _texture;
+    SpriteBatch* _spriteBatch;
+    std::vector<Cursor*> _cursors;
+    std::vector<Style*> _styles;
+    std::vector<Icon*> _icons;
+    std::vector<SliderIcon*> _sliders;
+    std::vector<ContainerRegion*> _containers;
+    std::set<Font*> _fonts;
+};
+
+}
+
+#endif

+ 81 - 0
gameplay/src/VerticalLayout.cpp

@@ -0,0 +1,81 @@
+#include "Base.h"
+#include "VerticalLayout.h"
+
+namespace gameplay
+{
+    VerticalLayout::VerticalLayout() : _bottomToTop(false)
+    {
+    }
+
+    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(const Container* container)
+    {
+        // Need border, padding.
+        Theme::Style* style = container->getStyle();
+        Theme::Border border;
+        Theme::ContainerRegion* containerRegion = style->getOverlay(container->getOverlayType())->getContainerRegion();
+        if (containerRegion)
+        {
+            border = containerRegion->getBorder();
+        }
+        Theme::Padding padding = style->getPadding();
+
+        float yPosition = border.top + padding.top;
+        float xPosition = border.left + padding.left;
+
+        std::vector<Control*> controls = container->getControls();
+
+        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(xPosition, yPosition);
+            control->update(container->getPosition());
+
+            yPosition += size.y + margin.bottom;
+
+            i += iter;
+        }
+    }
+}

+ 31 - 0
gameplay/src/VerticalLayout.h

@@ -0,0 +1,31 @@
+#ifndef VERTICALLAYOUT_H_
+#define VERTICALLAYOUT_H_
+
+#include "Layout.h"
+#include "Container.h"
+
+namespace gameplay
+{
+
+class VerticalLayout : public Layout
+{
+public:
+    static VerticalLayout* create();
+
+    void setBottomToTop(bool bottomToTop);
+
+    Layout::Type getType();
+
+    void update(const Container* container);
+
+private:
+    VerticalLayout();
+    VerticalLayout(const VerticalLayout& copy);
+    virtual ~VerticalLayout();
+
+    bool _bottomToTop;
+};
+
+}
+
+#endif

+ 12 - 1
gameplay/src/gameplay.h

@@ -66,4 +66,15 @@
 #include "PhysicsHingeConstraint.h"
 #include "PhysicsSocketConstraint.h"
 #include "PhysicsSpringConstraint.h"
-#include "PhysicsRigidBody.h"
+#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"

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio