Bladeren bron

Adding FlowLayout, ScrollLayout.
Clipping fix in SpriteBatch.
Added ability to override margin, padding from .form file.
Fixed signed/unsigned mismatches and floating-point ambiguity causing wrapping issues in Font.
Refactoring bounds calculations in Control::update().
Using new bounds to draw controls more efficiently.

Adam Blake 13 jaren geleden
bovenliggende
commit
faf409a2d6

+ 1 - 1
gameplay/android/jni/Android.mk

@@ -16,7 +16,7 @@ LOCAL_PATH := $(call my-dir)/../../src
 
 include $(CLEAR_VARS)
 LOCAL_MODULE    := libgameplay
-LOCAL_SRC_FILES := AbsoluteLayout.cpp Animation.cpp AnimationClip.cpp AnimationController.cpp AnimationTarget.cpp AnimationValue.cpp AudioBuffer.cpp AudioController.cpp AudioListener.cpp AudioSource.cpp BoundingBox.cpp BoundingSphere.cpp Bundle.cpp Button.cpp Camera.cpp CheckBox.cpp Container.cpp Control.cpp Curve.cpp DebugNew.cpp DepthStencilTarget.cpp Effect.cpp FileSystem.cpp Font.cpp Form.cpp FrameBuffer.cpp Frustum.cpp Game.cpp gameplay-main-android.cpp Image.cpp Joint.cpp Label.cpp Layout.cpp Light.cpp Material.cpp MaterialParameter.cpp Matrix.cpp Mesh.cpp MeshBatch.cpp MeshPart.cpp MeshSkin.cpp Model.cpp Node.cpp ParticleEmitter.cpp Pass.cpp PhysicsCharacter.cpp PhysicsCollisionObject.cpp PhysicsCollisionShape.cpp PhysicsConstraint.cpp PhysicsController.cpp PhysicsFixedConstraint.cpp PhysicsGenericConstraint.cpp PhysicsGhostObject.cpp PhysicsHingeConstraint.cpp PhysicsMotionState.cpp PhysicsRigidBody.cpp PhysicsSocketConstraint.cpp PhysicsSpringConstraint.cpp Plane.cpp PlatformAndroid.cpp Properties.cpp Quaternion.cpp RadioButton.cpp Ray.cpp Rectangle.cpp Ref.cpp RenderState.cpp RenderTarget.cpp Scene.cpp SceneLoader.cpp Slider.cpp SpriteBatch.cpp Technique.cpp TextBox.cpp Texture.cpp Theme.cpp ThemeStyle.cpp Transform.cpp Vector2.cpp Vector3.cpp Vector4.cpp VertexAttributeBinding.cpp VertexFormat.cpp VerticalLayout.cpp
+LOCAL_SRC_FILES := AbsoluteLayout.cpp Animation.cpp AnimationClip.cpp AnimationController.cpp AnimationTarget.cpp AnimationValue.cpp AudioBuffer.cpp AudioController.cpp AudioListener.cpp AudioSource.cpp BoundingBox.cpp BoundingSphere.cpp Bundle.cpp Button.cpp Camera.cpp CheckBox.cpp Container.cpp Control.cpp Curve.cpp DebugNew.cpp DepthStencilTarget.cpp Effect.cpp FileSystem.cpp FlowLayout.cpp Font.cpp Form.cpp FrameBuffer.cpp Frustum.cpp Game.cpp gameplay-main-android.cpp Image.cpp Joint.cpp Label.cpp Layout.cpp Light.cpp Material.cpp MaterialParameter.cpp Matrix.cpp Mesh.cpp MeshBatch.cpp MeshPart.cpp MeshSkin.cpp Model.cpp Node.cpp ParticleEmitter.cpp Pass.cpp PhysicsCharacter.cpp PhysicsCollisionObject.cpp PhysicsCollisionShape.cpp PhysicsConstraint.cpp PhysicsController.cpp PhysicsFixedConstraint.cpp PhysicsGenericConstraint.cpp PhysicsGhostObject.cpp PhysicsHingeConstraint.cpp PhysicsMotionState.cpp PhysicsRigidBody.cpp PhysicsSocketConstraint.cpp PhysicsSpringConstraint.cpp Plane.cpp PlatformAndroid.cpp Properties.cpp Quaternion.cpp RadioButton.cpp Ray.cpp Rectangle.cpp Ref.cpp RenderState.cpp RenderTarget.cpp Scene.cpp SceneLoader.cpp Slider.cpp SpriteBatch.cpp Technique.cpp TextBox.cpp Texture.cpp Theme.cpp ThemeStyle.cpp Transform.cpp Vector2.cpp Vector3.cpp Vector4.cpp VertexAttributeBinding.cpp VertexFormat.cpp VerticalLayout.cpp
 LOCAL_CFLAGS := -D__ANDROID__ -I"../../external-deps/bullet/include" -I"../../external-deps/libpng/include"
 LOCAL_STATIC_LIBRARIES := android_native_app_glue
 

+ 4 - 0
gameplay/gameplay.vcxproj

@@ -37,6 +37,7 @@
     <ClCompile Include="src\DepthStencilTarget.cpp" />
     <ClCompile Include="src\Effect.cpp" />
     <ClCompile Include="src\FileSystem.cpp" />
+    <ClCompile Include="src\FlowLayout.cpp" />
     <ClCompile Include="src\Font.cpp" />
     <ClCompile Include="src\Form.cpp" />
     <ClCompile Include="src\FrameBuffer.cpp" />
@@ -89,6 +90,7 @@
     <ClCompile Include="src\RenderTarget.cpp" />
     <ClCompile Include="src\Scene.cpp" />
     <ClCompile Include="src\SceneLoader.cpp" />
+    <ClCompile Include="src\ScrollLayout.cpp" />
     <ClCompile Include="src\Slider.cpp" />
     <ClCompile Include="src\SpriteBatch.cpp" />
     <ClCompile Include="src\Technique.cpp" />
@@ -128,6 +130,7 @@
     <ClInclude Include="src\DepthStencilTarget.h" />
     <ClInclude Include="src\Effect.h" />
     <ClInclude Include="src\FileSystem.h" />
+    <ClInclude Include="src\FlowLayout.h" />
     <ClInclude Include="src\Font.h" />
     <ClInclude Include="src\Form.h" />
     <ClInclude Include="src\FrameBuffer.h" />
@@ -179,6 +182,7 @@
     <ClInclude Include="src\Scene.h" />
     <ClInclude Include="src\SceneLoader.h" />
     <ClInclude Include="src\ScreenDisplayer.h" />
+    <ClInclude Include="src\ScrollLayout.h" />
     <ClInclude Include="src\Slider.h" />
     <ClInclude Include="src\SpriteBatch.h" />
     <ClInclude Include="src\Technique.h" />

+ 12 - 0
gameplay/gameplay.vcxproj.filters

@@ -276,6 +276,12 @@
     <ClCompile Include="src\Bundle.cpp">
       <Filter>src</Filter>
     </ClCompile>
+    <ClCompile Include="src\FlowLayout.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="src\ScrollLayout.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="src\Animation.h">
@@ -548,6 +554,12 @@
     <ClInclude Include="src\Bundle.h">
       <Filter>src</Filter>
     </ClInclude>
+    <ClInclude Include="src\FlowLayout.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="src\ScrollLayout.h">
+      <Filter>src</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <None Include="res\shaders\bumped-specular.vsh">

+ 1 - 1
gameplay/src/AbsoluteLayout.cpp

@@ -51,7 +51,7 @@ namespace gameplay
 
             if (control->isDirty() || control->isContainer())
             {
-                control->update(container->getClip());
+                control->update(container->getClip(), Vector2::zero());
             }
         }
     }

+ 5 - 16
gameplay/src/CheckBox.cpp

@@ -77,19 +77,11 @@ bool CheckBox::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int cont
         {
             if (_state == Control::ACTIVE)
             {
-                if (x > 0 && x <= _clipBounds.width &&
-                    y > 0 && y <= _clipBounds.height)
+                if (x > _clipBounds.x && x <= _clipBounds.x + _clipBounds.width &&
+                    y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height)
                 {
                     _checked = !_checked;
                     notifyListeners(Control::Listener::VALUE_CHANGED);
-
-                    // Animate between icons.  Old fades out, then the new fades in.
-                    /*
-                    AnimationController* animationController = Game::getInstance()->getAnimationController();
-                    float from[1] = { 1.0f };
-                    float to[1] = { 0.0f };
-                    animationController->createAnimationFromTo("CheckBox::toggle", this, CheckBox::ANIMATE_SPRITE_ALPHA, from, to, Curve::QUADRATIC_IN_OUT, 200L);
-                    */
                 }
             }
         }
@@ -99,9 +91,9 @@ bool CheckBox::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int cont
     return Button::touchEvent(evt, x, y, contactIndex);
 }
 
-void CheckBox::update(const Rectangle& clip)
+void CheckBox::update(const Rectangle& clip, const Vector2& offset)
 {
-    Label::update(clip);
+    Label::update(clip, offset);
 
     Vector2 size;
     if (_imageSize.isZero())
@@ -140,8 +132,6 @@ void CheckBox::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
 {
     // Left, v-center.
     // TODO: Set an alignment for icons.
-    const Theme::Border& border = getBorder(_state);
-    const Theme::Padding padding = getPadding();
     
     const Rectangle& region = _image->getRegion();
     const Theme::UVs& uvs = _image->getUVs();
@@ -158,8 +148,7 @@ void CheckBox::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
         size.set(_imageSize);
     }
 
-    Vector2 pos(clip.x + _bounds.x + border.left + padding.left,
-        clip.y + _bounds.y + (_clipBounds.height - border.bottom - padding.bottom) / 2.0f - size.y / 2.0f);
+    Vector2 pos(_viewportBounds.x, _viewportBounds.y + _viewportBounds.height * 0.5f - size.y * 0.5f);
 
     spriteBatch->draw(pos.x, pos.y, size.x, size.y, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color, _clip);
 }

