فهرست منبع

Merge pull request #40 from blackberry-gaming/next

Next
Sean Paul Taylor 14 سال پیش
والد
کامیت
41d81fdc6e
56فایلهای تغییر یافته به همراه2148 افزوده شده و 950 حذف شده
  1. 3 0
      gameplay/gameplay.vcxproj
  2. 9 0
      gameplay/gameplay.vcxproj.filters
  3. 2 2
      gameplay/res/shaders/colored-specular.fsh
  4. 1 1
      gameplay/res/shaders/colored.fsh
  5. 2 2
      gameplay/res/shaders/diffuse-specular.fsh
  6. 1 1
      gameplay/src/AbsoluteLayout.cpp
  7. 28 2
      gameplay/src/AbsoluteLayout.h
  8. 4 1
      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. 75 41
      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. 113 47
      gameplay/src/PhysicsCharacter.cpp
  29. 49 4
      gameplay/src/PhysicsCharacter.h
  30. 66 0
      gameplay/src/PhysicsCollisionObject.cpp
  31. 188 0
      gameplay/src/PhysicsCollisionObject.h
  32. 118 88
      gameplay/src/PhysicsController.cpp
  33. 23 14
      gameplay/src/PhysicsController.h
  34. 31 48
      gameplay/src/PhysicsRigidBody.cpp
  35. 21 96
      gameplay/src/PhysicsRigidBody.h
  36. 0 22
      gameplay/src/PhysicsRigidBody.inl
  37. 5 2
      gameplay/src/PlatformMacOS.mm
  38. 4 2
      gameplay/src/PlatformQNX.cpp
  39. 46 54
      gameplay/src/RadioButton.cpp
  40. 58 7
      gameplay/src/RadioButton.h
  41. 6 0
      gameplay/src/Rectangle.cpp
  42. 14 42
      gameplay/src/Slider.cpp
  43. 74 9
      gameplay/src/Slider.h
  44. 64 2
      gameplay/src/SpriteBatch.cpp
  45. 15 0
      gameplay/src/SpriteBatch.h
  46. 40 33
      gameplay/src/TextBox.cpp
  47. 2 2
      gameplay/src/TextBox.h
  48. 2 20
      gameplay/src/Texture.cpp
  49. 59 42
      gameplay/src/Theme.h
  50. 26 0
      gameplay/src/TimeListener.h
  51. 1 1
      gameplay/src/VertexAttributeBinding.cpp
  52. 39 0
      gameplay/src/VertexFormat.cpp
  53. 5 0
      gameplay/src/VertexFormat.h
  54. 9 6
      gameplay/src/VerticalLayout.cpp
  55. 33 6
      gameplay/src/VerticalLayout.h
  56. 1 1
      gameplay/src/gameplay.h

+ 3 - 0
gameplay/gameplay.vcxproj

@@ -62,6 +62,7 @@
     <ClCompile Include="src\Package.cpp" />
     <ClCompile Include="src\ParticleEmitter.cpp" />
     <ClCompile Include="src\PhysicsCharacter.cpp" />
+    <ClCompile Include="src\PhysicsCollisionObject.cpp" />
     <ClCompile Include="src\PhysicsConstraint.cpp" />
     <ClCompile Include="src\PhysicsController.cpp" />
     <ClCompile Include="src\PhysicsFixedConstraint.cpp" />
@@ -150,6 +151,7 @@
     <ClInclude Include="src\Package.h" />
     <ClInclude Include="src\ParticleEmitter.h" />
     <ClInclude Include="src\PhysicsCharacter.h" />
+    <ClInclude Include="src\PhysicsCollisionObject.h" />
     <ClInclude Include="src\PhysicsConstraint.h" />
     <ClInclude Include="src\PhysicsController.h" />
     <ClInclude Include="src\PhysicsFixedConstraint.h" />
@@ -177,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" />

+ 9 - 0
gameplay/gameplay.vcxproj.filters

@@ -267,6 +267,9 @@
     <ClCompile Include="src\PhysicsCharacter.cpp">
       <Filter>src</Filter>
     </ClCompile>
+    <ClCompile Include="src\PhysicsCollisionObject.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="src\Animation.h">
@@ -524,6 +527,12 @@
     <ClInclude Include="src\PhysicsCharacter.h">
       <Filter>src</Filter>
     </ClInclude>
+    <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">

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

@@ -29,7 +29,7 @@ void lighting(vec3 normalVector, vec3 cameraDirection, vec3 lightDirection, floa
     _ambientColor = _baseColor.rgb * u_ambientColor;
 
     // Diffuse
-	float ddot = abs(dot(normalVector, lightDirection));
+    float ddot = abs(dot(normalVector, lightDirection));
     float diffuseIntensity = attenuation * ddot;
     diffuseIntensity = max(0.0, diffuseIntensity);
     _diffuseColor = u_lightColor * _baseColor.rgb * diffuseIntensity;
@@ -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/colored.fsh

@@ -21,7 +21,7 @@ void lighting(vec3 normalVector, vec3 lightDirection, float attenuation)
     _ambientColor = _baseColor.rgb * u_ambientColor;
 
     // Diffuse
-	float ddot = dot(normalVector, lightDirection);
+    float ddot = dot(normalVector, lightDirection);
     float diffuseIntensity = attenuation * ddot;
     diffuseIntensity = max(0.0, diffuseIntensity);
     _diffuseColor = u_lightColor * _baseColor.rgb * diffuseIntensity;

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

@@ -25,7 +25,7 @@ void lighting(vec3 normalVector, vec3 cameraDirection, vec3 lightDirection, floa
     _ambientColor = _baseColor.rgb * u_ambientColor;
 
     // Diffuse
-	float ddot = dot(normalVector, lightDirection);
+    float ddot = dot(normalVector, lightDirection);
     float diffuseIntensity = attenuation * ddot;
     diffuseIntensity = max(0.0, diffuseIntensity);
     _diffuseColor = u_lightColor * _baseColor.rgb * diffuseIntensity;
@@ -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 - 1
gameplay/src/Base.h

@@ -203,8 +203,11 @@ 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>
-	#define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG		0x8C02
 #elif __APPLE__
     #include "TargetConditionals.h"
     #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR

+ 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);
 };
 
 }

+ 75 - 41
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)
+    {
+        // 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->drawBorder(spriteBatch, _clip);
+        }
+    }
+
+    void Container::drawSprites(SpriteBatch* spriteBatch, 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->drawSprites(spriteBatch, pos);
+            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,37 +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 false;
+            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;
         }
 
@@ -321,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.size() > 0)
+    {
+        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, std::vector<TimeEvent>, std::less<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);
         }

+ 113 - 47
gameplay/src/PhysicsCharacter.cpp

@@ -10,6 +10,16 @@
 #include "Game.h"
 #include "PhysicsController.h"
 
