Procházet zdrojové kódy

Merge branch 'next' of https://github.com/blackberry-gaming/GamePlay into next-sgrenier

Conflicts:
	gameplay/gameplay.vcxproj.filters
	gameplay/res/shaders/colored-specular.fsh
	gameplay/res/shaders/diffuse-specular.fsh
Steve Grenier před 14 roky
rodič
revize
b3d4adb693
52 změnil soubory, kde provedl 1685 přidání a 629 odebrání
  1. 5 5
      README.md
  2. 1 0
      gameplay/gameplay.vcxproj
  3. 4 1
      gameplay/gameplay.vcxproj.filters
  4. 1 1
      gameplay/res/shaders/colored-specular.fsh
  5. 1 1
      gameplay/res/shaders/diffuse-specular.fsh
  6. 1 1
      gameplay/src/AbsoluteLayout.cpp
  7. 28 2
      gameplay/src/AbsoluteLayout.h
  8. 4 0
      gameplay/src/Base.h
  9. 5 35
      gameplay/src/Button.cpp
  10. 53 6
      gameplay/src/Button.h
  11. 32 44
      gameplay/src/CheckBox.cpp
  12. 87 11
      gameplay/src/CheckBox.h
  13. 79 40
      gameplay/src/Container.cpp
  14. 146 23
      gameplay/src/Container.h
  15. 101 40
      gameplay/src/Control.cpp
  16. 167 42
      gameplay/src/Control.h
  17. 13 10
      gameplay/src/Font.cpp
  18. 40 54
      gameplay/src/Form.cpp
  19. 92 9
      gameplay/src/Form.h
  20. 35 0
      gameplay/src/Game.cpp
  21. 42 0
      gameplay/src/Game.h
  22. 3 57
      gameplay/src/Label.cpp
  23. 50 11
      gameplay/src/Label.h
  24. 37 5
      gameplay/src/Layout.h
  25. 1 1
      gameplay/src/Node.cpp
  26. 1 1
      gameplay/src/Node.h
  27. 6 3
      gameplay/src/ParticleEmitter.cpp
  28. 15 0
      gameplay/src/PhysicsCharacter.cpp
  29. 3 1
      gameplay/src/PhysicsCharacter.h
  30. 7 0
      gameplay/src/PhysicsCollisionObject.h
  31. 9 0
      gameplay/src/PhysicsController.cpp
  32. 3 0
      gameplay/src/PhysicsController.h
  33. 6 11
      gameplay/src/PhysicsRigidBody.cpp
  34. 3 1
      gameplay/src/PhysicsRigidBody.h
  35. 0 5
      gameplay/src/PhysicsRigidBody.inl
  36. 5 2
      gameplay/src/PlatformMacOS.mm
  37. 4 2
      gameplay/src/PlatformQNX.cpp
  38. 46 54
      gameplay/src/RadioButton.cpp
  39. 58 7
      gameplay/src/RadioButton.h
  40. 6 0
      gameplay/src/Rectangle.cpp
  41. 14 42
      gameplay/src/Slider.cpp
  42. 74 9
      gameplay/src/Slider.h
  43. 64 2
      gameplay/src/SpriteBatch.cpp
  44. 15 0
      gameplay/src/SpriteBatch.h
  45. 40 33
      gameplay/src/TextBox.cpp
  46. 2 2
      gameplay/src/TextBox.h
  47. 138 0
      gameplay/src/Texture.cpp
  48. 11 1
      gameplay/src/Texture.h
  49. 59 42
      gameplay/src/Theme.h
  50. 26 0
      gameplay/src/TimeListener.h
  51. 9 6
      gameplay/src/VerticalLayout.cpp
  52. 33 6
      gameplay/src/VerticalLayout.h

+ 5 - 5
README.md

@@ -2,17 +2,17 @@
 GamePlay is a open-source, cross-platform 3D native gaming framework making it easy to learn and write mobile and desktop games. 
 
 ## Supported Mobile Platforms
-- BlackBerry PlayBook 1/2 (using BlackBerry Native SDK 2)
+- BlackBerry PlayBook 2 (using BlackBerry Native SDK 2)
 - Google Android 4 (using Google Android NDK 7)
-- Apple iOS 4/5 (using Apple XCode 4)
+- Apple iOS 5 (using Apple XCode 4)
 
 ## Supported Desktop Platforms
-- Apple MacOS X (using Apple XCode 4)
 - Microsoft Windows XP/7 (using Microsoft Visual Studio 2010)
-    * Requires [Creative OpenAL 1.1] (http://connect.creativelabs.com/openal/Downloads/Forms/AllItems.aspx)
+- Apple MacOS X (using Apple XCode 4)
+    * Both require [Creative OpenAL 1.1] (http://connect.creativelabs.com/openal/Downloads/Forms/AllItems.aspx)
 
 ## Roadmap for 'next' branch
-- UI Overlays
+- Terrain and Water
 - Improvements to Lighting
 - More Samples and Tutorials
 

+ 1 - 0
gameplay/gameplay.vcxproj

@@ -179,6 +179,7 @@
     <ClInclude Include="src\TextBox.h" />
     <ClInclude Include="src\Texture.h" />
     <ClInclude Include="src\Theme.h" />
+    <ClInclude Include="src\TimeListener.h" />
     <ClInclude Include="src\Touch.h" />
     <ClInclude Include="src\Transform.h" />
     <ClInclude Include="src\Vector2.h" />

+ 4 - 1
gameplay/gameplay.vcxproj.filters

@@ -530,6 +530,9 @@
     <ClInclude Include="src\PhysicsCollisionObject.h">
       <Filter>src</Filter>
     </ClInclude>
+    <ClInclude Include="src\TimeListener.h">
+      <Filter>src</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <None Include="res\shaders\bumped-specular.vsh">
@@ -664,4 +667,4 @@
       <Filter>src</Filter>
     </None>
   </ItemGroup>
-</Project>
+</Project>

+ 1 - 1
gameplay/res/shaders/colored-specular.fsh

@@ -122,4 +122,4 @@ void main()
     // Light the pixel
     gl_FragColor.a = _baseColor.a;
     gl_FragColor.rgb = _ambientColor + _diffuseColor + _specularColor;
-}
+}

+ 1 - 1
gameplay/res/shaders/diffuse-specular.fsh

@@ -114,4 +114,4 @@ void main()
     // Light the pixel
     gl_FragColor.a = _baseColor.a;
     gl_FragColor.rgb = _ambientColor + _diffuseColor + _specularColor;
-}
+}

+ 1 - 1
gameplay/src/AbsoluteLayout.cpp

@@ -37,7 +37,7 @@ namespace gameplay
         {
             if (controls[i]->isDirty())
             {
-                controls[i]->update(container->getPosition());
+                controls[i]->update(container->getClip());
             }
         }
     }

+ 28 - 2
gameplay/src/AbsoluteLayout.h