+ 1 - 1
gameplay/src/CheckBox.h

@@ -115,7 +115,7 @@ protected:
      *
      * @param clip The clipping rectangle of this control's parent container.
      */
-    void update(const Rectangle& clip);
+    void update(const Rectangle& clip, const Vector2& offset = Vector2::zero());
 
     /**
      * Draw the checkbox icon associated with this control.

+ 47 - 11
gameplay/src/Container.cpp

@@ -2,7 +2,9 @@
 #include "Container.h"
 #include "Layout.h"
 #include "AbsoluteLayout.h"
+#include "FlowLayout.h"
 #include "VerticalLayout.h"
+#include "ScrollLayout.h"
 #include "Label.h"
 #include "Button.h"
 #include "CheckBox.h"
@@ -40,10 +42,14 @@ namespace gameplay
             layout = AbsoluteLayout::create();
             break;
         case Layout::LAYOUT_FLOW:
+            layout = FlowLayout::create();
             break;
         case Layout::LAYOUT_VERTICAL:
             layout = VerticalLayout::create();
             break;
+        case Layout::LAYOUT_SCROLL:
+            layout = ScrollLayout::create();
+            break;
         }
 
         Container* container = new Container();
@@ -227,25 +233,28 @@ namespace gameplay
         return NULL;
     }
 
-    void Container::update(const Rectangle& clip)
+    void Container::update(const Rectangle& clip, const Vector2& offset)
     {
         // Update this container's viewport.
-        Control::update(clip);
+        Control::update(clip, offset);
 
         _layout->update(this);
     }
 
-    void Container::drawBorder(SpriteBatch* spriteBatch, const Rectangle& clip)
+    void Container::drawBorder(SpriteBatch* spriteBatch, const Rectangle& clip, const Vector2& offset)
     {
         // First draw our own border.
-        Control::drawBorder(spriteBatch, clip);
+        Control::drawBorder(spriteBatch, clip, offset);
 
         // Now call drawBorder on all controls within this container.
         std::vector<Control*>::const_iterator it;
         for (it = _controls.begin(); it < _controls.end(); it++)
         {
             Control* control = *it;
-            control->drawBorder(spriteBatch, _clip);
+            if (_layout->getType() == Layout::LAYOUT_SCROLL)
+                control->drawBorder(spriteBatch, _clip, ((ScrollLayout*)_layout)->_scrollPosition);
+            else
+                control->drawBorder(spriteBatch, _clip, Vector2::zero());
         }
     }
 
@@ -308,6 +317,12 @@ namespace gameplay
         float xPos = border.left + padding.left;
         float yPos = border.top + padding.top;
 
+        Vector2* offset = NULL;
+        if (_layout->getType() == Layout::LAYOUT_SCROLL)
+        {
+            offset = &((ScrollLayout*)_layout)->_scrollPosition;
+        }
+
         std::vector<Control*>::const_iterator it;
         for (it = _controls.begin(); it < _controls.end(); it++)
         {
@@ -317,16 +332,24 @@ namespace gameplay
                 continue;
             }
 
-            const Rectangle& bounds = control->getClipBounds();
+            const Rectangle& bounds = control->getBounds();
+            float boundsX = bounds.x;
+            float boundsY = bounds.y;
+            if (offset)
+            {
+                boundsX += offset->x;
+                boundsY += offset->y;
+            }
+
             if (control->getState() != Control::NORMAL ||
                 (evt == Touch::TOUCH_PRESS &&
-                 x >= xPos + bounds.x &&
-                 x <= xPos + bounds.x + bounds.width &&
-                 y >= yPos + bounds.y &&
-                 y <= yPos + bounds.y + bounds.height))
+                 x >= xPos + boundsX &&
+                 x <= xPos + boundsX + bounds.width &&
+                 y >= yPos + boundsY &&
+                 y <= yPos + boundsY + bounds.height))
             {
                 // Pass on the event's clip relative to the control.
-                eventConsumed |= control->touchEvent(evt, x - xPos - bounds.x, y - yPos - bounds.y, contactIndex);
+                eventConsumed |= control->touchEvent(evt, x - xPos - boundsX, y - yPos - boundsY, contactIndex);
             }
         }
 
@@ -345,6 +368,15 @@ namespace gameplay
             break;
         }
 
+        if (!eventConsumed)
+        {
+            // Pass the event on to the layout.
+            if (_layout->touchEvent(evt, x - xPos, y - yPos, contactIndex))
+            {
+                _dirty = true;
+            }
+        }
+
         return (_consumeTouchEvents | eventConsumed);
     }
 
@@ -392,6 +424,10 @@ namespace gameplay
         {
             return Layout::LAYOUT_FLOW;
         }
+        else if (layoutName == "LAYOUT_SCROLL")
+        {
+            return Layout::LAYOUT_SCROLL;
+        }
         else
         {
             // Default.

+ 2 - 2
gameplay/src/Container.h

@@ -158,7 +158,7 @@ protected:
      *
      * @param clip The clipping rectangle of this container's parent container.
      */