+// Amount to walk collision normal when attempting to repair a collision.
+// To small a value will result in inefficient collision repairs (several iterations
+// to fix a collision and slow resolution), whereas larger values will result
+// in less accurate collision resolution.
+//#define COLLISION_REPAIR_INCREMENT 0.2f
+#define COLLISION_REPAIR_MARGIN 1.0f
+
+// Maximum number of iterations used to perform perform collision repair each update.
+#define COLLISION_REPAIR_MAX_ITERATIONS 4
+
 namespace gameplay
 {
 
@@ -58,7 +68,7 @@ PhysicsCharacter::PhysicsCharacter(Node* node, float radius, float height, const
     _fallVelocity(0, 0, 0), _currentVelocity(0,0,0), _normalizedVelocity(0,0,0),
     _colliding(false), _collisionNormal(0,0,0), _currentPosition(0,0,0),
     _ghostObject(NULL), _collisionShape(NULL), _ignoreTransformChanged(0),
-    _stepHeight(0.2f), _slopeAngle(0.0f), _cosSlopeAngle(0.0f)
+    _stepHeight(0.2f), _slopeAngle(0.0f), _cosSlopeAngle(0.0f), _physicsEnabled(true)
 {
     setMaxSlopeAngle(45.0f);
 
@@ -76,38 +86,64 @@ PhysicsCharacter::PhysicsCharacter(Node* node, float radius, float height, const
     // Set initial transform
     _motionState->getWorldTransform(_ghostObject->getWorldTransform());
 
-    // Create a capsule collision shape
-    _collisionShape = bullet_new<btCapsuleShape>((btScalar)radius, (btScalar)(height - radius*2));
+    PhysicsController* pc = Game::getInstance()->getPhysicsController();
+
+    // Create a capsule collision shape (this is automatically deleted by PhysicsController when our collision object is removed)
+    _collisionShape = static_cast<btConvexShape*>(pc->createCapsule(radius, height - radius*2.0f));
 
     // Set the collision shape on the ghost object (get it from the node's rigid body)
     _ghostObject->setCollisionShape(_collisionShape);
     _ghostObject->setCollisionFlags(btCollisionObject::CF_CHARACTER_OBJECT);
 
-    btDynamicsWorld* world = Game::getInstance()->getPhysicsController()->_world;
-    
-    // Register the ghost object for collisions with the world.
-    // For now specify static flag only, so character does not interact with dynamic objects
-    world->addCollisionObject(_ghostObject, btBroadphaseProxy::CharacterFilter, btBroadphaseProxy::StaticFilter | btBroadphaseProxy::CharacterFilter | btBroadphaseProxy::DefaultFilter);
+    // Add the collision object to the physics system
+    pc->addCollisionObject(this);
 
     // Register ourselves as an action on the physics world so we are called back during physics ticks
-    world->addAction(this);
+    pc->_world->addAction(this);
 }
 
 PhysicsCharacter::~PhysicsCharacter()
 {
-    // Unregister ourself with world
-    btDynamicsWorld* world = Game::getInstance()->getPhysicsController()->_world;
-    world->removeCollisionObject(_ghostObject);
-    world->removeAction(this);
+    // Remove ourself from physics system
+    PhysicsController* pc = Game::getInstance()->getPhysicsController();
+
+    pc->removeCollisionObject(this);
+
+    // Unregister ourselves as action from world
+    pc->_world->removeAction(this);
 
     SAFE_DELETE(_ghostObject);
-    SAFE_DELETE(_collisionShape);
 
     _node->removeListener(this);
     SAFE_RELEASE(_node);
     SAFE_DELETE(_motionState);
 }
 
+PhysicsCollisionObject::Type PhysicsCharacter::getType() const
+{
+    return PhysicsCollisionObject::CHARACTER;
+}
+
+btCollisionObject* PhysicsCharacter::getCollisionObject() const
+{
+    return _ghostObject;
+}
+
+bool PhysicsCharacter::isPhysicsEnabled() const
+{
+    return _physicsEnabled;
+}
+
+void PhysicsCharacter::setPhysicsEnabled(bool enabled)
+{
+    _physicsEnabled = enabled;
+}
+
+btCollisionShape* PhysicsCharacter::getCollisionShape() const
+{
+    return _collisionShape;
+}
+
 Node* PhysicsCharacter::getNode() const
 {
     return _node;
@@ -147,6 +183,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;
@@ -340,32 +390,40 @@ void PhysicsCharacter::updateAction(btCollisionWorld* collisionWorld, btScalar d
     // the following steps (movement) start from a clean slate, where the character
     // is not colliding with anything. Also, this step handles collision between
     // dynamic objects (i.e. objects that moved and now intersect the character).
-    _colliding = false;
-    int stepCount = 0;
-	while (fixCollision(collisionWorld))
-	{
-        _colliding = true;
-
-        // After a small number of attempts to fix a collision/penetration, give up...
-        if (++stepCount > 4)
-		{
-            WARN_VARG("Character '%s' could not recover from collision.", _node->getId());
-			break;
-		}
-	}
+    if (_physicsEnabled)
+    {
+        _colliding = fixCollision(collisionWorld);
+        /*_colliding = false;
+        int stepCount = 0;
+	    while (fixCollision(collisionWorld))
+	    {
+            _colliding = true;
+
+            // After a small number of attempts to fix a collision/penetration, give up.
+            // This hanldes the case where we are deeply penetrating some object and attempting
+            // to step out of it (by COLLISION_REPAIR_INCREMENT units) does not fix the collision.
+            if (++stepCount > COLLISION_REPAIR_MAX_ITERATIONS)
+		    {
+                WARN_VARG("Character '%s' could not recover from collision.", _node->getId());
+			    break;
+		    }
+	    }*/
+    }
 
     // Update current and target world positions
     btTransform transform = _ghostObject->getWorldTransform();
     _currentPosition = transform.getOrigin();
 
     // Process movement in the up direction
-    stepUp(collisionWorld, deltaTimeStep);
+    if (_physicsEnabled)
+        stepUp(collisionWorld, deltaTimeStep);
 
     // Process horizontal movement
     stepForwardAndStrafe(collisionWorld, deltaTimeStep);
 
     // Process movement in the down direction
-    stepDown(collisionWorld, deltaTimeStep);
+    if (_physicsEnabled)
+        stepDown(collisionWorld, deltaTimeStep);
 
     // Set new position
     transform.setOrigin(_currentPosition);
@@ -426,12 +484,6 @@ void PhysicsCharacter::stepForwardAndStrafe(btCollisionWorld* collisionWorld, fl
 
     updateCurrentVelocity();
 
-    if (_currentVelocity.isZero())
-    {
-        // No velocity, so we aren't moving
-        return;
-    }
-
     // Calculate final velocity
     btVector3 velocity(_currentVelocity);
     if (animationCount > 0)
@@ -440,9 +492,22 @@ void PhysicsCharacter::stepForwardAndStrafe(btCollisionWorld* collisionWorld, fl
     }
     velocity *= time; // since velocity is in meters per second
 
+    if (velocity.isZero())
+    {
+        // No velocity, so we aren't moving
+        return;
+    }
+
     // Translate the target position by the velocity vector (already scaled by t)
     btVector3 targetPosition = _currentPosition + velocity;
 
+    // If physics is disabled, simply update current position without checking collisions
+    if (!_physicsEnabled)
+    {
+        _currentPosition = targetPosition;
+        return;
+    }
+
     // Check for collisions by performing a bullet convex sweep test
     btTransform start, end;
 	start.setIdentity();
@@ -579,7 +644,7 @@ btVector3 perpindicularComponent(const btVector3& direction, const btVector3& no
 void PhysicsCharacter::updateTargetPositionFromCollision(btVector3& targetPosition, const btVector3& collisionNormal)
 {
     //btScalar tangentMag = 0.0;
-    btScalar normalMag = 1.0;
+    //btScalar normalMag = 1.0;
 
 	btVector3 movementDirection = targetPosition - _currentPosition;
 	btScalar movementLength = movementDirection.length();
@@ -591,10 +656,8 @@ void PhysicsCharacter::updateTargetPositionFromCollision(btVector3& targetPositi
 		btVector3 reflectDir = computeReflectionDirection(movementDirection, collisionNormal);
 		reflectDir.normalize();
 
-		btVector3 parallelDir, perpindicularDir;
-
-		parallelDir = parallelComponent(reflectDir, collisionNormal);
-		perpindicularDir = perpindicularComponent(reflectDir, collisionNormal);
+		//btVector3 parallelDir = parallelComponent(reflectDir, collisionNormal);
+		btVector3 perpindicularDir = perpindicularComponent(reflectDir, collisionNormal);
 
 		targetPosition = _currentPosition;
 		/*if (tangentMag != 0.0)
@@ -603,11 +666,11 @@ void PhysicsCharacter::updateTargetPositionFromCollision(btVector3& targetPositi
 			targetPosition +=  parComponent;
 		}*/
 
-		if (normalMag != 0.0)
-		{
-			btVector3 perpComponent = perpindicularDir * btScalar (normalMag * movementLength);
+		//if (normalMag != 0.0)
+		//{
+			btVector3 perpComponent = perpindicularDir * btScalar (/*normalMag **/ movementLength);
 			targetPosition += perpComponent;
-		}
+		//}
 	}
 }
 
@@ -640,8 +703,8 @@ bool PhysicsCharacter::fixCollision(btCollisionWorld* world)
 		{
 			btPersistentManifold* manifold = _manifoldArray[j];
 
-            // Get the direction of the contact points (used to scale normal vector in the correct direction)
-			btScalar directionSign = manifold->getBody0() == _ghostObject ? btScalar(-1.0) : btScalar(1.0);
+            // Get the direction of the contact points (used to scale normal vector in the correct direction).
+            btScalar directionSign = manifold->getBody0() == _ghostObject ? -1.0f : 1.0f;
 
 			for (int p = 0, contactCount = manifold->getNumContacts(); p < contactCount; ++p)
 			{
@@ -652,6 +715,7 @@ bool PhysicsCharacter::fixCollision(btCollisionWorld* world)
 
 				if (dist < 0.0)
 				{
+                    // A negative distance means the objects are overlapping
 					if (dist < maxPenetration)
 					{
                         // Store collision normal for this point
@@ -660,7 +724,9 @@ bool PhysicsCharacter::fixCollision(btCollisionWorld* world)
 					}
 
                     // Calculate new position for object, which is translated back along the collision normal
-					currentPosition += pt.m_normalWorldOnB * directionSign * dist * btScalar(0.2);
+                    btVector3 n(pt.m_normalWorldOnB);
+                    n.normalize();
+					currentPosition += /*pt.m_normalWorldOnB*/n * directionSign * dist * COLLISION_REPAIR_MARGIN;// + _collisionShape->getMargin());// * COLLISION_REPAIR_INCREMENT;
 					collision = true;
 				}
 			}

+ 49 - 4
gameplay/src/PhysicsCharacter.h

@@ -23,10 +23,8 @@ namespace gameplay
  * This class can also be used to control animations for a character. Animation
  * clips can be setup for typical character animations, such as walk, run, jump,
  * etc; and the controller will handle blending between these animations as needed.
- *
- * @todo Add support for collision listeners.
  */
-class PhysicsCharacter : public Transform::Listener, public btActionInterface
+class PhysicsCharacter : public PhysicsCollisionObject, public Transform::Listener, public btActionInterface
 {
     friend class PhysicsController;
 
@@ -53,13 +51,43 @@ public:
          ANIMATION_REPEAT
     };
 
+    /**
+     * @see PhysicsCollisionObject#getType
+     */
+    PhysicsCollisionObject::Type getType() const;
+
     /**
      * Returns the character node for this PhysicsCharacter.
      *
      * @return The character Node.
+     *
+     * @see PhysicsCollisionObject::getNode.
      */
     Node* getNode() const;
 
+    /**
+     * Returns whether physics simulation is enabled for the physics character.
+     *
+     * @return true if physics simulation is enabled, false otherwise.
+     *
+     * @see setPhysicsEnabled(bool)
+     */
+    bool isPhysicsEnabled() const;
+
+    /**
+     * Enables or disables phyiscs simulation for the character.
+     *
+     * When physics simulation is enabled (default), the physics character automatically
+     * responds to collisions in the physics world. For example, the character will
+     * automatically slide along walls, step up stairs, react to gravity, etc.
+     *
+     * When disabled, the character will not have any physics applied on it and will
+     * therefore be allowed to walk through walls, ceiling, floors, other objects, etc.
+     *
+     * @param enabled true to enable physics simulation, false otherwise.
+     */
+    void setPhysicsEnabled(bool enabled);
+
     /**
      * Returns the maximum step height for the character.
      *
@@ -111,7 +139,7 @@ public:
      *
      * @return The specified animation clip.
      */
-    AnimationClip* getAnimation(const char* name) const;
+    AnimationClip* getAnimation(const char* name);
 
     /**
      * Plays the specified animation.
@@ -247,6 +275,18 @@ public:
      */
 	void debugDraw(btIDebugDraw* debugDrawer);
 
+protected:
+
+    /**
+     * @see PhysicsCollisionObject::getCollisionObject
+     */
+    btCollisionObject* getCollisionObject() const;
+
+    /**
+     * @see PhysicsCollisionObject::getCollisionShape
+     */
+    btCollisionShape* getCollisionShape() const;
+
 private:
 
     struct CharacterAnimation
@@ -264,6 +304,8 @@ private:
     /**
      * Creates a new PhysicsCharacter.
      *
+     * Use PhysicsController::createCharacter to create physics characters.
+     *
      * @param node Scene node that represents the character.
      * @param radius Radius of capsule volume used for character collisions.
      * @param height Height of the capsule volume used for character collisions.
@@ -273,6 +315,8 @@ private:
 
     /**
      * Destructor.
+     *
+     * Use PhysicsController::destroyCharacter to destroy physics characters.
      */
     virtual ~PhysicsCharacter();
 
@@ -310,6 +354,7 @@ private:
     float _stepHeight;
     float _slopeAngle;
     float _cosSlopeAngle;
+    bool _physicsEnabled;
 };
 
 }

+ 66 - 0
gameplay/src/PhysicsCollisionObject.cpp

@@ -0,0 +1,66 @@
+#include "Base.h"
+#include "PhysicsCollisionObject.h"
+#include "PhysicsController.h"
+#include "Game.h"
+
+namespace gameplay
+{
+
+PhysicsCollisionObject::PhysicsCollisionObject()
+{
+}
+
+PhysicsCollisionObject::~PhysicsCollisionObject()
+{
+}
+
+void PhysicsCollisionObject::addCollisionListener(CollisionListener* listener, PhysicsCollisionObject* object)
+{
+    Game::getInstance()->getPhysicsController()->addCollisionListener(listener, this, object);
+}
+
+void PhysicsCollisionObject::removeCollisionListener(CollisionListener* listener, PhysicsCollisionObject* object)
+{
+    Game::getInstance()->getPhysicsController()->removeCollisionListener(listener, this, object);
+}
+
+bool PhysicsCollisionObject::collidesWith(PhysicsCollisionObject* object) const
+{
+    static CollidesWithCallback callback;
+
+    callback.result = false;
+    Game::getInstance()->getPhysicsController()->_world->contactPairTest(getCollisionObject(), object->getCollisionObject(), callback);
+    return callback.result;
+}
+
+PhysicsCollisionObject::CollisionPair::CollisionPair(PhysicsCollisionObject* objectA, PhysicsCollisionObject* objectB)
+    : objectA(objectA), objectB(objectB)
+{
+    // unused
+}
+
+bool PhysicsCollisionObject::CollisionPair::operator < (const CollisionPair& collisionPair) const
+{
+    // If the pairs are equal, then return false.
+    if ((objectA == collisionPair.objectA && objectB == collisionPair.objectB) || (objectA == collisionPair.objectB && objectB == collisionPair.objectA))
+        return false;
+
+    // We choose to compare based on objectA arbitrarily.
+    if (objectA < collisionPair.objectA)
+        return true;
+
+    if (objectA == collisionPair.objectA)
+        return objectB < collisionPair.objectB;
+
+    return false;
+}
+
+btScalar PhysicsCollisionObject::CollidesWithCallback::addSingleResult(btManifoldPoint& cp,
+    const btCollisionObject* a, int partIdA, int indexA, 
+    const btCollisionObject* b, int partIdB, int indexB)
+{
+    result = true;
+    return 0.0f;
+}
+
+}

+ 188 - 0
gameplay/src/PhysicsCollisionObject.h

@@ -0,0 +1,188 @@
+#ifndef PHYSICSCOLLISIONOBJECT_H_
+#define PHYSICSCOLLISIONOBJECT_H_
+
+#include "Vector3.h"
+
+namespace gameplay
+{
+
+class Node;
+
+/**
+ * Base class for all gameplay physics objects that support collision events.
+ */
+class PhysicsCollisionObject
+{
+    friend class PhysicsController;
+
+public:
+
+    /**
+     * Enumeration of all possible collision object types.
+     */
+    enum Type
+    {
+        /**
+         * PhysicsRigidBody type.
+         */
+        RIGID_BODY,
+
+        /**
+         * PhysicsCharacter type.
+         */
+        CHARACTER
+    };
+
+    /** 
+     * Defines a pair of rigid bodies that collided (or may collide).
+     */
+    class CollisionPair
+    {
+    public:
+
+        /**
+         * Constructor.
+         */
+        CollisionPair(PhysicsCollisionObject* objectA, PhysicsCollisionObject* objectB);
+
+        /**
+         * Less than operator (needed for use as a key in map).
+         * 
+         * @param collisionPair The collision pair to compare.
+         * @return True if this pair is "less than" the given pair; false otherwise.
+         */
+        bool operator < (const CollisionPair& collisionPair) const;
+
+        /**
+         * The first object in the collision.
+         */
+        PhysicsCollisionObject* objectA;
+
+        /**
+         * The second object in the collision.
+         */
+        PhysicsCollisionObject* objectB;
+    };
+
+    /**
+     * Collision listener interface.
+     */
+    class CollisionListener
+    {
+        friend class PhysicsCollisionObject;
+        friend class PhysicsController;
+
+    public:
+
+        /**
+         * The type of collision event.
+         */
+        enum EventType
+        {
+            /**
+             * Event fired when the two rigid bodies start colliding.
+             */
+            COLLIDING,
+
+            /**
+             * Event fired when the two rigid bodies no longer collide.
+             */
+            NOT_COLLIDING
+        };
+
+        /**
+         * Virtual destructor.
+         */
+        virtual ~CollisionListener() { }
+
+        /**
+         * Called when a collision occurs between two objects in the physics world.
+         * 
+         * @param type The type of collision event.
+         * @param collisionPair The two collision objects involved in the collision.
+         * @param contactPointA The contact point with the first object (in world space).
+         * @param contactPointB The contact point with the second object (in world space).
+         */
+        virtual void collisionEvent(PhysicsCollisionObject::CollisionListener::EventType type,
+                                    const PhysicsCollisionObject::CollisionPair& collisionPair,
+                                    const Vector3& contactPointA = Vector3::zero(),
+                                    const Vector3& contactPointB = Vector3::zero()) = 0;
+    };
+
+    /**
+     * Returns the type of the collision object.
+     */
+    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.
+     * 
+     * @param listener The listener to add.
+     * @param object Optional collision object used to filter the collision event.
+     */
+    void addCollisionListener(CollisionListener* listener, PhysicsCollisionObject* object = NULL);
+
+    /**
+     * Removes a collision listener.
+     *
+     * @param listener The listener to remove.
+     */
+    void removeCollisionListener(CollisionListener* listener, PhysicsCollisionObject* object = NULL);
+
+    /**
+     * Checks if this collision object collides with the given object.
+     * 
+     * @param object The collision object to test for collision with.
+     * 
+     * @return True if this object collides with the specified one; false otherwise.
+     */
+    bool collidesWith(PhysicsCollisionObject* object) const;
+
+protected:
+
+    /**
+     * Constructor.
+     */
+    PhysicsCollisionObject();
+
+    /**
+     * Virtual destructor.
+     */
+    virtual ~PhysicsCollisionObject();
+
+    /**
+     * Returns the Bullet Physics collision object.
+     *
+     * @return The Bullet collision object.
+     */
+    virtual btCollisionObject* getCollisionObject() const = 0;
+
+    /**
+     * Returns the Bullet Physics collision shape.
+     *
+     * @return The Bullet collision shape.
+     */
+    virtual btCollisionShape* getCollisionShape() const = 0;
+
+private:
+
+    // Internal class used to implement the collidesWith(PhysicsRigidBody*) function.
+    struct CollidesWithCallback : public btCollisionWorld::ContactResultCallback
+    {
+        btScalar addSingleResult(btManifoldPoint& cp, 
+                                 const btCollisionObject* a, int partIdA, int indexA, 
+                                 const btCollisionObject* b, int partIdB, int indexB);
+
+        bool result;
+    };
+
+};
+
+}
+
+#endif

+ 118 - 88
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
 
@@ -140,7 +142,7 @@ void PhysicsController::drawDebug(const Matrix& viewProjection)
     _debugDrawer->end();
 }
 
-PhysicsRigidBody* PhysicsController::rayTest(const Ray& ray, float distance, Vector3* hitPoint, float* hitFraction)
+PhysicsCollisionObject* PhysicsController::rayTest(const Ray& ray, float distance, Vector3* hitPoint, float* hitFraction)
 {
     btCollisionWorld::ClosestRayResultCallback callback(BV(ray.getOrigin()), BV(distance * ray.getDirection()));
     _world->rayTest(BV(ray.getOrigin()), BV(distance * ray.getDirection()), callback);
@@ -152,7 +154,7 @@ PhysicsRigidBody* PhysicsController::rayTest(const Ray& ray, float distance, Vec
         if (hitFraction)
             *hitFraction = callback.m_closestHitFraction;
 
-        return getRigidBody(callback.m_collisionObject);
+        return getCollisionObject(callback.m_collisionObject);
     }
 
     return NULL;
@@ -162,65 +164,57 @@ btScalar PhysicsController::addSingleResult(btManifoldPoint& cp, const btCollisi
     const btCollisionObject* b, int partIdB, int indexB)
 {
     // Get pointers to the PhysicsRigidBody objects.
-    PhysicsRigidBody* rbA = Game::getInstance()->getPhysicsController()->getRigidBody(a);
-    PhysicsRigidBody* rbB = Game::getInstance()->getPhysicsController()->getRigidBody(b);
-    
+    PhysicsCollisionObject* rbA = Game::getInstance()->getPhysicsController()->getCollisionObject(a);
+    PhysicsCollisionObject* rbB = Game::getInstance()->getPhysicsController()->getCollisionObject(b);
+
     // If the given rigid body pair has collided in the past, then
     // we notify the listeners only if the pair was not colliding
     // during the previous frame. Otherwise, it's a new pair, so add a
     // new entry to the cache with the appropriate listeners and notify them.
     PhysicsRigidBody::CollisionPair pair(rbA, rbB);
+
+    CollisionInfo* collisionInfo;
     if (_collisionStatus.count(pair) > 0)
     {
-        const CollisionInfo& collisionInfo = _collisionStatus[pair];
-        if ((collisionInfo._status & COLLISION) == 0)
-        {
-            std::vector<PhysicsRigidBody::Listener*>::const_iterator iter = collisionInfo._listeners.begin();
-            for (; iter != collisionInfo._listeners.end(); iter++)
-            {
-                if ((collisionInfo._status & REMOVE) == 0)
-                {
-                    (*iter)->collisionEvent(PhysicsRigidBody::Listener::COLLIDING, pair, Vector3(cp.getPositionWorldOnA().x(), cp.getPositionWorldOnA().y(), cp.getPositionWorldOnA().z()),
-                        Vector3(cp.getPositionWorldOnB().x(), cp.getPositionWorldOnB().y(), cp.getPositionWorldOnB().z()));
-                }
-            }
-        }
+        collisionInfo = &_collisionStatus[pair];
     }
     else
     {
-        CollisionInfo& collisionInfo = _collisionStatus[pair];
-
-        // Initialized the status for the new entry.
-        collisionInfo._status = 0;
+        // Add a new collision pair for these objects
+        collisionInfo = &_collisionStatus[pair];
 
         // Add the appropriate listeners.
-        PhysicsRigidBody::CollisionPair p1(pair.rigidBodyA, NULL);
+        PhysicsRigidBody::CollisionPair p1(pair.objectA, NULL);
         if (_collisionStatus.count(p1) > 0)
         {
             const CollisionInfo& ci = _collisionStatus[p1];
-            std::vector<PhysicsRigidBody::Listener*>::const_iterator iter = ci._listeners.begin();
+            std::vector<PhysicsCollisionObject::CollisionListener*>::const_iterator iter = ci._listeners.begin();
             for (; iter != ci._listeners.end(); iter++)
             {
-                collisionInfo._listeners.push_back(*iter);
+                collisionInfo->_listeners.push_back(*iter);
             }
         }
-        PhysicsRigidBody::CollisionPair p2(pair.rigidBodyB, NULL);
+        PhysicsRigidBody::CollisionPair p2(pair.objectB, NULL);
         if (_collisionStatus.count(p2) > 0)
         {
             const CollisionInfo& ci = _collisionStatus[p2];
-            std::vector<PhysicsRigidBody::Listener*>::const_iterator iter = ci._listeners.begin();
+            std::vector<PhysicsCollisionObject::CollisionListener*>::const_iterator iter = ci._listeners.begin();
             for (; iter != ci._listeners.end(); iter++)
             {
-                collisionInfo._listeners.push_back(*iter);
+                collisionInfo->_listeners.push_back(*iter);
             }
         }
+    }
 
-        std::vector<PhysicsRigidBody::Listener*>::iterator iter = collisionInfo._listeners.begin();
-        for (; iter != collisionInfo._listeners.end(); iter++)
+    // Fire collision event
+    if ((collisionInfo->_status & COLLISION) == 0)
+    {
+        std::vector<PhysicsCollisionObject::CollisionListener*>::const_iterator iter = collisionInfo->_listeners.begin();
+        for (; iter != collisionInfo->_listeners.end(); iter++)
         {
-            if ((collisionInfo._status & REMOVE) == 0)
+            if ((collisionInfo->_status & REMOVE) == 0)
             {
-                (*iter)->collisionEvent(PhysicsRigidBody::Listener::COLLIDING, pair, Vector3(cp.getPositionWorldOnA().x(), cp.getPositionWorldOnA().y(), cp.getPositionWorldOnA().z()),
+                (*iter)->collisionEvent(PhysicsCollisionObject::CollisionListener::COLLIDING, pair, Vector3(cp.getPositionWorldOnA().x(), cp.getPositionWorldOnA().y(), cp.getPositionWorldOnA().z()),
                     Vector3(cp.getPositionWorldOnB().x(), cp.getPositionWorldOnB().y(), cp.getPositionWorldOnB().z()));
             }
         }
@@ -229,8 +223,8 @@ btScalar PhysicsController::addSingleResult(btManifoldPoint& cp, const btCollisi
     // Update the collision status cache (we remove the dirty bit
     // set in the controller's update so that this particular collision pair's
     // status is not reset to 'no collision' when the controller's update completes).
-    _collisionStatus[pair]._status &= ~DIRTY;
-    _collisionStatus[pair]._status |= COLLISION;
+    collisionInfo->_status &= ~DIRTY;
+    collisionInfo->_status |= COLLISION;
     return 0.0f;
 }
 
@@ -324,7 +318,6 @@ void PhysicsController::update(long elapsedTime)
                 (*_listeners)[k]->statusEvent(_status);
             }
         }
-        
     }
 
     // All statuses are set with the DIRTY bit before collision processing occurs.
@@ -360,10 +353,10 @@ void PhysicsController::update(long elapsedTime)
         // of collision pairs in the status cache that we did not explicitly register for.)
         if ((iter->second._status & REGISTERED) != 0 && (iter->second._status & REMOVE) == 0)
         {
-            if (iter->first.rigidBodyB)
-                _world->contactPairTest(iter->first.rigidBodyA->_body, iter->first.rigidBodyB->_body, *this);
+            if (iter->first.objectB)
+                _world->contactPairTest(iter->first.objectA->getCollisionObject(), iter->first.objectB->getCollisionObject(), *this);
             else
-                _world->contactTest(iter->first.rigidBodyA->_body, *this);
+                _world->contactTest(iter->first.objectA->getCollisionObject(), *this);
         }
     }
 
@@ -373,12 +366,12 @@ void PhysicsController::update(long elapsedTime)
     {
         if ((iter->second._status & DIRTY) != 0)
         {
-            if ((iter->second._status & COLLISION) != 0 && iter->first.rigidBodyB)
+            if ((iter->second._status & COLLISION) != 0 && iter->first.objectB)
             {
                 unsigned int size = iter->second._listeners.size();
                 for (unsigned int i = 0; i < size; i++)
                 {
-                    iter->second._listeners[i]->collisionEvent(PhysicsRigidBody::Listener::NOT_COLLIDING, iter->first);
+                    iter->second._listeners[i]->collisionEvent(PhysicsCollisionObject::CollisionListener::NOT_COLLIDING, iter->first);
                 }
             }
 
@@ -387,85 +380,103 @@ void PhysicsController::update(long elapsedTime)
     }
 }
 
-void PhysicsController::addCollisionListener(PhysicsRigidBody::Listener* listener, PhysicsRigidBody* rbA, PhysicsRigidBody* rbB)
+void PhysicsController::addCollisionListener(PhysicsCollisionObject::CollisionListener* listener, PhysicsCollisionObject* objectA, PhysicsCollisionObject* objectB)
 {
-    PhysicsRigidBody::CollisionPair pair(rbA, rbB);
-
-    // Make sure the status of the entry is initialized properly.
-    if (_collisionStatus.count(pair) == 0)
-        _collisionStatus[pair]._status = 0;
+    PhysicsCollisionObject::CollisionPair pair(objectA, objectB);
 
     // Add the listener and ensure the status includes that this collision pair is registered.
-    _collisionStatus[pair]._listeners.push_back(listener);
-    if ((_collisionStatus[pair]._status & PhysicsController::REGISTERED) == 0)
-        _collisionStatus[pair]._status |= PhysicsController::REGISTERED;
+    CollisionInfo& info = _collisionStatus[pair];
+    info._listeners.push_back(listener);
+    info._status |= PhysicsController::REGISTERED;
 }
 
-void PhysicsController::addRigidBody(PhysicsRigidBody* body)
+void PhysicsController::removeCollisionListener(PhysicsCollisionObject::CollisionListener* listener, PhysicsCollisionObject* objectA, PhysicsCollisionObject* objectB)
 {
-    _world->addRigidBody(body->_body);
-    _bodies.push_back(body);
+    // Mark the collision pair for these objects for removal
+    PhysicsCollisionObject::CollisionPair pair(objectA, objectB);
+    if (_collisionStatus.count(pair) > 0)
+    {
+        _collisionStatus[pair]._status |= REMOVE;
+    }
 }
-    
-void PhysicsController::removeRigidBody(PhysicsRigidBody* rigidBody)
+
+void PhysicsController::addCollisionObject(PhysicsCollisionObject* object)
 {
-    // Find the rigid body and remove it from the world.
-    for (int i = _world->getNumCollisionObjects() - 1; i >= 0 ; i--)
+    // Assign user pointer for the bullet collision object to allow efficient
+    // lookups of bullet objects -> gameplay objects.
+    object->getCollisionObject()->setUserPointer(object);
+
+    // Add the object to the physics world
+    switch (object->getType())
     {
-        btCollisionObject* obj = _world->getCollisionObjectArray()[i];
-        if (rigidBody->_body == obj)
-        {
-            _world->removeCollisionObject(obj);
-            break;
-        }
+    case PhysicsCollisionObject::RIGID_BODY:
+        _world->addRigidBody(static_cast<btRigidBody*>(object->getCollisionObject()));
+        break;
+
+    case PhysicsCollisionObject::CHARACTER:
+        _world->addCollisionObject(object->getCollisionObject(), btBroadphaseProxy::CharacterFilter, btBroadphaseProxy::StaticFilter | btBroadphaseProxy::CharacterFilter | btBroadphaseProxy::DefaultFilter);
+        break;
+
+    default:
+        assert(0); // unexpected (new type?)
+        break;
     }
+}
 
-    // Find the rigid body's collision shape and release the rigid body's reference to it.
-    for (unsigned int i = 0; i < _shapes.size(); i++)
+void PhysicsController::removeCollisionObject(PhysicsCollisionObject* object)
+{
+    // Remove the collision object from the world
+    if (object->getCollisionObject())
     {
-        if (_shapes[i]->_shape == rigidBody->_shape)
+        switch (object->getType())
         {
-            if (_shapes[i]->getRefCount() == 1)
-            {
-                _shapes[i]->release();
-                _shapes.erase(_shapes.begin() + i);
-            }
-            else
-                _shapes[i]->release();
+        case PhysicsCollisionObject::RIGID_BODY:
+            _world->removeRigidBody(static_cast<btRigidBody*>(object->getCollisionObject()));
+            break;
+
+        case PhysicsCollisionObject::CHARACTER:
+            _world->removeCollisionObject(object->getCollisionObject());
+            break;
 
+        default:
+            assert(0); // unexpected (new type?)
             break;
         }
     }
 
-    // Remove the rigid body from the controller's list.
-    for (unsigned int i = 0; i < _bodies.size(); i++)
+    // Release collision shape
+    if (object->getCollisionShape())
     {
-        if (_bodies[i] == rigidBody)
+        PhysicsCollisionShape* shape = reinterpret_cast<PhysicsCollisionShape*>(object->getCollisionShape()->getUserPointer());
+        if (shape)
         {
-            _bodies.erase(_bodies.begin() + i);
-            break;
+            if (shape->getRefCount() == 1)
+            {
+                std::vector<PhysicsCollisionShape*>::iterator shapeItr = std::find(_shapes.begin(), _shapes.end(), shape);
+                shape->release();
+                if (shapeItr != _shapes.end())
+                    _shapes.erase(shapeItr);
+            }
+            else
+            {
+                shape->release();
+            }
         }
     }
 
-    // Find all references to the rigid body in the collision status cache and mark them for removal.
+    // Find all references to the object in the collision status cache and mark them for removal.
     std::map<PhysicsRigidBody::CollisionPair, CollisionInfo>::iterator iter = _collisionStatus.begin();
     for (; iter != _collisionStatus.end(); iter++)
     {
-        if (iter->first.rigidBodyA == rigidBody || iter->first.rigidBodyB == rigidBody)
+        if (iter->first.objectA == object || iter->first.objectB == object)
             iter->second._status |= REMOVE;
     }
 }
 
-PhysicsRigidBody* PhysicsController::getRigidBody(const btCollisionObject* collisionObject)
+PhysicsCollisionObject* PhysicsController::getCollisionObject(const btCollisionObject* collisionObject) const
 {
-    // Find the rigid body and remove it from the world.
-    for (unsigned int i = 0; i < _bodies.size(); i++)
-    {
-        if (_bodies[i]->_body == collisionObject)
-            return _bodies[i];
-    }
-    
-    return NULL;
+    // Gameplay rigid bodies are stored in the userPointer data of bullet collision objects
+    return reinterpret_cast<PhysicsCollisionObject*>(collisionObject->getUserPointer());
 }
 
 btCollisionShape* PhysicsController::createBox(const Vector3& min, const Vector3& max, const Vector3& scale)
@@ -679,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);
@@ -837,4 +855,16 @@ int	PhysicsController::DebugDrawer::getDebugMode() const
     return _mode;
 }
 
+PhysicsController::PhysicsCollisionShape::PhysicsCollisionShape(btCollisionShape* shape)
+    : _shape(shape)
+{
+    // Assign user pointer to allow efficient lookup of PhysicsCollisionShape from bullet object
+    shape->setUserPointer(this);
+}
+
+PhysicsController::PhysicsCollisionShape::~PhysicsCollisionShape()
+{
+    SAFE_DELETE(_shape);
+}
+
 }

+ 23 - 14
gameplay/src/PhysicsController.h

@@ -22,6 +22,7 @@ class PhysicsController : public btCollisionWorld::ContactResultCallback
     friend class PhysicsConstraint;
     friend class PhysicsRigidBody;
     friend class PhysicsCharacter;
+    friend class PhysicsCollisionObject;
 
 public:
 
@@ -237,7 +238,7 @@ public:
      *      (as a fraction between 0-1) where the intersection occurred.
      * @return The first rigid body that the ray intersects, or NULL if no intersection was found.
      */
-    PhysicsRigidBody* rayTest(const Ray& ray, float distance, Vector3* hitPoint = NULL, float* hitFraction = NULL);
+    PhysicsCollisionObject* rayTest(const Ray& ray, float distance, Vector3* hitPoint = NULL, float* hitFraction = NULL);
 
 protected:
 
@@ -257,15 +258,17 @@ private:
     // Represents the collision listeners and status for a given collision pair (used by the collision status cache).
     struct CollisionInfo
     {
-        std::vector<PhysicsRigidBody::Listener*> _listeners;
+        CollisionInfo() : _status(0) { }
+
+        std::vector<PhysicsCollisionObject::CollisionListener*> _listeners;
         int _status;
     };
 
     // Wraps Bullet collision shapes (used for implementing shape caching).
     struct PhysicsCollisionShape : public Ref
     {
-        PhysicsCollisionShape(btCollisionShape* shape) : _shape(shape) {}
-        ~PhysicsCollisionShape() { SAFE_DELETE(_shape); }
+        PhysicsCollisionShape(btCollisionShape* shape);
+        ~PhysicsCollisionShape();
 
         btCollisionShape* _shape;
     };
@@ -305,17 +308,20 @@ private:
      */
     void update(long elapsedTime);
 
-    // Adds the given collision listener for the two given rigid bodies.
-    void addCollisionListener(PhysicsRigidBody::Listener* listener, PhysicsRigidBody* rbA, PhysicsRigidBody* rbB);
+    // Adds the given collision listener for the two given collision objects.
+    void addCollisionListener(PhysicsCollisionObject::CollisionListener* listener, PhysicsCollisionObject* objectA, PhysicsCollisionObject* objectB);
+
+    // Removes the given collision listener.
+    void removeCollisionListener(PhysicsCollisionObject::CollisionListener* listener, PhysicsCollisionObject* objectA, PhysicsCollisionObject* objectB);
 
-    // Adds the given rigid body to the world.
-    void addRigidBody(PhysicsRigidBody* body);
+    // Adds the given collision object to the world.
+    void addCollisionObject(PhysicsCollisionObject* object);
     
-    // Removes the given rigid body from the simulated physics world.
-    void removeRigidBody(PhysicsRigidBody* rigidBody);
+    // Removes the given collision object from the simulated physics world.
+    void removeCollisionObject(PhysicsCollisionObject* object);
     
     // Gets the corresponding GamePlay object for the given Bullet object.
-    PhysicsRigidBody* getRigidBody(const btCollisionObject* collisionObject);
+    PhysicsCollisionObject* getCollisionObject(const btCollisionObject* collisionObject) const;
     
     // Creates a box collision shape to be used in the creation of a rigid body.
     btCollisionShape* createBox(const Vector3& min, const Vector3& max, const Vector3& scale);
@@ -329,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);
 
@@ -370,7 +379,7 @@ private:
         const Matrix* _viewProjection;
         MeshBatch* _meshBatch;
     };