@@ -6,13 +6,39 @@
 namespace gameplay
 {
 
+/**
+ * Defines a Layout for forms and containers that requires the user
+ * to specify absolute positions for all contained controls.
+ */
 class AbsoluteLayout : public Layout
 {
-public:
-    static AbsoluteLayout* create();
+    friend class Form;
+    friend class Container;
 
+public:
+    /**
+     * Get the type of this Layout.
+     *
+     * @return Layout::LAYOUT_ABSOLUTE
+     */
     Layout::Type getType();
 
+protected:
+    /**
+     * Create an AbsoluteLayout.
+     *
+     * @return an AbsoluteLayout object.
+     */
+    static AbsoluteLayout* create();
+
+    /**
+     * Update the controls contained by the specified container.
+     *
+     * An AbsoluteLayout does nothing to modify the layout of controls.
+     * It simply calls update() on any control that is dirty.
+     *
+     * @param container The container to update.
+     */
     void update(const Container* container);
 
 private:

+ 4 - 0
gameplay/src/Base.h

@@ -203,6 +203,10 @@ extern void printError(const char* format, ...);
     #define OPENGL_ES
 #elif WIN32
     #define WIN32_LEAN_AND_MEAN
+	#define GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG                      0x8C00
+	#define GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG                      0x8C01
+	#define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG                     0x8C02
+	#define GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG                     0x8C03
     #include <GL/glew.h>
 #elif __APPLE__
     #include "TargetConditionals.h"

+ 5 - 35
gameplay/src/Button.cpp

@@ -3,8 +3,6 @@
 
 namespace gameplay
 {
-    static std::vector<Button*> __buttons;
-
     Button::Button() : _callback(NULL)
     {
     }
@@ -18,38 +16,10 @@ namespace gameplay
     {
         Button* button = new Button();
         button->init(style, properties);
-        __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;
-    }
-
     bool Button::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
     {
         if (!isEnabled())
@@ -60,22 +30,22 @@ namespace gameplay
         switch (evt)
         {
         case Touch::TOUCH_PRESS:
-            _state = Control::STATE_ACTIVE;
+            _state = Control::ACTIVE;
             _dirty = true;
             return _consumeTouchEvents;
         case Touch::TOUCH_RELEASE:
             if (_callback &&
-                x > 0 && x <= _size.x &&
-                y > 0 && y <= _size.y)
+                x > 0 && x <= _bounds.width &&
+                y > 0 && y <= _bounds.height)
             {
                 // Button-clicked callback.
                 _callback->trigger(this);
-                setState(Control::STATE_NORMAL);
+                setState(Control::NORMAL);
                 _dirty = true;
                 return _consumeTouchEvents;
             }
             _dirty = true;
-            setState(Control::STATE_NORMAL);
+            setState(Control::NORMAL);
             break;
         }
 

+ 53 - 6
gameplay/src/Button.h

@@ -9,29 +9,73 @@
 namespace gameplay
 {
 
+/**
+ * Defines a button UI control.  This is essentially a label that can have a callback method set on it.
+ *
+ * The following properties are available for buttons:
+ *
+ * button <Button ID>
+ * {
+ *      style       = <Style ID>
+ *      position    = <x, y>
+ *      size        = <width, height>
+ *      text        = <string>
+ * }
+ */
 class Button : public Label
 {
+    friend class Container;
+
     class Callback;
 
 public:
-    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);
-
+    /**
+     * Set a callback method on this button.  The callback will be triggered when the button is
+     * clicked -- i.e. consecutive TOUCH_PRESS and TOUCH_RELEASE events that both fall within
+     * the bounds of the button.  
+     *
+     * @param instance The object to call the method on.
+     * @param callbackMethod The method to call.
+     */
     template <class ClassType>
     void setCallback(ClassType* instance, void (ClassType::*callbackMethod)(Control*));
 
+protected:
+    /**
+     * Create a button with a given style and properties.
+     *
+     * @param style The style to apply to this button.
+     * @param properties The properties to set on this button.
+     *
+     * @return The new button.
+     */
+    static Button* create(Theme::Style* style, Properties* properties);
+
+    /**
+     * Touch callback on touch events.  Controls return true if they consume the touch event.
+     *
+     * @param evt The touch event that occurred.
+     * @param x The x position of the touch in pixels. Left edge is zero.
+     * @param y The y position of the touch in pixels. Top edge is zero.
+     * @param contactIndex The order of occurrence for multiple touch contacts starting at zero.
+     *
+     * @return Whether the touch event was consumed by the control.
+     *
+     * @see Touch::TouchEvent
+     */
     bool touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
 
-protected:
     Button();
     virtual ~Button();
 
-    Callback* _callback;
+    Callback* _callback;    // The callback method pointer interface.
 
 private:
     Button(const Button& copy);
 
+    /**
+     * Abstract callback interface.
+     */
     class Callback
     {
     public:
@@ -39,6 +83,9 @@ private:
         virtual void trigger(Control* button) = 0;
     };
 
+    /**
+     * Implementation of the callback interface for a specific class.
+     */
     template <class ClassType>
     class CallbackImpl : public Callback
     {

+ 32 - 44
gameplay/src/CheckBox.cpp

@@ -4,8 +4,6 @@
 namespace gameplay
 {
 
-static std::vector<CheckBox*> __checkBoxes;
-
 CheckBox::CheckBox() : _checked(false)
 {
 }
@@ -26,29 +24,30 @@ CheckBox* CheckBox::create(Theme::Style* style, Properties* properties)
     checkBox->init(style, properties);
     properties->getVector2("iconSize", &checkBox->_iconSize);
     checkBox->_checked = properties->getBool("checked");
-    __checkBoxes.push_back(checkBox);
 
     return checkBox;
 }
 
-CheckBox* CheckBox::getCheckBox(const char* id)
+bool CheckBox::isChecked()
 {
-    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 _checked;
+}
 
-    return NULL;
+void CheckBox::setIconSize(float width, float height)
+{
+    _iconSize.set(width, height);
 }
 
-bool CheckBox::isChecked()
+const Vector2& CheckBox::getIconSize() const
 {
-    return _checked;
+    Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
+    Theme::Icon* icon = overlay->getCheckBoxIcon();
+    if (_iconSize.isZero() && icon)
+    {
+        return icon->getSize();
+    }
+
+    return _iconSize;
 }
 
 bool CheckBox::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
@@ -62,10 +61,10 @@ bool CheckBox::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int cont
     {
     case Touch::TOUCH_RELEASE:
         {
-            if (_state == Control::STATE_ACTIVE)
+            if (_state == Control::ACTIVE)
             {
-                if (x > 0 && x <= _size.x &&
-                    y > 0 && y <= _size.y)
+                if (x > 0 && x <= _bounds.width &&
+                    y > 0 && y <= _bounds.height)
                 {
                     _checked = !_checked;
                 }
@@ -77,35 +76,24 @@ bool CheckBox::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int cont
     return Button::touchEvent(evt, x, y, contactIndex);
 }
 
-void CheckBox::update(const Vector2& position)
+void CheckBox::update(const Rectangle& clip)
 {
+    Control::update(clip);
+
     Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
     Theme::Icon* icon = overlay->getCheckBoxIcon();
-    Theme::Border border;
-    Theme::ContainerRegion* containerRegion = overlay->getContainerRegion();
-    if (containerRegion)
+    Vector2& size = _iconSize;
+    if (_iconSize.isZero() && icon)
     {
-        border = overlay->getContainerRegion()->getBorder();
+        size = icon->getSize();
     }
-    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);
+    float iconWidth = size.x;
 
-    _viewport.set(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 - overlay->getFontSize());
+    _clip.x += iconWidth;
+    _clip.width -= iconWidth;
 }
 
-void CheckBox::drawSprites(SpriteBatch* spriteBatch, const Vector2& position)
+void CheckBox::drawSprites(SpriteBatch* spriteBatch, const Rectangle& clip)
 {
     // Left, v-center.
     // TODO: Set an alignment for icons.
@@ -129,8 +117,8 @@ void CheckBox::drawSprites(SpriteBatch* spriteBatch, const Vector2& position)
 
         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);
+        Vector2 pos(clip.x + _position.x + border.left + padding.left,
+            clip.y + _position.y + (_bounds.height - border.bottom - padding.bottom) / 2.0f - size.y / 2.0f);
 
         if (_checked)
         {
@@ -145,7 +133,7 @@ void CheckBox::drawSprites(SpriteBatch* spriteBatch, const Vector2& position)
     }
 }
 
-void CheckBox::drawText(const Vector2& position)
+void CheckBox::drawText(const Rectangle& clip)
 {
     // TODO: Batch all labels that use the same font.
     Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
@@ -153,7 +141,7 @@ void CheckBox::drawText(const Vector2& position)
     
     // Draw the text.
     font->begin();
-    font->drawText(_text.c_str(), _viewport, overlay->getTextColor(), overlay->getFontSize(), overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
+    font->drawText(_text.c_str(), _clip, overlay->getTextColor(), overlay->getFontSize(), overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
     font->end();
 
     _dirty = false;

+ 87 - 11
gameplay/src/CheckBox.h

@@ -9,28 +9,104 @@
 namespace gameplay
 {
 
+/**
+ * Defines a checkbox UI control.  This is a button that toggles between two icons when clicked.
+ *
+ * The following properties are available for checkboxes:
+ *
+ * checkBox <CheckBox ID>
+ * {
+ *      style       = <Style ID>
+ *      position    = <x, y>
+ *      size        = <width, height>
+ *      text        = <string>
+ *      checked     = <bool>
+ *      iconSize    = <width, height>   // The size to draw the checkbox icon, if different from its size in the texture.
+ * }
+ */
 class CheckBox : public Button
 {
-public:
-    static CheckBox* create(Theme::Style* style, Properties* properties);
-    static CheckBox* getCheckBox(const char* id);
+    friend class Container;
 
+public:
+    /**
+     * Gets whether this checkbox is checked.
+     *
+     * @return Whether this checkbox is checked.
+     */
     bool isChecked();
 
-    bool touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
-
-    void update(const Vector2& position);
+    /**
+     * Set the size to draw the checkbox icon.
+     *
+     * @param width The width to draw the checkbox icon.
+     * @param height The height to draw the checkbox icon.
+     */
+    void setIconSize(float width, float height);
 
-    void drawSprites(SpriteBatch* spriteBatch, const Vector2& position);
-    void drawText(const Vector2& position);
+    /**
+     * Get the size at which the checkbox icon will be drawn.
+     *
+     * @return The size of the checkbox icon.
+     */
+    const Vector2& getIconSize() const;
 
 protected:
     CheckBox();
-    CheckBox(const CheckBox& copy);
     ~CheckBox();
 
-    bool _checked;
-    Vector2 _iconSize;
+    /**
+     * Create a checkbox with a given style and properties.
+     *
+     * @param style The style to apply to this checkbox.
+     * @param properties The properties to set on this checkbox.
+     *
+     * @return The new checkbox.
+     */
+    static CheckBox* create(Theme::Style* style, Properties* properties);
+
+    /**
+     * Touch callback on touch events.  Controls return true if they consume the touch event.
+     *
+     * @param evt The touch event that occurred.
+     * @param x The x position of the touch in pixels. Left edge is zero.
+     * @param y The y position of the touch in pixels. Top edge is zero.
+     * @param contactIndex The order of occurrence for multiple touch contacts starting at zero.
+     *
+     * @return Whether the touch event was consumed by the control.
+     *
+     * @see Touch::TouchEvent
+     */
+    bool touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
+
+    /**
+     * Called when a control's properties change.  Updates this control's internal rendering
+     * properties, such as its text viewport.
+     *
+     * @param position The control's position within its container.
+     */
+    void update(const Rectangle& clip);
+
+    /**
+     * Draw the checkbox icon associated with this control.
+     *
+     * @param spriteBatch The sprite batch containing this control's icons.
+     * @param position The container position this control is relative to.
+     */
+    void drawSprites(SpriteBatch* spriteBatch, const Rectangle& clip);
+
+    /**
+     * Draw this control's text.
+     *
+     * @param position The container position this control is relative to.
+     */
+    void drawText(const Rectangle& clip);
+
+    bool _checked;      // Whether this checkbox is currently checked.
+    Vector2 _iconSize;  // The size to draw the checkbox icon, if different from its size in the texture.
+
+private:
+    CheckBox(const CheckBox& copy);
 };
 
 }

+ 79 - 40
gameplay/src/Container.cpp

@@ -12,8 +12,6 @@
 
 namespace gameplay
 {
-    static std::vector<Container*> __containers;
-
     Container::Container() : _layout(NULL)
     {
     }
@@ -51,8 +49,6 @@ namespace gameplay
         Container* container = new Container();
         container->_layout = layout;
 
-        __containers.push_back(container);
-
         return container;
     }
 
@@ -123,21 +119,6 @@ namespace gameplay
         }
     }
 
-    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;
@@ -203,6 +184,14 @@ namespace gameplay
             {
                 return c;
             }
+            else if (c->isContainer())
+            {
+                Control* cc = ((Container*)c)->getControl(id);
+                if (cc)
+                {
+                    return cc;
+                }
+            }
         }
 
         return NULL;
@@ -213,36 +202,54 @@ namespace gameplay
         return _controls;
     }
 
-    void Container::update(const Vector2& position)
+    void Container::update(const Rectangle& clip)
     {
-        // Should probably have sizeChanged() for this.
+        // Update this container's viewport.
+        Control::update(clip);
+
         if (isDirty())
         {
             _layout->update(this);
         }
     }
 
-    void Container::drawSprites(SpriteBatch* spriteBatch, const Vector2& position)
+    void Container::drawBorder(SpriteBatch* spriteBatch, const Rectangle& clip)
     {
-        Vector2 pos(position.x + _position.x, position.y + _position.y);
+        // First draw our own border.
+        Control::drawBorder(spriteBatch, clip);
+
+        // Now call drawBorder on all controls within this container.
+        //Vector2 pos(clip.x + _position.x, clip.y + _position.y);
+        //const Rectangle newClip(clip.x + _position.x, clip.y + _position.y, _size.x, _size.y);
         std::vector<Control*>::const_iterator it;
         for (it = _controls.begin(); it < _controls.end(); it++)
         {
             Control* control = *it;
-            control->drawSprites(spriteBatch, pos);
+            control->drawBorder(spriteBatch, _clip);
+        }
+    }
+
+    void Container::drawSprites(SpriteBatch* spriteBatch, const Rectangle& clip)
+    {
+        //const Rectangle newClip(clip.x + _position.x, clip.y + _position.y, _size.x, _size.y);
+        std::vector<Control*>::const_iterator it;
+        for (it = _controls.begin(); it < _controls.end(); it++)
+        {
+            Control* control = *it;
+            control->drawSprites(spriteBatch, _clip);
         }
 
         _dirty = false;
     }
 
-    void Container::drawText(const Vector2& position)
+    void Container::drawText(const Rectangle& clip)
     {
-        Vector2 pos(position.x + _position.x, position.y + _position.y);
+        //const Rectangle newClip(clip.x + _position.x, clip.y + _position.y, _size.x, _size.y);
         std::vector<Control*>::const_iterator it;
         for (it = _controls.begin(); it < _controls.end(); it++)
         {
             Control* control = *it;
-            control->drawText(pos);
+            control->drawText(_clip);
         }
 
         _dirty = false;
@@ -278,32 +285,51 @@ namespace gameplay
 
         bool eventConsumed = false;
 
+        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();
+        float xPos = border.left + padding.left;
+        float yPos = border.top + padding.top;
+
         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 ||
+            if (!control->isEnabled())
+            {
+                continue;
+            }
+
+            const Rectangle& bounds = control->getBounds();
+            if (control->getState() != Control::NORMAL ||
                 (evt == Touch::TOUCH_PRESS &&
-                 x >= position.x &&
-                 x <= position.x + size.x &&
-                 y >= position.y &&
-                 y <= position.y + size.y))
+                 x >= xPos + bounds.x &&
+                 x <= xPos + bounds.x + bounds.width &&
+                 y >= yPos + bounds.y &&
+                 y <= yPos + bounds.y + bounds.height))
             {
-                // Pass on the event's position relative to the control.
-                eventConsumed |= control->touchEvent(evt, x - position.x, y - position.y, contactIndex);
+                // Pass on the event's clip relative to the control.
+                eventConsumed |= control->touchEvent(evt, x - xPos - bounds.x, y - yPos - bounds.y, contactIndex);
             }
         }
 
+        if (!isEnabled())
+        {
+            return (_consumeTouchEvents | eventConsumed);
+        }
+
         switch (evt)
         {
         case Touch::TOUCH_PRESS:
-            setState(Control::STATE_FOCUS);
+            setState(Control::FOCUS);
             break;
         case Touch::TOUCH_RELEASE:
-            setState(Control::STATE_NORMAL);
+            setState(Control::NORMAL);
             break;
         }
 
@@ -316,10 +342,23 @@ namespace gameplay
         for (it = _controls.begin(); it < _controls.end(); it++)
         {
             Control* control = *it;
-            control->keyEvent(evt, key);
+            if (!control->isEnabled())
+            {
+                continue;
+            }
+
+            if (control->isContainer() || control->getState() == Control::FOCUS)
+            {
+                control->keyEvent(evt, key);
+            }
         }
     }
 
+    bool Container::isContainer()
+    {
+        return true;
+    }
+
     Layout::Type Container::getLayoutType(const char* layoutString)
     {
         if (!layoutString)

+ 146 - 23
gameplay/src/Container.h

@@ -7,16 +7,37 @@
 namespace gameplay
 {
 
+/**
+ * A container is a UI control that can contain other controls.
+ *
+ * The following properties are available for containers:
+ *
+ * container <Container ID>
+ * {
+ *      // Container properties.
+ *      layout   = <Layout Type>        // A value from the Layout::Type enum.  E.g.: LAYOUT_VERTICAL
+ *      style    = <Style ID>           // A style from the form's theme.
+ *      position = <x, y>               // Position of the container on-screen, measured in pixels.
+ *      size     = <width, height>      // Size of the container, measured in pixels.
+ *   
+ *      // All the controls within this container.
+ *      container{}
+ *      label{}
+ *      textBox{}
+ *      button{}
+ *      checkBox{}
+ *      radioButton{}
+ *      slider{}
+ * }
+ */
 class Container : public Control
 {
 public:
     /**
-     * A Container's layout type must be specified at creation time.
+     * Get this container's layout.
+     *
+     * @return This container's layout object.
      */
-    static Container* create(Layout::Type type);
-    static Container* create(Theme::Style* style, Properties* properties, Theme* theme);
-    static Container* getContainer(const char* id);
-
     Layout* getLayout();
 
     /**
@@ -30,15 +51,32 @@ public:
     unsigned int addControl(Control* control);
 
     /**
-     * Insert a Control at a specific index.
+     * Insert a control at a specific index.
      *
-     * @param control The Control to add.
-     * @param index The index at which to insert the Control.
+     * @param control The control to add.
+     * @param index The index at which to insert the control.
      */
     void insertControl(Control* control, unsigned int index);
 
+    /**
+     * Remove a control at a specific index.
+     *
+     * @param index The index from which to remove the control.
+     */
     void removeControl(unsigned int index);
+
+    /**
+     * Remove a control with the given ID.
+     *
+     * @param ID The ID of the control to remove.
+     */
     void removeControl(const char* id);
+
+    /**
+     * Remove a specific control.
+     *
+     * @param control The control to remove.
+     */
     void removeControl(Control* control);
 
     /**
@@ -57,34 +95,119 @@ public:
      */
     Control* getControl(const char* id) const;
 
+    /**
+     * Get the vector of controls within this container.
+     *
+     * @return The vector of the controls within this container.
+     */
     std::vector<Control*> getControls() const;
 
+protected:
+    Container();
+    virtual ~Container();
+
     /**
-     * Updates the position of each Control within this Container
-     * according to the Container's Layout.
+     * Create an empty container.  A container's layout type must be specified at creation time.
+     *
+     * @param type The container's layout type.
      */
-    virtual void update(const Vector2& position);
+    static Container* create(Layout::Type type);
 
-    //void draw(Theme* theme, const Vector2& position);
-    virtual void drawSprites(SpriteBatch* spriteBatch, const Vector2& position);
-    virtual void drawText(const Vector2& position);
+    /**
+     * Create a container with a given style and properties, including a list of controls.
+     *
+     * @param style The style to apply to this container.
+     * @param properties The properties to set on this container, including nested controls.
+     * @param theme The theme to search for control styles within.
+     *
+     * @return The new container.
+     */
+    static Container* create(Theme::Style* style, Properties* properties, Theme* theme);
 
-    virtual bool touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
+    /**
+     * Updates each control within this container,
+     * and positions them according to the container's layout.
+     *
+     * @param clip The clipping rectangle of this container's parent container.
+     */
+    virtual void update(const Rectangle& clip);
 
-    virtual void keyEvent(Keyboard::KeyEvent evt, int key);
+    /**
+     * Draws the themed border and background of this container and all its controls.
+     *
+     * @param spriteBatch The sprite batch containing this container's border images.
+     * @param clip The clipping rectangle of this container's parent container.
+     */
+    void drawBorder(SpriteBatch* spriteBatch, const Rectangle& clip);
 
-protected:
-    Container();
-    Container(const Container& copy);
-    virtual ~Container();
+    /**
+     * Draws the icons of all controls within this container.
+     *
+     * @param spriteBatch The sprite batch containing this control's icons.
+     * @param clip The clipping rectangle of this container's parent container.
+     */
+    virtual void drawSprites(SpriteBatch* spriteBatch, const Rectangle& clip);
+
+    /**
+     * Draws the text of all controls within this container.
+     *
+     * @param clip The clipping rectangle of this container's parent container.
+     */
+    virtual void drawText(const Rectangle& clip);
+
+    /**
+     * Touch callback on touch events.  Controls return true if they consume the touch event.
+     *
+     * @param evt The touch event that occurred.
+     * @param x The x position of the touch in pixels. Left edge is zero.
+     * @param y The y position of the touch in pixels. Top edge is zero.
+     * @param contactIndex The order of occurrence for multiple touch contacts starting at zero.
+     *
+     * @return Whether the touch event was consumed by a control within this container.
+     *
+     * @see Touch::TouchEvent
+     */
+    virtual bool touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
+    
+    /**
+     * Keyboard callback on key events.  Passes key events on to the currently focused control.
+     *
+     * @param evt The key event that occured.
+     * @param key If evt is KEY_PRESS or KEY_RELEASE then key is the key code from Keyboard::Key.
+     *            If evt is KEY_CHAR then key is the unicode value of the character.
+     * 
+     * @see Keyboard::KeyEvent
+     * @see Keyboard::Key
+     */
+    virtual void keyEvent(Keyboard::KeyEvent evt, int key);
 
+    /**
+     * Gets a Layout::Type enum from a matching string.
+     */
     static Layout::Type getLayoutType(const char* layoutString);
 
-    bool isDirty();
+    /**
+     * Returns whether this control is a container.
+     * This is true in this case.
+     */
+    bool isContainer();
+
+    /**
+     * Returns whether this container or any of its controls have been modified and require an update.
+     */
+    virtual bool isDirty();
+
+    /**
+     * Adds controls nested within a properties object to this container,
+     * searching for styles within the given theme.
+     */
     void addControls(Theme* theme, Properties* properties);
 
-    Layout* _layout;
-    std::vector<Control*> _controls;
+    Layout* _layout;                    // This container's layout.
+    std::vector<Control*> _controls;    // List of controls within this container.
+
+private:
+    Container(const Container& copy);
 };
 
 }

+ 101 - 40
gameplay/src/Control.cpp

@@ -4,7 +4,7 @@
 namespace gameplay
 {
     Control::Control()
-        : _id(""), _state(Control::STATE_NORMAL), _size(Vector2::zero()), _position(Vector2::zero()), _border(Vector2::zero()), _padding(Vector2::zero()),
+        : _id(""), _state(Control::NORMAL), _position(Vector2::zero()), _size(Vector2::zero()), _bounds(Rectangle::empty()), _clip(Rectangle::empty()),
           _autoWidth(true), _autoHeight(true), _dirty(true), _consumeTouchEvents(true)
     {
     }
@@ -33,17 +33,11 @@ namespace gameplay
         }
     }
 
-    const char* Control::getID()
+    const char* Control::getID() const
     {
         return _id.c_str();
     }
 
-    const Rectangle Control::getBounds(bool includePadding) const
-    {
-        // TODO
-        return Rectangle();
-    }
-
     void Control::setPosition(float x, float y)
     {
         _position.set(x, y);
@@ -64,6 +58,16 @@ namespace gameplay
         return _size;
     }
 
+    const Rectangle& Control::getBounds() const
+    {
+        return _bounds;
+    }
+
+    const Rectangle& Control::getClip() const
+    {
+        return _clip;
+    }
+
     void Control::setAutoSize(bool width, bool height)
     {
         _autoWidth = width;
@@ -92,30 +96,30 @@ namespace gameplay
 
     void Control::disable()
     {
-        _state = STATE_DISABLED;
+        _state = DISABLED;
     }
 
     void Control::enable()
     {
-        _state = STATE_NORMAL;
+        _state = NORMAL;
     }
 
     bool Control::isEnabled()
     {
-        return _state != STATE_DISABLED;
+        return _state != DISABLED;
     }
 
     Theme::Style::OverlayType Control::getOverlayType() const
     {
         switch (_state)
         {
-        case Control::STATE_NORMAL:
+        case Control::NORMAL:
             return Theme::Style::OVERLAY_NORMAL;
-        case Control::STATE_FOCUS:
+        case Control::FOCUS:
             return Theme::Style::OVERLAY_FOCUS;
-        case Control::STATE_ACTIVE:
+        case Control::ACTIVE:
             return Theme::Style::OVERLAY_ACTIVE;
-        case Control::STATE_DISABLED:
+        case Control::DISABLED:
             return Theme::Style::OVERLAY_DISABLED;
         default:
             return Theme::Style::OVERLAY_NORMAL;
@@ -142,13 +146,65 @@ namespace gameplay
     {
     }
 
-    void Control::update(const Vector2& position)
+    void Control::update(const Rectangle& clip)
     {
+        // Calculate the bounds.
+        float x = clip.x + _position.x;
+        float y = clip.y + _position.y;
+        float width = _size.x;
+        float height = _size.y;
+
+        float clipX2 = clip.x + clip.width;
+        float x2 = x + width;
+        if (x2 > clipX2)
+        {
+            width = clipX2 - x;
+        }
+
+        float clipY2 = clip.y + clip.height;
+        float y2 = y + height;
+        if (y2 > clipY2)
+        {
+            height = clipY2 - y;
+        }
+
+        _bounds.set(_position.x, _position.y, width, height);
+
+        // Calculate the clipping viewport.
+        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();
+
+        x +=  border.left + padding.left;
+        y +=  border.top + padding.top;
+        width = _size.x - border.left - padding.left - border.right - padding.right;
+        height = _size.y - border.top - padding.top - border.bottom - padding.bottom;
+
+        clipX2 = clip.x + clip.width;
+        x2 = x + width;
+        if (x2 > clipX2)
+        {
+            width = clipX2 - x;
+        }
+
+        clipY2 = clip.y + clip.height;
+        y2 = y + height;
+        if (y2 > clipY2)
+        {
+            height = clipY2 - y;
+        }
+
+        _clip.set(x, y, width, height);
     }
 
-    void Control::drawBorder(SpriteBatch* spriteBatch, const Vector2& position)
+    void Control::drawBorder(SpriteBatch* spriteBatch, const Rectangle& clip)
     {
-        Vector2 pos(position.x + _position.x, position.y + _position.y);
+        Vector2 pos(clip.x + _position.x, clip.y + _position.y);
 
         // Get the border and background images for this control's current state.
         Theme::ContainerRegion* containerRegion = _style->getOverlay(getOverlayType())->getContainerRegion();
@@ -182,38 +238,38 @@ namespace gameplay
             if (!border.left && !border.right && !border.top && !border.bottom)
             {
                 // No border, just draw the image.
-                spriteBatch->draw(pos.x, pos.y, _size.x, _size.y, center.u1, center.v1, center.u2, center.v2, borderColor);
+                spriteBatch->draw(pos.x, pos.y, _size.x, _size.y, center.u1, center.v1, center.u2, center.v2, borderColor, clip);
             }
             else
             {
                 if (border.left && border.top)
-                    spriteBatch->draw(pos.x, pos.y, border.left, border.top, topLeft.u1, topLeft.v1, topLeft.u2, topLeft.v2, borderColor);
+                    spriteBatch->draw(pos.x, pos.y, border.left, border.top, topLeft.u1, topLeft.v1, topLeft.u2, topLeft.v2, borderColor, clip);
                 if (border.top)
-                    spriteBatch->draw(pos.x + border.left, pos.y, midWidth, border.top, top.u1, top.v1, top.u2, top.v2, borderColor);
+                    spriteBatch->draw(pos.x + border.left, pos.y, midWidth, border.top, top.u1, top.v1, top.u2, top.v2, borderColor, clip);
                 if (border.right && border.top)
-                    spriteBatch->draw(rightX, pos.y, border.right, border.top, topRight.u1, topRight.v1, topRight.u2, topRight.v2, borderColor);
+                    spriteBatch->draw(rightX, pos.y, border.right, border.top, topRight.u1, topRight.v1, topRight.u2, topRight.v2, borderColor, clip);
                 if (border.left)
-                    spriteBatch->draw(pos.x, midY, border.left, midHeight, left.u1, left.v1, left.u2, left.v2, borderColor);
+                    spriteBatch->draw(pos.x, midY, border.left, midHeight, left.u1, left.v1, left.u2, left.v2, borderColor, clip);
                 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);
+                        center.u1, center.v1, center.u2, center.v2, borderColor, clip);
                 if (border.right)
-                    spriteBatch->draw(rightX, midY, border.right, midHeight, right.u1, right.v1, right.u2, right.v2, borderColor);
+                    spriteBatch->draw(rightX, midY, border.right, midHeight, right.u1, right.v1, right.u2, right.v2, borderColor, clip);
                 if (border.bottom && border.left)
-                    spriteBatch->draw(pos.x, bottomY, border.left, border.bottom, bottomLeft.u1, bottomLeft.v1, bottomLeft.u2, bottomLeft.v2, borderColor);
+                    spriteBatch->draw(pos.x, bottomY, border.left, border.bottom, bottomLeft.u1, bottomLeft.v1, bottomLeft.u2, bottomLeft.v2, borderColor, clip);
                 if (border.bottom)
-                    spriteBatch->draw(midX, bottomY, midWidth, border.bottom, bottom.u1, bottom.v1, bottom.u2, bottom.v2, borderColor);
+                    spriteBatch->draw(midX, bottomY, midWidth, border.bottom, bottom.u1, bottom.v1, bottom.u2, bottom.v2, borderColor, clip);
                 if (border.bottom && border.right)
-                    spriteBatch->draw(rightX, bottomY, border.right, border.bottom, bottomRight.u1, bottomRight.v1, bottomRight.u2, bottomRight.v2, borderColor);
+                    spriteBatch->draw(rightX, bottomY, border.right, border.bottom, bottomRight.u1, bottomRight.v1, bottomRight.u2, bottomRight.v2, borderColor, clip);
             }
         }
     }
 
-    void Control::drawSprites(SpriteBatch* spriteBatch, const Vector2& position)
+    void Control::drawSprites(SpriteBatch* spriteBatch, const Rectangle& position)
     {
     }
 
-    void Control::drawText(const Vector2& position)
+    void Control::drawText(const Rectangle& position)
     {
     }
 
@@ -222,30 +278,35 @@ namespace gameplay
         return _dirty;
     }
 