-    virtual void update(const Rectangle& clip);
+    virtual void update(const Rectangle& clip, const Vector2& offset);
 
     /**
      * Draws the themed border and background of this container and all its controls.
@@ -166,7 +166,7 @@ protected:
      * @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);
+    void drawBorder(SpriteBatch* spriteBatch, const Rectangle& clip, const Vector2& offset = Vector2::zero());
 
     /**
      * Draws the icons of all controls within this container.

+ 98 - 51
gameplay/src/Control.cpp

@@ -74,29 +74,39 @@ namespace gameplay
         overrideThemedProperties(properties, STATE_ALL);
 
         // Override themed properties on specific states.
-        Properties* stateSpace = properties->getNextNamespace();
-        while (stateSpace != NULL)
+        Properties* innerSpace = properties->getNextNamespace();
+        while (innerSpace != NULL)
         {
-            std::string stateName(stateSpace->getNamespace());
-            std::transform(stateName.begin(), stateName.end(), stateName.begin(), (int(*)(int))toupper);
-            if (stateName == "STATENORMAL")
+            std::string spaceName(innerSpace->getNamespace());
+            std::transform(spaceName.begin(), spaceName.end(), spaceName.begin(), (int(*)(int))toupper);
+            if (spaceName == "STATENORMAL")
             {
-                overrideThemedProperties(stateSpace, NORMAL);
+                overrideThemedProperties(innerSpace, NORMAL);
             }
-            else if (stateName == "STATEFOCUS")
+            else if (spaceName == "STATEFOCUS")
             {
-                overrideThemedProperties(stateSpace, FOCUS);
+                overrideThemedProperties(innerSpace, FOCUS);
             }
-            else if (stateName == "STATEACTIVE")
+            else if (spaceName == "STATEACTIVE")
             {
-                overrideThemedProperties(stateSpace, ACTIVE);
+                overrideThemedProperties(innerSpace, ACTIVE);
             }
-            else if (stateName == "STATEDISABLED")
+            else if (spaceName == "STATEDISABLED")
             {
-                overrideThemedProperties(stateSpace, DISABLED);
+                overrideThemedProperties(innerSpace, DISABLED);
+            }
+            else if (spaceName == "MARGIN")
+            {
+                setMargin(innerSpace->getFloat("top"), innerSpace->getFloat("bottom"),
+                    innerSpace->getFloat("left"), innerSpace->getFloat("right"));
+            }
+            else if (spaceName == "PADDING")
+            {
+                setPadding(innerSpace->getFloat("top"), innerSpace->getFloat("bottom"),
+                    innerSpace->getFloat("left"), innerSpace->getFloat("right"));
             }
 
-            stateSpace = properties->getNextNamespace();
+            innerSpace = properties->getNextNamespace();
         }
     }
 
@@ -602,8 +612,8 @@ namespace gameplay
             notifyListeners(Listener::RELEASE);
 
             // Only trigger Listener::CLICK if both PRESS and RELEASE took place within the control's bounds.
-            if (x > 0 && x <= _clipBounds.width &&
-                y > 0 && y <= _clipBounds.height)
+            if (x > 0 && x <= _bounds.width &&
+                y > 0 && y <= _bounds.height)
             {
                 notifyListeners(Listener::CLICK);
             }
@@ -633,37 +643,67 @@ namespace gameplay
         }
     }
 
-    void Control::update(const Rectangle& clip)
+    void Control::update(const Rectangle& clip, const Vector2& offset)
     {
-        // Calculate the bounds.
-        float x = clip.x + _bounds.x;
-        float y = clip.y + _bounds.y;
+        // Calculate the clipped bounds.
+        float x = _bounds.x + offset.x;
+        float y = _bounds.y + offset.y;
         float width = _bounds.width;
         float height = _bounds.height;
 
         float clipX2 = clip.x + clip.width;
-        float x2 = x + width;
+        float x2 = clip.x + x + width;
         if (x2 > clipX2)
-            width = clipX2 - x;
+            width -= x2 - clipX2;
 
         float clipY2 = clip.y + clip.height;
-        float y2 = y + height;
+        float y2 = clip.y + y + height;
         if (y2 > clipY2)
-            height = clipY2 - y;
+            height -= y2 - clipY2;
+
+        if (x < 0)
+        {
+            width += x;
+            x = -x;
+        }
+        else
+        {
+            x = 0;
+        }
+
+        if (y < 0)
+        {
+            height += y;
+            y = -y;
+        }
+        else
+        {
+            y = 0;
+        }
 
-        _clipBounds.set(_bounds.x, _bounds.y, width, height);
+        _clipBounds.set(x, y, width, height);
 
-        // Calculate the clipping viewport.
+        // Calculate the absolute bounds.
+        x = _bounds.x + offset.x + clip.x;
+        y = _bounds.y + offset.y + clip.y;
+
+        _absoluteBounds.set(x, y, _bounds.width, _bounds.height);
+
+        // Calculate the absolute viewport bounds.
+        // Absolute bounds minus border and padding.
         const Theme::Border& border = getBorder(_state);
         const Theme::Padding& padding = getPadding();
 
-        x +=  border.left + padding.left;
-        y +=  border.top + padding.top;
+        x += border.left + padding.left;
+        y += border.top + padding.top;
         width = _bounds.width - border.left - padding.left - border.right - padding.right;
         height = _bounds.height - border.top - padding.top - border.bottom - padding.bottom;
 
-        _textBounds.set(x, y, width, height);
+        _viewportBounds.set(x, y, width, height);
 
+        // Calculate the clip area.
+        // Absolute bounds, minus border and padding,
+        // clipped to the parent container's clip area.
         clipX2 = clip.x + clip.width;
         x2 = x + width;
         if (x2 > clipX2)
@@ -675,24 +715,31 @@ namespace gameplay
             height = clipY2 - y;
 
         if (x < clip.x)
+        {
+            float dx = clip.x - x;
+            width -= dx;
             x = clip.x;
+        }
 
         if (y < clip.y)
+        {
+            float dy = clip.y - y;
+            height -= dy;
             y = clip.y;
-
+        }
+ 
         _clip.set(x, y, width, height);
 
+        // Cache themed attributes for performance.
         _skin = getSkin(_state);
         _opacity = getOpacity(_state);
     }
 
-    void Control::drawBorder(SpriteBatch* spriteBatch, const Rectangle& clip)
+    void Control::drawBorder(SpriteBatch* spriteBatch, const Rectangle& clip, const Vector2& offset)
     {
         if (!_skin || _bounds.width <= 0 || _bounds.height <= 0)
             return;
 
-        Vector2 pos(clip.x + _bounds.x, clip.y + _bounds.y);
-
         // Get the border and background images for this control's current state.
         const Theme::UVs& topLeft = _skin->getUVs(Theme::Skin::TOP_LEFT);
         const Theme::UVs& top = _skin->getUVs(Theme::Skin::TOP);
@@ -712,34 +759,34 @@ namespace gameplay
 
         float midWidth = _bounds.width - border.left - border.right;
         float midHeight = _bounds.height - border.top - border.bottom;
-        float midX = pos.x + border.left;
-        float midY = pos.y + border.top;
-        float rightX = pos.x + _bounds.width - border.right;
-        float bottomY = pos.y + _bounds.height - border.bottom;
+        float midX = _absoluteBounds.x + border.left;
+        float midY = _absoluteBounds.y + border.top;
+        float rightX = _absoluteBounds.x + _bounds.width - border.right;
+        float bottomY = _absoluteBounds.y + _bounds.height - border.bottom;
 
         // Draw themed border sprites.
         if (!border.left && !border.right && !border.top && !border.bottom)
         {
             // No border, just draw the image.
-            spriteBatch->draw(pos.x, pos.y, _bounds.width, _bounds.height, center.u1, center.v1, center.u2, center.v2, skinColor, clip);
+            spriteBatch->draw(_absoluteBounds.x, _absoluteBounds.y, _bounds.width, _bounds.height, center.u1, center.v1, center.u2, center.v2, skinColor, 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, skinColor, clip);
+                spriteBatch->draw(_absoluteBounds.x, _absoluteBounds.y, border.left, border.top, topLeft.u1, topLeft.v1, topLeft.u2, topLeft.v2, skinColor, clip);
             if (border.top)
-                spriteBatch->draw(pos.x + border.left, pos.y, midWidth, border.top, top.u1, top.v1, top.u2, top.v2, skinColor, clip);
+                spriteBatch->draw(_absoluteBounds.x + border.left, _absoluteBounds.y, midWidth, border.top, top.u1, top.v1, top.u2, top.v2, skinColor, clip);
             if (border.right && border.top)
-                spriteBatch->draw(rightX, pos.y, border.right, border.top, topRight.u1, topRight.v1, topRight.u2, topRight.v2, skinColor, clip);
+                spriteBatch->draw(rightX, _absoluteBounds.y, border.right, border.top, topRight.u1, topRight.v1, topRight.u2, topRight.v2, skinColor, clip);
             if (border.left)
-                spriteBatch->draw(pos.x, midY, border.left, midHeight, left.u1, left.v1, left.u2, left.v2, skinColor, clip);
+                spriteBatch->draw(_absoluteBounds.x, midY, border.left, midHeight, left.u1, left.v1, left.u2, left.v2, skinColor, clip);
             if (border.left && border.right && border.top && border.bottom)
-                spriteBatch->draw(pos.x + border.left, pos.y + border.top, _bounds.width - border.left - border.right, _bounds.height - border.top - border.bottom,
+                spriteBatch->draw(_absoluteBounds.x + border.left, _absoluteBounds.y + border.top, _bounds.width - border.left - border.right, _bounds.height - border.top - border.bottom,
                     center.u1, center.v1, center.u2, center.v2, skinColor, clip);
             if (border.right)
                 spriteBatch->draw(rightX, midY, border.right, midHeight, right.u1, right.v1, right.u2, right.v2, skinColor, clip);
             if (border.bottom && border.left)
-                spriteBatch->draw(pos.x, bottomY, border.left, border.bottom, bottomLeft.u1, bottomLeft.v1, bottomLeft.u2, bottomLeft.v2, skinColor, clip);
+                spriteBatch->draw(_absoluteBounds.x, bottomY, border.left, border.bottom, bottomLeft.u1, bottomLeft.v1, bottomLeft.u2, bottomLeft.v2, skinColor, clip);
             if (border.bottom)
                 spriteBatch->draw(midX, bottomY, midWidth, border.bottom, bottom.u1, bottom.v1, bottom.u2, bottom.v2, skinColor, clip);
             if (border.bottom && border.right)
@@ -827,8 +874,8 @@ namespace gameplay
             value->setFloat(1, _bounds.y);
             break;
         case ANIMATE_SIZE:
-            value->setFloat(0, _clipBounds.width);
-            value->setFloat(1, _clipBounds.height);
+            value->setFloat(0, _bounds.width);
+            value->setFloat(1, _bounds.height);
             break;
         case ANIMATE_POSITION_X:
             value->setFloat(0, _bounds.x);
@@ -837,10 +884,10 @@ namespace gameplay
             value->setFloat(0, _bounds.y);
             break;
         case ANIMATE_SIZE_WIDTH:
-            value->setFloat(0, _clipBounds.width);
+            value->setFloat(0, _bounds.width);
             break;
         case ANIMATE_SIZE_HEIGHT:
-            value->setFloat(0, _clipBounds.height);
+            value->setFloat(0, _bounds.height);
             break;
         case ANIMATE_OPACITY:
         default:
@@ -915,9 +962,9 @@ namespace gameplay
         }
         else
         {
-            width = Curve::lerp(blendWeight, _clipBounds.width, width);
+            width = Curve::lerp(blendWeight, _bounds.width, width);
         }
-        _clipBounds.width = width;
+        _bounds.width = width;
         _dirty = true;
     }
 
@@ -929,9 +976,9 @@ namespace gameplay
         }
         else
         {
-            height = Curve::lerp(blendWeight, _clipBounds.height, height);
+            height = Curve::lerp(blendWeight, _bounds.height, height);
         }
-        _clipBounds.height = height;
+        _bounds.height = height;
         _dirty = true;
     }
 

+ 17 - 3
gameplay/src/Control.h

@@ -24,6 +24,7 @@ class Control : public Ref, public AnimationTarget
     friend class AbsoluteLayout;
     friend class VerticalLayout;
     friend class FlowLayout;
+    friend class ScrollLayout;
 
 public:
 
@@ -731,14 +732,16 @@ protected:
      * properties, such as its text viewport.
      *
      * @param clip The clipping rectangle of this control's parent container.
+     * @param offset Layout-computed positioning offset to add to the control's position.
      */
-    virtual void update(const Rectangle& clip);
+    virtual void update(const Rectangle& clip, const Vector2& offset);
 
     /**
      * Draw the images 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.
+     * @param offset Layout-computed positioning offset to add to the control's position.
      */
     virtual void drawImages(SpriteBatch* spriteBatch, const Rectangle& clip);
 
@@ -746,6 +749,7 @@ protected:
      * Draw this control's text.
      *
      * @param clip The clipping rectangle of this control's parent container.
+     * @param offset Layout-computed positioning offset to add to the control's position.
      */
     virtual void drawText(const Rectangle& clip);
 
@@ -777,6 +781,14 @@ protected:
      */
     static State getState(const char* state);
 
+    /**
+     * Get a ThemeImage from its ID, for a given state.
+     *
+     * @param id The ID of the image to retrieve.
+     * @param state The state to get this image from.
+     *
+     * @return The requested image, or NULL if none was found.
+     */
     Theme::ThemeImage* getImage(const char* id, State state);
 
     /**
@@ -792,7 +804,8 @@ protected:
     State _state;           // Determines overlay used during draw().
     Rectangle _bounds;      // Position, relative to parent container's clipping window, and desired size.
     Rectangle _clipBounds;  // The position and size of this control, relative to parent container's bounds, including border and padding, after clipping.
-    Rectangle _textBounds;  // The position and size of this control's text area, before clipping.  Used for text alignment.
+    Rectangle _absoluteBounds;
+    Rectangle _viewportBounds;
     Rectangle _clip;        // Clipping window of this control's content, after clipping.
     bool _dirty;
     bool _consumeTouchEvents;
@@ -850,8 +863,9 @@ private:
      *
      * @param spriteBatch The sprite batch containing this control's border images.
      * @param clip The clipping rectangle of this control's parent container.
+     * @param offset Layout-computed positioning offset to add to the control's position.
      */