-    
+
     btDefaultCollisionConfiguration* _collisionConfiguration;
     btCollisionDispatcher* _dispatcher;
     btBroadphaseInterface* _overlappingPairCache;
@@ -381,9 +390,9 @@ private:
     DebugDrawer* _debugDrawer;
     Listener::EventType _status;
     std::vector<Listener*>* _listeners;
-    std::vector<PhysicsRigidBody*> _bodies;
     Vector3 _gravity;
-    std::map<PhysicsRigidBody::CollisionPair, CollisionInfo> _collisionStatus;  
+    std::map<PhysicsCollisionObject::CollisionPair, CollisionInfo> _collisionStatus;
+
 };
 
 }

+ 31 - 48
gameplay/src/PhysicsRigidBody.cpp

@@ -5,22 +5,20 @@
 #include "PhysicsMotionState.h"
 #include "PhysicsRigidBody.h"
 
-#include "BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h"
-
 namespace gameplay
 {
 
 // Internal values used for creating mesh, heightfield, and capsule rigid bodies.
-#define SHAPE_MESH ((PhysicsRigidBody::Type)(PhysicsRigidBody::SHAPE_NONE + 1))
-#define SHAPE_HEIGHTFIELD ((PhysicsRigidBody::Type)(PhysicsRigidBody::SHAPE_NONE + 2))
-#define SHAPE_CAPSULE ((PhysicsRigidBody::Type)(PhysicsRigidBody::SHAPE_NONE + 3))
+#define SHAPE_MESH ((PhysicsRigidBody::ShapeType)(PhysicsRigidBody::SHAPE_NONE + 1))
+#define SHAPE_HEIGHTFIELD ((PhysicsRigidBody::ShapeType)(PhysicsRigidBody::SHAPE_NONE + 2))
+#define SHAPE_CAPSULE ((PhysicsRigidBody::ShapeType)(PhysicsRigidBody::SHAPE_NONE + 3))
 
 // Helper function for calculating heights from heightmap (image) or heightfield data.
 static float calculateHeight(float* data, unsigned int width, unsigned int height, float x, float y);
 
-PhysicsRigidBody::PhysicsRigidBody(Node* node, PhysicsRigidBody::Type type, float mass, 
+PhysicsRigidBody::PhysicsRigidBody(Node* node, PhysicsRigidBody::ShapeType type, float mass, 
     float friction, float restitution, float linearDamping, float angularDamping)
-        : _shape(NULL), _body(NULL), _node(node), _listeners(NULL), _angularVelocity(NULL),
+        : _shape(NULL), _body(NULL), _node(node), _angularVelocity(NULL),
         _anisotropicFriction(NULL), _gravity(NULL), _linearVelocity(NULL), _vertexData(NULL),
         _indexData(NULL), _heightfieldData(NULL), _inverse(NULL), _inverseIsDirty(true)
 {
@@ -63,12 +61,12 @@ PhysicsRigidBody::PhysicsRigidBody(Node* node, PhysicsRigidBody::Type type, floa
         _body = createRigidBodyInternal(_shape, mass, node, friction, restitution, linearDamping, angularDamping);
 
     // Add the rigid body to the physics world.
-    Game::getInstance()->getPhysicsController()->addRigidBody(this);
+    Game::getInstance()->getPhysicsController()->addCollisionObject(this);
 }
 
 PhysicsRigidBody::PhysicsRigidBody(Node* node, Image* image, float mass,
     float friction, float restitution, float linearDamping, float angularDamping)
-        : _shape(NULL), _body(NULL), _node(node), _listeners(NULL), _angularVelocity(NULL),
+        : _shape(NULL), _body(NULL), _node(node), _angularVelocity(NULL),
         _anisotropicFriction(NULL), _gravity(NULL), _linearVelocity(NULL), _vertexData(NULL),
         _indexData(NULL), _heightfieldData(NULL), _inverse(NULL), _inverseIsDirty(true)
 {
@@ -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.
@@ -142,7 +139,7 @@ PhysicsRigidBody::PhysicsRigidBody(Node* node, Image* image, float mass,
         _body = createRigidBodyInternal(_shape, mass, node, friction, restitution, linearDamping, angularDamping);
 
     // Add the rigid body to the physics world.
-    Game::getInstance()->getPhysicsController()->addRigidBody(this);
+    Game::getInstance()->getPhysicsController()->addCollisionObject(this);
 
     // Add the rigid body as a listener on the node's transform.
     _node->addListener(this);
@@ -150,7 +147,7 @@ PhysicsRigidBody::PhysicsRigidBody(Node* node, Image* image, float mass,
 
 PhysicsRigidBody::PhysicsRigidBody(Node* node, float radius, float height, float mass, float friction,
     float restitution, float linearDamping, float angularDamping)
-        : _shape(NULL), _body(NULL), _node(node), _listeners(NULL), _angularVelocity(NULL),
+        : _shape(NULL), _body(NULL), _node(node), _angularVelocity(NULL),
         _anisotropicFriction(NULL), _gravity(NULL), _linearVelocity(NULL), _vertexData(NULL),
         _indexData(NULL), _heightfieldData(NULL), _inverse(NULL), _inverseIsDirty(true)
 {
@@ -175,7 +172,7 @@ PhysicsRigidBody::PhysicsRigidBody(Node* node, float radius, float height, float
         _body = createRigidBodyInternal(_shape, mass, node, friction, restitution, linearDamping, angularDamping);
 
     // Add the rigid body to the physics world.
-    Game::getInstance()->getPhysicsController()->addRigidBody(this);
+    Game::getInstance()->getPhysicsController()->addCollisionObject(this);
 }
 
 PhysicsRigidBody::~PhysicsRigidBody()
@@ -189,17 +186,16 @@ PhysicsRigidBody::~PhysicsRigidBody()
         SAFE_DELETE(ptr);
     }
 
+    Game::getInstance()->getPhysicsController()->removeCollisionObject(this);
+
     // Clean up the rigid body and its related objects.
     if (_body)
     {
         if (_body->getMotionState())
             delete _body->getMotionState();
-
-        Game::getInstance()->getPhysicsController()->removeRigidBody(this);
         SAFE_DELETE(_body);
     }
 
-    SAFE_DELETE(_listeners);
     SAFE_DELETE(_angularVelocity);
     SAFE_DELETE(_anisotropicFriction);
     SAFE_DELETE(_gravity);
@@ -213,9 +209,24 @@ PhysicsRigidBody::~PhysicsRigidBody()
     SAFE_DELETE(_inverse);
 }
 
-void PhysicsRigidBody::addCollisionListener(Listener* listener, PhysicsRigidBody* body)
+Node* PhysicsRigidBody::getNode() const
 {
-    Game::getInstance()->getPhysicsController()->addCollisionListener(listener, this, body);
+    return _node;
+}
+
+PhysicsCollisionObject::Type PhysicsRigidBody::getType() const
+{
+    return PhysicsCollisionObject::RIGID_BODY;
+}
+
+btCollisionObject* PhysicsRigidBody::getCollisionObject() const
+{
+    return _body;
+}
+
+btCollisionShape* PhysicsRigidBody::getCollisionShape() const
+{
+    return _shape;
 }
 
 void PhysicsRigidBody::applyForce(const Vector3& force, const Vector3* relativePosition)
@@ -271,15 +282,6 @@ void PhysicsRigidBody::applyTorqueImpulse(const Vector3& torque)
     }
 }
 
-bool PhysicsRigidBody::collidesWith(PhysicsRigidBody* body)
-{
-    static CollidesWithCallback callback;
-
-    callback.result = false;
-    Game::getInstance()->getPhysicsController()->_world->contactPairTest(_body, body->_body, callback);
-    return callback.result;
-}
-
 PhysicsRigidBody* PhysicsRigidBody::create(Node* node, const char* filePath)
 {
     assert(filePath);
@@ -310,7 +312,7 @@ PhysicsRigidBody* PhysicsRigidBody::create(Node* node, Properties* properties)
     }
 
     // Set values to their defaults.
-    PhysicsRigidBody::Type type = PhysicsRigidBody::SHAPE_NONE;
+    PhysicsRigidBody::ShapeType type = PhysicsRigidBody::SHAPE_NONE;
     float mass = 0.0;
     float friction = 0.5;
     float restitution = 0.0;
@@ -560,25 +562,6 @@ void PhysicsRigidBody::transformChanged(Transform* transform, long cookie)
     _inverseIsDirty = true;
 }
 
-PhysicsRigidBody::CollisionPair::CollisionPair(PhysicsRigidBody* rigidBodyA, PhysicsRigidBody* rigidBodyB)
-    : rigidBodyA(rigidBodyA), rigidBodyB(rigidBodyB)
-{
-    // Unused
-}
-
-PhysicsRigidBody::Listener::~Listener()
-{
-    // Unused
-}
-
-btScalar PhysicsRigidBody::CollidesWithCallback::addSingleResult(btManifoldPoint& cp, 
-                                                                 const btCollisionObject* a, int partIdA, int indexA, 
-                                                                 const btCollisionObject* b, int partIdB, int indexB)
-{
-    result = true;
-    return 0.0f;
-}
-
 float calculateHeight(float* data, unsigned int width, unsigned int height, float x, float y)
 {
     unsigned int x1 = x;

+ 21 - 96
gameplay/src/PhysicsRigidBody.h

@@ -5,6 +5,7 @@
 #include "PhysicsConstraint.h"
 #include "Transform.h"
 #include "Vector3.h"
+#include "PhysicsCollisionObject.h"
 
 namespace gameplay
 {
@@ -15,7 +16,7 @@ class PhysicsConstraint;
 /**
  * Defines a class for physics rigid bodies.
  */
-class PhysicsRigidBody : public Transform::Listener
+class PhysicsRigidBody : public PhysicsCollisionObject, public Transform::Listener
 {
     friend class Node;
     friend class PhysicsCharacter;
@@ -32,7 +33,7 @@ public:
     /**
      * Represents the different types of rigid bodies.
      */
-    enum Type
+    enum ShapeType
     {
         SHAPE_BOX,
         SHAPE_SPHERE,
@@ -40,81 +41,10 @@ public:
         SHAPE_MAX = 10
     };
 
-    /** 
-     * Defines a pair of rigid bodies that collided (or may collide).
-     */
-    class CollisionPair
-    {
-    public:
-
-        /**
-         * Constructor.
-         */
-        CollisionPair(PhysicsRigidBody* rigidBodyA, PhysicsRigidBody* rigidBodyB);
-
-        /**
-         * Less than operator (needed for use as a key in map).
-         * 
-         * @param collisionPair The collision pair to compare.
-         * @return True if this pair is "less than" the given pair; false otherwise.
-         */
-        bool operator<(const CollisionPair& collisionPair) const;
-
-        /** The first rigid body in the collision. */
-        PhysicsRigidBody* rigidBodyA;
-
-        /** The second rigid body in the collision. */
-        PhysicsRigidBody* rigidBodyB;
-    };
-
     /**
-     * Collision listener interface.
+     * @see PhysicsCollisionObject#getType
      */
-    class Listener
-    {
-        friend class PhysicsRigidBody;
-        friend class PhysicsController;
-
-    public:
-        /**
-         * The type of collision event.
-         */
-        enum EventType
-        {
-            /**
-             * Event fired when the two rigid bodies start colliding.
-             */
-            COLLIDING,
-
-            /**
-             * Event fired when the two rigid bodies no longer collide.
-             */
-            NOT_COLLIDING
-        };
-
-        /**
-         * Destructor.
-         */
-        virtual ~Listener();
-
-        /**
-         * Handles when a collision starts or stops occurring for the rigid body where this listener is registered.
-         * 
-         * @param type The type of collision event.
-         * @param collisionPair The two rigid bodies involved in the collision.
-         * @param contactPointA The contact point with the first rigid body (in world space).
-         * @param contactPointB The contact point with the second rigid body (in world space).
-         */
-        virtual void collisionEvent(EventType type, const CollisionPair& collisionPair, const Vector3& contactPointA = Vector3(), const Vector3& contactPointB = Vector3()) = 0;
-    };
-
-    /**
-     * Adds a collision listener for this rigid body.
-     * 
-     * @param listener The listener to add.
-     * @param body Specifies that only collisions with the given rigid body should trigger a notification.
-     */
-    void addCollisionListener(Listener* listener, PhysicsRigidBody* body = NULL);
+    PhysicsCollisionObject::Type getType() const;
 
     /**
      * Applies the given force to the rigid body (optionally, from the given relative position).
@@ -145,14 +75,6 @@ public:
      * @param torque The torque impulse to be applied.
      */
     void applyTorqueImpulse(const Vector3& torque);
-    
-    /**
-     * Checks if this rigid body collides with the given rigid body.
-     * 
-     * @param body The rigid body to test collision with.
-     * @return True if this rigid body collides with the given rigid body; false otherwise.
-     */
-    bool collidesWith(PhysicsRigidBody* body);
 
     /**
      * Gets the rigid body's angular damping.
@@ -217,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.
@@ -305,6 +229,18 @@ public:
      */
     inline void setRestitution(float restitution);
 
+protected:
+
+    /**
+     * @see PhysicsCollisionObject::getCollisionObject
+     */
+    btCollisionObject* getCollisionObject() const;
+
+    /**
+     * @see PhysicsCollisionObject::getCollisionShape
+     */
+    btCollisionShape* getCollisionShape() const;
+
 private:
 
     /**
@@ -320,7 +256,7 @@ private:
      * @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).
      */
-    PhysicsRigidBody(Node* node, PhysicsRigidBody::Type type, float mass, float friction = 0.5,
+    PhysicsRigidBody(Node* node, PhysicsRigidBody::ShapeType type, float mass, float friction = 0.5,
         float restitution = 0.0, float linearDamping = 0.0, float angularDamping = 0.0);
 
     /**
@@ -404,21 +340,10 @@ private:
     // Used for implementing getHeight() when the heightfield has a transform that can change.
     void transformChanged(Transform* transform, long cookie);
 
-    // Internal class used to implement the collidesWith(PhysicsRigidBody*) function.
-    struct CollidesWithCallback : public btCollisionWorld::ContactResultCallback
-    {
-        btScalar addSingleResult(btManifoldPoint& cp, 
-                                 const btCollisionObject* a, int partIdA, int indexA, 
-                                 const btCollisionObject* b, int partIdB, int indexB);
-
-        bool result;
-    };
-
     btCollisionShape* _shape;
     btRigidBody* _body;
     Node* _node;
     std::vector<PhysicsConstraint*> _constraints;
-    std::vector<Listener*>* _listeners;
     mutable Vector3* _angularVelocity;
     mutable Vector3* _anisotropicFriction;
     mutable Vector3* _gravity;

+ 0 - 22
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();
@@ -133,21 +128,4 @@ inline void PhysicsRigidBody::setRestitution(float restitution)
     _body->setRestitution(restitution);
 }
 
-inline bool PhysicsRigidBody::CollisionPair::operator<(const CollisionPair& collisionPair) const
-{
-    // If the pairs are equal, then return false.
-    if ((rigidBodyA == collisionPair.rigidBodyA && rigidBodyB == collisionPair.rigidBodyB) || (rigidBodyA == collisionPair.rigidBodyB && rigidBodyB == collisionPair.rigidBodyA))
-        return false;
-    else
-    {
-        // We choose to compare based on rigidBodyA arbitrarily.
-        if (rigidBodyA < collisionPair.rigidBodyA)
-            return true;
-        else if (rigidBodyA == collisionPair.rigidBodyA)
-            return rigidBodyB < collisionPair.rigidBodyB;
-        else
-            return false;
-    }
-}
-
 }

+ 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();

+ 2 - 20
gameplay/src/Texture.cpp

@@ -152,6 +152,7 @@ Texture* Texture::createCompressedPVR(const char* path)
 	    PVRTextureFlagTypePVRTC_2 = 24,
 	    PVRTextureFlagTypePVRTC_4
 	};
+
 	struct pvr_file_header
 	{
 		unsigned int size;          		// size of the structure
@@ -198,6 +199,7 @@ Texture* Texture::createCompressedPVR(const char* path)
 		fclose(file);
 	    return NULL;
 	}
+
 	// Format flags for GLenum format
 	GLenum format;
 	unsigned int formatFlags = header.formatflags & 0xff;
@@ -254,33 +256,13 @@ Texture* Texture::createCompressedPVR(const char* path)
 	{
 		if (formatFlags == PVRTextureFlagTypePVRTC_4)
 		{
-			/*
-			blockSize = 4 * 4; 		// Pixel by pixel block size for 4bpp
-			widthBlocks = width / 4;
-			heightBlocks = height / 4;
-			bpp = 4;
-			*/
 			dataSize = ( max((int)width, 8) * max((int)height, 8) * 4 + 7) / 8;
 		}
 		else
 		{
-			/*
-			blockSize = 8 * 4; 		// Pixel by pixel block size for 2bpp
-			widthBlocks = width / 8;
-			heightBlocks = height / 4;
-			bpp = 2;
-			*/
 			dataSize = ( max((int)width, 16) * max((int)height, 8) * 2 + 7) / 8;
 		}
 
-		/* Clamp to minimum number of blocks
-		if (widthBlocks < 2)
-			widthBlocks = 2;
-		if (heightBlocks < 2)
-			heightBlocks = 2;
-*/
-		//dataSize = widthBlocks * heightBlocks * ((blockSize * bpp) / 8);
-
 		GL_ASSERT( glCompressedTexImage2D(GL_TEXTURE_2D, level, (GLenum)format, width, height, 0, dataSize, dataOffset) );
 
 		dataOffset += dataSize;

+ 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

+ 1 - 1
gameplay/src/VertexAttributeBinding.cpp

@@ -195,7 +195,7 @@ VertexAttributeBinding* VertexAttributeBinding::create(Mesh* mesh, const VertexF
 
         if (attrib == -1)
         {
-            WARN_VARG("Warning: Vertex attribute not found for usage %d", (int)e.usage);
+            WARN_VARG("Warning: Vertex element with usage '%s' in mesh '%s' does not correspond to an attribute in effect '%s'.", VertexFormat::toString(e.usage), mesh->getUrl(), effect->getId());
         }
         else
         {

+ 39 - 0
gameplay/src/VertexFormat.cpp

@@ -80,4 +80,43 @@ bool VertexFormat::Element::operator != (const VertexFormat::Element& e) const
     return !(*this == e);
 }
 
+const char* VertexFormat::toString(Usage usage)
+{
+    switch (usage)
+    {
+    case POSITION:
+        return "POSITION";
+    case NORMAL:
+        return "NORMAL";
+    case COLOR:
+        return "COLOR";
+    case TANGENT:
+        return "TANGENT";
+    case BINORMAL:
+        return "BINORMAL";
+    case BLENDWEIGHTS:
+        return "BLENDWEIGHTS";
+    case BLENDINDICES:
+        return "BLENDINDICES";
+    case TEXCOORD0:
+        return "TEXCOORD0";
+    case TEXCOORD1:
+        return "TEXCOORD1";
+    case TEXCOORD2:
+        return "TEXCOORD2";
+    case TEXCOORD3:
+        return "TEXCOORD3";
+    case TEXCOORD4:
+        return "TEXCOORD4";
+    case TEXCOORD5:
+        return "TEXCOORD5";
+    case TEXCOORD6:
+        return "TEXCOORD6";
+    case TEXCOORD7:
+        return "TEXCOORD7";
+    default:
+        return "UNKNOWN";
+    }
+}
+
 }

+ 5 - 0
gameplay/src/VertexFormat.h

@@ -141,6 +141,11 @@ public:
      */
     bool operator != (const VertexFormat& f) const;
 
+    /**
+     * Returns a string representation of a Usage enumeration value.
+     */
+    static const char* toString(Usage usage);
+
 private:
 
     std::vector<Element> _elements;

+ 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);
 };
 
 }

+ 1 - 1
gameplay/src/gameplay.h

@@ -66,10 +66,10 @@
 #include "PhysicsHingeConstraint.h"
 #include "PhysicsSocketConstraint.h"
 #include "PhysicsSpringConstraint.h"
+#include "PhysicsCollisionObject.h"
 #include "PhysicsRigidBody.h"
 #include "PhysicsCharacter.h"
 
-
 // UI
 #include "Theme.h"
 #include "Control.h"