+    bool Control::isContainer()
+    {
+        return false;
+    }
+
     Control::State Control::getStateFromString(const char* state)
     {
         if (!state)
         {
-            return STATE_NORMAL;
+            return NORMAL;
         }
 
-        if (strcmp(state, "STATE_NORMAL") == 0)
+        if (strcmp(state, "NORMAL") == 0)
         {
-            return STATE_NORMAL;
+            return NORMAL;
         }
-        else if (strcmp(state, "STATE_ACTIVE") == 0)
+        else if (strcmp(state, "ACTIVE") == 0)
         {
-            return STATE_ACTIVE;
+            return ACTIVE;
         }
-        else if (strcmp(state, "STATE_FOCUS") == 0)
+        else if (strcmp(state, "FOCUS") == 0)
         {
-            return STATE_FOCUS;
+            return FOCUS;
         }
-        else if (strcmp(state, "STATE_DISABLED") == 0)
+        else if (strcmp(state, "DISABLED") == 0)
         {
-            return STATE_DISABLED;
+            return DISABLED;
         }
 
-        return STATE_NORMAL;
+        return NORMAL;
     }
 }

+ 167 - 42
gameplay/src/Control.h

@@ -12,31 +12,38 @@
 namespace gameplay
 {
 
+/**
+ * Base class for UI controls.
+ */
 class Control : public Ref
 {
-public:
+    friend class Form;
+    friend class Container;
+    friend class Layout;
+    friend class AbsoluteLayout;
+    friend class VerticalLayout;
 
+public:
+    /**
+     * The possible states a control can be in.
+     */
     enum State
     {
-        STATE_NORMAL,
-        STATE_FOCUS,
-        STATE_ACTIVE,
-        STATE_DISABLED
+        NORMAL,
+        FOCUS,
+        ACTIVE,
+        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.
+     * Get this control's ID string.
+     *
+     * @return This control's ID.
      */
-    const Rectangle getBounds(bool includePadding) const;
+    const char* getID() const;
 
     /**
-     * Set the position of this Control relative to its parent Container.
+     * Set the position of this control relative to its parent container.
      *
      * @param x The x coordinate.
      * @param y The y coordinate.
@@ -44,14 +51,14 @@ public:
     void setPosition(float x, float y);
 
     /**
-     * Get the position of this Control relative to its parent Container.
+     * Get the position of this control relative to its parent container.
      *
-     * @return The position vector.
+     * @return The position of this control relative to its parent container.
      */
     const Vector2& getPosition() const;
 
     /**
-     * Set the size of this Control, including its border and padding.
+     * Set the desired size of this control, including its border and padding, before clipping.
      *
      * @param width The width.
      * @param height The height.
@@ -59,12 +66,26 @@ public:
     void setSize(float width, float height);
 
     /**
-     * Get the size of this Control, including its border and padding.
+     * Get the desired size of this control, including its border and padding, before clipping.
      *
-     * @return The size vector.
+     * @return The size of this control.
      */
     const Vector2& getSize() const;
 
+    /**
+     * Get the bounds of this control, relative to its parent container, after clipping.
+     *
+     * @return The bounds of this control.
+     */
+    const Rectangle& getBounds() const;
+
+    /**
+     * Get the content area of this control, in screen coordinates, after clipping.
+     *
+     * @return The clipping area of this control.
+     */
+    const Rectangle& getClip() 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.).
@@ -77,64 +98,168 @@ public:
      */
     void setAutoSize(bool width, bool height);
 
+    /**
+     * Change this control's state.
+     *
+     * @param state The state to switch this control to.
+     */
     void setState(State state);
+
+    /**
+     * Get this control's current state.
+     *
+     * @return This control's current state.
+     */
     State getState();
 
+    /**
+     * Disable this control.
+     */
     void disable();
+
+    /**
+     * Enable this control.
+     */
     void enable();
-    bool isEnabled();
 
-    Theme::Style::OverlayType getOverlayType() const;
+    /**
+     * Get whether this control is currently enabled.
+     *
+     * @return Whether this control is currently enabled.
+     */
+    bool isEnabled();
 
+    /**
+     * Set whether this control consumes touch events,
+     * preventing them from being passed to the game.
+     *
+     * @param consume Whether this control consumes touch events.
+     */
     void setConsumeTouchEvents(bool consume);
+
+    /**
+     * Get whether this control consumes touch events.
+     *
+     * @return Whether this control consumes touch events.
+     */
     bool getConsumeTouchEvents();
 
     /**
-     * Defaults to empty stub.
+     * Set the style this control will use when rendering.
+     *
+     * @param style The style this control will use when rendering.
+     */
+    void setStyle(Theme::Style* style);
+
+    /**
+     * Get this control's style.
+     *
+     * @return This control's style.
+     */
+    Theme::Style* getStyle() const;
+
+protected:
+    Control();
+    virtual ~Control();
+
+    /**
+     * Get the overlay type corresponding to this control's current state.
+     *
+     * @return The overlay type corresponding to this control's current state.
+     */
+    Theme::Style::OverlayType getOverlayType() const;
+
+    /**
+     * Touch callback on touch events.  Controls return true if they consume the touch event.
+     *
+     * @param evt The touch event that occurred.
+     * @param x The x position of the touch in pixels. Left edge is zero.
+     * @param y The y position of the touch in pixels. Top edge is zero.
+     * @param contactIndex The order of occurrence for multiple touch contacts starting at zero.
+     *
+     * @return Whether the touch event was consumed by this control.
+     *
+     * @see Touch::TouchEvent
      */
     virtual bool touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
 
+    /**
+     * Keyboard callback on key events.
+     *
+     * @param evt The key event that occured.
+     * @param key If evt is KEY_PRESS or KEY_RELEASE then key is the key code from Keyboard::Key.
+     *            If evt is KEY_CHAR then key is the unicode value of the character.
+     * 
+     * @see Keyboard::KeyEvent
+     * @see Keyboard::Key
+     */
     virtual void keyEvent(Keyboard::KeyEvent evt, int key);
 
-    virtual void update(const Vector2& position);
+    /**
+     * Called when a control's properties change.  Updates this control's internal rendering
+     * properties, such as its text viewport.
+     *
+     * @param clip The clipping rectangle of this control's parent container.
+     */
+    virtual void update(const Rectangle& clip);
 
     /**
      * Draws the themed border and background of a control.
+     *
+     * @param spriteBatch The sprite batch containing this control's border images.
+     * @param clip The clipping rectangle of this control's parent container.
      */
-    void drawBorder(SpriteBatch* spriteBatch, const Vector2& position);
-    virtual void drawSprites(SpriteBatch* spriteBatch, const Vector2& position);
-    virtual void drawText(const Vector2& position);
+    virtual void drawBorder(SpriteBatch* spriteBatch, const Rectangle& clip);
 
     /**
-     * Returns whether this Control has been modified since the last time
-     * isDirty() was called, and resets its dirty flag.
+     * Draw the icons associated with this control.
+     *
+     * @param spriteBatch The sprite batch containing this control's icons.
+     * @param clip The clipping rectangle of this control's parent container.
      */
-    virtual bool isDirty();
+    virtual void drawSprites(SpriteBatch* spriteBatch, const Rectangle& clip);
 
-    void setStyle(Theme::Style* Style);
-    Theme::Style* getStyle() const;
+    /**
+     * Draw this control's text.
+     *
+     * @param clip The clipping rectangle of this control's parent container.
+     */
+    virtual void drawText(const Rectangle& clip);
 
-    static State getStateFromString(const char* state);
+    /**
+     * Initialize properties common to all Controls.
+     */
+    virtual void init(Theme::Style* style, Properties* properties);
 
-protected:
-    Control();
-    Control(const Control& copy);
-    virtual ~Control();
+    /**
+     * Container and classes that extend it should implement this and return true.
+     */
+    virtual bool isContainer();
 
-    // Set properties common to all Controls.
-    virtual void init(Theme::Style* style, Properties* properties);
+    /**
+     * Returns whether this control has been modified and requires an update.
+     */
+    virtual bool isDirty();
+
+    /**
+     * Get a Control::State enum from a matching string.
+     */
+    static State getStateFromString(const char* state);
 
     std::string _id;
     State _state;           // Determines overlay used during draw().
-    Vector2 _size;
-    Vector2 _position;
-    Vector2 _border;
-    Vector2 _padding;
+    Vector2 _position;      // Position, relative to parent container's clipping window.
+    Vector2 _size;          // Desired size.  Will be clipped.
+    Rectangle _bounds;      // The position and size of this control, relative to parent container's bounds, including border and padding, after clipping.
+    Rectangle _clip;        // Clipping window of this control's content.
     bool _autoWidth;
     bool _autoHeight;
     bool _dirty;
     bool _consumeTouchEvents;
     Theme::Style* _style;
+
+private:
+    Control(const Control& copy);
 };
 
 }