-    virtual void drawBorder(SpriteBatch* spriteBatch, const Rectangle& clip);
+    virtual void drawBorder(SpriteBatch* spriteBatch, const Rectangle& clip, const Vector2& offset = Vector2::zero());
     
     bool _styleOverridden;
     Theme::Skin* _skin;

+ 94 - 0
gameplay/src/FlowLayout.cpp

@@ -0,0 +1,94 @@
+#include "Base.h"
+#include "Control.h"
+#include "FlowLayout.h"
+#include "Container.h"
+
+namespace gameplay
+{
+
+static FlowLayout* __instance;
+
+FlowLayout::FlowLayout()
+{
+}
+
+FlowLayout::FlowLayout(const FlowLayout& copy)
+{
+}
+
+FlowLayout::~FlowLayout()
+{
+}
+
+FlowLayout* FlowLayout::create()
+{
+    if (!__instance)
+    {
+        __instance = new FlowLayout();
+    }
+    else
+    {
+        __instance->addRef();
+    }
+
+    return __instance;
+}
+
+Layout::Type FlowLayout::getType()
+{
+    return Layout::LAYOUT_FLOW;
+}
+
+void FlowLayout::update(const Container* container)
+{
+    const Rectangle& containerBounds = container->getBounds();
+    const Theme::Border& containerBorder = container->getBorder(container->getState());
+    const Theme::Padding& containerPadding = container->getPadding();
+
+    float clipWidth = containerBounds.width - containerBorder.left - containerBorder.right - containerPadding.left - containerPadding.right;
+    float clipHeight = containerBounds.height - containerBorder.top - containerBorder.bottom - containerPadding.top - containerPadding.bottom;
+
+    float xPosition = 0;
+    float yPosition = 0;
+    float rowY = 0;
+    float tallestHeight = 0;
+
+    std::vector<Control*> controls = container->getControls();
+    unsigned int controlsCount = controls.size();
+    for (unsigned int i = 0; i < controlsCount; i++)
+    {
+        Control* control = controls.at(i);
+
+        //align(control, container);
+
+        const Rectangle& bounds = control->getBounds();
+        const Theme::Margin& margin = control->getMargin();
+
+        xPosition += margin.left;
+
+        // Wrap to next row if we've gone past the edge of the container.
+        if (xPosition + bounds.width >= clipWidth)
+        {
+            xPosition = margin.left;
+            rowY += tallestHeight;
+        }
+
+        yPosition = rowY + margin.top;
+
+        control->setPosition(xPosition, yPosition);
+        if (control->isDirty() || control->isContainer())
+        {
+            control->update(container->getClip(), Vector2::zero());
+        }
+
+        xPosition += bounds.width + margin.right;
+
+        float height = bounds.height + margin.top + margin.bottom;
+        if (height > tallestHeight)
+        {
+            tallestHeight = height;
+        }
+    }
+}
+
+}

+ 60 - 0
gameplay/src/FlowLayout.h

@@ -0,0 +1,60 @@
+#ifndef FLOWLAYOUT_H_
+#define FLOWLAYOUT_H_
+
+#include "Layout.h"
+
+namespace gameplay
+{
+
+class FlowLayout : public Layout
+{
+    friend class Form;
+    friend class Container;
+
+public:
+
+    /**
+     * Get the type of this Layout.
+     *
+     * @return Layout::LAYOUT_FLOW
+     */
+    Layout::Type getType();
+
+protected:
+
+    /**
+     * Create a FlowLayout.
+     *
+     * @return A FlowLayout object.
+     */
+    static FlowLayout* create();
+
+    /**
+     * Update the controls contained by the specified container.
+     *
+     * @param container The container to update.
+     */
+    void update(const Container* container);
+
+private:
+
+    /**
+     * Constructor.
+     */
+    FlowLayout();
+
+    /**
+     * Constructor.
+     */
+    FlowLayout(const FlowLayout& copy);
+
+    /**
+     * Destructor.
+     */
+    virtual ~FlowLayout();
+
+};
+
+}
+
+#endif

+ 42 - 37
gameplay/src/Font.cpp

@@ -207,7 +207,7 @@ void Font::drawText(const char* text, int x, int y, const Vector4& color, unsign
                 switch (delimiter)
                 {
                 case ' ':
-                    xPos += (float)size*0.5f;
+                    xPos += size >> 1;
                     break;
                 case '\r':
                 case '\n':
@@ -215,7 +215,7 @@ void Font::drawText(const char* text, int x, int y, const Vector4& color, unsign
                     xPos = x;
                     break;
                 case '\t':
-                    xPos += ((float)size*0.5f)*4;
+                    xPos += (size >> 1)*4;
                     break;
                 case 0:
                     done = true;
@@ -256,7 +256,7 @@ void Font::drawText(const char* text, int x, int y, const Vector4& color, unsign
             switch (c)
             {
             case ' ':
-                xPos += (float)size*0.5f;
+                xPos += size >> 1;
                 break;
             case '\r':
             case '\n':
@@ -264,7 +264,7 @@ void Font::drawText(const char* text, int x, int y, const Vector4& color, unsign
                 xPos = x;
                 break;
             case '\t':
-                xPos += ((float)size*0.5f)*4;
+                xPos += (size >> 1)*4;
                 break;
             default:
                 int index = c - 32; // HACK for ASCII
@@ -272,7 +272,7 @@ void Font::drawText(const char* text, int x, int y, const Vector4& color, unsign
                 {
                     Glyph& g = _glyphs[index];
                     _batch->draw(xPos, yPos, g.width * scale, size, g.uvs[0], g.uvs[1], g.uvs[2], g.uvs[3], color);
-                    xPos += g.width * scale + ((float)size*0.125f);
+                    xPos += floor(g.width * scale + (float)(size >> 3));
                     break;
                 }
             }
@@ -343,7 +343,7 @@ void Font::drawText(const char* text, const Rectangle& area, const Vector4& colo
                     switch (delimiter)
                     {
                         case ' ':
-                            delimWidth += (float)size*0.5f;
+                            delimWidth += size >> 1;
                             lineLength++;
                             break;
                         case '\r':
@@ -360,7 +360,7 @@ void Font::drawText(const char* text, const Rectangle& area, const Vector4& colo
                             delimWidth = 0;
                             break;
                         case '\t':
-                            delimWidth += ((float)size*0.5f)*4;
+                            delimWidth += (size >> 1)*4;
                             lineLength++;
                             break;
                         case 0:
@@ -388,7 +388,7 @@ void Font::drawText(const char* text, const Rectangle& area, const Vector4& colo
                 // Wrap if necessary.
                 if (lineWidth + tokenWidth + delimWidth > area.width)
                 {
-                    yPos += size;
+                    yPos += (int)size;
 
                     // Push position of current line.
                     if (lineLength)
@@ -439,7 +439,7 @@ void Font::drawText(const char* text, const Rectangle& area, const Vector4& colo
                 char delimiter = token[0];
                 while (delimiter == '\n')
                 {
-                    yPos += size;
+                    yPos += (int)size;
                     ++token;
                     delimiter = token[0];
                 }
@@ -529,10 +529,10 @@ void Font::drawText(const char* text, const Rectangle& area, const Vector4& colo
 
         // Wrap if necessary.
         if (wrap &&
-            xPos + tokenWidth > area.x + area.width ||
+            xPos + (int)tokenWidth > area.x + area.width ||
             (rightToLeft && currentLineLength > lineLength))
         {
-            yPos += size;
+            yPos += (int)size;
             currentLineLength = tokenLength;
 
             if (xPositionsIt != xPositions.end())
@@ -587,7 +587,7 @@ void Font::drawText(const char* text, const Rectangle& area, const Vector4& colo
                         }
                     }
                 }
-                xPos += g.width*scale + ((float)size*0.125f);
+                xPos += (int)(g.width)*scale + (size >> 3);
             }
         }
 
@@ -729,7 +729,7 @@ void Font::measureText(const char* text, const Rectangle& clip, unsigned int siz
                 switch (delimiter)
                 {
                     case ' ':
-                        delimWidth += (float)size*0.5f;
+                        delimWidth += size >> 1;
                         break;
                     case '\r':
                     case '\n':
@@ -765,7 +765,7 @@ void Font::measureText(const char* text, const Rectangle& clip, unsigned int siz
                         delimWidth = 0;
                         break;
                     case '\t':
-                        delimWidth += ((float)size*0.5f)*4;
+                        delimWidth += (size >> 1)*4;
                         break;
                     case 0:
                         reachedEOF = true;
@@ -1010,7 +1010,7 @@ void Font::measureText(const char* text, const Rectangle& clip, unsigned int siz
     }
 }
 
-unsigned int Font::getIndexAtLocation(const char* text, const Rectangle& area, unsigned int size, const Vector2& inLocation, Vector2* outLocation,
+int Font::getIndexAtLocation(const char* text, const Rectangle& area, unsigned int size, const Vector2& inLocation, Vector2* outLocation,
                                       Justify justify, bool wrap, bool rightToLeft)
 {
     return getIndexOrLocation(text, area, size, inLocation, outLocation, -1, justify, wrap, rightToLeft);
@@ -1022,7 +1022,7 @@ void Font::getLocationAtIndex(const char* text, const Rectangle& clip, unsigned
     getIndexOrLocation(text, clip, size, *outLocation, outLocation, (const int)destIndex, justify, wrap, rightToLeft);
 }
 
-unsigned int Font::getIndexOrLocation(const char* text, const Rectangle& area, unsigned int size, const Vector2& inLocation, Vector2* outLocation,
+int Font::getIndexOrLocation(const char* text, const Rectangle& area, unsigned int size, const Vector2& inLocation, Vector2* outLocation,
                                       const int destIndex, Justify justify, bool wrap, bool rightToLeft)
 {
     unsigned int charIndex = 0;
@@ -1078,7 +1078,7 @@ unsigned int Font::getIndexOrLocation(const char* text, const Rectangle& area, u
                     switch (delimiter)
                     {
                         case ' ':
-                            delimWidth += (float)size*0.5f;
+                            delimWidth += size >> 1;
                             lineLength++;
                             break;
                         case '\r':
@@ -1095,7 +1095,7 @@ unsigned int Font::getIndexOrLocation(const char* text, const Rectangle& area, u
                             delimWidth = 0;
                             break;
                         case '\t':
-                            delimWidth += ((float)size*0.5f)*4;
+                            delimWidth += (size >> 1)*4;
                             lineLength++;
                             break;
                         case 0:
@@ -1248,11 +1248,7 @@ unsigned int Font::getIndexOrLocation(const char* text, const Rectangle& area, u
         }
 
         currentLineLength += delimLength;
-        if (result == 0)
-        {
-            break;
-        }
-        else if (result == 2)
+        if (result == 0 || result == 2)
         {
             outLocation->x = xPos;
             outLocation->y = yPos;
@@ -1261,7 +1257,7 @@ unsigned int Font::getIndexOrLocation(const char* text, const Rectangle& area, u
 
         if (destIndex == (int)charIndex ||
             (destIndex == -1 &&
-             inLocation.x >= xPos && inLocation.x < floor(xPos + ((float)size*0.125f)) &&
+             inLocation.x >= xPos && inLocation.x < floor(xPos + (float)(size >> 3)) &&
              inLocation.y >= yPos && inLocation.y < yPos + size))
         {
             outLocation->x = xPos;
@@ -1293,7 +1289,7 @@ unsigned int Font::getIndexOrLocation(const char* text, const Rectangle& area, u
 
         // Wrap if necessary.
         if (wrap &&
-            xPos + tokenWidth > area.x + area.width ||
+            xPos + (int)tokenWidth > area.x + area.width ||
             (rightToLeft && currentLineLength > lineLength))
         {
             yPos += size;
@@ -1334,7 +1330,7 @@ unsigned int Font::getIndexOrLocation(const char* text, const Rectangle& area, u
                 // Check against inLocation.
                 if (destIndex == (int)charIndex ||
                     (destIndex == -1 &&
-                    inLocation.x >= xPos && inLocation.x < floor(xPos + g.width*scale + ((float)size*0.125f)) &&
+                    inLocation.x >= xPos && inLocation.x < floor(xPos + g.width*scale + (float)(size >> 3)) &&
                     inLocation.y >= yPos && inLocation.y < yPos + size))
                 {
                     outLocation->x = xPos;
@@ -1342,7 +1338,7 @@ unsigned int Font::getIndexOrLocation(const char* text, const Rectangle& area, u
                     return charIndex;
                 }
 
-                xPos += g.width*scale + ((float)size*0.125f);
+                xPos += floor(g.width*scale + (float)(size >> 3));
                 charIndex++;
             }
         }
@@ -1413,9 +1409,18 @@ unsigned int Font::getIndexOrLocation(const char* text, const Rectangle& area, u
         }
     }
 
-    outLocation->x = xPos;
-    outLocation->y = yPos;
-    return charIndex;
+
+    if (destIndex == (int)charIndex ||
+        (destIndex == -1 &&
+         inLocation.x >= xPos && inLocation.x < floor(xPos + (float)(size >> 3)) &&
+         inLocation.y >= yPos && inLocation.y < yPos + size))
+    {
+        outLocation->x = xPos;
+        outLocation->y = yPos;
+        return charIndex;
+    }
+    
+    return -1;
 }
 
 unsigned int Font::getTokenWidth(const char* token, unsigned int length, unsigned int size, float scale)
@@ -1428,17 +1433,17 @@ unsigned int Font::getTokenWidth(const char* token, unsigned int length, unsigne
         switch (c)
         {
         case ' ':
-            tokenWidth += (float)size*0.5f;
+            tokenWidth += size >> 1;
             break;
         case '\t':
-            tokenWidth += ((float)size*0.5f)*4;
+            tokenWidth += (size >> 1)*4;
             break;
         default:
             int glyphIndex = c - 32;
             if (glyphIndex >= 0 && glyphIndex < (int)_glyphCount)
             {
                 Glyph& g = _glyphs[glyphIndex];
-                tokenWidth += g.width * scale + ((float)size*0.125f);
+                tokenWidth += floor(g.width * scale + (float)(size >> 3));
             }
             break;
         }
@@ -1481,8 +1486,8 @@ int Font::handleDelimiters(const char** token, const unsigned int size, const in
             delimiter == 0)
     {
         if ((stopAtPosition &&
-            stopAtPosition->x >= *xPos && stopAtPosition->x < floor(*xPos + ((float)size*0.5f)) &&
-            stopAtPosition->y >= *yPos && stopAtPosition->y < *yPos + size) ||
+            stopAtPosition->x >= *xPos && stopAtPosition->x < *xPos + ((int)size >> 1) &&
+            stopAtPosition->y >= *yPos && stopAtPosition->y < *yPos + (int)size) ||
             (currentIndex >= 0 && destIndex >= 0 && currentIndex + (int)*lineLength == destIndex))
         {
             // Success + stopAtPosition was reached.
@@ -1492,7 +1497,7 @@ int Font::handleDelimiters(const char** token, const unsigned int size, const in
         switch (delimiter)
         {
             case ' ':
-                *xPos += (float)size*0.5f;
+                *xPos += size >> 1;
                 (*lineLength)++;
                 if (charIndex)
                 {
@@ -1524,7 +1529,7 @@ int Font::handleDelimiters(const char** token, const unsigned int size, const in
                 }
                 break;
             case '\t':
-                *xPos += ((float)size*0.5f)*4;
+                *xPos += (size >> 1)*4;
                 (*lineLength)++;
                 if (charIndex)
                 {

+ 2 - 2
gameplay/src/Font.h

@@ -181,7 +181,7 @@ public:
     /**
      * Get an index into a string corresponding to the character nearest the given location within the clip region.
      */
-    unsigned int getIndexAtLocation(const char* text, const Rectangle& clip, unsigned int size, const Vector2& inLocation, Vector2* outLocation,
+    int getIndexAtLocation(const char* text, const Rectangle& clip, unsigned int size, const Vector2& inLocation, Vector2* outLocation,
                                     Justify justify = ALIGN_TOP_LEFT, bool wrap = true, bool rightToLeft = false);
 
     /**
@@ -225,7 +225,7 @@ private:
      */
     ~Font();
 
-    unsigned int getIndexOrLocation(const char* text, const Rectangle& clip, unsigned int size, const Vector2& inLocation, Vector2* outLocation,
+    int getIndexOrLocation(const char* text, const Rectangle& clip, unsigned int size, const Vector2& inLocation, Vector2* outLocation,
                                     const int destIndex = -1, Justify justify = ALIGN_TOP_LEFT, bool wrap = true, bool rightToLeft = false);
 
     unsigned int getTokenWidth(const char* token, unsigned int length, unsigned int size, float scale);

+ 3 - 3
gameplay/src/Form.cpp

@@ -153,7 +153,7 @@ namespace gameplay
     {
         if (isDirty())
         {
-            Container::update(Rectangle(0, 0, _bounds.width, _bounds.height));
+            Container::update(Rectangle(0, 0, _bounds.width, _bounds.height), Vector2::zero());
         }
     }
 
@@ -343,7 +343,7 @@ namespace gameplay
                             m.transformPoint(&point);
 
                             // Pass the touch event on.
-                            const Rectangle& bounds = form->getClipBounds();
+                            const Rectangle& bounds = form->getBounds();
                             if (form->getState() == Control::FOCUS ||
                                 (evt == Touch::TOUCH_PRESS &&
                                  point.x >= bounds.x &&
@@ -362,7 +362,7 @@ namespace gameplay
                 else
                 {
                     // Simply compare with the form's bounds.
-                    const Rectangle& bounds = form->getClipBounds();
+                    const Rectangle& bounds = form->getBounds();
                     if (form->getState() == Control::FOCUS ||
                         (evt == Touch::TOUCH_PRESS &&
                          x >= bounds.x &&

+ 7 - 2
gameplay/src/Label.cpp

@@ -19,6 +19,7 @@ namespace gameplay
     {
         Label* label = new Label();
         label->initialize(style, properties);
+        label->_consumeTouchEvents = false;
 
         return label;
     }
@@ -48,6 +49,8 @@ namespace gameplay
             eventFlags &= ~Listener::VALUE_CHANGED;
         }
 
+        _consumeTouchEvents = true;
+
         Control::addListener(listener, eventFlags);
     }
     
@@ -64,9 +67,11 @@ namespace gameplay
         return _text.c_str();
     }
 
-    void Label::update(const Rectangle& clip)
+    void Label::update(const Rectangle& clip, const Vector2& offset)
     {
-        Control::update(clip);
+        Control::update(clip, offset);
+
+        _textBounds.set(_viewportBounds);
 
         _font = getFont(_state);
         _textColor = getTextColor(_state);

+ 2 - 1
gameplay/src/Label.h

@@ -86,7 +86,7 @@ protected:
      */
     virtual void initialize(Theme::Style* style, Properties* properties);
 
-    void update(const Rectangle& clip);
+    void update(const Rectangle& clip, const Vector2& offset);
 
     /**
      * Draw this label's text.
@@ -98,6 +98,7 @@ protected:
     std::string _text;      // The text displayed by this label.
     Font* _font;
     Vector4 _textColor;
+    Rectangle _textBounds;  // The position and size of this control's text area, before clipping.  Used for text alignment.
 
 private:
 

+ 6 - 1
gameplay/src/Layout.cpp

@@ -11,7 +11,7 @@ namespace gameplay
             control->_autoWidth || control->_autoHeight)
         {
             Rectangle controlBounds = control->getBounds();
-            const Rectangle& containerBounds = container->getClipBounds();
+            const Rectangle& containerBounds = container->getBounds();
             const Theme::Border& containerBorder = container->getBorder(container->getState());
             const Theme::Padding& containerPadding = container->getPadding();
 
@@ -51,4 +51,9 @@ namespace gameplay
             control->setBounds(controlBounds);
         }
     }
+
+    bool Layout::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+    {
+        return false;
+    }
 }

+ 23 - 1
gameplay/src/Layout.h

@@ -2,6 +2,7 @@
 #define LAYOUT_H_
 
 #include "Ref.h"
+#include "Touch.h"
 
 namespace gameplay
 {
@@ -42,7 +43,15 @@ public:
          * Absolute layout: Controls are not modified at all by this layout.
          * They must be positioned and sized manually.
          */
-        LAYOUT_ABSOLUTE
+        LAYOUT_ABSOLUTE,
+
+        /**
+         * Scroll layout: Controls may be placed outside the bounds of the container.
+         * The user can then touch and drag to scroll.  By default controls are placed
+         * based on absolute positions in the .form file, but vertical or horizontal
+         * automatic positioning is an available option.
+         */
+        LAYOUT_SCROLL
     };
 
     /**
@@ -67,6 +76,19 @@ protected:
      * @param container The container to align the control within.
      */
     virtual void align(Control* control, const Container* container);
+
+    /**
+     * Touch callback on touch events.  Coordinates are given relative to the container's
+     * content area.
+     *
+     * @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.
+     *
+     * @see Touch::TouchEvent
+     */
+    virtual bool touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
 };
 
 }

+ 0 - 5
gameplay/src/ParticleEmitter.cpp

@@ -34,11 +34,6 @@ ParticleEmitter::ParticleEmitter(SpriteBatch* batch, unsigned int particleCountM
 
     _spriteBatch->getStateBlock()->setDepthWrite(false);
     _spriteBatch->getStateBlock()->setDepthTest(true);
-    /*
-    _spriteBatch->getStateBlock()->setBlend(true);
-    _spriteBatch->getStateBlock()->setBlendSrc(RenderState::BLEND_SRC_ALPHA);
-    _spriteBatch->getStateBlock()->setBlendDst(RenderState::BLEND_ONE_MINUS_SRC_ALPHA);
-    */
 }
 
 ParticleEmitter::~ParticleEmitter()

+ 7 - 7
gameplay/src/ParticleEmitter.h

@@ -610,6 +610,13 @@ public:
      */
     void draw();
 
+    /**
+     * Gets a BlendMode enum from a corresponding string.
+     */
+    static TextureBlending getTextureBlendingFromString(const char* src);
+
+    void setTextureBlending(TextureBlending blending);
+
 private:
 
     /**
@@ -651,13 +658,6 @@ private:
      */
     void generateColor(const Vector4& base, const Vector4& variance, Vector4* dst);
 
-    /**
-     * Gets a BlendMode enum from a corresponding string.
-     */
-    static TextureBlending getTextureBlendingFromString(const char* src);
-
-    void setTextureBlending(TextureBlending blending);
-
     /**
      * Defines the data for a single particle in the system.
      */

+ 6 - 10
gameplay/src/RadioButton.cpp

@@ -87,8 +87,8 @@ bool RadioButton::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int c
         {
             if (_state == Control::ACTIVE)
             {
-                if (x > 0 && x <= _clipBounds.width &&
-                    y > 0 && y <= _clipBounds.height)
+                if (x > _clipBounds.x && x <= _clipBounds.x + _clipBounds.width &&
+                    y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height)
                 {
                     if (!_selected)
                     {
@@ -120,9 +120,9 @@ void RadioButton::clearSelected(const std::string& groupId)
     }
 }
 
-void RadioButton::update(const Rectangle& clip)
+void RadioButton::update(const Rectangle& clip, const Vector2& offset)
 {
-    Label::update(clip);
+    Label::update(clip, offset);
 
     Vector2 size;
     if (_imageSize.isZero())
@@ -160,10 +160,7 @@ void RadioButton::update(const Rectangle& clip)
 void RadioButton::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
 {
     // Left, v-center.
-    // TODO: Set an alignment for radio button images.
-    const Theme::Border& border = getBorder(_state);
-    const Theme::Padding padding = getPadding();
-    
+    // TODO: Set an alignment for radio button images.   
     const Rectangle& region = _image->getRegion();
     const Theme::UVs& uvs = _image->getUVs();
     Vector4 color = _image->getColor();
@@ -179,8 +176,7 @@ void RadioButton::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
         size.set(_imageSize);
     }
 
-    Vector2 pos(clip.x + _bounds.x + border.left + padding.left,
-        clip.y + _bounds.y + (_clipBounds.height - border.bottom - padding.bottom) / 2.0f - size.y / 2.0f);
+    Vector2 pos(_viewportBounds.x, _viewportBounds.y + _viewportBounds.height * 0.5f - size.y * 0.5f);
 
     spriteBatch->draw(pos.x, pos.y, size.x, size.y, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color, _clip);
 }

+ 1 - 1
gameplay/src/RadioButton.h

@@ -114,7 +114,7 @@ protected:
      *
      * @param clip The clipping rectangle of this control's parent container.
      */
-    void update(const Rectangle& clip);
+    void update(const Rectangle& clip, const Vector2& offset);
 
     /**
      * Draw the images associated with this control.

+ 140 - 0
gameplay/src/ScrollLayout.cpp

@@ -0,0 +1,140 @@
+#include "Base.h"
+#include "Control.h"
+#include "ScrollLayout.h"
+#include "Container.h"
+
+namespace gameplay
+{
+
+ScrollLayout::ScrollLayout() 
+    : _scrollPosition(Vector2::zero()), _lastX(0), _lastY(0), _scrolling(false),
+      _positionVertically(false), _positionHorizontally(false)
+{
+}
+
+ScrollLayout::ScrollLayout(const ScrollLayout& copy)
+{
+}
+
+ScrollLayout::~ScrollLayout()
+{
+}
+
+ScrollLayout* ScrollLayout::create()
+{
+    return new ScrollLayout();
+}
+
+Layout::Type ScrollLayout::getType()
+{
+    return Layout::LAYOUT_SCROLL;
+}
+
+void ScrollLayout::update(const Container* container)
+{
+    // Position controls if automatic positioning is enabled.
+    if (_positionVertically && _positionHorizontally)
+    {
+        // Treat as scrollable flow layout.
+    }
+    else if (_positionVertically)
+    {
+        // Scrollable vertical layout.
+    }
+    else if (_positionHorizontally)
+    {
+        // Scrollable horizontal layout.
+    }
+
+    // Calculate total width and height.
+    float totalWidth = 0;
+    float totalHeight = 0;
+    std::vector<Control*> controls = container->getControls();
+    unsigned int controlsCount = controls.size();
+    for (unsigned int i = 0; i < controlsCount; i++)
+    {
+        Control* control = controls.at(i);
+
+        const Rectangle& bounds = control->getBounds();
+        const Theme::Margin& margin = control->getMargin();
+
+        float newWidth = bounds.x + bounds.width + margin.left + margin.right;
+        if (newWidth > totalWidth)
+        {
+            totalWidth = newWidth;
+        }
+
+        float newHeight = bounds.y + bounds.height + margin.top + margin.bottom;
+        if (newHeight > totalHeight)
+        {
+            totalHeight = newHeight;
+        }
+    }
+
+    const Rectangle& containerBounds = container->getBounds();
+    const Theme::Border& containerBorder = container->getBorder(container->getState());
+    const Theme::Padding& containerPadding = container->getPadding();
+
+    float clipWidth = containerBounds.width - containerBorder.left - containerBorder.right - containerPadding.left - containerPadding.right;
+    float clipHeight = containerBounds.height - containerBorder.top - containerBorder.bottom - containerPadding.top - containerPadding.bottom;
+
+    // Stop scrolling when the far edge is reached.
+    if (-_scrollPosition.x > totalWidth - clipWidth)
+    {
+        _scrollPosition.x = -(totalWidth - clipWidth);
+    }
+    
+    if (-_scrollPosition.y > totalHeight - clipHeight)
+    {
+        _scrollPosition.y = -(totalHeight - clipHeight);
+    }
+
+    if (_scrollPosition.x > 0)
+    {
+        _scrollPosition.x = 0;
+    }
+
+    if (_scrollPosition.y > 0)
+    {
+        _scrollPosition.y = 0;
+    }
+
+    // Position controls within scroll area.
+    for (unsigned int i = 0; i < controlsCount; i++)
+    {
+        Control* control = controls.at(i);
+        control->update(container->getClip(), _scrollPosition);
+    }
+}
+
+bool ScrollLayout::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+{
+    switch(evt)
+    {
+    case Touch::TOUCH_PRESS:
+        _lastX = x;
+        _lastY = y;
+        _scrolling = true;
+        break;
+
+    case Touch::TOUCH_MOVE:
+        if (_scrolling)
+        {
+            // Calculate the latest movement delta for the next update to use.
+            _scrollPosition.x += x - _lastX;
+            _scrollPosition.y += y - _lastY;
+            _lastX = x;
+            _lastY = y;
+            return true;
+        }
+        break;
+
+    case Touch::TOUCH_RELEASE:
+        _scrolling = false;
+        break;
+    }
+
+    return false;
+}
+
+}

+ 72 - 0
gameplay/src/ScrollLayout.h

@@ -0,0 +1,72 @@
+#ifndef SCROLLLAYOUT_H_
+#define SCROLLLAYOUT_H_
+
+#include "Layout.h"
+
+namespace gameplay
+{
+
+class ScrollLayout : public Layout
+{
+    friend class Form;
+    friend class Container;
+
+public:
+
+    /**
+     * Get the type of this Layout.
+     *
+     * @return Layout::LAYOUT_SCROLL
+     */
+    Layout::Type getType();
+
+protected:
+
+    /**
+     * Create a ScrollLayout.
+     *
+     * @return A ScrollLayout object.
+     */
+    static ScrollLayout* create();
+
+    /**
+     * Update the controls contained by the specified container.
+     *
+     * @param container The container to update.
+     */
+    void update(const Container* container);
+
+    bool touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
+
+private:
+
+    /**
+     * Constructor.
+     */
+    ScrollLayout();
+
+    /**
+     * Constructor.
+     */
+    ScrollLayout(const ScrollLayout& copy);
+
+    /**
+     * Destructor.
+     */
+    virtual ~ScrollLayout();
+
+    Vector2 _scrollPosition;
+
+    // Previous touch point.
+    int _lastX;
+    int _lastY;
+
+    bool _scrolling;
+
+    bool _positionVertically;
+    bool _positionHorizontally;
+};
+
+}
+
+#endif

+ 14 - 15
gameplay/src/Slider.cpp

@@ -3,7 +3,7 @@
 namespace gameplay
 {
 
-Slider::Slider()
+Slider::Slider() : _minImage(NULL), _maxImage(NULL), _trackImage(NULL), _markerImage(NULL)
 {
 }
 
@@ -87,21 +87,20 @@ bool Slider::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contac
     case Touch::TOUCH_PRESS:
         _state = Control::ACTIVE;
         // Fall through to calculate new value.
-
     case Touch::TOUCH_MOVE:
     case Touch::TOUCH_RELEASE:
         if (_state == ACTIVE &&
-            x > 0 && x <= _clipBounds.width &&
-            y > 0 && y <= _clipBounds.height)
+            x > _clipBounds.x && x <= _clipBounds.x + _clipBounds.width &&
+            y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height)
         {
             // Horizontal case.
             const Theme::Border& border = getBorder(_state);
             const Theme::Padding& padding = getPadding();
-            const Rectangle& minCapRegion = getImageRegion("minCap", _state);
-            const Rectangle& maxCapRegion = getImageRegion("maxCap", _state);
+            const Rectangle& minCapRegion = _minImage->getRegion();
+            const Rectangle& maxCapRegion = _maxImage->getRegion();
 
             float markerPosition = ((float)x - maxCapRegion.width - border.left - padding.left) /
-                (_clipBounds.width - border.left - border.right - padding.left - padding.right - minCapRegion.width - maxCapRegion.width);
+                (_bounds.width - border.left - border.right - padding.left - padding.right - minCapRegion.width - maxCapRegion.width);
             
             if (markerPosition > 1.0f)
             {
@@ -141,9 +140,9 @@ bool Slider::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contac
     return Control::touchEvent(evt, x, y, contactIndex);
 }
 
-void Slider::update(const Rectangle& clip)
+void Slider::update(const Rectangle& clip, const Vector2& offset)
 {
-    Label::update(clip);
+    Label::update(clip, offset);
 
     _minImage = getImage("minCap", _state);
     _maxImage = getImage("maxCap", _state);
@@ -182,21 +181,21 @@ void Slider::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
     trackColor.w *= _opacity;
 
     // Draw order: track, caps, marker.
-    float midY = clip.y + _clipBounds.y + (_clipBounds.height - border.bottom - padding.bottom) / 2.0f;
-    Vector2 pos(clip.x + _clipBounds.x + border.left + padding.left, midY - trackRegion.height / 2.0f);
-    spriteBatch->draw(pos.x, pos.y, _clipBounds.width, trackRegion.height, track.u1, track.v1, track.u2, track.v2, trackColor, _clip);
+    float midY = _viewportBounds.y + (_viewportBounds.height) * 0.5f;
+    Vector2 pos(_viewportBounds.x, midY - trackRegion.height * 0.5f);
+    spriteBatch->draw(pos.x, pos.y, _viewportBounds.width, trackRegion.height, track.u1, track.v1, track.u2, track.v2, trackColor, _clip);
 
     pos.y = midY - minCapRegion.height * 0.5f;
     pos.x -= minCapRegion.width * 0.5f;
     spriteBatch->draw(pos.x, pos.y, minCapRegion.width, minCapRegion.height, minCap.u1, minCap.v1, minCap.u2, minCap.v2, minCapColor, _clip);
         
-    pos.x = clip.x + _clipBounds.x + _clipBounds.width - border.right - padding.right - maxCapRegion.width * 0.5f;
+    pos.x = _viewportBounds.x + _viewportBounds.width - maxCapRegion.width * 0.5f;
     spriteBatch->draw(pos.x, pos.y, maxCapRegion.width, maxCapRegion.height, maxCap.u1, maxCap.v1, maxCap.u2, maxCap.v2, maxCapColor, _clip);
 
     // Percent across.
     float markerPosition = (_value - _min) / (_max - _min);
-    markerPosition *= _clipBounds.width - border.left - padding.left - border.right - padding.right - minCapRegion.width * 0.5f - maxCapRegion.width * 0.5f - markerRegion.width;
-    pos.x = clip.x + _clipBounds.x + border.left + padding.left + minCapRegion.width * 0.5f + markerPosition;
+    markerPosition *= _viewportBounds.width - minCapRegion.width * 0.5f - maxCapRegion.width * 0.5f - markerRegion.width;
+    pos.x = _viewportBounds.x + minCapRegion.width * 0.5f + markerPosition;
     pos.y = midY - markerRegion.height / 2.0f;
     spriteBatch->draw(pos.x, pos.y, markerRegion.width, markerRegion.height, marker.u1, marker.v1, marker.u2, marker.v2, markerColor, _clip);
 }

+ 1 - 1
gameplay/src/Slider.h

@@ -149,7 +149,7 @@ protected:
      */
     void drawImages(SpriteBatch* spriteBatch, const Rectangle& clip);
 
-    void update(const Rectangle& clip);
+    void update(const Rectangle& clip, const Vector2& offset);
 
     float _min;
     float _max;

+ 5 - 8
gameplay/src/SpriteBatch.cpp

@@ -297,14 +297,7 @@ void SpriteBatch::draw(float x, float y, float width, float height, float u1, fl
 
 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.
-    //
+    // Clip the rectangle given by { x, y, width, height } into clip.
     // We need to scale the uvs accordingly as we do this.
 
     // First check to see if we need to draw at all.
@@ -321,7 +314,9 @@ void SpriteBatch::draw(float x, float y, float width, float height, float u1, fl
     if (x < clip.x)
     {
         const float percent = (clip.x - x) / width;
+        const float dx = clip.x - x;
         x = clip.x;
+        width -= dx;
         u1 += uvWidth * percent;
     }
 
@@ -329,7 +324,9 @@ void SpriteBatch::draw(float x, float y, float width, float height, float u1, fl
     if (y < clip.y)
     {
         const float percent = (clip.y - y) / height;
+        const float dy = clip.y - y;
         y = clip.y;
+        height -= dy;
         v1 += uvHeight * percent;
     }
 

+ 57 - 43
gameplay/src/TextBox.cpp

@@ -57,8 +57,14 @@ bool TextBox::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int conta
             _dirty = true;
             return _consumeTouchEvents;
         }
-        else if (!(x > 0 && x <= _clipBounds.width &&
-                    y > 0 && y <= _clipBounds.height))
+        else if (x > _clipBounds.x && x <= _clipBounds.x + _clipBounds.width &&
+                 y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height)
+        {
+            setCaretLocation(x, y);
+            _dirty = true;
+            return _consumeTouchEvents;
+        }
+        else
         {
             _state = NORMAL;
             Game::getInstance()->displayKeyboard(false);
@@ -68,8 +74,8 @@ bool TextBox::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int conta
         break;
     case Touch::TOUCH_MOVE:
         if (_state == FOCUS &&
-            x > 0 && x <= _clipBounds.width &&
-            y > 0 && y <= _clipBounds.height)
+            x > _clipBounds.x && x <= _clipBounds.x + _clipBounds.width &&
+            y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height)
         {
             setCaretLocation(x, y);
             _dirty = true;
@@ -77,14 +83,21 @@ bool TextBox::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int conta
         }
         break;
     case Touch::TOUCH_RELEASE:
-        if (x > 0 && x <= _clipBounds.width &&
-            y > 0 && y <= _clipBounds.height)
+        if (x > _clipBounds.x && x <= _clipBounds.x + _clipBounds.width &&
+            y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height)
         {
             setCaretLocation(x, y);
             _state = FOCUS;
             _dirty = true;
             return _consumeTouchEvents;
         }
+        else
+        {
+            _state = NORMAL;
+            Game::getInstance()->displayKeyboard(false);
+            _dirty = true;
+            return _consumeTouchEvents;
+        }
         break;
     }
 
@@ -122,10 +135,11 @@ void TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
                         Font::Justify textAlignment = getTextAlignment(_state);
                         bool rightToLeft = getTextRightToLeft(_state);
 
-                        unsigned int textIndex = font->getIndexAtLocation(_text.c_str(), _clip, fontSize, _caretLocation, &_caretLocation,
+                        int textIndex = font->getIndexAtLocation(_text.c_str(), _textBounds, fontSize, _caretLocation, &_caretLocation,
                             textAlignment, true, rightToLeft);
+                        
                         _text.erase(textIndex, 1);
-                        font->getLocationAtIndex(_text.c_str(), _clip, fontSize, &_caretLocation, textIndex,
+                        font->getLocationAtIndex(_text.c_str(), _textBounds, fontSize, &_caretLocation, textIndex,
                             textAlignment, true, rightToLeft);
                         _dirty = true;
                         notifyListeners(Listener::TEXT_CHANGED);
@@ -138,9 +152,9 @@ void TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
                         Font::Justify textAlignment = getTextAlignment(_state);
                         bool rightToLeft = getTextRightToLeft(_state);
 
-                        unsigned int textIndex = font->getIndexAtLocation(_text.c_str(), _clip, fontSize, _caretLocation, &_caretLocation,
+                        unsigned int textIndex = font->getIndexAtLocation(_text.c_str(), _textBounds, fontSize, _caretLocation, &_caretLocation,
                             textAlignment, true, rightToLeft);
-                        font->getLocationAtIndex(_text.c_str(), _clip, fontSize, &_caretLocation, textIndex - 1,
+                        font->getLocationAtIndex(_text.c_str(), _textBounds, fontSize, &_caretLocation, textIndex - 1,
                             textAlignment, true, rightToLeft);
                         _dirty = true;
                         break;
@@ -152,9 +166,9 @@ void TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
                         Font::Justify textAlignment = getTextAlignment(_state);
                         bool rightToLeft = getTextRightToLeft(_state);
 
-                        unsigned int textIndex = font->getIndexAtLocation(_text.c_str(), _clip, fontSize, _caretLocation, &_caretLocation,
+                        int textIndex = font->getIndexAtLocation(_text.c_str(), _textBounds, fontSize, _caretLocation, &_caretLocation,
                             textAlignment, true, rightToLeft);
-                        font->getLocationAtIndex(_text.c_str(), _clip, fontSize, &_caretLocation, textIndex + 1,
+                        font->getLocationAtIndex(_text.c_str(), _textBounds, fontSize, &_caretLocation, textIndex + 1,
                             textAlignment, true, rightToLeft);
                         _dirty = true;
                         break;
@@ -167,7 +181,7 @@ void TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
                         bool rightToLeft = getTextRightToLeft(_state);
 
                         _caretLocation.y -= fontSize;
-                        font->getIndexAtLocation(_text.c_str(), _clip, fontSize, _caretLocation, &_caretLocation,
+                        font->getIndexAtLocation(_text.c_str(), _textBounds, fontSize, _caretLocation, &_caretLocation,
                             textAlignment, true, rightToLeft);
                         _dirty = true;
                         break;
@@ -180,7 +194,7 @@ void TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
                         bool rightToLeft = getTextRightToLeft(_state);
 
                         _caretLocation.y += fontSize;
-                        font->getIndexAtLocation(_text.c_str(), _clip, fontSize, _caretLocation, &_caretLocation,
+                        font->getIndexAtLocation(_text.c_str(), _textBounds, fontSize, _caretLocation, &_caretLocation,
                             textAlignment, true, rightToLeft);
                         _dirty = true;
                         break;
@@ -196,7 +210,7 @@ void TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
                 Font::Justify textAlignment = getTextAlignment(_state);
                 bool rightToLeft = getTextRightToLeft(_state);
 
-                unsigned int textIndex = font->getIndexAtLocation(_text.c_str(), _clip, fontSize, _caretLocation, &_caretLocation,
+                int textIndex = font->getIndexAtLocation(_text.c_str(), _textBounds, fontSize, _caretLocation, &_caretLocation,
                     textAlignment, true, rightToLeft);
 
                 switch (key)
@@ -207,7 +221,7 @@ void TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
                         {
                             --textIndex;
                             _text.erase(textIndex, 1);
-                            font->getLocationAtIndex(_text.c_str(), _clip, fontSize, &_caretLocation, textIndex,
+                            font->getLocationAtIndex(_text.c_str(), _textBounds, fontSize, &_caretLocation, textIndex,
                                 textAlignment, true, rightToLeft);
 
                             _dirty = true;
@@ -223,18 +237,18 @@ void TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
                         _text.insert(textIndex, 1, (char)key);
 
                         // Get new location of caret.
-                        font->getLocationAtIndex(_text.c_str(), _clip, fontSize, &_caretLocation, textIndex + 1,
+                        font->getLocationAtIndex(_text.c_str(), _textBounds, fontSize, &_caretLocation, textIndex + 1,
                             textAlignment, true, rightToLeft);
 
                         if (key == ' ')
                         {
                             // If a space was entered, check that caret is still within bounds.
-                            if (_caretLocation.x >= _clip.x + _clip.width ||
-                                _caretLocation.y >= _clip.y + _clip.height)
+                            if (_caretLocation.x >= _textBounds.x + _textBounds.width ||
+                                _caretLocation.y >= _textBounds.y + _textBounds.height)
                             {
                                 // If not, undo the character insertion.
                                 _text.erase(textIndex, 1);
-                                font->getLocationAtIndex(_text.c_str(), _clip, fontSize, &_caretLocation, textIndex,
+                                font->getLocationAtIndex(_text.c_str(), _textBounds, fontSize, &_caretLocation, textIndex,
                                     textAlignment, true, rightToLeft);
 
                                 // No need to check again.
@@ -244,13 +258,13 @@ void TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
 
                         // Always check that the text still fits within the clip region.
                         Rectangle textBounds;
-                        font->measureText(_text.c_str(), _clip, fontSize, &textBounds, textAlignment, true, true);
-                        if (textBounds.x <= _clip.x || textBounds.y <= _clip.y ||
-                            textBounds.width >= _clip.width || textBounds.height >= _clip.height)
+                        font->measureText(_text.c_str(), _textBounds, fontSize, &textBounds, textAlignment, true, true);
+                        if (textBounds.x <= _textBounds.x || textBounds.y <= _textBounds.y ||
+                            textBounds.width >= _textBounds.width || textBounds.height >= _textBounds.height)
                         {
                             // If not, undo the character insertion.
                             _text.erase(textIndex, 1);
-                            font->getLocationAtIndex(_text.c_str(), _clip, fontSize, &_caretLocation, textIndex,
+                            font->getLocationAtIndex(_text.c_str(), _textBounds, fontSize, &_caretLocation, textIndex,
                                 textAlignment, true, rightToLeft);
 
                             // TextBox is not dirty.
@@ -272,21 +286,9 @@ void TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
     _lastKeypress = key;
 }
 
-void TextBox::update(const Rectangle& clip)
+void TextBox::update(const Rectangle& clip, const Vector2& offset)
 {
-    Label::update(clip);
-
-    // Get index into string and cursor location from the last recorded touch location.
-    if (_state == FOCUS)
-    {
-        Font* font = getFont(_state);
-        unsigned int fontSize = getFontSize(_state);
-        Font::Justify textAlignment = getTextAlignment(_state);
-        bool rightToLeft = getTextRightToLeft(_state);
-
-        font->getIndexAtLocation(_text.c_str(), _clip, fontSize, _caretLocation, &_caretLocation,
-            textAlignment, true, rightToLeft);
-    }
+    Label::update(clip, offset);
 
     _fontSize = getFontSize(_state);
     _caretImage = getImage("textCaret", _state);
@@ -304,7 +306,7 @@ void TextBox::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
             Vector4 color = _caretImage->getColor();
             color.w *= _opacity;
 
-            spriteBatch->draw(_caretLocation.x - (region.width / 2.0f), _caretLocation.y, region.width, _fontSize, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color);
+            spriteBatch->draw(_caretLocation.x - (region.width / 2.0f), _caretLocation.y, region.width, _fontSize, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color, _clip);
         }
     }
 
@@ -313,11 +315,23 @@ void TextBox::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
 
 void TextBox::setCaretLocation(int x, int y)
 {
-    const Theme::Border& border = getBorder(_state);
-    Theme::Padding padding = getPadding();
+    // Get index into string and cursor location from the latest touch location.
+    _prevCaretLocation.set(_caretLocation);
+    _caretLocation.set(x + _absoluteBounds.x,
+                       y + _absoluteBounds.y);
 
-    _caretLocation.set(x - border.left - padding.left + _clip.x,
-                       y - border.top - padding.top + _clip.y);
+    Font* font = getFont(_state);
+    unsigned int fontSize = getFontSize(_state);
+    Font::Justify textAlignment = getTextAlignment(_state);
+    bool rightToLeft = getTextRightToLeft(_state);
+
+    int index = font->getIndexAtLocation(_text.c_str(), _textBounds, fontSize, _caretLocation, &_caretLocation,
+            textAlignment, true, rightToLeft);
+
+    if (index == -1)
+    {
+        _caretLocation.set(_prevCaretLocation);
+    }
 }
 
 }

+ 2 - 1
gameplay/src/TextBox.h

@@ -112,7 +112,7 @@ protected:
      *
      * @param clip The clipping rectangle of this control's parent container.
      */
-    void update(const Rectangle& clip);
+    void update(const Rectangle& clip, const Vector2& offset);
 
     /**
      * Draw the images associated with this control.
@@ -123,6 +123,7 @@ protected:
     void drawImages(SpriteBatch* spriteBatch, const Rectangle& clip);
 
     Vector2 _caretLocation;
+    Vector2 _prevCaretLocation;
     unsigned int textIndex;
     int _lastKeypress;
     unsigned int _fontSize;

+ 2 - 2
gameplay/src/VerticalLayout.cpp

@@ -71,7 +71,7 @@ namespace gameplay
 
             align(control, container);
 
-            const Rectangle& bounds = control->getClipBounds();
+            const Rectangle& bounds = control->getBounds();
             const Theme::Margin& margin = control->getMargin();
 
             yPosition += margin.top;
@@ -79,7 +79,7 @@ namespace gameplay
             control->setPosition(0, yPosition);
             if (control->isDirty() || control->isContainer())
             {
-                control->update(container->getClip());
+                control->update(container->getClip(), Vector2::zero());
             }
 
             yPosition += bounds.height + margin.bottom;