+ 13 - 10
gameplay/src/Font.cpp

@@ -297,6 +297,7 @@ void Font::drawText(const char* text, const Rectangle& area, const Vector4& colo
     const char* token = text;
     const int length = strlen(text);
     int yPos = area.y;
+    const float areaHeight = area.height - size;
 
     Justify vAlign = static_cast<Justify>(justify & 0xF0);
     if (vAlign == 0)
@@ -417,7 +418,7 @@ void Font::drawText(const char* text, const Rectangle& area, const Vector4& colo
 
             // Final calculation of vertical position.
             int textHeight = yPos - area.y;
-            int vWhiteSpace = area.height - textHeight;
+            int vWhiteSpace = areaHeight - textHeight;
             if (vAlign == ALIGN_VCENTER)
             {
                 yPos = area.y + vWhiteSpace / 2;
@@ -456,7 +457,7 @@ void Font::drawText(const char* text, const Rectangle& area, const Vector4& colo
             }
 
             int textHeight = yPos - area.y;
-            int vWhiteSpace = area.height - textHeight;
+            int vWhiteSpace = areaHeight - textHeight;
             if (vAlign == ALIGN_VCENTER)
             {
                 yPos = area.y + vWhiteSpace / 2;
@@ -550,7 +551,7 @@ void Font::drawText(const char* text, const Rectangle& area, const Vector4& colo
             // Skip drawing until line break or wrap.
             draw = false;
         }
-        else if (yPos > area.y + area.height)
+        else if (yPos > area.y + areaHeight)
         {
             // Truncate below area's vertical limit.
             break;
@@ -702,6 +703,7 @@ void Font::measureText(const char* text, const Rectangle& viewport, unsigned int
 
     unsigned int lineWidth = 0;
     int yPos = viewport.y;
+    const float viewportHeight = viewport.height - size;
 
     if (wrap)
     {
@@ -891,7 +893,7 @@ void Font::measureText(const char* text, const Rectangle& viewport, unsigned int
     int height = yPos - viewport.y;
 
     // Calculate top of text without clipping.
-    int vWhitespace = viewport.height - height;
+    int vWhitespace = viewportHeight - height;
     if (vAlign == ALIGN_VCENTER)
     {
         y += vWhitespace / 2;
@@ -909,7 +911,7 @@ void Font::measureText(const char* text, const Rectangle& viewport, unsigned int
         if (y >= viewport.y)
         {
             // Text goes off the bottom of the viewport.
-            clippedBottom = (height - viewport.height) / size + 1;
+            clippedBottom = (height - viewportHeight) / size + 1;
             if (clippedBottom > 0)
             {
                 // Also need to crop empty lines above non-empty lines that have been clipped.
@@ -948,7 +950,7 @@ void Font::measureText(const char* text, const Rectangle& viewport, unsigned int
             if (vAlign == ALIGN_VCENTER)
             {
                 // In this case lines may be clipped off the bottom as well.
-                clippedBottom = (height - viewport.height + vWhitespace/2 + 0.01) / size + 1;
+                clippedBottom = (height - viewportHeight + vWhitespace/2 + 0.01) / size + 1;
                 if (clippedBottom > 0)
                 {
                     emptyIndex = emptyLines.size() - clippedBottom;
@@ -990,7 +992,7 @@ void Font::measureText(const char* text, const Rectangle& viewport, unsigned int
         out->x = (x >= viewport.x)? x : viewport.x;
         out->y = (y >= viewport.y)? y : viewport.y;
         out->width = (width <= viewport.width)? width : viewport.width;
-        out->height = (height <= viewport.height)? height : viewport.height;
+        out->height = (height <= viewportHeight)? height : viewportHeight;
     }
     else
     {
@@ -1023,6 +1025,7 @@ unsigned int Font::getIndexOrLocation(const char* text, const Rectangle& area, u
     const char* token = text;
     const int length = strlen(text);
     int yPos = area.y;
+    const float areaHeight = area.height - size;
 
     Justify vAlign = static_cast<Justify>(justify & 0xF0);
     if (vAlign == 0)
@@ -1143,7 +1146,7 @@ unsigned int Font::getIndexOrLocation(const char* text, const Rectangle& area, u
 
             // Final calculation of vertical position.
             int textHeight = yPos - area.y;
-            int vWhiteSpace = area.height - textHeight;
+            int vWhiteSpace = areaHeight - textHeight;
             if (vAlign == ALIGN_VCENTER)
             {
                 yPos = area.y + vWhiteSpace / 2;
@@ -1182,7 +1185,7 @@ unsigned int Font::getIndexOrLocation(const char* text, const Rectangle& area, u
             }
 
             int textHeight = yPos - area.y;
-            int vWhiteSpace = area.height - textHeight;
+            int vWhiteSpace = areaHeight - textHeight;
             if (vAlign == ALIGN_VCENTER)
             {
                 yPos = area.y + vWhiteSpace / 2;
@@ -1299,7 +1302,7 @@ unsigned int Font::getIndexOrLocation(const char* text, const Rectangle& area, u
             }
         }
 
-        if (yPos > area.y + area.height)
+        if (yPos > area.y + areaHeight)
         {
             // Truncate below area's vertical limit.
             break;

+ 40 - 54
gameplay/src/Form.cpp

@@ -63,7 +63,7 @@ namespace gameplay
         const char* layoutString = formProperties->getString("layout");
         Form* form = Form::create(themeFile, getLayoutType(layoutString));
 
-        Theme* theme = form->getTheme();
+        Theme* theme = form->_theme;
         const char* styleName = formProperties->getString("style");
         form->init(theme->getStyle(styleName), formProperties);
 
@@ -118,16 +118,6 @@ namespace gameplay
         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);
@@ -145,7 +135,11 @@ namespace gameplay
     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);
+        if (!_quad)
+        {
+            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);
@@ -160,39 +154,33 @@ namespace gameplay
 
     void Form::update()
     {
-        Container::update(Vector2::zero());
+        Container::update(Rectangle(0, 0, _size.x, _size.y));
     }
 
     void Form::draw()
     {
-        // If this Form has a Node then it's a 3D Form.  The contents will be rendered
-        // into a FrameBuffer which will be used to texture a quad.  The quad will be
+        // 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.
+        // by the user, unless they call setQuad() themselves.
 
-        // On the other hand, if this Form has not been set on a Node it will render
+        // 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)
         {
+            // Check whether this form has changed since the last call to draw()
+            // and if so, render into the framebuffer.
             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(), _clip);
 
-                draw(_theme->getSpriteBatch(), _position);
+                // Rebind the default framebuffer and game viewport.
                 FrameBuffer::bindDefault();
-
-                // Rebind the game viewport.
+                Game* game = Game::getInstance();
                 GL_ASSERT( glViewport(0, 0, game->getWidth(), game->getHeight()) );
             }
 
@@ -200,11 +188,11 @@ namespace gameplay
         }
         else
         {
-            draw(_theme->getSpriteBatch(), _position);
+            draw(_theme->getSpriteBatch(), _clip);
         }
     }
 
-    void Form::draw(SpriteBatch* spriteBatch, const Vector2& position)
+    void Form::draw(SpriteBatch* spriteBatch, const Rectangle& clip)
     {
         std::vector<Control*>::const_iterator it;
 
@@ -213,7 +201,7 @@ namespace gameplay
 
         // 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());
+        Control::drawBorder(spriteBatch, Rectangle(0, 0, _size.x, _size.y));
 
         // Draw each control's border and background.
         for (it = _controls.begin(); it < _controls.end(); it++)
@@ -222,10 +210,10 @@ namespace gameplay
 
             //if (!_node || (*it)->isDirty())
             {
-                control->drawBorder(spriteBatch, position);
+                control->drawBorder(spriteBatch, clip);
 
                 // Add all themed foreground sprites (checkboxes etc.) to the same batch.
-                control->drawSprites(spriteBatch, position);
+                control->drawSprites(spriteBatch, clip);
             }
         }
         spriteBatch->end();
@@ -237,7 +225,7 @@ namespace gameplay
 
             //if (!_node || (*it)->isDirty())
             {
-                control->drawText(position);
+                control->drawText(clip);
             }
         }
 
@@ -309,7 +297,6 @@ namespace gameplay
                     {
                         // Get info about the form's position.
                         Matrix m = node->getMatrix();
-                        const Vector2& size = form->getSize();
                         Vector3 min(0, 0, 0);
                         m.transformPoint(&min);
 
@@ -343,17 +330,15 @@ namespace gameplay
                             m.transformPoint(&point);
 
                             // Pass the touch event on.
-                            const Vector2& size = form->getSize();
-                            const Vector2& position = form->getPosition();
-
-                            if (form->getState() == Control::STATE_FOCUS ||
+                            const Rectangle& bounds = form->getBounds();
+                            if (form->getState() == Control::FOCUS ||
                                 (evt == Touch::TOUCH_PRESS &&
-                                 point.x >= position.x &&
-                                 point.x <= position.x + size.x &&
-                                 point.y >= position.y &&
-                                 point.y <= position.y + size.y))
+                                 point.x >= bounds.x &&
+                                 point.x <= bounds.x + bounds.width &&
+                                 point.y >= bounds.y &&
+                                 point.y <= bounds.y + bounds.height))
                             {
-                               if (form->touchEvent(evt, point.x, size.y - point.y, contactIndex))
+                               if (form->touchEvent(evt, point.x - bounds.x, bounds.height - point.y - bounds.y, contactIndex))
                                {
                                    return true;
                                }
@@ -364,18 +349,16 @@ namespace gameplay
                 else
                 {
                     // Simply compare with the form's bounds.
-                    const Vector2& size = form->getSize();
-                    const Vector2& position = form->getPosition();
-
-                    if (form->getState() == Control::STATE_FOCUS ||
+                    const Rectangle& bounds = form->getBounds();
+                    if (form->getState() == Control::FOCUS ||
                         (evt == Touch::TOUCH_PRESS &&
-                         x >= position.x &&
-                         x <= position.x + size.x &&
-                         y >= position.y &&
-                         y <= position.y + size.y))
+                         x >= bounds.x &&
+                         x <= bounds.x + bounds.width &&
+                         y >= bounds.y &&
+                         y <= bounds.y + bounds.height))
                     {
                         // Pass on the event's position relative to the form.
-                        if (form->touchEvent(evt, x - position.x, y - position.y, contactIndex))
+                        if (form->touchEvent(evt, x - bounds.x, y - bounds.y, contactIndex))
                         {
                             return true;
                         }
@@ -393,7 +376,10 @@ namespace gameplay
         for (it = __forms.begin(); it < __forms.end(); it++)
         {
             Form* form = *it;
-            form->keyEvent(evt, key);
+            if (form->isEnabled())
+            {
+                form->keyEvent(evt, key);
+            }
         }
     }
 }

+ 92 - 9
gameplay/src/Form.h

@@ -14,47 +14,127 @@ namespace gameplay
 
 class Theme;
 
+/**
+ * Top-level container of UI controls.
+ */
 class Form : public Container
 {
     friend class Platform;
 
 public:
     /**
-     * Create from .form file.
+     * Create from properties file.
+     * The top-most namespace in the file must be named 'form'.  The following properties are available for forms:
+     *
+     * form <Form ID>
+     * {
+     *      // Form properties.
+     *      theme    = <Path to Theme File> // See Theme.h.
+     *      layout   = <Layout Type>        // A value from the Layout::Type enum.  E.g.: LAYOUT_VERTICAL
+     *      style    = <Style ID>           // A style from the referenced theme.
+     *      position = <x, y>               // Position of the form on-screen, measured in pixels.
+     *      size     = <width, height>      // Size of the form, measured in pixels.
+     *   
+     *      // All the controls within this form.
+     *      container{}
+     *      label{}
+     *      textBox{}
+     *      button{}
+     *      checkBox{}
+     *      radioButton{}
+     *      slider{}
+     * }
+     *
+     * @param path Path to the properties file to create a new form from.
      */
     static Form* create(const char* path);
-    static Form* create(const char* textureFile, Layout::Type type);
-    static Form* getForm(const char* id);
 
-    void setTheme(Theme* theme);
-    Theme* getTheme() const;
+    /**
+     * Get a form from its ID.
+     *
+     * @param id The ID of the form to search for.
+     *
+     * @return A form with the given ID, or null if one was not found.
+     */
+    static Form* getForm(const char* id);
 
     /**
      * Create a 3D quad to texture with this Form.
+     *
+     * The specified points should describe a triangle strip with the first 3 points
+     * forming a triangle wound in counter-clockwise direction, with the second triangle
+     * formed from the last three points in clockwise direction.
+     *
+     * @param p1 The first point.
+     * @param p2 The second point.
+     * @param p3 The third point.
+     * @param p4 The fourth point.
      */
     void setQuad(const Vector3& p1, const Vector3& p2, const Vector3& p3, const Vector3& p4);
 
     /**
      * Create a 2D quad to texture with this Form.
+     *
+     * @param x The x coordinate.
+     * @param y The y coordinate.
+     * @param width The width of the quad.
+     * @param height The height of the quad.
      */
     void setQuad(float x, float y, float width, float height);
 
+    /**
+     * Attach this form to a node.
+     *
+     * A form can be drawn as part of the 3-dimensional world if it is attached to a node.
+     * The form's contents will be rendered into a framebuffer which will be used to texture a quad.
+     * This quad will be given the same dimensions as the form and must be transformed appropriately.
+     * Alternatively, a quad can be set explicitly on a form with the setQuad() methods.
+     *
+     * @param node The node to attach this form to.
+     */
     void setNode(Node* node);
 
+    /**
+     * Updates each control within this form, and positions them according to its layout.
+     */
     void update();
+
+    /**
+     * Draws this form.
+     */
     void draw();
 
-private:
+protected:
     Form();
-    Form(const Form& copy);
     virtual ~Form();
 
+    static Form* create(const char* textureFile, Layout::Type type);
+
+    /**
+     * Initialize a quad for this form in order to draw it in 3D.
+     *
+     * @param mesh The mesh to create a model from.
+     */
     void initQuad(Mesh* mesh);
-    void draw(SpriteBatch* spriteBatch);
-    void draw(SpriteBatch* spriteBatch, const Vector2& position);
 
+    /**
+     * Draw this form into the current framebuffer.
+     *
+     * @param spriteBatch The sprite batch containing this form's theme texture.
+     * @param clip The form's clipping rectangle.
+     */
+    void draw(SpriteBatch* spriteBatch, const Rectangle& clip);
+
+    /**
+     * Propagate touch events to enabled forms.
+     *
+     * @return Whether the touch event was consumed by a form.
+     */
     static bool touchEventInternal(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
 
+    /**
+     * Propagate key events to enabled forms.
+     */
     static void keyEventInternal(Keyboard::KeyEvent evt, int key);
 
     Theme* _theme;              // The Theme applied to this Form.
@@ -63,6 +143,9 @@ private:
     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.
+
+private:
+    Form(const Form& copy);
 };
 
 }

+ 35 - 0
gameplay/src/Game.cpp

@@ -177,6 +177,9 @@ void Game::frame()
 
         // Update the scheduled and running animations.
         _animationController->update(elapsedTime);
+
+        // Fire time events to scheduled TimeListeners
+        fireTimeEvents(frameTime);
     
         // Update the physics.
         _physicsController->update(elapsedTime);
@@ -262,6 +265,13 @@ void Game::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactI
 {
 }
 
+void Game::schedule(long timeOffset, TimeListener* timeListener, void* cookie)
+{
+    assert(timeListener);
+    TimeEvent timeEvent(getGameTime() + timeOffset, timeListener, cookie);
+    _timeEvents.push(timeEvent);
+}
+
 bool Game::mouseEvent(Mouse::MouseEvent evt, int x, int y, int wheelDelta)
 {
     return false;
@@ -281,4 +291,29 @@ void Game::updateOnce()
     _audioController->update(elapsedTime);
 }
 
+void Game::fireTimeEvents(long frameTime)
+{
+    while (!_timeEvents.empty())
+    {
+        TimeEvent* timeEvent = &_timeEvents.top();
+        if (timeEvent->time > frameTime)
+        {
+            break;
+        }
+        timeEvent->listener->timeEvent(frameTime - timeEvent->time, timeEvent->cookie);
+        _timeEvents.pop();
+    }
+}
+
+Game::TimeEvent::TimeEvent(long time, TimeListener* timeListener, void* cookie)
+            : time(time), listener(timeListener), cookie(cookie)
+{
+}
+
+bool Game::TimeEvent::operator<(const TimeEvent& v) const
+{
+    // The first element of std::priority_queue is the greatest.
+    return time > v.time;
+}
+
 }

+ 42 - 0
gameplay/src/Game.h

@@ -1,6 +1,8 @@
 #ifndef GAME_H_
 #define GAME_H_
 
+#include <queue>
+
 #include "Keyboard.h"
 #include "Touch.h"
 #include "Mouse.h"
@@ -8,6 +10,7 @@
 #include "AnimationController.h"
 #include "PhysicsController.h"
 #include "Vector4.h"
+#include "TimeListener.h"
 
 namespace gameplay
 {
@@ -254,6 +257,16 @@ public:
      */
     inline void getAccelerometerValues(float* pitch, float* roll);
 
+    /**
+     * Schedules a time event to be sent to the given TimeListener a given of game milliseconds from now.
+     * Game time stops while the game is paused. A time offset of zero will fire the time event in the next frame.
+     * 
+     * @param timeOffset The number of game milliseconds in the future to schedule the event to be fired.
+     * @param timeListener The TimeListener that will receive the event.
+     * @param cookie The cookie data that the time event will contain.
+     */
+    void schedule(long timeOffset, TimeListener* timeListener, void* cookie = 0);
+
 protected:
 
     /**
@@ -310,6 +323,27 @@ protected:
 
 private:
 
+    /**
+     * TimeEvent represents the event that is sent to TimeListeners as a result of 
+     * calling Game::schedule().
+     */
+    class TimeEvent
+    {
+    public:
+
+        TimeEvent(long time, TimeListener* timeListener, void* cookie);
+        // The comparator is used to determine the order of time events in the priority queue.
+        bool operator<(const TimeEvent& v) const;
+        
+        /**
+         * The game time.
+         * @see Game::getGameTime()
+         */
+        long time;
+        TimeListener* listener;
+        void* cookie;
+    };
+
     /**
      * Constructor.
      *
@@ -327,6 +361,13 @@ private:
      */
     void shutdown();
 
+    /**
+     * Fires the time events that were scheduled to be called.
+     * 
+     * @param frameTime The current game frame time. Used to determine which time events need to be fired.
+     */
+    void fireTimeEvents(long frameTime);
+
     bool _initialized;                          // If game has initialized yet.
     State _state;                               // The game state.
     static long _pausedTimeLast;                // The last time paused.
@@ -342,6 +383,7 @@ private:
     AnimationController* _animationController;  // Controls the scheduling and running of animations.
     AudioController* _audioController;          // Controls audio sources that are playing in the game.
     PhysicsController* _physicsController;      // Controls the simulation of a physics scene and entities.
+    std::priority_queue<TimeEvent> _timeEvents; // Contains the scheduled time events.
 };
 
 }

+ 3 - 57
gameplay/src/Label.cpp

@@ -3,8 +3,6 @@
 
 namespace gameplay
 {
-    static std::vector<Label*> __labels;
-
     Label::Label() : _text("")
     {
     }
@@ -20,23 +18,7 @@ namespace gameplay
     Label* Label::create(Theme::Style* style, Properties* properties)
     {
         Label* label = new Label();
-        label->_style = style;
-        properties->getVector2("position", &label->_position);
-        properties->getVector2("size", &label->_size);
-
-        const char* id = properties->getId();
-        if (id)
-        {
-            label->_id = id;
-        }
-
-        const char* text = properties->getString("text");
-        if (text)
-        {
-            label->_text = text;
-        }
-
-        __labels.push_back(label);
+        label->init(style, properties);
 
         return label;
     }
@@ -51,21 +33,6 @@ namespace gameplay
             _text = text;
         }
     }
-
-    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)
     {
@@ -80,28 +47,7 @@ namespace gameplay
         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)
+    void Label::drawText(const Rectangle& clip)
     {
         if (_text.size() <= 0)
             return;
@@ -112,7 +58,7 @@ namespace gameplay
 
         // Draw the text.
         font->begin();
-        font->drawText(_text.c_str(), _viewport, overlay->getTextColor(), overlay->getFontSize(), overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
+        font->drawText(_text.c_str(), _clip, overlay->getTextColor(), overlay->getFontSize(), overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
         font->end();
 
         _dirty = false;

+ 50 - 11
gameplay/src/Label.h

@@ -7,29 +7,68 @@
 namespace gameplay
 {
 
+/**
+ * A label is the most basic type of control, capable only of rendering text within its border.
+ *
+ * The following properties are available for labels:
+ *
+ * label <Label ID>
+ * {
+ *      style       = <Style ID>
+ *      position    = <x, y>
+ *      size        = <width, height>
+ *      text        = <string>
+ * }
+ */
 class Label : public Control
 {
+    friend class Container;
+
 public:
-    static Label* create(Theme::Style* style, Properties* properties);
-    static Label* getLabel(const char* id);
-    
+    /**
+     * Set the text for this label to display.
+     *
+     * @param text The text to display.
+     */
     void setText(const char* text);
-    const char* getText();
 
-    void update(const Vector2& position);
-
-    void drawText(const Vector2& position);
+    /**
+     * Get the text displayed by this label.
+     *
+     * @return The text displayed by this label.
+     */
+    const char* getText();
 
 protected:
-
     Label();
-    Label(const Label& copy);
     virtual ~Label();
 
+    /**
+     * Create a label with a given style and properties.
+     *
+     * @param style The style to apply to this label.
+     * @param properties The properties to set on this label.
+     *
+     * @return The new label.
+     */
+    static Label* create(Theme::Style* style, Properties* properties);
+
+    /**
+     * Initialize this label.
+     */
     virtual void init(Theme::Style* style, Properties* properties);
 
-    std::string _text;
-    Rectangle _viewport;
+    /**
+     * Draw this label's text.
+     *
+     * @param clip The clipping rectangle of this label's parent container.
+     */
+    void drawText(const Rectangle& clip);
+
+    std::string _text;  // The text displayed by this label.
+
+private:
+    Label(const Label& copy);
 };
 
 }

+ 37 - 5
gameplay/src/Layout.h

@@ -8,24 +8,56 @@ namespace gameplay
 
 class Container;
 
+/**
+ * The layout interface for UI containers.
+ *
+ * Implementations of this interface are responsible for positioning, resizing
+ * and then calling update on all the controls within a container.
+ */
 class Layout : public Ref
 {
+    friend class Container;
+
 public:
+    /**
+     * Layout types available to containers.
+     */
     enum Type
     {
+        /**
+         * Flow layout: Controls are placed next to one another horizontally
+         * until the right-most edge of the container is reached, at which point
+         * a new row is started.
+         */
         LAYOUT_FLOW,
+
+        /**
+         * Vertical layout: Controls are placed next to one another vertically until
+         * the bottom-most edge of the container is reached.
+         */
         LAYOUT_VERTICAL,
+
+        /**
+         * Absolute layout: Controls are not modified at all by this layout.
+         * They must be positioned and sized manually.
+         */
         LAYOUT_ABSOLUTE
     };
 
+    /**
+     * Get the type of this layout.
+     *
+     * @return The type of this layout.
+     */
     virtual Type getType() = 0;
 
-    virtual void update(const Container* container) = 0;
-
 protected:
-    //Layout();
-    //Layout(const Layout& copy);
-    //virtual ~Layout();
+    /**
+     * Position, resize, and update the controls within a container.
+     *
+     * @param container The container to update.
+     */
+    virtual void update(const Container* container) = 0;
 };
 
 }

+ 1 - 1
gameplay/src/Node.cpp

@@ -754,7 +754,7 @@ PhysicsRigidBody* Node::getRigidBody() const
     return _physicsRigidBody;
 }
 
-void Node::setRigidBody(PhysicsRigidBody::Type type, float mass, float friction,
+void Node::setRigidBody(PhysicsRigidBody::ShapeType type, float mass, float friction,
         float restitution, float linearDamping, float angularDamping)
 {
     SAFE_DELETE(_physicsRigidBody);

+ 1 - 1
gameplay/src/Node.h

@@ -387,7 +387,7 @@ public:
      * @param linearDamping The percentage of linear velocity lost per second (between 0.0 and 1.0).
      * @param angularDamping The percentage of angular velocity lost per second (between 0.0 and 1.0).
      */
-    void setRigidBody(PhysicsRigidBody::Type type, float mass = 0.0f, float friction = 0.5f,
+    void setRigidBody(PhysicsRigidBody::ShapeType type, float mass = 0.0f, float friction = 0.5f,
         float restitution = 0.0f, float linearDamping = 0.0f, float angularDamping = 0.0f);
 
     /**

+ 6 - 3
gameplay/src/ParticleEmitter.cpp

@@ -895,14 +895,17 @@ void ParticleEmitter::draw()
         Vector2 pivot(0.5f, 0.5f);
 
         // 3D Rotation so that particles always face the camera.
-        Vector3 right = _node->getScene()->getActiveCamera()->getNode()->getRightVector();
-        Vector3 forward = _node->getScene()->getActiveCamera()->getNode()->getUpVector();
+        const Matrix& cameraWorldMatrix = _node->getScene()->getActiveCamera()->getNode()->getWorldMatrix();
+        Vector3 right;
+        cameraWorldMatrix.getRightVector(&right);
+        Vector3 up;
+        cameraWorldMatrix.getUpVector(&up);
 
         for (unsigned int i = 0; i < _particleCount; i++)
         {
             Particle* p = &_particles[i];
 
-            _spriteBatch->draw(p->_position, right, forward, p->_size, p->_size,
+            _spriteBatch->draw(p->_position, right, up, p->_size, p->_size,
                                _spriteTextureCoords[p->_frame * 4], _spriteTextureCoords[p->_frame * 4 + 1], _spriteTextureCoords[p->_frame * 4 + 2], _spriteTextureCoords[p->_frame * 4 + 3],
                                p->_color, pivot, p->_angle);
         }

+ 15 - 0
gameplay/src/PhysicsCharacter.cpp

@@ -116,6 +116,7 @@ PhysicsCharacter::~PhysicsCharacter()
 
     _node->removeListener(this);
     SAFE_RELEASE(_node);
+    SAFE_DELETE(_motionState);
 }
 
 PhysicsCollisionObject::Type PhysicsCharacter::getType() const
@@ -172,6 +173,20 @@ void PhysicsCharacter::addAnimation(const char* name, AnimationClip* clip, float
     _animations[name] = a;
 }
 
+AnimationClip* PhysicsCharacter::getAnimation(const char* name)
+{
+    if (name)
+    {
+        // Lookup the specified animation
+        std::map<const char*, CharacterAnimation>::iterator aitr = _animations.find(name);
+        if (aitr != _animations.end())
+        {
+            return aitr->second.clip;
+        }
+    }
+    return NULL;
+}
+
 void PhysicsCharacter::play(const char* name, AnimationFlags flags, float speed, unsigned int blendDuration, unsigned int layer)
 {
     CharacterAnimation* animation = NULL;

+ 3 - 1
gameplay/src/PhysicsCharacter.h

@@ -60,6 +60,8 @@ public:
      * Returns the character node for this PhysicsCharacter.
      *
      * @return The character Node.
+     *
+     * @see PhysicsCollisionObject::getNode.
      */
     Node* getNode() const;
 
@@ -114,7 +116,7 @@ public:
      *
      * @return The specified animation clip.
      */
-    AnimationClip* getAnimation(const char* name) const;
+    AnimationClip* getAnimation(const char* name);
 
     /**
      * Plays the specified animation.

+ 7 - 0
gameplay/src/PhysicsCollisionObject.h

@@ -6,6 +6,8 @@
 namespace gameplay
 {
 
+class Node;
+
 /**
  * Base class for all gameplay physics objects that support collision events.
  */
@@ -112,6 +114,11 @@ public:
      */
     virtual PhysicsCollisionObject::Type getType() const = 0;
 
+    /**
+     * Returns the node associated with this collision object.
+     */
+    virtual Node* getNode() const = 0;
+
     /**
      * Adds a collision listener for this collision object.
      * 

+ 9 - 0
gameplay/src/PhysicsController.cpp

@@ -5,6 +5,8 @@
 #include "PhysicsMotionState.h"
 #include "Package.h"
 
+#include "BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h"
+
 // The initial capacity of the Bullet debug drawer's vertex batch.
 #define INITIAL_CAPACITY 280
 
@@ -688,6 +690,13 @@ btCollisionShape* PhysicsController::createMesh(PhysicsRigidBody* body, const Ve
     return shape;
 }
 
+btCollisionShape* PhysicsController::createHeightfield(int width, int height, void* heightfieldData, float minHeight, float maxHeight)
+{
+    btCollisionShape* shape = bullet_new<btHeightfieldTerrainShape>(width, height, heightfieldData, 1.0f, minHeight, maxHeight, 1, PHY_FLOAT, false);
+    _shapes.push_back(new PhysicsCollisionShape(shape));
+    return shape;
+}
+
 void PhysicsController::addConstraint(PhysicsRigidBody* a, PhysicsRigidBody* b, PhysicsConstraint* constraint)
 {
     a->addConstraint(constraint);

+ 3 - 0
gameplay/src/PhysicsController.h

@@ -335,6 +335,9 @@ private:
     // Creates a triangle mesh collision shape to be used in the creation of a rigid body.
     btCollisionShape* createMesh(PhysicsRigidBody* body, const Vector3& scale);
 
+    // Creates a heightfield collision shape to be used in the creation of a rigid body.
+    btCollisionShape* createHeightfield(int width, int height, void* heightfieldData, float minHeight, float maxHeight);
+
     // Sets up the given constraint for the given two rigid bodies.
     void addConstraint(PhysicsRigidBody* a, PhysicsRigidBody* b, PhysicsConstraint* constraint);
 

+ 6 - 11
gameplay/src/PhysicsRigidBody.cpp

@@ -5,8 +5,6 @@
 #include "PhysicsMotionState.h"
 #include "PhysicsRigidBody.h"
 
-#include "BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h"
-
 namespace gameplay
 {
 
@@ -126,8 +124,7 @@ PhysicsRigidBody::PhysicsRigidBody(Node* node, Image* image, float mass,
     SAFE_DELETE_ARRAY(data);
 
     // Create the heightfield collision shape.
-    _shape = bullet_new<btHeightfieldTerrainShape>(_width, _height, _heightfieldData,
-        1.0f, minHeight, maxHeight, 1, PHY_FLOAT, false);
+    _shape = Game::getInstance()->getPhysicsController()->createHeightfield(_width, _height, _heightfieldData, minHeight, maxHeight);
 
     // Offset the heightmap's center of mass according to the way that Bullet calculates the origin 
     // of its heightfield collision shape; see documentation for the btHeightfieldTerrainShape for more info.
@@ -208,15 +205,13 @@ PhysicsRigidBody::~PhysicsRigidBody()
     {
         SAFE_DELETE_ARRAY(_indexData[i]);
     }
+    SAFE_DELETE_ARRAY(_heightfieldData);
     SAFE_DELETE(_inverse);
+}
 
-    // Special case: For heightfield rigid bodies, we need to delete the collision
-    // shape here since it is not stored and managed by the PhysicsController.
-    if (_heightfieldData)
-    {
-        SAFE_DELETE(_shape);
-        SAFE_DELETE_ARRAY(_heightfieldData);
-    }
+Node* PhysicsRigidBody::getNode() const
+{
+    return _node;
 }
 
 PhysicsCollisionObject::Type PhysicsRigidBody::getType() const

+ 3 - 1
gameplay/src/PhysicsRigidBody.h

@@ -139,8 +139,10 @@ public:
      * Gets the node that the rigid body is attached to.
      * 
      * @return The node.
+     *
+     * @see PhysicsCollisionObject::getNode.
      */
-    inline Node* getNode();
+    Node* getNode() const;
 
     /**
      * Gets the rigid body's restitution.

+ 0 - 5
gameplay/src/PhysicsRigidBody.inl

@@ -59,11 +59,6 @@ inline const Vector3& PhysicsRigidBody::getLinearVelocity() const
     return *_linearVelocity;
 }
 
-inline Node* PhysicsRigidBody::getNode()
-{
-    return _node;
-}
-
 inline float PhysicsRigidBody::getRestitution() const
 {
     return _body->getRestitution();

+ 5 - 2
gameplay/src/PlatformMacOS.mm

@@ -678,10 +678,13 @@ 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);
+    if (!Form::touchEventInternal(evt, x, y, contactIndex))
+    {
+        Game::getInstance()->touchEvent(evt, x, y, contactIndex);
+    }
 }
 
 }

+ 4 - 2
gameplay/src/PlatformQNX.cpp

@@ -1086,8 +1086,10 @@ void Platform::displayKeyboard(bool display)
 
 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);
+    if (!Form::touchEventInternal(evt, x, y, contactIndex))
+    {
+        Game::getInstance()->touchEvent(evt, x, y, contactIndex);
+    }
 }
 
 void Platform::keyEventInternal(Keyboard::KeyEvent evt, int key)

+ 46 - 54
gameplay/src/RadioButton.cpp

@@ -3,7 +3,6 @@
 
 namespace gameplay
 {
-
 static std::vector<RadioButton*> __radioButtons;
 
 RadioButton::RadioButton() : _selected(false)
@@ -17,7 +16,12 @@ RadioButton::RadioButton(const RadioButton& copy)
 
 RadioButton::~RadioButton()
 {
-
+    // Remove this RadioButton from the global list.
+    std::vector<RadioButton*>::iterator it = std::find(__radioButtons.begin(), __radioButtons.end(), this);
+    if (it != __radioButtons.end())
+    {
+        __radioButtons.erase(it);
+    }
 }
 
 RadioButton* RadioButton::create(Theme::Style* style, Properties* properties)
@@ -44,50 +48,48 @@ RadioButton* RadioButton::create(Theme::Style* style, Properties* properties)
     return radioButton;
 }
 
-RadioButton* RadioButton::getRadioButton(const char* id)
+void RadioButton::setIconSize(float width, float height)
 {
-    std::vector<RadioButton*>::const_iterator it;
-    for (it = __radioButtons.begin(); it < __radioButtons.end(); it++)
+    _iconSize.set(width, height);
+}
+
+const Vector2& RadioButton::getIconSize() const
+{
+    Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
+    Theme::Icon* icon = overlay->getCheckBoxIcon();
+    if (_iconSize.isZero() && icon)
     {
-        RadioButton* radioButton = *it;
-        if (strcmp(id, radioButton->getID()) == 0)
-        {
-            return radioButton;
-        }
+        return icon->getSize();
     }
 
-    return NULL;
+    return _iconSize;
 }
 
 bool RadioButton::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
 {
-    if (isEnabled())
+    if (!isEnabled())
+    {
+        return false;
+    }
+
+    switch (evt)
     {
-        switch (evt)
+    case Touch::TOUCH_RELEASE:
         {
-        case Touch::TOUCH_RELEASE:
+            if (_state == Control::ACTIVE)
             {
-                if (_state == Control::STATE_ACTIVE)
+                if (x > 0 && x <= _bounds.width &&
+                    y > 0 && y <= _bounds.height)
                 {
-                    if (x > 0 && x <= _size.x &&
-                        y > 0 && y <= _size.y)
-                    {
-                        if (_callback)
-                        {
-                            _callback->trigger(this);
-                        }
-                        RadioButton::clearSelected(_groupId);
-                        _selected = true;
-                    }
+                    RadioButton::clearSelected(_groupId);
+                    _selected = true;
                 }
             }
-            break;
         }
-
-        return Button::touchEvent(evt, x, y, contactIndex);
+        break;
     }
 
-    return false;
+    return Button::touchEvent(evt, x, y, contactIndex);
 }
 
 void RadioButton::clearSelected(const std::string& groupId)
@@ -99,11 +101,12 @@ void RadioButton::clearSelected(const std::string& groupId)
         if (groupId == radioButton->_groupId)
         {
             radioButton->_selected = false;
+            radioButton->_dirty = true;
         }
     }
 }
 
-void RadioButton::drawSprites(SpriteBatch* spriteBatch, const Vector2& position)
+void RadioButton::drawSprites(SpriteBatch* spriteBatch, const Rectangle& clip)
 {
     // Left, v-center.
     // TODO: Set an alignment for icons.
@@ -126,8 +129,8 @@ void RadioButton::drawSprites(SpriteBatch* spriteBatch, const Vector2& position)
         }
         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);
+        Vector2 pos(clip.x + _position.x + border.left + padding.left,
+            clip.y + _position.y + (_bounds.height - border.bottom - padding.bottom) / 2.0f - size.y / 2.0f);
 
         if (_selected)
         {
@@ -142,35 +145,24 @@ void RadioButton::drawSprites(SpriteBatch* spriteBatch, const Vector2& position)
     }
 }
 
-void RadioButton::update(const Vector2& position)
+void RadioButton::update(const Rectangle& clip)
 {
+    Control::update(clip);
+
     Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
     Theme::Icon* icon = overlay->getCheckBoxIcon();
-    Theme::Border border;
-    Theme::ContainerRegion* containerRegion = overlay->getContainerRegion();
-    if (containerRegion)
+    Vector2& size = _iconSize;
+    if (_iconSize.isZero() && icon)
     {
-        border = overlay->getContainerRegion()->getBorder();
+        size = icon->getSize();
     }
-    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);
+    float iconWidth = size.x;
 
-    _viewport.set(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 - overlay->getFontSize());
+    _clip.x += iconWidth;
+    _clip.width -= iconWidth;
 }
 
-void RadioButton::drawText(const Vector2& position)
+void RadioButton::drawText(const Rectangle& clip)
 {
     // TODO: Batch all labels that use the same font.
     Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
@@ -178,7 +170,7 @@ void RadioButton::drawText(const Vector2& position)
     
     // Draw the text.
     font->begin();
-    font->drawText(_text.c_str(), _viewport, overlay->getTextColor(), overlay->getFontSize(), overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
+    font->drawText(_text.c_str(), _clip, overlay->getTextColor(), overlay->getFontSize(), overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
     font->end();
 
     _dirty = false;

+ 58 - 7
gameplay/src/RadioButton.h

@@ -8,31 +8,82 @@
 namespace gameplay
 {
 
+/**
+ * Similar to a checkbox, a radio button can be toggled between two states.
+ *
+ * However, a radio button can belong to a group, and only one radio button
+ * from a group can be selected at one time.
+ *
+ * The following properties are available for radio buttons:
+ *
+ * radioButton <RadioButton ID>
+ * {
+ *      style       = <Style ID>
+ *      position    = <x, y>
+ *      size        = <width, height>
+ *      text        = <string>
+ *      group       = <string>
+ *      iconSize    = <width, height>   // The size to draw the radio button icon, if different from its size in the texture.
+ * }
+ */
 class RadioButton : public Button
 {
+    friend class Container;
+
 public:
+    /**
+     * Get whether this radio button is currently selected.
+     *
+     * @return Whether this radio button is currently selected.
+     */
+    bool isSelected();
+
+    /**
+     * Set the size to draw the radio button icon.
+     *
+     * @param width The width to draw the radio button icon.
+     * @param height The height to draw the radio button icon.
+     */
+    void setIconSize(float width, float height);
+
+    /**
+     * Get the size at which the radio button icon will be drawn.
+     *
+     * @return The size of the radio button icon.
+     */
+    const Vector2& getIconSize() const;
+
+protected:
     RadioButton();
     virtual ~RadioButton();
 
+    /**
+     * Create a radio button with a given style and properties.
+     *
+     * @param style The style to apply to this radio button.
+     * @param properties The properties to set on this radio button.
+     *
+     * @return The new radio button.
+     */
     static RadioButton* create(Theme::Style* style, Properties* properties);
-    static RadioButton* getRadioButton(const char* id);
 
     bool touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
 
-    void update(const Vector2& position);
+    void update(const Rectangle& clip);
 
-    void drawSprites(SpriteBatch* spriteBatch, const Vector2& position);
-    void drawText(const Vector2& position);
+    void drawSprites(SpriteBatch* spriteBatch, const Rectangle& clip);
 
-private:
-    RadioButton(const RadioButton& copy);
+    void drawText(const Rectangle& clip);
 
-    // Clear the _selected flag of all RadioButtons in the given group.
+    // Clear the _selected flag of all radio buttons in the given group.
     static void clearSelected(const std::string& groupId);
 
     std::string _groupId;
     bool _selected;
     Vector2 _iconSize;
+
+private:
+    RadioButton(const RadioButton& copy);
 };
 
 }

+ 6 - 0
gameplay/src/Rectangle.cpp

@@ -44,6 +44,12 @@ void Rectangle::set(const Rectangle& r)
     set(r.x, r.y, r.width, r.height);
 }
 
+void Rectangle::set(float x, float y)
+{
+    this->x = x;
+    this->y = y;
+}
+
 void Rectangle::set(float x, float y, float width, float height)
 {
     this->x = x;

+ 14 - 42
gameplay/src/Slider.cpp

@@ -3,8 +3,6 @@
 namespace gameplay
 {
 
-static std::vector<Slider*> __sliders;
-
 Slider::Slider()
 {
 }
@@ -23,33 +21,9 @@ Slider* Slider::create(Theme::Style* style, Properties* properties)
     slider->_value = properties->getFloat("value");
     slider->_step = properties->getFloat("step");
 
-    __sliders.push_back(slider);
-
     return slider;
 }
 
-Slider* Slider::create(const char* id, float min, float max, float defaultPosition, 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::setMin(float min)
 {
     _min = min;
@@ -87,7 +61,7 @@ float Slider::getValue()
 
 void Slider::setValue(float value)
 {
-    _value = value;
+    _value = MATH_CLAMP(value, _min, _max);
 }
 
 bool Slider::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
@@ -97,20 +71,18 @@ bool Slider::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contac
         return false;
     }
 
-    bool consumeEvent = false;
-
     switch (evt)
     {
     case Touch::TOUCH_PRESS:
-        _state = Control::STATE_ACTIVE;
+        _state = Control::ACTIVE;
         _dirty = true;
         // Fall through to calculate new value.
 
     case Touch::TOUCH_MOVE:
     case Touch::TOUCH_RELEASE:
-        if (_state == STATE_ACTIVE &&
-            x > 0 && x <= _size.x &&
-            y > 0 && y <= _size.y)
+        if (_state == ACTIVE &&
+            x > 0 && x <= _bounds.width &&
+            y > 0 && y <= _bounds.height)
         {
             // Horizontal case.
             Theme::Border border;
@@ -128,7 +100,7 @@ bool Slider::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contac
             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);
+                                    (_bounds.width - border.left - border.right - padding.left - padding.right - minCapSize.x - maxCapSize.x);
             if (markerPosition > 1.0f)
             {
                 markerPosition = 1.0f;
@@ -158,10 +130,10 @@ bool Slider::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contac
         }
     }
 
-    return consumeEvent;
+    return _consumeTouchEvents;
 }
 
-void Slider::drawSprites(SpriteBatch* spriteBatch, const Vector2& position)
+void Slider::drawSprites(SpriteBatch* spriteBatch, const Rectangle& clip)
 {
     // TODO: Vertical slider.
 
@@ -193,21 +165,21 @@ void Slider::drawSprites(SpriteBatch* spriteBatch, const Vector2& position)
         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);
+        float midY = clip.y + _bounds.y + (_bounds.height - border.bottom - padding.bottom) / 2.0f;
+        Vector2 pos(clip.x + _bounds.x + border.left + padding.left, midY - trackSize.y / 2.0f);
+        spriteBatch->draw(pos.x, pos.y, _bounds.width, trackSize.y, track.u1, track.v1, track.u2, track.v2, color);
 
         pos.y = midY - minCapSize.y * 0.5f;
         pos.x -= minCapSize.x * 0.5f;
         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.right - padding.right - maxCapSize.x * 0.5f;
+        pos.x = clip.x + _bounds.x + _bounds.width - border.right - padding.right - maxCapSize.x * 0.5f;
         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 * 0.5f - maxCapSize.x * 0.5f - markerSize.x;
-        pos.x = position.x + _position.x + border.left + padding.left + minCapSize.x * 0.5f + markerPosition;
+        markerPosition *= _bounds.width - border.left - padding.left - border.right - padding.right - minCapSize.x * 0.5f - maxCapSize.x * 0.5f - markerSize.x;
+        pos.x = clip.x + _bounds.x + border.left + padding.left + minCapSize.x * 0.5f + 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);
     }

+ 74 - 9
gameplay/src/Slider.h

@@ -10,38 +10,103 @@
 namespace gameplay
 {
 
+/**
+ *  A slider consists of a marker that can slide along a track between two end-caps.
+ *  The following properties are available for sliders:
+ *
+ *  slider
+ *  {
+ *      style       = <Style ID>                // A Style from the Theme.
+ *      position    = <x, y>                    // Position of the Control on-screen, measured in pixels.
+ *      size        = <width, height>           // Size of the Control, measured in pixels.
+ *      // TODO: orientation = <HORIZONTAL or VERTICAL>  // Determines whether a slider is stretched along its width or its height
+ *      min         = <float>                   // The value of the left- / bottom-most point on the slider.
+ *      max         = <float>                   // The value of the right- / top-most point on the slider.
+ *      value       = <float>                   // The default position of the marker.
+ *      step        = <float>                   // If greater than 0, force the marker to snap to discrete multiples of 'step'.
+ *      text        = <string>                  // Text to display above, below or alongside the slider (depending on the style).
+ *  }
+ */
 class Slider : public Button
 {
-public:
-    static Slider* create(Theme::Style* style, Properties* properties);
-    static Slider* create(const char* id, float min, float max, float defaultPosition = 0.0f, float step = 1.0f);
-    static Slider* getSlider(const char* id);
+    friend class Container;
 
+public:
+    /**
+     * Set the minimum value that can be set on this slider.
+     *
+     * @param min The new minimum.
+     */
     void setMin(float min);
+
+    /**
+     * Get the minimum value that can be set on this slider.
+     *
+     * @return The minimum value that can be set on this slider.
+     */
     float getMin();
 
+    /**
+     * Set the maximum value that can be set on this slider.
+     *
+     * @param max The new maximum.
+     */
     void setMax(float max);
+
+    /**
+     * Get the maximum value that can be set on this slider.
+     *
+     * @return The maximum value that can be set on this slider.
+     */
     float getMax();
 
+    /**
+     * Set this slider's step size.  If this is greater than zero, the marker
+     * will snap to discrete multiples of the step size.
+     *
+     * @param step The new step size.
+     */
     void setStep(float step);
+
+    /**
+     * Get this slider's step size.
+     *
+     * @return This slider's step size.
+     */
     float getStep();
 
+    /**
+     * Set this slider's value.  The new value will be clamped to fit within
+     * the slider's minimum and maximum values.
+     *
+     * @param The new value.
+     */
     void setValue(float value);
-    float getValue();
-
-    bool touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
 
-    void drawSprites(SpriteBatch* spriteBatch, const Vector2& position);
+    /**
+     * Get this slider's current value.
+     *
+     * @return This slider's current value.
+     */
+    float getValue();
 
 protected:
     Slider();
     ~Slider();
 
-private:
+    static Slider* create(Theme::Style* style, Properties* properties);
+
+    bool touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
+
+    void drawSprites(SpriteBatch* spriteBatch, const Rectangle& clip);
+
     float _min;
     float _max;
     float _step;
     float _value;
+
+private:
+    Slider(const Slider& copy);
 };
 
 }

+ 64 - 2
gameplay/src/SpriteBatch.cpp

@@ -295,6 +295,68 @@ void SpriteBatch::draw(float x, float y, float width, float height, float u1, fl
     draw(x, y, 0, width, height, u1, v1, u2, v2, color);
 }
 
+void SpriteBatch::draw(float x, float y, float width, float height, float u1, float v1, float u2, float v2, const Vector4& color, const Rectangle& clip)
+{
+    // Need to clip the rectangle given by { x, y, width, height } into clip by potentially:
+    //  - Moving x to the right.
+    //  - Moving y down.
+    //  - Moving width to the left.
+    //  - Moving height up.
+    //  - A combination of the above.
+    //  - Not drawing at all.
+    //
+    // We need to scale the uvs accordingly as we do this.
+
+    // First check to see if we need to draw at all.
+    if (x + width < clip.x || x > clip.x + clip.width ||
+        y + height < clip.y || y > clip.y + clip.height)
+    {
+        return;
+    }
+
+    const float uvWidth = u2 - u1;
+    const float uvHeight = v2 - v1;
+
+    // Moving x to the right.
+    if (x < clip.x)
+    {
+        const float percent = (clip.x - x) / width;
+        x = clip.x;
+        u1 += uvWidth * percent;
+    }
+
+    // Moving y down.
+    if (y < clip.y)
+    {
+        const float percent = (clip.y - y) / height;
+        y = clip.y;
+        v1 += uvHeight * percent;
+    }
+
+    // Moving width to the left.
+    const float clipX2 = clip.x + clip.width;
+    float x2 = x + width;
+    if (x2 > clipX2)
+    {
+        const float percent = (x2 - clipX2) / width;
+        width = clipX2 - x;
+        u2 -= uvWidth * percent;
+    }
+
+    // Moving height up.
+    const float clipY2 = clip.y + clip.height;
+    float y2 = y + height;
+    if (y2 > clipY2)
+    {
+        const float percent = (y2 - clipY2) / height;
+        height = clipY2 - y;
+        v2 -= uvHeight * percent;
+    }
+
+    // Now we can perform a normal draw call.
+    draw(x, y, 0, width, height, u1, v1, u2, v2, color);
+}
+
 void SpriteBatch::draw(float x, float y, float z, float width, float height, float u1, float v1, float u2, float v2, const Vector4& color, bool positionIsCenter)
 {
     // Treat the given position as the center if the user specified it as such.
@@ -305,8 +367,8 @@ void SpriteBatch::draw(float x, float y, float z, float width, float height, flo
     }
 
     // Write sprite vertex data.
-    float x2 = x + width;
-    float y2 = y + height;
+    const float x2 = x + width;
+    const float y2 = y + height;
     static SpriteVertex v[4];
     ADD_SPRITE_VERTEX(v[0], x, y, z, u1, v1, color.x, color.y, color.z, color.w);
     ADD_SPRITE_VERTEX(v[1], x, y2, z, u1, v2, color.x, color.y, color.z, color.w);

+ 15 - 0
gameplay/src/SpriteBatch.h

@@ -180,6 +180,21 @@ public:
      */
     void draw(float x, float y, float width, float height, float u1, float v1, float u2, float v2, const Vector4& color);
 
+    /**
+     * Draws a single sprite, clipped within a rectangle.
+     * 
+     * @param x The x coordinate.
+     * @param y The y coordinate.
+     * @param width The sprite width.
+     * @param height The sprite height
+     * @param u1 Texture coordinate.
+     * @param v1 Texture coordinate.
+     * @param u2 Texture coordinate.
+     * @param v2 Texture coordinate.
+     * @param color The color to tint the sprite. Use white for no tint.
+     */
+    void draw(float x, float y, float width, float height, float u1, float v1, float u2, float v2, const Vector4& color, const Rectangle& clip);
+
     /**
      * Draws a single sprite.
      * 

+ 40 - 33
gameplay/src/TextBox.cpp

@@ -53,8 +53,8 @@ void TextBox::setCursorLocation(int x, int y)
     }
     Theme::Padding padding = _style->getPadding();
 
-    _cursorLocation.set(x - border.left - padding.left + _viewport.x,
-                       y - border.top - padding.top + _viewport.y);
+    _cursorLocation.set(x - border.left - padding.left + _clip.x,
+                       y - border.top - padding.top + _clip.y);
 }
 
 bool TextBox::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
@@ -67,26 +67,26 @@ bool TextBox::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int conta
     switch (evt)
     {
     case Touch::TOUCH_PRESS: 
-        if (_state == STATE_NORMAL)
+        if (_state == NORMAL)
         {
-            _state = STATE_ACTIVE;
+            _state = ACTIVE;
             Game::getInstance()->displayKeyboard(true);
             _dirty = true;
             return _consumeTouchEvents;
         }
-        else if (!(x > 0 && x <= _size.x &&
-                    y > 0 && y <= _size.y))
+        else if (!(x > 0 && x <= _bounds.width &&
+                    y > 0 && y <= _bounds.height))
         {
-            _state = STATE_NORMAL;
+            _state = NORMAL;
             Game::getInstance()->displayKeyboard(false);
             _dirty = true;
             return _consumeTouchEvents;
         }
         break;
     case Touch::TOUCH_MOVE:
-        if (_state == STATE_FOCUS &&
-            x > 0 && x <= _size.x &&
-            y > 0 && y <= _size.y)
+        if (_state == FOCUS &&
+            x > 0 && x <= _bounds.width &&
+            y > 0 && y <= _bounds.height)
         {
             setCursorLocation(x, y);
             _dirty = true;
@@ -94,11 +94,11 @@ bool TextBox::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int conta
         }
         break;
     case Touch::TOUCH_RELEASE:
-        if (x > 0 && x <= _size.x &&
-            y > 0 && y <= _size.y)
+        if (x > 0 && x <= _bounds.width &&
+            y > 0 && y <= _bounds.height)
         {
             setCursorLocation(x, y);
-            _state = STATE_FOCUS;
+            _state = FOCUS;
             _dirty = true;
             return _consumeTouchEvents;
         }
@@ -110,7 +110,7 @@ bool TextBox::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int conta
 
 void TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
 {
-    if (_state == STATE_FOCUS)
+    if (_state == FOCUS)
     {
         switch (evt)
         {
@@ -123,7 +123,7 @@ void TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
                         // TODO: Move cursor to beginning of line.
                         // This only works for left alignment...
                         
-                        //_cursorLocation.x = _viewport.x;
+                        //_cursorLocation.x = _clip.x;
                         //_dirty = true;
                         break;
                     }
@@ -137,11 +137,11 @@ void TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
                         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,
+                        unsigned int textIndex = font->getIndexAtLocation(_text.c_str(), _clip, fontSize, _cursorLocation, &_cursorLocation,
                             overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
 
                         _text.erase(textIndex, 1);
-                        font->getLocationAtIndex(_text.c_str(), _viewport, fontSize, &_cursorLocation, textIndex,
+                        font->getLocationAtIndex(_text.c_str(), _clip, fontSize, &_cursorLocation, textIndex,
                             overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
                         _dirty = true;
                         break;
@@ -151,10 +151,10 @@ void TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
                         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,
+                        unsigned int textIndex = font->getIndexAtLocation(_text.c_str(), _clip, fontSize, _cursorLocation, &_cursorLocation,
                             overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
 
-                        font->getLocationAtIndex(_text.c_str(), _viewport, fontSize, &_cursorLocation, textIndex - 1,
+                        font->getLocationAtIndex(_text.c_str(), _clip, fontSize, &_cursorLocation, textIndex - 1,
                             overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
                         _dirty = true;
                         break;
@@ -164,10 +164,10 @@ void TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
                         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,
+                        unsigned int textIndex = font->getIndexAtLocation(_text.c_str(), _clip, fontSize, _cursorLocation, &_cursorLocation,
                             overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
 
-                        font->getLocationAtIndex(_text.c_str(), _viewport, fontSize, &_cursorLocation, textIndex + 1,
+                        font->getLocationAtIndex(_text.c_str(), _clip, fontSize, &_cursorLocation, textIndex + 1,
                             overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
                         _dirty = true;
                         break;
@@ -179,7 +179,7 @@ void TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
                         unsigned int fontSize = overlay->getFontSize();
 
                         _cursorLocation.y -= fontSize;
-                        font->getIndexAtLocation(_text.c_str(), _viewport, fontSize, _cursorLocation, &_cursorLocation,
+                        font->getIndexAtLocation(_text.c_str(), _clip, fontSize, _cursorLocation, &_cursorLocation,
                             overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
                         _dirty = true;
                         break;
@@ -191,7 +191,7 @@ void TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
                         unsigned int fontSize = overlay->getFontSize();
 
                         _cursorLocation.y += fontSize;
-                        font->getIndexAtLocation(_text.c_str(), _viewport, fontSize, _cursorLocation, &_cursorLocation,
+                        font->getIndexAtLocation(_text.c_str(), _clip, fontSize, _cursorLocation, &_cursorLocation,
                             overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
                         _dirty = true;
                         break;
@@ -205,7 +205,7 @@ void TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
                 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,
+                unsigned int textIndex = font->getIndexAtLocation(_text.c_str(), _clip, fontSize, _cursorLocation, &_cursorLocation,
                     overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
 
                 switch (key)
@@ -216,7 +216,7 @@ void TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
                         {
                             --textIndex;
                             _text.erase(textIndex, 1);
-                            font->getLocationAtIndex(_text.c_str(), _viewport, fontSize, &_cursorLocation, textIndex,
+                            font->getLocationAtIndex(_text.c_str(), _clip, fontSize, &_cursorLocation, textIndex,
                                 overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
 
                             _dirty = true;
@@ -232,7 +232,7 @@ void TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
                         _text.insert(textIndex, 1, (char)key);
 
                         // Get new location of cursor.
-                        font->getLocationAtIndex(_text.c_str(), _viewport, fontSize, &_cursorLocation, textIndex + 1,
+                        font->getLocationAtIndex(_text.c_str(), _clip, fontSize, &_cursorLocation, textIndex + 1,
                             overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
                 
                         _dirty = true;
@@ -246,9 +246,10 @@ void TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
     }
 }
 
-void TextBox::update(const Vector2& position)
+void TextBox::update(const Rectangle& clip)
 {
-    Vector2 pos(position.x + _position.x, position.y + _position.y);
+    /*
+    Vector2 pos(clip.x + _position.x, clip.y + _position.y);
     Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
     Theme::Border border;
     Theme::ContainerRegion* containerRegion = overlay->getContainerRegion();
@@ -260,22 +261,28 @@ void TextBox::update(const Vector2& position)
 
     // Set up the text viewport.
     Font* font = overlay->getFont();
-    _viewport.set(pos.x + border.left + padding.left,
+    _clip.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());
 
+                  */
+
+    Control::update(clip);
+
     // Get index into string and cursor location from the last recorded touch location.
-    if (_state == STATE_FOCUS)
+    if (_state == FOCUS)
     {
-        font->getIndexAtLocation(_text.c_str(), _viewport, overlay->getFontSize(), _cursorLocation, &_cursorLocation,
+        Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
+        Font* font = overlay->getFont();
+        font->getIndexAtLocation(_text.c_str(), _clip, overlay->getFontSize(), _cursorLocation, &_cursorLocation,
             overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
     }
 }
 
-void TextBox::drawSprites(SpriteBatch* spriteBatch, const Vector2& position)
+void TextBox::drawSprites(SpriteBatch* spriteBatch, const Rectangle& clip)
 {
-    if (_state == STATE_FOCUS)
+    if (_state == FOCUS)
     {
         // Draw the cursor at its current location.
         Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());

+ 2 - 2
gameplay/src/TextBox.h

@@ -19,10 +19,10 @@ public:
 
     void keyEvent(Keyboard::KeyEvent evt, int key);
 
-    void update(const Vector2& position);
+    void update(const Rectangle& clip);
 
     // Draw the cursor.
-    void drawSprites(SpriteBatch* spriteBatch, const Vector2& position);
+    void drawSprites(SpriteBatch* spriteBatch, const Rectangle& clip);
 
 private:
     TextBox();

+ 138 - 0
gameplay/src/Texture.cpp

@@ -1,6 +1,7 @@
 #include "Base.h"
 #include "Image.h"
 #include "Texture.h"
+#include "FileSystem.h"
 
 namespace gameplay
 {
@@ -72,6 +73,11 @@ Texture* Texture::create(const char* path, bool generateMipmaps)
                     texture = create(image, generateMipmaps);
                 SAFE_RELEASE(image);
             }
+			else if (tolower(ext[1]) == 'p' && tolower(ext[2]) == 'v' && tolower(ext[3]) == 'r')
+			{
+            	// PowerVR Compressed RGBA
+				texture = createCompressedPVR(path);
+			}
             break;
         }
     }
@@ -137,6 +143,138 @@ Texture* Texture::create(Format format, unsigned int width, unsigned int height,
     return texture;
 }
 
+Texture* Texture::createCompressedPVR(const char* path)
+{
+	char PVRTexIdentifier[] = "PVR!";
+
+	enum
+	{
+	    PVRTextureFlagTypePVRTC_2 = 24,
+	    PVRTextureFlagTypePVRTC_4
+	};
+
+	struct pvr_file_header
+	{
+		unsigned int size;          		// size of the structure
+		unsigned int height;              	// height of surface to be created
+		unsigned int width;               	// width of input surface
+		unsigned int mipmapCount;         	// number of mip-map levels requested
+		unsigned int formatflags;           // pixel format flags
+		unsigned int dataSize;     			// total size in bytes
+		unsigned int bpp;            		// number of bits per pixel
+		unsigned int redBitMask;            // mask for red bit
+		unsigned int greenBitMask;          // mask for green bits
+		unsigned int blueBitMask;           // mask for blue bits
+		unsigned int alphaBitMask;        	// mask for alpha channel
+		unsigned int pvrTag;                // magic number identifying pvr file
+		unsigned int surfaceCount;          // number of surfaces present in the pvr
+	} ;
+
+	FILE* file = FileSystem::openFile(path, "rb");
+	if (file == NULL)
+	{
+		LOG_ERROR_VARG("Failed to load file: %s", path);
+		return NULL;
+	}
+
+	// Read the file header
+	unsigned int size = sizeof(pvr_file_header);
+	pvr_file_header header;
+	unsigned int read = (int)fread(&header, 1, size, file);
+	assert(read == size);
+	if (read != size)
+	{
+		LOG_ERROR_VARG("Read file header error for pvr file: %s (%d < %d)", path, (int)read, (int)size);
+		fclose(file);
+		return NULL;
+	}
+
+	// Proper file header identifier
+	if (PVRTexIdentifier[0] != (char)((header.pvrTag >>  0) & 0xff) ||
+		PVRTexIdentifier[1] != (char)((header.pvrTag >>  8) & 0xff) ||
+	    PVRTexIdentifier[2] != (char)((header.pvrTag >> 16) & 0xff) ||
+	    PVRTexIdentifier[3] != (char)((header.pvrTag >> 24) & 0xff))
+	 {
+		LOG_ERROR_VARG("Invalid PVR texture file: %s", path);
+		fclose(file);
+	    return NULL;
+	}
+
+	// Format flags for GLenum format
+	GLenum format;
+	unsigned int formatFlags = header.formatflags & 0xff;
+	if (formatFlags == PVRTextureFlagTypePVRTC_4)
+	{
+		format = header.alphaBitMask ? COMPRESSED_RGBA_PVRTC_4BPP : COMPRESSED_RGB_PVRTC_4BPP;
+	}
+	else if (formatFlags == PVRTextureFlagTypePVRTC_2)
+	{
+		format = header.alphaBitMask ? COMPRESSED_RGBA_PVRTC_2BPP : COMPRESSED_RGB_PVRTC_2BPP;
+	}
+	else
+	{
+		LOG_ERROR_VARG("Invalid PVR texture format flags for file: %s", path);
+		fclose(file);
+		return NULL;
+	}
+
+	unsigned char* data = new unsigned char[header.dataSize];
+	read = (int)fread(data, 1, header.dataSize, file);
+	assert(read == header.dataSize);
+	if (read != header.dataSize)
+	{
+		LOG_ERROR_VARG("Read file data error for pvr file: %s (%d < %d)", path, (int)read, (int)header.dataSize);
+		SAFE_DELETE_ARRAY(data);
+		fclose(file);
+		return NULL;
+	}
+	// Close file
+	fclose(file);
+
+	// Load our texture.
+	GLuint textureId;
+	GL_ASSERT( glGenTextures(1, &textureId) );
+	GL_ASSERT( glBindTexture(GL_TEXTURE_2D, textureId) );
+	GL_ASSERT( glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, header.mipmapCount > 0 ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR ) );
+
+    Texture* texture = new Texture();
+    texture->_handle = textureId;
+    texture->_width = header.width;
+    texture->_height = header.height;
+
+	// Load the data for each level
+	unsigned int width = header.width;
+	unsigned int height = header.height;
+	unsigned int blockSize = 0;
+	unsigned int widthBlocks = 0;
+	unsigned int heightBlocks = 0;
+	unsigned int bpp = 0;
+	unsigned int dataSize = 0;
+	unsigned char* dataOffset = data;
+
+	for (unsigned int level = 0; level <= header.mipmapCount; level++)
+	{
+		if (formatFlags == PVRTextureFlagTypePVRTC_4)
+		{
+			dataSize = ( max((int)width, 8) * max((int)height, 8) * 4 + 7) / 8;
+		}
+		else
+		{
+			dataSize = ( max((int)width, 16) * max((int)height, 8) * 2 + 7) / 8;
+		}
+
+		GL_ASSERT( glCompressedTexImage2D(GL_TEXTURE_2D, level, (GLenum)format, width, height, 0, dataSize, dataOffset) );
+
+		dataOffset += dataSize;
+		width = max((int)width >> 1, 1);
+		height = max((int)height >> 1, 1);
+	}
+
+	SAFE_DELETE_ARRAY(data);
+
+	return texture;
+}
+
 unsigned int Texture::getWidth() const
 {
     return _width;

+ 11 - 1
gameplay/src/Texture.h

@@ -10,6 +10,10 @@ class Image;
 
 /**
  * Represents a texture.
+ *
+ * TODO: Addd support for the following: 
+ * COMPRESSED_RGBA_ATITC = GL_ATC_RGBA_EXPLICIT_ALPHA_AMD,
+ * COMPRESSED_RGBA_DXT1 = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT
  */
 class Texture : public Ref
 {
@@ -25,7 +29,11 @@ public:
         RGB     = GL_RGB,
         RGBA    = GL_RGBA,
         ALPHA   = GL_ALPHA,
-        DEPTH   = GL_DEPTH_COMPONENT
+        DEPTH   = GL_DEPTH_COMPONENT,
+        COMPRESSED_RGB_PVRTC_4BPP = GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG,
+		COMPRESSED_RGBA_PVRTC_4BPP = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG,
+		COMPRESSED_RGB_PVRTC_2BPP = GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG,
+		COMPRESSED_RGBA_PVRTC_2BPP = GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG
     };
 
     /**
@@ -203,6 +211,8 @@ private:
      */
     virtual ~Texture();
 
+	static Texture* createCompressedPVR(const char* path);
+
     std::string _path;
     TextureHandle _handle;
     unsigned int _width;

+ 59 - 42
gameplay/src/Theme.h

@@ -15,8 +15,8 @@
 namespace gameplay
 {
 
-#define MAX_OVERLAYS 4
-#define MAX_OVERLAY_REGIONS 9
+static const unsigned int MAX_OVERLAYS = 4;
+static const unsigned int MAX_OVERLAY_REGIONS = 9;
 
 /**
  * This class represents the appearance of an UI form described in a 
@@ -26,10 +26,38 @@ namespace gameplay
  */
 class Theme: public Ref
 {
+    friend class Control;
+    friend class Form;
+    friend class Container;
+
 public:
     class Style;
     class Cursor;
 
+private:
+    /**
+     * 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;
+
+public:
     typedef struct UVs
     {
         float u1;
@@ -50,10 +78,9 @@ public:
 
     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);
+        friend class Theme;
 
+    public:
         const char* getId() const;
         const Vector2& getSize() const;
         const Vector4& getColor() const;
@@ -65,6 +92,9 @@ public:
         Icon(const Icon& copy);
         ~Icon();
 
+        static Icon* create(const char* id, const Texture& texture, const Vector2& size,
+                            const Vector2& offPosition, const Vector2& onPosition, const Vector4& color);
+
         std::string _id;
         Vector2 _size;
         Vector4 _color;
@@ -74,10 +104,9 @@ public:
 
     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);
+        friend class Theme;
 
+    public:
         const char* getId() const;
         const Theme::UVs& getMinCapUVs() const;
         const Theme::UVs& getMaxCapUVs() const;
@@ -95,6 +124,9 @@ public:
         SliderIcon(const SliderIcon& copy);
         ~SliderIcon();
 
+        static SliderIcon* create(const char* id, const Texture& texture, const Vector4& minCapRegion, const Vector4& maxCapRegion,
+                                  const Vector4& markerRegion, const Vector4& trackRegion, const Vector4& color);
+
         std::string _id;
         UVs _minCap;
         UVs _maxCap;
@@ -112,9 +144,9 @@ public:
      */
     class Cursor : public Ref
     {
-    public:
-        static Theme::Cursor* create(const char* id, const Texture& texture, const Rectangle& region, const Vector4& color);
+        friend class Theme;
 
+    public:
        /**
         * Returns the Id of this Cursor.
         */
@@ -134,6 +166,8 @@ public:
         Cursor(const Cursor& copy);
         ~Cursor();
 
+        static Theme::Cursor* create(const char* id, const Texture& texture, const Rectangle& region, const Vector4& color);
+
         std::string _id;
         UVs _uvs;
         Vector2 _size;
@@ -142,9 +176,9 @@ public:
 
     class ContainerRegion : public Ref
     {
-    public:
-        static ContainerRegion* create(const char* id, const Texture& texture, const Rectangle& region, const Theme::Border& border, const Vector4& color);
+        friend class Theme;
 
+    public:
         enum ContainerArea
         {
             TOP_LEFT, TOP, TOP_RIGHT,
@@ -170,40 +204,22 @@ public:
         ContainerRegion(const Texture& texture, const Rectangle& region, const Theme::Border& border, const Vector4& color);
         ContainerRegion(const ContainerRegion& copy);
         ~ContainerRegion();
+
+        static ContainerRegion* create(const char* id, const Texture& texture, const Rectangle& region, const Theme::Border& border, const Vector4& color);
     
         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.
+     * This class represents the apperance of a control.
      */
     class Style
     {
+        friend class Theme;
+
     public:
         class Overlay;
 
@@ -215,11 +231,6 @@ public:
             OVERLAY_DISABLED
         };
 
-        Style(const char* id, const Theme::Margin& margin, const Theme::Padding& padding,
-            Theme::Style::Overlay* normal, Theme::Style::Overlay* focus, Theme::Style::Overlay* active, Theme::Style::Overlay* disabled);
-
-        ~Style();
-
         /**
          * Returns the Id of this Style.
          */
@@ -259,9 +270,10 @@ public:
          */
         class Overlay : public Ref
         {
-        public:
-            static Overlay* create();
+            friend class Theme;
+            friend class Style;
 
+        public:
            /**
             * Returns the Overlay type.
             */
@@ -315,6 +327,8 @@ public:
             Overlay(const Overlay& copy);
             ~Overlay();
 
+            static Overlay* create();
+
             ContainerRegion* _container;
             Cursor* _textCursor;
             Cursor* _mouseCursor;
@@ -329,7 +343,10 @@ public:
         };
 
     private:
+        Style(const char* id, const Theme::Margin& margin, const Theme::Padding& padding,
+            Theme::Style::Overlay* normal, Theme::Style::Overlay* focus, Theme::Style::Overlay* active, Theme::Style::Overlay* disabled);
         Style(const Style& style);
+        ~Style();
         
         std::string _id;
         Margin _margin;

+ 26 - 0
gameplay/src/TimeListener.h

@@ -0,0 +1,26 @@
+#ifndef TIMELISTENER_H_
+#define TIMELISTENER_H_
+
+namespace gameplay
+{
+
+/**
+ * The TimeListener interface allows a class to be scheduled and called at a later time using Game::schedule().
+ */
+class TimeListener
+{
+public:
+
+    /**
+     * Callback method that is called when the scheduled event is fired.
+     * 
+     * @param timeDiff The time difference between the current game time and the target time.
+     *                 The time differences will always be non-negative because scheduled events will not fire early.
+     * @param cookie The cookie data that was passed when the event was scheduled.
+     */
+    virtual void timeEvent(long timeDiff, void* cookie) = 0;
+};
+
+}
+
+#endif

+ 9 - 6
gameplay/src/VerticalLayout.cpp

@@ -43,8 +43,7 @@ namespace gameplay
         }
         Theme::Padding padding = style->getPadding();
 
-        float yPosition = border.top + padding.top;
-        float xPosition = border.left + padding.left;
+        float yPosition = 0;
 
         std::vector<Control*> controls = container->getControls();
 
@@ -65,15 +64,19 @@ namespace gameplay
         while (i != end)
         {
             Control* control = controls.at(i);
-            const Vector2& size = control->getSize();
+
+            const Rectangle& bounds = control->getBounds();
             const Theme::Margin& margin = control->getStyle()->getMargin();
 
             yPosition += margin.top;
 
-            control->setPosition(xPosition, yPosition);
-            control->update(container->getPosition());
+            control->setPosition(0, yPosition);
+            if (control->isDirty())
+            {
+                control->update(container->getClip());
+            }
 
-            yPosition += size.y + margin.bottom;
+            yPosition += bounds.height + margin.bottom;
 
             i += iter;
         }

+ 33 - 6
gameplay/src/VerticalLayout.h

@@ -7,23 +7,50 @@
 namespace gameplay
 {
 
+/**
+ * Vertical layout: Controls are placed next to one another vertically until
+ * the bottom-most edge of the container is reached.
+ */
 class VerticalLayout : public Layout
 {
-public:
-    static VerticalLayout* create();
+    friend class Form;
+    friend class Container;
 
+public:
+    /**
+     * Set whether this layout will start laying out controls from the bottom of the container.
+     * This setting defaults to 'false', meaning controls will start at the top.
+     *
+     * @param bottomToTop Whether to start laying out controls from the bottom of the container.
+     */
     void setBottomToTop(bool bottomToTop);
 
+    /**
+     * Get whether this layout will start laying out controls from the bottom of the container.
+     *
+     * @return Whether to start laying out controls from the bottom of the container.
+     */
+    bool getBottomToTop();
+
+    /**
+     * Get the type of this Layout.
+     *
+     * @return Layout::LAYOUT_VERTICAL
+     */
     Layout::Type getType();
 
-    void update(const Container* container);
-
-private:
+protected:
     VerticalLayout();
-    VerticalLayout(const VerticalLayout& copy);
     virtual ~VerticalLayout();
 
+    static VerticalLayout* create();
+
+    void update(const Container* container);
+
     bool _bottomToTop;
+
+private:
+    VerticalLayout(const VerticalLayout& copy);
 };
 
 }