Преглед изворни кода

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

Next ablake
Sean Paul Taylor пре 13 година
родитељ
комит
1f4a9874a9

+ 2 - 0
gameplay/gameplay.vcxproj

@@ -90,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" />
@@ -181,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" />

+ 6 - 0
gameplay/gameplay.vcxproj.filters

@@ -279,6 +279,9 @@
     <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">
@@ -554,6 +557,9 @@
     <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">

+ 41 - 41
gameplay/src/AbsoluteLayout.cpp

@@ -5,56 +5,56 @@
 
 namespace gameplay
 {
-    static AbsoluteLayout* __instance;
 
-    AbsoluteLayout::AbsoluteLayout()
-    {
-    }
+static AbsoluteLayout* __instance;
 
-    AbsoluteLayout::AbsoluteLayout(const AbsoluteLayout& copy)
-    {
-    }
+AbsoluteLayout::AbsoluteLayout()
+{
+}
 
-    AbsoluteLayout::~AbsoluteLayout()
-    {
-    }
+AbsoluteLayout::AbsoluteLayout(const AbsoluteLayout& copy)
+{
+}
+
+AbsoluteLayout::~AbsoluteLayout()
+{
+    __instance = NULL;
+}
 
-    AbsoluteLayout* AbsoluteLayout::create()
+AbsoluteLayout* AbsoluteLayout::create()
+{
+    if (!__instance)
     {
-        if (!__instance)
-        {
-            __instance = new AbsoluteLayout();
-        }
-        else
-        {
-            __instance->addRef();
-        }
-
-        return __instance;
+        __instance = new AbsoluteLayout();
     }
-
-    Layout::Type AbsoluteLayout::getType()
+    else
     {
-        return Layout::LAYOUT_ABSOLUTE;
+        __instance->addRef();
     }
 
-    void AbsoluteLayout::update(const Container* container)
+    return __instance;
+}
+
+Layout::Type AbsoluteLayout::getType()
+{
+    return Layout::LAYOUT_ABSOLUTE;
+}
+
+void AbsoluteLayout::update(const Container* container)
+{
+    GP_ASSERT(container);
+
+    // An AbsoluteLayout does nothing to modify the layout of Controls.
+    std::vector<Control*> controls = container->getControls();
+    unsigned int controlsCount = controls.size();
+    for (unsigned int i = 0; i < controlsCount; i++)
     {
-        GP_ASSERT(container);
-
-        // An AbsoluteLayout does nothing to modify the layout of Controls.
-        std::vector<Control*> controls = container->getControls();
-        unsigned int controlsCount = controls.size();
-        for (unsigned int i = 0; i < controlsCount; i++)
-        {
-            Control* control = controls[i];
-            GP_ASSERT(control);
-
-            align(control, container);
-            if (control->isDirty() || control->isContainer())
-            {
-                control->update(container->getClip());
-            }
-        }
+        Control* control = controls[i];
+        GP_ASSERT(control);
+
+        align(control, container);
+        control->update(container->getClip(), Vector2::zero());
     }
+}
+
 }

+ 30 - 30
gameplay/src/Button.cpp

@@ -3,41 +3,41 @@
 
 namespace gameplay
 {
-    Button::Button()
-    {
-    }
 
-    Button::~Button()
-    {
-    }
+Button::Button()
+{
+}
 
-    Button* Button::create(Theme::Style* style, Properties* properties)
-    {
-        Button* button = new Button();
-        button->initialize(style, properties);
+Button::~Button()
+{
+}
 
-        return button;
+Button* Button::create(Theme::Style* style, Properties* properties)
+{
+    Button* button = new Button();
+    button->initialize(style, properties);
+
+    return button;
+}
+
+bool Button::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+{
+    if (!isEnabled())
+    {
+        return false;
     }
 
-    bool Button::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+    switch (evt)
     {
-        if (!isEnabled())
-        {
-            return false;
-        }
-
-        switch (evt)
-        {
-        case Touch::TOUCH_PRESS:
-            _state = Control::ACTIVE;
-            _dirty = true;
-            break;
-        case Touch::TOUCH_RELEASE:
-            _dirty = true;
-            setState(Control::NORMAL);
-            break;
-        }
-
-        return Control::touchEvent(evt, x, y, contactIndex);
+    case Touch::TOUCH_PRESS:
+        setState(Control::ACTIVE);
+        break;
+    case Touch::TOUCH_RELEASE:
+        setState(Control::NORMAL);
+        break;
     }
+
+    return Control::touchEvent(evt, x, y, contactIndex);
+}
+
 }

+ 7 - 17
gameplay/src/CheckBox.cpp

@@ -41,6 +41,7 @@ void CheckBox::setChecked(bool checked)
     if (_checked != checked)
     {
         _checked = checked;
+        _dirty = true;
         notifyListeners(Control::Listener::VALUE_CHANGED);
     }
 }
@@ -79,19 +80,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);
-                    */
                 }
             }
         }
@@ -101,9 +94,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())
@@ -145,8 +138,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();
@@ -163,10 +154,9 @@ 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);
+    spriteBatch->draw(pos.x, pos.y, size.x, size.y, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color, _viewportClipBounds);
 }
 
 }

+ 1 - 1
gameplay/src/CheckBox.h

@@ -120,7 +120,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.

+ 335 - 309
gameplay/src/Container.cpp

@@ -4,421 +4,447 @@
 #include "AbsoluteLayout.h"
 #include "FlowLayout.h"
 #include "VerticalLayout.h"
+#include "ScrollLayout.h"
 #include "Label.h"
 #include "Button.h"
 #include "CheckBox.h"
 #include "RadioButton.h"
 #include "Slider.h"
 #include "TextBox.h"
+#include "Game.h"
 
 namespace gameplay
 {
-    Container::Container() : _layout(NULL)
-    {
-    }
 
-    Container::Container(const Container& copy)
+Container::Container() : _layout(NULL)
+{
+}
+
+Container::Container(const Container& copy)
+{
+}
+
+Container::~Container()
+{
+    std::vector<Control*>::iterator it;
+    for (it = _controls.begin(); it < _controls.end(); it++)
     {
+        SAFE_RELEASE((*it));
     }
 
-    Container::~Container()
-    {
-        std::vector<Control*>::iterator it;
-        for (it = _controls.begin(); it < _controls.end(); it++)
-        {
-            SAFE_RELEASE((*it));
-        }
+    SAFE_RELEASE(_layout);
+}
 
-        SAFE_RELEASE(_layout);
+Container* Container::create(Layout::Type type)
+{
+    Layout* layout = NULL;
+    switch (type)
+    {
+    case Layout::LAYOUT_ABSOLUTE:
+        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::create(Layout::Type type)
-    {
-        Layout* layout = NULL;
-        switch (type)
-        {
-        case Layout::LAYOUT_ABSOLUTE:
-            layout = AbsoluteLayout::create();
-            break;
-        case Layout::LAYOUT_FLOW:
-            layout = FlowLayout::create();
-            break;
-        case Layout::LAYOUT_VERTICAL:
-            layout = VerticalLayout::create();
-            break;
-        }
+    Container* container = new Container();
+    container->_layout = layout;
 
-        Container* container = new Container();
-        container->_layout = layout;
+    return container;
+}
 
-        return container;
-    }
+Container* Container::create(Theme::Style* style, Properties* properties, Theme* theme)
+{
+    GP_ASSERT(properties);
 
-    Container* Container::create(Theme::Style* style, Properties* properties, Theme* theme)
-    {
-        GP_ASSERT(properties);
+    const char* layoutString = properties->getString("layout");
+    Container* container = Container::create(getLayoutType(layoutString));
+    container->initialize(style, properties);
+    container->addControls(theme, properties);
 
-        const char* layoutString = properties->getString("layout");
-        Container* container = Container::create(getLayoutType(layoutString));
-        container->initialize(style, properties);
-        container->addControls(theme, properties);
+    return container;
+}
 
-        return container;
-    }
+void Container::addControls(Theme* theme, Properties* properties)
+{
+    GP_ASSERT(theme);
+    GP_ASSERT(properties);
 
-    void Container::addControls(Theme* theme, Properties* properties)
+    // Add all the controls to this container.
+    Properties* controlSpace = properties->getNextNamespace();
+    while (controlSpace != NULL)
     {
-        GP_ASSERT(theme);
-        GP_ASSERT(properties);
-
-        // Add all the controls to this container.
-        Properties* controlSpace = properties->getNextNamespace();
-        while (controlSpace != NULL)
-        {
-            Control* control = NULL;
-
-            const char* controlStyleName = controlSpace->getString("style");
-            Theme::Style* controlStyle = NULL;
-            GP_ASSERT(controlStyleName);
-            controlStyle = theme->getStyle(controlStyleName);
-            GP_ASSERT(controlStyle);
+        Control* control = NULL;
 
-            std::string controlName(controlSpace->getNamespace());
-            std::transform(controlName.begin(), controlName.end(), controlName.begin(), (int(*)(int))toupper);
-            if (controlName == "LABEL")
-            {
-                control = Label::create(controlStyle, controlSpace);
-            }
-            else if (controlName == "BUTTON")
-            {
-                control = Button::create(controlStyle, controlSpace);
-            }
-            else if (controlName == "CHECKBOX")
-            {
-                control = CheckBox::create(controlStyle, controlSpace);
-            }
-            else if (controlName == "RADIOBUTTON")
-            {
-                control = RadioButton::create(controlStyle, controlSpace);
-            }
-            else if (controlName == "CONTAINER")
-            {
-                control = Container::create(controlStyle, controlSpace, theme);
-            }
-            else if (controlName == "SLIDER")
-            {
-                control = Slider::create(controlStyle, controlSpace);
-            }
-            else if (controlName == "TEXTBOX")
-            {
-                control = TextBox::create(controlStyle, controlSpace);
-            }
-            else
-            {
-                GP_ERROR("Failed to create control; unrecognized control name \'%s\'.", controlName.c_str());
-            }
+        const char* controlStyleName = controlSpace->getString("style");
+        Theme::Style* controlStyle = NULL;
+        GP_ASSERT(controlStyleName);
+        controlStyle = theme->getStyle(controlStyleName);
+        GP_ASSERT(controlStyle);
 
-            // Add the new control to the form.
-            if (control)
-            {
-                addControl(control);
-            }
+        std::string controlName(controlSpace->getNamespace());
+        std::transform(controlName.begin(), controlName.end(), controlName.begin(), (int(*)(int))toupper);
+        if (controlName == "LABEL")
+        {
+            control = Label::create(controlStyle, controlSpace);
+        }
+        else if (controlName == "BUTTON")
+        {
+            control = Button::create(controlStyle, controlSpace);
+        }
+        else if (controlName == "CHECKBOX")
+        {
+            control = CheckBox::create(controlStyle, controlSpace);
+        }
+        else if (controlName == "RADIOBUTTON")
+        {
+            control = RadioButton::create(controlStyle, controlSpace);
+        }
+        else if (controlName == "CONTAINER")
+        {
+            control = Container::create(controlStyle, controlSpace, theme);
+        }
+        else if (controlName == "SLIDER")
+        {
+            control = Slider::create(controlStyle, controlSpace);
+        }
+        else if (controlName == "TEXTBOX")
+        {
+            control = TextBox::create(controlStyle, controlSpace);
+        }
+        else
+        {
+            GP_ERROR("Failed to create control; unrecognized control name \'%s\'.", controlName.c_str());
+        }
 
-            // Get the next control.
-            controlSpace = properties->getNextNamespace();
+        // Add the new control to the form.
+        if (control)
+        {
+            addControl(control);
         }
-    }
 
-    Layout* Container::getLayout()
-    {
-        return _layout;
+        // Get the next control.
+        controlSpace = properties->getNextNamespace();
     }
+}
 
-    unsigned int Container::addControl(Control* control)
-    {
-        GP_ASSERT(control);
-        _controls.push_back(control);
+Layout* Container::getLayout()
+{
+    return _layout;
+}
 
-        return _controls.size() - 1;
-    }
+unsigned int Container::addControl(Control* control)
+{
+    GP_ASSERT(control);
+    _controls.push_back(control);
 
-    void Container::insertControl(Control* control, unsigned int index)
-    {
-        GP_ASSERT(control);
-        std::vector<Control*>::iterator it = _controls.begin() + index;
-        _controls.insert(it, control);
-    }
+    return _controls.size() - 1;
+}
 
-    void Container::removeControl(unsigned int index)
-    {
-        std::vector<Control*>::iterator it = _controls.begin() + index;
-        _controls.erase(it);
-    }
+void Container::insertControl(Control* control, unsigned int index)
+{
+    GP_ASSERT(control);
+    std::vector<Control*>::iterator it = _controls.begin() + index;
+    _controls.insert(it, control);
+}
 
-    void Container::removeControl(const char* id)
+void Container::removeControl(unsigned int index)
+{
+    std::vector<Control*>::iterator it = _controls.begin() + index;
+    _controls.erase(it);
+}
+
+void Container::removeControl(const char* id)
+{
+    std::vector<Control*>::iterator it;
+    for (it = _controls.begin(); it < _controls.end(); it++)
     {
-        std::vector<Control*>::iterator it;
-        for (it = _controls.begin(); it < _controls.end(); it++)
+        Control* c = *it;
+        if (strcmp(id, c->getID()) == 0)
         {
-            Control* c = *it;
-            if (strcmp(id, c->getID()) == 0)
-            {
-                _controls.erase(it);
-                return;
-            }
+            _controls.erase(it);
+            return;
         }
     }
+}
 
-    void Container::removeControl(Control* control)
+void Container::removeControl(Control* control)
+{
+    GP_ASSERT(control);
+    std::vector<Control*>::iterator it;
+    for (it = _controls.begin(); it < _controls.end(); it++)
     {
-        GP_ASSERT(control);
-        std::vector<Control*>::iterator it;
-        for (it = _controls.begin(); it < _controls.end(); it++)
+        if (*it == control)
         {
-            if (*it == control)
-            {
-                _controls.erase(it);
-                return;
-            }
+            _controls.erase(it);
+            return;
         }
     }
+}
 
-    Control* Container::getControl(unsigned int index) const
-    {
-        std::vector<Control*>::const_iterator it = _controls.begin() + index;
-        return *it;
-    }
+Control* Container::getControl(unsigned int index) const
+{
+    std::vector<Control*>::const_iterator it = _controls.begin() + index;
+    return *it;
+}
 
-    Control* Container::getControl(const char* id) const
+Control* Container::getControl(const char* id) const
+{
+    GP_ASSERT(id);
+    std::vector<Control*>::const_iterator it;
+    for (it = _controls.begin(); it < _controls.end(); it++)
     {
-        GP_ASSERT(id);
-        std::vector<Control*>::const_iterator it;
-        for (it = _controls.begin(); it < _controls.end(); it++)
+        Control* c = *it;
+        GP_ASSERT(c);
+        if (strcmp(id, c->getID()) == 0)
         {
-            Control* c = *it;
-            GP_ASSERT(c);
-            if (strcmp(id, c->getID()) == 0)
-            {
-                return c;
-            }
-            else if (c->isContainer())
+            return c;
+        }
+        else if (c->isContainer())
+        {
+            Control* cc = ((Container*)c)->getControl(id);
+            if (cc)
             {
-                Control* cc = ((Container*)c)->getControl(id);
-                if (cc)
-                {
-                    return cc;
-                }
+                return cc;
             }
         }
-
-        return NULL;
     }
 
-    const std::vector<Control*>& Container::getControls() const
-    {
-        return _controls;
-    }
+    return NULL;
+}
 
-    Animation* Container::getAnimation(const char* id) const
-    {
-        std::vector<Control*>::const_iterator itr = _controls.begin();
-        std::vector<Control*>::const_iterator end = _controls.end();
+const std::vector<Control*>& Container::getControls() const
+{
+    return _controls;
+}
+
+Animation* Container::getAnimation(const char* id) const
+{
+    std::vector<Control*>::const_iterator itr = _controls.begin();
+    std::vector<Control*>::const_iterator end = _controls.end();
         
-        Control* control = NULL;
-        for (; itr != end; itr++)
+    Control* control = NULL;
+    for (; itr != end; itr++)
+    {
+        control = *itr;
+        GP_ASSERT(control);
+        Animation* animation = control->getAnimation(id);
+        if (animation)
+            return animation;
+
+        if (control->isContainer())
         {
-            control = *itr;
-            GP_ASSERT(control);
-            Animation* animation = control->getAnimation(id);
+            animation = ((Container*)control)->getAnimation(id);
             if (animation)
                 return animation;
-
-            if (control->isContainer())
-            {
-                animation = ((Container*)control)->getAnimation(id);
-                if (animation)
-                    return animation;
-            }
         }
-
-        return NULL;
     }
 
-    void Container::update(const Rectangle& clip)
-    {
-        // Update this container's viewport.
-        Control::update(clip);
+    return NULL;
+}
 
-        GP_ASSERT(_layout);
-        _layout->update(this);
-    }
+void Container::update(const Rectangle& clip, const Vector2& offset)
+{
+    // Update this container's viewport.
+    Control::update(clip, offset);
 
-    void Container::drawBorder(SpriteBatch* spriteBatch, const Rectangle& clip)
-    {
-        // First draw our own border.
-        Control::drawBorder(spriteBatch, clip);
+    GP_ASSERT(_layout);
+    _layout->update(this);
+}
 
-        // 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;
-            GP_ASSERT(control);
-            control->drawBorder(spriteBatch, _clip);
-        }
+void Container::draw(SpriteBatch* spriteBatch, const Rectangle& clip, bool needsClear, float targetHeight)
+{
+    if (_skin && needsClear)
+    {
+        GL_ASSERT( glEnable(GL_SCISSOR_TEST) );
+        GL_ASSERT( glClearColor(0, 0, 0, 1) );
+        float clearY = targetHeight - _clearBounds.y - _clearBounds.height;
+        GL_ASSERT( glScissor(_clearBounds.x, clearY,
+            _clearBounds.width, _clearBounds.height) );
+        GL_ASSERT( glClear(GL_COLOR_BUFFER_BIT) );
+        GL_ASSERT( glDisable(GL_SCISSOR_TEST) );
+
+        needsClear = false;
     }
 
-    void Container::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
+    Control::drawBorder(spriteBatch, clip);
+
+    std::vector<Control*>::const_iterator it;
+    Rectangle boundsUnion = Rectangle::empty();
+    for (it = _controls.begin(); it < _controls.end(); it++)
     {
-        std::vector<Control*>::const_iterator it;
-        for (it = _controls.begin(); it < _controls.end(); it++)
+        Control* control = *it;
+        GP_ASSERT(control);
+        if (!needsClear || control->isDirty() || control->_clearBounds.intersects(boundsUnion))
         {
-            Control* control = *it;
-            GP_ASSERT(control);
-            control->drawImages(spriteBatch, _clip);
+            control->draw(spriteBatch, _viewportClipBounds, needsClear, targetHeight);
+            Rectangle::combine(control->_clearBounds, boundsUnion, &boundsUnion);
         }
-
-        _dirty = false;
     }
 
-    void Container::drawText(const Rectangle& clip)
-    {
-        std::vector<Control*>::const_iterator it;
-        for (it = _controls.begin(); it < _controls.end(); it++)
-        {
-            Control* control = *it;
-            GP_ASSERT(control);
-            control->drawText(_clip);
-        }
+    _dirty = false;
+}
 
-        _dirty = false;
+bool Container::isDirty()
+{
+    if (_dirty)
+    {
+        return true;
     }
-
-    bool Container::isDirty()
+    else
     {
-        if (_dirty)
-        {
-            return true;
-        }
-        else
+        std::vector<Control*>::const_iterator it;
+        for (it = _controls.begin(); it < _controls.end(); it++)
         {
-            std::vector<Control*>::const_iterator it;
-            for (it = _controls.begin(); it < _controls.end(); it++)
+            GP_ASSERT(*it);
+            if ((*it)->isDirty())
             {
-                GP_ASSERT(*it);
-                if ((*it)->isDirty())
-                {
-                    return true;
-                }
+                return true;
             }
         }
+    }
+
+    return false;
+}
 
+bool Container::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+{
+    if (!isEnabled())
+    {
         return false;
     }
 
-    bool Container::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
-    {
-        if (!isEnabled())
-        {
-            return false;
-        }
+    bool eventConsumed = false;
+    const Theme::Border& border = getBorder(_state);
+    const Theme::Padding& padding = getPadding();
+    float xPos = border.left + padding.left;
+    float yPos = border.top + padding.top;
 
-        bool eventConsumed = false;
-        const Theme::Border& border = getBorder(_state);
-        const Theme::Padding& padding = getPadding();
-        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++)
+    std::vector<Control*>::const_iterator it;
+    for (it = _controls.begin(); it < _controls.end(); it++)
+    {
+        Control* control = *it;
+        GP_ASSERT(control);
+        if (!control->isEnabled())
         {
-            Control* control = *it;
-            GP_ASSERT(control);
-            if (!control->isEnabled())
-            {
-                continue;
-            }
-
-            const Rectangle& bounds = control->getClipBounds();
-            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))
-            {
-                // Pass on the event's clip relative to the control.
-                eventConsumed |= control->touchEvent(evt, x - xPos - bounds.x, y - yPos - bounds.y, contactIndex);
-            }
+            continue;
         }
 
-        if (!isEnabled())
+        const Rectangle& bounds = control->getBounds();
+        float boundsX = bounds.x;
+        float boundsY = bounds.y;
+        if (offset)
         {
-            return (_consumeTouchEvents | eventConsumed);
+            boundsX += offset->x;
+            boundsY += offset->y;
         }
 
-        switch (evt)
+        if (control->getState() != Control::NORMAL ||
+            (evt == Touch::TOUCH_PRESS &&
+                x >= xPos + boundsX &&
+                x <= xPos + boundsX + bounds.width &&
+                y >= yPos + boundsY &&
+                y <= yPos + boundsY + bounds.height))
         {
-        case Touch::TOUCH_PRESS:
-            setState(Control::FOCUS);
-            break;
-        case Touch::TOUCH_RELEASE:
-            setState(Control::NORMAL);
-            break;
+            // Pass on the event's clip relative to the control.
+            eventConsumed |= control->touchEvent(evt, x - xPos - boundsX, y - yPos - boundsY, contactIndex);
         }
-
-        return (_consumeTouchEvents | eventConsumed);
     }
 
-    void Container::keyEvent(Keyboard::KeyEvent evt, int key)
+    if (!isEnabled())
     {
-        std::vector<Control*>::const_iterator it;
-        for (it = _controls.begin(); it < _controls.end(); it++)
-        {
-            Control* control = *it;
-            GP_ASSERT(control);
-            if (!control->isEnabled())
-            {
-                continue;
-            }
-
-            if (control->isContainer() || control->getState() == Control::FOCUS)
-            {
-                control->keyEvent(evt, key);
-            }
-        }
+        return (_consumeTouchEvents | eventConsumed);
     }
 
-    bool Container::isContainer()
+    switch (evt)
     {
-        return true;
+    case Touch::TOUCH_PRESS:
+        setState(Control::FOCUS);
+        break;
+    case Touch::TOUCH_RELEASE:
+        setState(Control::NORMAL);
+        break;
     }
 
-    Layout::Type Container::getLayoutType(const char* layoutString)
+    if (!eventConsumed)
     {
-        if (!layoutString)
+        // Pass the event on to the layout.
+        if (_layout->touchEvent(evt, x - xPos, y - yPos, contactIndex))
         {
-            return Layout::LAYOUT_ABSOLUTE;
+            _dirty = true;
         }
+    }
 
-        std::string layoutName(layoutString);
-        std::transform(layoutName.begin(), layoutName.end(), layoutName.begin(), (int(*)(int))toupper);
-        if (layoutName == "LAYOUT_ABSOLUTE")
-        {
-            return Layout::LAYOUT_ABSOLUTE;
-        }
-        else if (layoutName == "LAYOUT_VERTICAL")
-        {
-            return Layout::LAYOUT_VERTICAL;
-        }
-        else if (layoutName == "LAYOUT_FLOW")
+    return (_consumeTouchEvents | eventConsumed);
+}
+
+void Container::keyEvent(Keyboard::KeyEvent evt, int key)
+{
+    std::vector<Control*>::const_iterator it;
+    for (it = _controls.begin(); it < _controls.end(); it++)
+    {
+        Control* control = *it;
+        GP_ASSERT(control);
+        if (!control->isEnabled())
         {
-            return Layout::LAYOUT_FLOW;
+            continue;
         }
-        else
+
+        if (control->isContainer() || control->getState() == Control::FOCUS)
         {
-            // Default.
-            return Layout::LAYOUT_ABSOLUTE;
+            control->keyEvent(evt, key);
         }
     }
 }
+
+bool Container::isContainer()
+{
+    return true;
+}
+
+Layout::Type Container::getLayoutType(const char* layoutString)
+{
+    if (!layoutString)
+    {
+        return Layout::LAYOUT_ABSOLUTE;
+    }
+
+    std::string layoutName(layoutString);
+    std::transform(layoutName.begin(), layoutName.end(), layoutName.begin(), (int(*)(int))toupper);
+    if (layoutName == "LAYOUT_ABSOLUTE")
+    {
+        return Layout::LAYOUT_ABSOLUTE;
+    }
+    else if (layoutName == "LAYOUT_VERTICAL")
+    {
+        return Layout::LAYOUT_VERTICAL;
+    }
+    else if (layoutName == "LAYOUT_FLOW")
+    {
+        return Layout::LAYOUT_FLOW;
+    }
+    else if (layoutName == "LAYOUT_SCROLL")
+    {
+        return Layout::LAYOUT_SCROLL;
+    }
+    else
+    {
+        // Default.
+        return Layout::LAYOUT_ABSOLUTE;
+    }
+}
+
+}

+ 6 - 4
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.
@@ -174,14 +174,14 @@ protected:
      * @param spriteBatch The sprite batch containing this control's icons.
      * @param clip The clipping rectangle of this container's parent container.
      */
-    virtual void drawImages(SpriteBatch* spriteBatch, const Rectangle& clip);
+    //virtual void drawImages(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);
+    //virtual void drawText(const Rectangle& clip);
 
     /**
      * Touch callback on touch events.  Controls return true if they consume the touch event.
@@ -244,6 +244,8 @@ protected:
 private:
 
     Container(const Container& copy);
+
+    virtual void draw(SpriteBatch* spriteBatch, const Rectangle& clip, bool needsClear, float targetHeight);
 };
 
 }

+ 993 - 910
gameplay/src/Control.cpp

@@ -4,1185 +4,1268 @@
 
 namespace gameplay
 {
-    Control::Control()
-        : _id(""), _state(Control::NORMAL), _bounds(Rectangle::empty()), _clipBounds(Rectangle::empty()), _clip(Rectangle::empty()),
-            _dirty(true), _consumeTouchEvents(true), _listeners(NULL), _styleOverridden(false)
-    {
-    }
 
-    Control::Control(const Control& copy)
-    {
-    }
+Control::Control()
+    : _id(""), _state(Control::NORMAL), _bounds(Rectangle::empty()), _clipBounds(Rectangle::empty()), _viewportClipBounds(Rectangle::empty()),
+        _dirty(true), _consumeTouchEvents(true), _listeners(NULL), _styleOverridden(false)
+{
+}
+
+Control::Control(const Control& copy)
+{
+}
 
-    Control::~Control()
+Control::~Control()
+{
+    if (_listeners)
     {
-        if (_listeners)
+        for (std::map<Listener::EventType, std::list<Listener*>*>::const_iterator itr = _listeners->begin(); itr != _listeners->end(); itr++)
         {
-            for (std::map<Listener::EventType, std::list<Listener*>*>::const_iterator itr = _listeners->begin(); itr != _listeners->end(); itr++)
-            {
-                std::list<Listener*>* list = itr->second;
-                SAFE_DELETE(list);
-            }
-            SAFE_DELETE(_listeners);
+            std::list<Listener*>* list = itr->second;
+            SAFE_DELETE(list);
         }
+        SAFE_DELETE(_listeners);
+    }
 
-        if (_styleOverridden)
-        {
-            SAFE_DELETE(_style);
-        }
+    if (_styleOverridden)
+    {
+        SAFE_DELETE(_style);
     }
+}
+
+void Control::initialize(Theme::Style* style, Properties* properties)
+{
+    GP_ASSERT(properties);
+    _style = style;
+
+    // Properties not defined by the style.
+    _alignment = getAlignment(properties->getString("alignment"));
+    _autoWidth = properties->getBool("autoWidth");
+    _autoHeight = properties->getBool("autoHeight");
 
-    void Control::initialize(Theme::Style* style, Properties* properties)
+    Vector2 position;
+    Vector2 size;
+    if (properties->exists("position"))
+    {
+        properties->getVector2("position", &position);
+    }
+    else
+    {
+        position.x = properties->getFloat("x");
+        position.y = properties->getFloat("y");
+    }
+        
+    if (properties->exists("size"))
+    {
+        properties->getVector2("size", &size);
+    }
+    else
     {
-        GP_ASSERT(properties);
-        _style = style;
+        size.x = properties->getFloat("width");
+        size.y = properties->getFloat("height");
+    }
+    _bounds.set(position.x, position.y, size.x, size.y);
 
-        // Properties not defined by the style.
-        _alignment = getAlignment(properties->getString("alignment"));
-        _autoWidth = properties->getBool("autoWidth");
-        _autoHeight = properties->getBool("autoHeight");
+    _state = Control::getState(properties->getString("state"));
 
-        Vector2 position;
-        Vector2 size;
-        if (properties->exists("position"))
+    const char* id = properties->getId();
+    if (id)
+        _id = id;
+
+    // Potentially override themed properties for all states.
+    overrideThemedProperties(properties, STATE_ALL);
+
+    // Override themed properties on specific states.
+    Properties* innerSpace = properties->getNextNamespace();
+    while (innerSpace != NULL)
+    {
+        std::string spaceName(innerSpace->getNamespace());
+        std::transform(spaceName.begin(), spaceName.end(), spaceName.begin(), (int(*)(int))toupper);
+        if (spaceName == "STATENORMAL")
         {
-            properties->getVector2("position", &position);
+            overrideThemedProperties(innerSpace, NORMAL);
         }
-        else
+        else if (spaceName == "STATEFOCUS")
         {
-            position.x = properties->getFloat("x");
-            position.y = properties->getFloat("y");
+            overrideThemedProperties(innerSpace, FOCUS);
         }
-        
-        if (properties->exists("size"))
+        else if (spaceName == "STATEACTIVE")
         {
-            properties->getVector2("size", &size);
+            overrideThemedProperties(innerSpace, ACTIVE);
         }
-        else
+        else if (spaceName == "STATEDISABLED")
         {
-            size.x = properties->getFloat("width");
-            size.y = properties->getFloat("height");
+            overrideThemedProperties(innerSpace, DISABLED);
         }
-        _bounds.set(position.x, position.y, size.x, size.y);
-
-        _state = Control::getState(properties->getString("state"));
-
-        const char* id = properties->getId();
-        if (id)
-            _id = id;
-
-        // Potentially override themed properties for all states.
-        overrideThemedProperties(properties, STATE_ALL);
-
-        // Override themed properties on specific states.
-        Properties* stateSpace = properties->getNextNamespace();
-        while (stateSpace != NULL)
+        else if (spaceName == "MARGIN")
         {
-            std::string stateName(stateSpace->getNamespace());
-            std::transform(stateName.begin(), stateName.end(), stateName.begin(), (int(*)(int))toupper);
-            if (stateName == "STATENORMAL")
-            {
-                overrideThemedProperties(stateSpace, NORMAL);
-            }
-            else if (stateName == "STATEFOCUS")
-            {
-                overrideThemedProperties(stateSpace, FOCUS);
-            }
-            else if (stateName == "STATEACTIVE")
-            {
-                overrideThemedProperties(stateSpace, ACTIVE);
-            }
-            else if (stateName == "STATEDISABLED")
-            {
-                overrideThemedProperties(stateSpace, DISABLED);
-            }
-            else
-            {
-                // Ignore this case because there can be an inner namespace of a control that does not override a state.
-            }
-
-            stateSpace = properties->getNextNamespace();
+            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"));
         }
-    }
 
-    const char* Control::getID() const
-    {
-        return _id.c_str();
+        innerSpace = properties->getNextNamespace();
     }
+}
 
-    void Control::setPosition(float x, float y)
+const char* Control::getID() const
+{
+    return _id.c_str();
+}
+
+void Control::setPosition(float x, float y)
+{
+    if (x != _bounds.x || y != _bounds.y)
     {
         _bounds.x = x;
         _bounds.y = y;
         _dirty = true;
     }
+}
 
-    void Control::setSize(float width, float height)
+void Control::setSize(float width, float height)
+{
+    if (width != _bounds.width || height != _bounds.height)
     {
         _bounds.width = width;
         _bounds.height = height;
         _dirty = true;
     }
+}
 
-    void Control::setBounds(const Rectangle& bounds)
-    {
-        _bounds.set(bounds);
-    }
-
-    const Rectangle& Control::getBounds() const
-    {
-        return _bounds;
-    }
+void Control::setBounds(const Rectangle& bounds)
+{
+    _bounds.set(bounds);
+}
 
-    float Control::getX() const
-    {
-        return _bounds.x;
-    }
+const Rectangle& Control::getBounds() const
+{
+    return _bounds;
+}
 
-    float Control::getY() const
-    {
-        return _bounds.y;
-    }
+float Control::getX() const
+{
+    return _bounds.x;
+}
 
-    float Control::getWidth() const
-    {
-        return _bounds.width;
-    }
+float Control::getY() const
+{
+    return _bounds.y;
+}
 
-    float Control::getHeight() const
-    {
-        return _bounds.height;
-    }
+float Control::getWidth() const
+{
+    return _bounds.width;
+}
 
-    void Control::setAlignment(Alignment alignment)
-    {
-        _alignment = alignment;
-    }
+float Control::getHeight() const
+{
+    return _bounds.height;
+}
 
-    Control::Alignment Control::getAlignment() const
-    {
-        return _alignment;
-    }
+void Control::setAlignment(Alignment alignment)
+{
+    _alignment = alignment;
+}
 
-    void Control::setAutoWidth(bool autoWidth)
-    {
-        _autoWidth = autoWidth;
-    }
+Control::Alignment Control::getAlignment() const
+{
+    return _alignment;
+}
 
-    bool Control::getAutoWidth() const
-    {
-        return _autoWidth;
-    }
+void Control::setAutoWidth(bool autoWidth)
+{
+    _autoWidth = autoWidth;
+}
 
-    void Control::setAutoHeight(bool autoHeight)
-    {
-        _autoHeight = autoHeight;
-    }
+bool Control::getAutoWidth() const
+{
+    return _autoWidth;
+}
 
-    void Control::setOpacity(float opacity, unsigned char states)
-    {
-        overrideStyle();
-        Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
-        getOverlays(states, overlays);
+void Control::setAutoHeight(bool autoHeight)
+{
+    _autoHeight = autoHeight;
+}
 
-        for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
-        {
-            overlays[i]->setOpacity(opacity);
-        }
-        
-        _dirty = true;
-    }
+void Control::setOpacity(float opacity, unsigned char states)
+{
+    overrideStyle();
+    Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
+    getOverlays(states, overlays);
 
-    float Control::getOpacity(State state) const
+    for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
     {
-        Theme::Style::Overlay* overlay = getOverlay(state);
-        GP_ASSERT(overlay);
-        return overlay->getOpacity();
+        overlays[i]->setOpacity(opacity);
     }
+        
+    _dirty = true;
+}
 
-    void Control::setBorder(float top, float bottom, float left, float right, unsigned char states)
-    {
-        overrideStyle();
-        Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
-        getOverlays(states, overlays);
-
-        for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
-        {
-            overlays[i]->setBorder(top, bottom, left, right);
-        }
+float Control::getOpacity(State state) const
+{
+    Theme::Style::Overlay* overlay = getOverlay(state);
+    GP_ASSERT(overlay);
+    return overlay->getOpacity();
+}
 
-        _dirty = true;
-    }
+void Control::setBorder(float top, float bottom, float left, float right, unsigned char states)
+{
+    overrideStyle();
+    Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
+    getOverlays(states, overlays);
 
-    const Theme::Border& Control::getBorder(State state) const
+    for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
     {
-        Theme::Style::Overlay* overlay = getOverlay(state);
-        GP_ASSERT(overlay);
-        return overlay->getBorder();
+        overlays[i]->setBorder(top, bottom, left, right);
     }
 
-    void Control::setSkinRegion(const Rectangle& region, unsigned char states)
-    {
-        overrideStyle();
-        Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
-        getOverlays(states, overlays);
+    _dirty = true;
+}
 
-        for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
-        {
-            overlays[i]->setSkinRegion(region, _style->_tw, _style->_th);
-        }
+const Theme::Border& Control::getBorder(State state) const
+{
+    Theme::Style::Overlay* overlay = getOverlay(state);
+    GP_ASSERT(overlay);
+    return overlay->getBorder();
+}
 
-        _dirty = true;
-    }
+void Control::setSkinRegion(const Rectangle& region, unsigned char states)
+{
+    overrideStyle();
+    Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
+    getOverlays(states, overlays);
 
-    const Rectangle& Control::getSkinRegion(State state) const
+    for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
     {
-        Theme::Style::Overlay* overlay = getOverlay(state);
-        GP_ASSERT(overlay);
-        return overlay->getSkinRegion();
+        overlays[i]->setSkinRegion(region, _style->_tw, _style->_th);
     }
 
-    const Theme::UVs& Control::getSkinUVs(Theme::Skin::SkinArea area, State state) const
-    {
-        Theme::Style::Overlay* overlay = getOverlay(state);
-        GP_ASSERT(overlay);
-        return overlay->getSkinUVs(area);
-    }
+    _dirty = true;
+}
 
-    void Control::setSkinColor(const Vector4& color, unsigned char states)
-    {
-        overrideStyle();
-        Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
-        getOverlays(states, overlays);
+const Rectangle& Control::getSkinRegion(State state) const
+{
+    Theme::Style::Overlay* overlay = getOverlay(state);
+    GP_ASSERT(overlay);
+    return overlay->getSkinRegion();
+}
 
-        for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
-        {
-            overlays[i]->setSkinColor(color);
-        }
+const Theme::UVs& Control::getSkinUVs(Theme::Skin::SkinArea area, State state) const
+{
+    Theme::Style::Overlay* overlay = getOverlay(state);
+    GP_ASSERT(overlay);
+    return overlay->getSkinUVs(area);
+}
 
-        _dirty = true;
-    }
+void Control::setSkinColor(const Vector4& color, unsigned char states)
+{
+    overrideStyle();
+    Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
+    getOverlays(states, overlays);
 
-    const Vector4& Control::getSkinColor(State state) const
+    for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
     {
-        Theme::Style::Overlay* overlay = getOverlay(state);
-        GP_ASSERT(overlay);
-        return overlay->getSkinColor();
+        overlays[i]->setSkinColor(color);
     }
 
-    void Control::setMargin(float top, float bottom, float left, float right)
-    {
-        GP_ASSERT(_style);
-        _style->setMargin(top, bottom, left, right);
-        _dirty = true;
-    }
+    _dirty = true;
+}
 
-    const Theme::Margin& Control::getMargin() const
-    {
-        GP_ASSERT(_style);
-        return _style->getMargin();
-    }
+const Vector4& Control::getSkinColor(State state) const
+{
+    Theme::Style::Overlay* overlay = getOverlay(state);
+    GP_ASSERT(overlay);
+    return overlay->getSkinColor();
+}
 
-    void Control::setPadding(float top, float bottom, float left, float right)
-    {
-        GP_ASSERT(_style);
-        _style->setPadding(top, bottom, left, right);
-        _dirty = true;
-    }
-    
-    const Theme::Padding& Control::getPadding() const
-    {
-        GP_ASSERT(_style);
-        return _style->getPadding();
-    }
+void Control::setMargin(float top, float bottom, float left, float right)
+{
+    GP_ASSERT(_style);
+    overrideStyle();
+    _style->setMargin(top, bottom, left, right);
+    _dirty = true;
+}
 
-    void Control::setImageRegion(const char* id, const Rectangle& region, unsigned char states)
-    {
-        overrideStyle();
-        Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
-        getOverlays(states, overlays);
+const Theme::Margin& Control::getMargin() const
+{
+    GP_ASSERT(_style);
+    return _style->getMargin();
+}
 
-        for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
-        {
-            overlays[i]->setImageRegion(id, region, _style->_tw, _style->_th);
-        }
+void Control::setPadding(float top, float bottom, float left, float right)
+{
+    GP_ASSERT(_style);
+    overrideStyle();
+    _style->setPadding(top, bottom, left, right);
+    _dirty = true;
+}
+    
+const Theme::Padding& Control::getPadding() const
+{
+    GP_ASSERT(_style);
+    return _style->getPadding();
+}
 
-        _dirty = true;
-    }
+void Control::setImageRegion(const char* id, const Rectangle& region, unsigned char states)
+{
+    overrideStyle();
+    Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
+    getOverlays(states, overlays);
 
-    const Rectangle& Control::getImageRegion(const char* id, State state) const
+    for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
     {
-        Theme::Style::Overlay* overlay = getOverlay(state);
-        GP_ASSERT(overlay);
-        return overlay->getImageRegion(id);
+        overlays[i]->setImageRegion(id, region, _style->_tw, _style->_th);
     }
 
-    void Control::setImageColor(const char* id, const Vector4& color, unsigned char states)
-    {
-        overrideStyle();
-        Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
-        getOverlays(states, overlays);
+    _dirty = true;
+}
 
-        for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
-        {
-            overlays[i]->setImageColor(id, color);
-        }
+const Rectangle& Control::getImageRegion(const char* id, State state) const
+{
+    Theme::Style::Overlay* overlay = getOverlay(state);
+    GP_ASSERT(overlay);
+    return overlay->getImageRegion(id);
+}
 
-        _dirty = true;
-    }
+void Control::setImageColor(const char* id, const Vector4& color, unsigned char states)
+{
+    overrideStyle();
+    Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
+    getOverlays(states, overlays);
 
-    const Vector4& Control::getImageColor(const char* id, State state) const
+    for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
     {
-        Theme::Style::Overlay* overlay = getOverlay(state);
-        GP_ASSERT(overlay);
-        return overlay->getImageColor(id);
+        overlays[i]->setImageColor(id, color);
     }
 
-    const Theme::UVs& Control::getImageUVs(const char* id, State state) const
-    {
-        Theme::Style::Overlay* overlay = getOverlay(state);
-        GP_ASSERT(overlay);
-        return overlay->getImageUVs(id);
-    }
+    _dirty = true;
+}
 
-    void Control::setCursorRegion(const Rectangle& region, unsigned char states)
-    {
-        overrideStyle();
-        Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
-        getOverlays(states, overlays);
+const Vector4& Control::getImageColor(const char* id, State state) const
+{
+    Theme::Style::Overlay* overlay = getOverlay(state);
+    GP_ASSERT(overlay);
+    return overlay->getImageColor(id);
+}
 
-        for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
-        {
-            overlays[i]->setCursorRegion(region, _style->_tw, _style->_th);
-        }
+const Theme::UVs& Control::getImageUVs(const char* id, State state) const
+{
+    Theme::Style::Overlay* overlay = getOverlay(state);
+    GP_ASSERT(overlay);
+    return overlay->getImageUVs(id);
+}
 
-        _dirty = true;
-    }
+void Control::setCursorRegion(const Rectangle& region, unsigned char states)
+{
+    overrideStyle();
+    Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
+    getOverlays(states, overlays);
 
-    const Rectangle& Control::getCursorRegion(State state) const
+    for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
     {
-        Theme::Style::Overlay* overlay = getOverlay(state);
-        GP_ASSERT(overlay);
-        return overlay->getCursorRegion();
+        overlays[i]->setCursorRegion(region, _style->_tw, _style->_th);
     }
 
-    void Control::setCursorColor(const Vector4& color, unsigned char states)
-    {
-        overrideStyle();
-        Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
-        getOverlays(states, overlays);
+    _dirty = true;
+}
 
-        for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
-        {
-            overlays[i]->setCursorColor(color);
-        }
+const Rectangle& Control::getCursorRegion(State state) const
+{
+    Theme::Style::Overlay* overlay = getOverlay(state);
+    GP_ASSERT(overlay);
+    return overlay->getCursorRegion();
+}
 
-        _dirty = true;
-    }
+void Control::setCursorColor(const Vector4& color, unsigned char states)
+{
+    overrideStyle();
+    Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
+    getOverlays(states, overlays);
 
-    const Vector4& Control::getCursorColor(State state)
+    for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
     {
-        Theme::Style::Overlay* overlay = getOverlay(state);
-        GP_ASSERT(overlay);
-        return overlay->getCursorColor();
-    }
-    
-    const Theme::UVs& Control::getCursorUVs(State state)
-    {
-        Theme::Style::Overlay* overlay = getOverlay(state);
-        GP_ASSERT(overlay);
-        return overlay->getCursorUVs();
+        overlays[i]->setCursorColor(color);
     }
 
-    void Control::setFont(Font* font, unsigned char states)
-    {
-        overrideStyle();
-        Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
-        getOverlays(states, overlays);
+    _dirty = true;
+}
 
-        for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
-        {
-            overlays[i]->setFont(font);
-        }
+const Vector4& Control::getCursorColor(State state)
+{
+    Theme::Style::Overlay* overlay = getOverlay(state);
+    GP_ASSERT(overlay);
+    return overlay->getCursorColor();
+}
+    
+const Theme::UVs& Control::getCursorUVs(State state)
+{
+    Theme::Style::Overlay* overlay = getOverlay(state);
+    GP_ASSERT(overlay);
+    return overlay->getCursorUVs();
+}
 
-        _dirty = true;
-    }
+void Control::setFont(Font* font, unsigned char states)
+{
+    overrideStyle();
+    Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
+    getOverlays(states, overlays);
 
-    Font* Control::getFont(State state) const
+    for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
     {
-        Theme::Style::Overlay* overlay = getOverlay(state);
-        GP_ASSERT(overlay);
-        return overlay->getFont();
+        overlays[i]->setFont(font);
     }
 
-    void Control::setFontSize(unsigned int fontSize, unsigned char states)
-    {
-        overrideStyle();
-        Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
-        getOverlays(states, overlays);
+    _dirty = true;
+}
 
-        for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
-        {
-            overlays[i]->setFontSize(fontSize);
-        }
+Font* Control::getFont(State state) const
+{
+    Theme::Style::Overlay* overlay = getOverlay(state);
+    GP_ASSERT(overlay);
+    return overlay->getFont();
+}
 
-        _dirty = true;
-    }
+void Control::setFontSize(unsigned int fontSize, unsigned char states)
+{
+    overrideStyle();
+    Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
+    getOverlays(states, overlays);
 
-    unsigned int Control::getFontSize(State state) const
+    for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
     {
-        Theme::Style::Overlay* overlay = getOverlay(state);
-        GP_ASSERT(overlay);
-        return overlay->getFontSize();
+        overlays[i]->setFontSize(fontSize);
     }
 
-    void Control::setTextColor(const Vector4& color, unsigned char states)
-    {
-        overrideStyle();
-        Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
-        getOverlays(states, overlays);
+    _dirty = true;
+}
 
-        for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
-        {
-            overlays[i]->setTextColor(color);
-        }
+unsigned int Control::getFontSize(State state) const
+{
+    Theme::Style::Overlay* overlay = getOverlay(state);
+    GP_ASSERT(overlay);
+    return overlay->getFontSize();
+}
 
-        _dirty = true;
-    }
+void Control::setTextColor(const Vector4& color, unsigned char states)
+{
+    overrideStyle();
+    Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
+    getOverlays(states, overlays);
 
-    const Vector4& Control::getTextColor(State state) const
+    for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
     {
-        Theme::Style::Overlay* overlay = getOverlay(state);
-        GP_ASSERT(overlay);
-        return overlay->getTextColor();
+        overlays[i]->setTextColor(color);
     }
 
-    void Control::setTextAlignment(Font::Justify alignment, unsigned char states)
-    {
-        overrideStyle();
-        Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
-        getOverlays(states, overlays);
+    _dirty = true;
+}
 
-        for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
-        {
-            overlays[i]->setTextAlignment(alignment);
-        }
+const Vector4& Control::getTextColor(State state) const
+{
+    Theme::Style::Overlay* overlay = getOverlay(state);
+    GP_ASSERT(overlay);
+    return overlay->getTextColor();
+}
 
-        _dirty = true;
-    }
+void Control::setTextAlignment(Font::Justify alignment, unsigned char states)
+{
+    overrideStyle();
+    Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
+    getOverlays(states, overlays);
 
-    Font::Justify Control::getTextAlignment(State state) const
+    for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
     {
-        Theme::Style::Overlay* overlay = getOverlay(state);
-        GP_ASSERT(overlay);
-        return overlay->getTextAlignment();
+        overlays[i]->setTextAlignment(alignment);
     }
 
-    void Control::setTextRightToLeft(bool rightToLeft, unsigned char states)
-    {
-        overrideStyle();
-        Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
-        getOverlays(states, overlays);
+    _dirty = true;
+}
 
-        for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
-        {
-            overlays[i]->setTextRightToLeft(rightToLeft);
-        }
+Font::Justify Control::getTextAlignment(State state) const
+{
+    Theme::Style::Overlay* overlay = getOverlay(state);
+    GP_ASSERT(overlay);
+    return overlay->getTextAlignment();
+}
 
-        _dirty = true;
-    }
+void Control::setTextRightToLeft(bool rightToLeft, unsigned char states)
+{
+    overrideStyle();
+    Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
+    getOverlays(states, overlays);
 
-    bool Control::getTextRightToLeft(State state) const
+    for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
     {
-        Theme::Style::Overlay* overlay = getOverlay(state);
-        GP_ASSERT(overlay);
-        return overlay->getTextRightToLeft();
+        overlays[i]->setTextRightToLeft(rightToLeft);
     }
 
-    const Rectangle& Control::getClipBounds() const
-    {
-        return _clipBounds;
-    }
+    _dirty = true;
+}
 
-    const Rectangle& Control::getClip() const
-    {
-        return _clip;
-    }
+bool Control::getTextRightToLeft(State state) const
+{
+    Theme::Style::Overlay* overlay = getOverlay(state);
+    GP_ASSERT(overlay);
+    return overlay->getTextRightToLeft();
+}
 
-    void Control::setStyle(Theme::Style* style)
-    {
-        if (style != _style)
-        {
-            _dirty = true;
-        }
+const Rectangle& Control::getClipBounds() const
+{
+    return _clipBounds;
+}
 
-        _style = style;
-    }
+const Rectangle& Control::getClip() const
+{
+    return _viewportClipBounds;
+}
 
-    Theme::Style* Control::getStyle() const
+void Control::setStyle(Theme::Style* style)
+{
+    if (style != _style)
     {
-        return _style;
+        _dirty = true;
     }
 
-    void Control::setState(State state)
-    {
-        _state = state;
+    _style = style;
+}
+
+Theme::Style* Control::getStyle() const
+{
+    return _style;
+}
+
+void Control::setState(State state)
+{
+    if (getOverlay(_state) != getOverlay(state))
         _dirty = true;
-    }
 
-    Control::State Control::getState() const
-    {
-        return _state;
+    _state = state;
+}
+
+Control::State Control::getState() const
+{
+    return _state;
+}
+
+void Control::disable()
+{
+    _state = DISABLED;
+    _dirty = true;
+}
+
+void Control::enable()
+{
+    _state = NORMAL;
+    _dirty = true;
+}
+
+bool Control::isEnabled()
+{
+    return _state != DISABLED;
+}
+
+Theme::Style::OverlayType Control::getOverlayType() const
+{
+    switch (_state)
+    {
+    case Control::NORMAL:
+        return Theme::Style::OVERLAY_NORMAL;
+    case Control::FOCUS:
+        return Theme::Style::OVERLAY_FOCUS;
+    case Control::ACTIVE:
+        return Theme::Style::OVERLAY_ACTIVE;
+    case Control::DISABLED:
+        return Theme::Style::OVERLAY_DISABLED;
+    default:
+        return Theme::Style::OVERLAY_NORMAL;
     }
+}
+
+void Control::setConsumeTouchEvents(bool consume)
+{
+    _consumeTouchEvents = consume;
+}
+    
+bool Control::getConsumeTouchEvents()
+{
+    return _consumeTouchEvents;
+}
 
-    void Control::disable()
+void Control::addListener(Control::Listener* listener, int eventFlags)
+{
+    GP_ASSERT(listener);
+
+    if ((eventFlags & Listener::PRESS) == Listener::PRESS)
     {
-        _state = DISABLED;
-        _dirty = true;
+        addSpecificListener(listener, Listener::PRESS);
     }
 
-    void Control::enable()
+    if ((eventFlags & Listener::RELEASE) == Listener::RELEASE)
     {
-        _state = NORMAL;
-        _dirty = true;
+        addSpecificListener(listener, Listener::RELEASE);
     }
 
-    bool Control::isEnabled()
+    if ((eventFlags & Listener::CLICK) == Listener::CLICK)
     {
-        return _state != DISABLED;
+        addSpecificListener(listener, Listener::CLICK);
     }
 
-    Theme::Style::OverlayType Control::getOverlayType() const
+    if ((eventFlags & Listener::VALUE_CHANGED) == Listener::VALUE_CHANGED)
     {
-        switch (_state)
-        {
-        case Control::NORMAL:
-            return Theme::Style::OVERLAY_NORMAL;
-        case Control::FOCUS:
-            return Theme::Style::OVERLAY_FOCUS;
-        case Control::ACTIVE:
-            return Theme::Style::OVERLAY_ACTIVE;
-        case Control::DISABLED:
-            return Theme::Style::OVERLAY_DISABLED;
-        default:
-            return Theme::Style::OVERLAY_NORMAL;
-        }
+        addSpecificListener(listener, Listener::VALUE_CHANGED);
     }
 
-    void Control::setConsumeTouchEvents(bool consume)
+    if ((eventFlags & Listener::TEXT_CHANGED) == Listener::TEXT_CHANGED)
     {
-        _consumeTouchEvents = consume;
+        addSpecificListener(listener, Listener::TEXT_CHANGED);
     }
-    
-    bool Control::getConsumeTouchEvents()
+}
+
+void Control::addSpecificListener(Control::Listener* listener, Listener::EventType eventType)
+{
+    GP_ASSERT(listener);
+
+    if (!_listeners)
     {
-        return _consumeTouchEvents;
+        _listeners = new std::map<Listener::EventType, std::list<Listener*>*>();
     }
 
-    void Control::addListener(Control::Listener* listener, int eventFlags)
+    std::map<Listener::EventType, std::list<Listener*>*>::const_iterator itr = _listeners->find(eventType);
+    if (itr == _listeners->end())
     {
-        GP_ASSERT(listener);
-
-        if ((eventFlags & Listener::PRESS) == Listener::PRESS)
-        {
-            addSpecificListener(listener, Listener::PRESS);
-        }
+        _listeners->insert(std::make_pair(eventType, new std::list<Listener*>()));
+        itr = _listeners->find(eventType);
+    }
 
-        if ((eventFlags & Listener::RELEASE) == Listener::RELEASE)
-        {
-            addSpecificListener(listener, Listener::RELEASE);
-        }
+    std::list<Listener*>* listenerList = itr->second;
+    listenerList->push_back(listener);
+}
 
-        if ((eventFlags & Listener::CLICK) == Listener::CLICK)
-        {
-            addSpecificListener(listener, Listener::CLICK);
-        }
+bool Control::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+{
+    if (!isEnabled())
+    {
+        return false;
+    }
 
-        if ((eventFlags & Listener::VALUE_CHANGED) == Listener::VALUE_CHANGED)
-        {
-            addSpecificListener(listener, Listener::VALUE_CHANGED);
-        }
+    switch (evt)
+    {
+    case Touch::TOUCH_PRESS:
+        notifyListeners(Listener::PRESS);
+        break;
+            
+    case Touch::TOUCH_RELEASE:
+        // Always trigger Listener::RELEASE
+        notifyListeners(Listener::RELEASE);
 
-        if ((eventFlags & Listener::TEXT_CHANGED) == Listener::TEXT_CHANGED)
+        // Only trigger Listener::CLICK if both PRESS and RELEASE took place within the control's bounds.
+        if (x > 0 && x <= _bounds.width &&
+            y > 0 && y <= _bounds.height)
         {
-            addSpecificListener(listener, Listener::TEXT_CHANGED);
+            notifyListeners(Listener::CLICK);
         }
+        break;
     }
 
-    void Control::addSpecificListener(Control::Listener* listener, Listener::EventType eventType)
-    {
-        GP_ASSERT(listener);
+    return _consumeTouchEvents;
+}
 
-        if (!_listeners)
-        {
-            _listeners = new std::map<Listener::EventType, std::list<Listener*>*>();
-        }
+void Control::keyEvent(Keyboard::KeyEvent evt, int key)
+{
+}
 
+void Control::notifyListeners(Listener::EventType eventType)
+{
+    if (_listeners)
+    {
         std::map<Listener::EventType, std::list<Listener*>*>::const_iterator itr = _listeners->find(eventType);
-        if (itr == _listeners->end())
+        if (itr != _listeners->end())
         {
-            _listeners->insert(std::make_pair(eventType, new std::list<Listener*>()));
-            itr = _listeners->find(eventType);
+            std::list<Listener*>* listenerList = itr->second;
+            for (std::list<Listener*>::iterator listenerItr = listenerList->begin(); listenerItr != listenerList->end(); listenerItr++)
+            {
+                GP_ASSERT(*listenerItr);
+                (*listenerItr)->controlEvent(this, eventType);
+            }
         }
-
-        std::list<Listener*>* listenerList = itr->second;
-        listenerList->push_back(listener);
     }
+}
 
-    bool Control::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
-    {
-        if (!isEnabled())
-        {
-            return false;
-        }
+void Control::update(const Rectangle& clip, const Vector2& offset)
+{
+    _clearBounds.set(_absoluteClipBounds);
 
-        switch (evt)
-        {
-        case Touch::TOUCH_PRESS:
-            notifyListeners(Listener::PRESS);
-            break;
-            
-        case Touch::TOUCH_RELEASE:
-            // Always trigger Listener::RELEASE
-            notifyListeners(Listener::RELEASE);
+    // Calculate the clipped bounds.
+    float x = _bounds.x + offset.x;
+    float y = _bounds.y + offset.y;
+    float width = _bounds.width;
+    float height = _bounds.height;
 
-            // 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)
-            {
-                notifyListeners(Listener::CLICK);
-            }
-            break;
-        }
+    float clipX2 = clip.x + clip.width;
+    float x2 = clip.x + x + width;
+    if (x2 > clipX2)
+        width -= x2 - clipX2;
 
-        return _consumeTouchEvents;
-    }
+    float clipY2 = clip.y + clip.height;
+    float y2 = clip.y + y + height;
+    if (y2 > clipY2)
+        height -= y2 - clipY2;
 
-    void Control::keyEvent(Keyboard::KeyEvent evt, int key)
+    if (x < 0)
     {
+        width += x;
+        x = -x;
     }
-
-    void Control::notifyListeners(Listener::EventType eventType)
+    else
     {
-        if (_listeners)
-        {
-            std::map<Listener::EventType, std::list<Listener*>*>::const_iterator itr = _listeners->find(eventType);
-            if (itr != _listeners->end())
-            {
-                std::list<Listener*>* listenerList = itr->second;
-                for (std::list<Listener*>::iterator listenerItr = listenerList->begin(); listenerItr != listenerList->end(); listenerItr++)
-                {
-                    GP_ASSERT(*listenerItr);
-                    (*listenerItr)->controlEvent(this, eventType);
-                }
-            }
-        }
+        x = 0;
     }
 
-    void Control::update(const Rectangle& clip)
+    if (y < 0)
     {
-        // Calculate the bounds.
-        float x = clip.x + _bounds.x;
-        float y = clip.y + _bounds.y;
-        float width = _bounds.width;
-        float height = _bounds.height;
+        height += y;
+        y = -y;
+    }
+    else
+    {
+        y = 0;
+    }
 
-        float clipX2 = clip.x + clip.width;
-        float x2 = x + width;
-        if (x2 > clipX2)
-            width = clipX2 - x;
+    _clipBounds.set(x, y, width, height);
 
-        float clipY2 = clip.y + clip.height;
-        float y2 = y + height;
-        if (y2 > clipY2)
-            height = clipY2 - y;
+    // 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);
 
-        _clipBounds.set(_bounds.x, _bounds.y, width, height);
+    // Calculate the absolute viewport bounds.
+    // Absolute bounds minus border and padding.
+    const Theme::Border& border = getBorder(_state);
+    const Theme::Padding& padding = getPadding();
 
-        // Calculate the clipping viewport.
-        const Theme::Border& border = getBorder(_state);
-        const Theme::Padding& padding = getPadding();
+    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;
 
-        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;
+    _viewportBounds.set(x, y, width, height);
 
-        _textBounds.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)
+        width = clipX2 - x;
 
-        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;
 
-        clipY2 = clip.y + clip.height;
-        y2 = y + height;
-        if (y2 > clipY2)
-            height = clipY2 - y;
+    if (x < clip.x)
+    {
+        float dx = clip.x - x;
+        width -= dx;
+        x = clip.x;
+    }
 
-        if (x < clip.x)
-            x = clip.x;
+    if (y < clip.y)
+    {
+        float dy = clip.y - y;
+        height -= dy;
+        y = clip.y;
+    }
+ 
+    _viewportClipBounds.set(x, y, width, height);
 
-        if (y < clip.y)
-            y = clip.y;
+    _absoluteClipBounds.set(x - border.left - padding.left, y - border.top - padding.top,
+        width + border.left + padding.left + border.right + padding.right,
+        height + border.top + padding.top + border.bottom + padding.bottom);
+    if (_clearBounds.isEmpty())
+    {
+        _clearBounds.set(_absoluteClipBounds);
+    }
 
-        _clip.set(x, y, width, height);
+    // Cache themed attributes for performance.
+    _skin = getSkin(_state);
+    _opacity = getOpacity(_state);
+}
 
-        _skin = getSkin(_state);
-        _opacity = getOpacity(_state);
+void Control::drawBorder(SpriteBatch* spriteBatch, const Rectangle& clip)
+{
+    if (!spriteBatch || !_skin || _bounds.width <= 0 || _bounds.height <= 0)
+        return;
+
+    // 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);
+    const Theme::UVs& topRight = _skin->getUVs(Theme::Skin::TOP_RIGHT);
+    const Theme::UVs& left = _skin->getUVs(Theme::Skin::LEFT);
+    const Theme::UVs& center = _skin->getUVs(Theme::Skin::CENTER);
+    const Theme::UVs& right = _skin->getUVs(Theme::Skin::RIGHT);
+    const Theme::UVs& bottomLeft = _skin->getUVs(Theme::Skin::BOTTOM_LEFT);
+    const Theme::UVs& bottom = _skin->getUVs(Theme::Skin::BOTTOM);
+    const Theme::UVs& bottomRight = _skin->getUVs(Theme::Skin::BOTTOM_RIGHT);
+
+    // Calculate screen-space positions.
+    const Theme::Border& border = getBorder(_state);
+    const Theme::Padding& padding = getPadding();
+    Vector4 skinColor = _skin->getColor();
+    skinColor.w *= _opacity;
+
+    float midWidth = _bounds.width - border.left - border.right;
+    float midHeight = _bounds.height - border.top - 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(_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(_absoluteBounds.x, _absoluteBounds.y, border.left, border.top, topLeft.u1, topLeft.v1, topLeft.u2, topLeft.v2, skinColor, clip);
+        if (border.top)
+            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, _absoluteBounds.y, border.right, border.top, topRight.u1, topRight.v1, topRight.u2, topRight.v2, skinColor, clip);
+        if (border.left)
+            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(_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(_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)
+            spriteBatch->draw(rightX, bottomY, border.right, border.bottom, bottomRight.u1, bottomRight.v1, bottomRight.u2, bottomRight.v2, skinColor, clip);
     }
+}
 
-    void Control::drawBorder(SpriteBatch* spriteBatch, const Rectangle& clip)
-    {
-        if (!spriteBatch || !_skin || _bounds.width <= 0 || _bounds.height <= 0)
-            return;
+void Control::drawImages(SpriteBatch* spriteBatch, const Rectangle& position)
+{
+}
 
-        Vector2 pos(clip.x + _bounds.x, clip.y + _bounds.y);
+void Control::drawText(const Rectangle& position)
+{
+}
 
-        // 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);
-        const Theme::UVs& topRight = _skin->getUVs(Theme::Skin::TOP_RIGHT);
-        const Theme::UVs& left = _skin->getUVs(Theme::Skin::LEFT);
-        const Theme::UVs& center = _skin->getUVs(Theme::Skin::CENTER);
-        const Theme::UVs& right = _skin->getUVs(Theme::Skin::RIGHT);
-        const Theme::UVs& bottomLeft = _skin->getUVs(Theme::Skin::BOTTOM_LEFT);
-        const Theme::UVs& bottom = _skin->getUVs(Theme::Skin::BOTTOM);
-        const Theme::UVs& bottomRight = _skin->getUVs(Theme::Skin::BOTTOM_RIGHT);
+void Control::draw(SpriteBatch* spriteBatch, const Rectangle& clip, bool needsClear, float targetHeight)
+{
+    if (needsClear)
+    {
+        GL_ASSERT( glEnable(GL_SCISSOR_TEST) );
+        GL_ASSERT( glClearColor(0, 0, 0, 1) );
+        GL_ASSERT( glScissor(_clearBounds.x, targetHeight - _clearBounds.y - _clearBounds.height,
+            _clearBounds.width, _clearBounds.height) );
+        GL_ASSERT( glClear(GL_COLOR_BUFFER_BIT) );
+        GL_ASSERT( glDisable(GL_SCISSOR_TEST) );
+    }
 
-        // Calculate screen-space positions.
-        const Theme::Border& border = getBorder(_state);
-        const Theme::Padding& padding = getPadding();
-        Vector4 skinColor = _skin->getColor();
-        skinColor.w *= _opacity;
+    drawBorder(spriteBatch, clip);
+    drawImages(spriteBatch, clip);
+    drawText(clip);
+    _dirty = false;
+}
 
-        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;
+bool Control::isDirty()
+{
+    return _dirty;
+}
 
-        // 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);
-        }
-        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);
-            if (border.top)
-                spriteBatch->draw(pos.x + border.left, pos.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);
-            if (border.left)
-                spriteBatch->draw(pos.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,
-                    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);
-            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)
-                spriteBatch->draw(rightX, bottomY, border.right, border.bottom, bottomRight.u1, bottomRight.v1, bottomRight.u2, bottomRight.v2, skinColor, clip);
-        }
-    }
+bool Control::isContainer()
+{
+    return false;
+}
 
-    void Control::drawImages(SpriteBatch* spriteBatch, const Rectangle& position)
+Control::State Control::getState(const char* state)
+{
+    if (!state)
     {
+        return NORMAL;
     }
 
-    void Control::drawText(const Rectangle& position)
+    if (strcmp(state, "NORMAL") == 0)
     {
+        return NORMAL;
     }
-
-    bool Control::isDirty()
+    else if (strcmp(state, "ACTIVE") == 0)
     {
-        return _dirty;
+        return ACTIVE;
     }
-
-    bool Control::isContainer()
+    else if (strcmp(state, "FOCUS") == 0)
     {
-        return false;
+        return FOCUS;
+    }
+    else if (strcmp(state, "DISABLED") == 0)
+    {
+        return DISABLED;
     }
 
-    Control::State Control::getState(const char* state)
+    return NORMAL;
+}
+
+Theme::ThemeImage* Control::getImage(const char* id, State state)
+{
+    Theme::Style::Overlay* overlay = getOverlay(state);
+    GP_ASSERT(overlay);
+    Theme::ImageList* imageList = overlay->getImageList();
+    GP_ASSERT(imageList);
+    return imageList->getImage(id);
+}
+
+// Implementation of AnimationHandler
+unsigned int Control::getAnimationPropertyComponentCount(int propertyId) const
+{
+    switch(propertyId)
     {
-        if (!state)
-        {
-            return NORMAL;
-        }
+    case ANIMATE_POSITION:
+    case ANIMATE_SIZE:
+        return 2;
 
-        if (strcmp(state, "NORMAL") == 0)
-        {
-            return NORMAL;
-        }
-        else if (strcmp(state, "ACTIVE") == 0)
-        {
-            return ACTIVE;
-        }
-        else if (strcmp(state, "FOCUS") == 0)
-        {
-            return FOCUS;
-        }
-        else if (strcmp(state, "DISABLED") == 0)
-        {
-            return DISABLED;
-        }
+    case ANIMATE_POSITION_X:
+    case ANIMATE_POSITION_Y:
+    case ANIMATE_SIZE_WIDTH:
+    case ANIMATE_SIZE_HEIGHT:
+    case ANIMATE_OPACITY:
+        return 1;
 
-        return NORMAL;
+    default:
+        return -1;
     }
+}
 
-    Theme::ThemeImage* Control::getImage(const char* id, State state)
+void Control::getAnimationPropertyValue(int propertyId, AnimationValue* value)
+{
+    GP_ASSERT(value);
+
+    switch(propertyId)
+    {
+    case ANIMATE_POSITION:
+        value->setFloat(0, _bounds.x);
+        value->setFloat(1, _bounds.y);
+        break;
+    case ANIMATE_SIZE:
+        value->setFloat(0, _bounds.width);
+        value->setFloat(1, _bounds.height);
+        break;
+    case ANIMATE_POSITION_X:
+        value->setFloat(0, _bounds.x);
+        break;
+    case ANIMATE_POSITION_Y:
+        value->setFloat(0, _bounds.y);
+        break;
+    case ANIMATE_SIZE_WIDTH:
+        value->setFloat(0, _bounds.width);
+        break;
+    case ANIMATE_SIZE_HEIGHT:
+        value->setFloat(0, _bounds.height);
+        break;
+    case ANIMATE_OPACITY:
+    default:
+        break;
+    }
+}
+
+void Control::setAnimationPropertyValue(int propertyId, AnimationValue* value, float blendWeight)
+{
+    GP_ASSERT(value);
+
+    switch(propertyId)
     {
-        Theme::Style::Overlay* overlay = getOverlay(state);
-        GP_ASSERT(overlay);
-        Theme::ImageList* imageList = overlay->getImageList();
-        GP_ASSERT(imageList);
-        return imageList->getImage(id);
+    case ANIMATE_POSITION:
+        _bounds.x = Curve::lerp(blendWeight, _bounds.x, value->getFloat(0));
+        _bounds.y = Curve::lerp(blendWeight, _bounds.y, value->getFloat(1));
+        _dirty = true;
+        break;
+    case ANIMATE_POSITION_X:
+        _bounds.x = Curve::lerp(blendWeight, _bounds.x, value->getFloat(0));
+        _dirty = true;
+        break;
+    case ANIMATE_POSITION_Y:
+        _bounds.y = Curve::lerp(blendWeight, _bounds.y, value->getFloat(0));
+        _dirty = true;
+        break;
+    case ANIMATE_SIZE:
+        _bounds.width = Curve::lerp(blendWeight, _bounds.width, value->getFloat(0));
+        _bounds.height = Curve::lerp(blendWeight, _bounds.height, value->getFloat(1));
+        _dirty = true;
+        break;
+    case ANIMATE_SIZE_WIDTH:
+        _bounds.width = Curve::lerp(blendWeight, _bounds.width, value->getFloat(0));
+        _dirty = true;
+        break;
+    case ANIMATE_SIZE_HEIGHT:
+        _bounds.height = Curve::lerp(blendWeight, _bounds.height, value->getFloat(0));
+        _dirty = true;
+        break;
+    case ANIMATE_OPACITY:
+        _dirty = true;
+    default:
+        break;
     }
+}
+    
 
-    // Implementation of AnimationHandler
-    unsigned int Control::getAnimationPropertyComponentCount(int propertyId) const
+Theme::Style::Overlay** Control::getOverlays(unsigned char overlayTypes, Theme::Style::Overlay** overlays)
+{
+    GP_ASSERT(overlays);
+    GP_ASSERT(_style);
+
+    unsigned int index = 0;
+    if ((overlayTypes & NORMAL) == NORMAL)
     {
-        switch(propertyId)
-        {
-        case ANIMATE_POSITION:
-        case ANIMATE_SIZE:
-            return 2;
-
-        case ANIMATE_POSITION_X:
-        case ANIMATE_POSITION_Y:
-        case ANIMATE_SIZE_WIDTH:
-        case ANIMATE_SIZE_HEIGHT:
-        case ANIMATE_OPACITY:
-            return 1;
-
-        default:
-            return -1;
-        }
+        overlays[index++] = _style->getOverlay(Theme::Style::OVERLAY_NORMAL);
     }
 
-    void Control::getAnimationPropertyValue(int propertyId, AnimationValue* value)
+    if ((overlayTypes & FOCUS) == FOCUS)
     {
-        GP_ASSERT(value);
+        overlays[index++] = _style->getOverlay(Theme::Style::OVERLAY_FOCUS);
+    }
 
-        switch(propertyId)
-        {
-        case ANIMATE_POSITION:
-            value->setFloat(0, _bounds.x);
-            value->setFloat(1, _bounds.y);
-            break;
-        case ANIMATE_SIZE:
-            value->setFloat(0, _bounds.width);
-            value->setFloat(1, _bounds.height);
-            break;
-        case ANIMATE_POSITION_X:
-            value->setFloat(0, _bounds.x);
-            break;
-        case ANIMATE_POSITION_Y:
-            value->setFloat(0, _bounds.y);
-            break;
-        case ANIMATE_SIZE_WIDTH:
-            value->setFloat(0, _bounds.width);
-            break;
-        case ANIMATE_SIZE_HEIGHT:
-            value->setFloat(0, _bounds.height);
-            break;
-        case ANIMATE_OPACITY:
-        default:
-            break;
-        }
+    if ((overlayTypes & ACTIVE) == ACTIVE)
+    {
+        overlays[index++] = _style->getOverlay(Theme::Style::OVERLAY_ACTIVE);
     }
 
-    void Control::setAnimationPropertyValue(int propertyId, AnimationValue* value, float blendWeight)
+    if ((overlayTypes & DISABLED) == DISABLED)
     {
-        GP_ASSERT(value);
+        overlays[index++] = _style->getOverlay(Theme::Style::OVERLAY_DISABLED);
+    }
 
-        switch(propertyId)
-        {
-        case ANIMATE_POSITION:
-            _bounds.x = Curve::lerp(blendWeight, _bounds.x, value->getFloat(0));
-            _bounds.y = Curve::lerp(blendWeight, _bounds.y, value->getFloat(1));
-            _dirty = true;
-            break;
-        case ANIMATE_POSITION_X:
-            _bounds.x = Curve::lerp(blendWeight, _bounds.x, value->getFloat(0));
-            _dirty = true;
-            break;
-        case ANIMATE_POSITION_Y:
-            _bounds.y = Curve::lerp(blendWeight, _bounds.y, value->getFloat(0));
-            _dirty = true;
-            break;
-        case ANIMATE_SIZE:
-            _bounds.width = Curve::lerp(blendWeight, _bounds.width, value->getFloat(0));
-            _bounds.height = Curve::lerp(blendWeight, _bounds.height, value->getFloat(1));
-            _dirty = true;
-            break;
-        case ANIMATE_SIZE_WIDTH:
-            _bounds.width = Curve::lerp(blendWeight, _bounds.width, value->getFloat(0));
-            _dirty = true;
-            break;
-        case ANIMATE_SIZE_HEIGHT:
-            _bounds.height = Curve::lerp(blendWeight, _bounds.height, value->getFloat(0));
-            _dirty = true;
-            break;
-        case ANIMATE_OPACITY:
-            _dirty = true;
-        default:
-            break;
-        }
+    return overlays;
+}
+
+Theme::Style::Overlay* Control::getOverlay(State state) const
+{
+    GP_ASSERT(_style);
+
+    switch(state)
+    {
+    case Control::NORMAL:
+        return _style->getOverlay(Theme::Style::OVERLAY_NORMAL);
+    case Control::FOCUS:
+        return _style->getOverlay(Theme::Style::OVERLAY_FOCUS);
+    case Control::ACTIVE:
+        return _style->getOverlay(Theme::Style::OVERLAY_ACTIVE);
+    case Control::DISABLED:
+        return _style->getOverlay(Theme::Style::OVERLAY_DISABLED);
+    default:
+        return NULL;
     }
-    
-    Theme::Style::Overlay** Control::getOverlays(unsigned char overlayTypes, Theme::Style::Overlay** overlays)
+}
+
+void Control::overrideStyle()
+{
+    if (_styleOverridden)
     {
-        GP_ASSERT(overlays);
-        GP_ASSERT(_style);
+        return;
+    }
 
-        unsigned int index = 0;
-        if ((overlayTypes & NORMAL) == NORMAL)
-        {
-            overlays[index++] = _style->getOverlay(Theme::Style::OVERLAY_NORMAL);
-        }
+    // Copy the style.
+    GP_ASSERT(_style);
+    _style = new Theme::Style(*_style);
+    _styleOverridden = true;
+}
 
-        if ((overlayTypes & FOCUS) == FOCUS)
-        {
-            overlays[index++] = _style->getOverlay(Theme::Style::OVERLAY_FOCUS);
-        }
+void Control::overrideThemedProperties(Properties* properties, unsigned char states)
+{
+    GP_ASSERT(properties);
+    GP_ASSERT(_style);
+    GP_ASSERT(_style->_theme);
 
-        if ((overlayTypes & ACTIVE) == ACTIVE)
-        {
-            overlays[index++] = _style->getOverlay(Theme::Style::OVERLAY_ACTIVE);
-        }
+    Theme::ImageList* imageList = NULL;
+    Theme::ThemeImage* cursor = NULL;
+    Theme::Skin* skin = NULL;
+    _style->_theme->lookUpSprites(properties, &imageList, &cursor, &skin);
 
-        if ((overlayTypes & DISABLED) == DISABLED)
-        {
-            overlays[index++] = _style->getOverlay(Theme::Style::OVERLAY_DISABLED);
-        }
+    if (imageList)
+    {
+        setImageList(imageList, states);
+    }
 
-        return overlays;
+    if (cursor)
+    {
+        setCursor(cursor, states);
     }
 
-    Theme::Style::Overlay* Control::getOverlay(State state) const
+    if (skin)
     {
-        GP_ASSERT(_style);
+        setSkin(skin, states);
+    }
 
-        switch(state)
-        {
-        case Control::NORMAL:
-            return _style->getOverlay(Theme::Style::OVERLAY_NORMAL);
-        case Control::FOCUS:
-            return _style->getOverlay(Theme::Style::OVERLAY_FOCUS);
-        case Control::ACTIVE:
-            return _style->getOverlay(Theme::Style::OVERLAY_ACTIVE);
-        case Control::DISABLED:
-            return _style->getOverlay(Theme::Style::OVERLAY_DISABLED);
-        default:
-            return NULL;
-        }
+    if (properties->exists("font"))
+    {
+        Font* font = Font::create(properties->getString("font"));
+        setFont(font, states);
+        font->release();
     }
 
-    void Control::overrideStyle()
+    if (properties->exists("fontSize"))
     {
-        if (_styleOverridden)
-        {
-            return;
-        }
+        setFontSize(properties->getInt("fontSize"), states);
+    }
 
-        // Copy the style.
-        GP_ASSERT(_style);
-        _style = new Theme::Style(*_style);
-        _styleOverridden = true;
+    if (properties->exists("textColor"))
+    {
+        Vector4 textColor(0, 0, 0, 1);
+        properties->getColor("textColor", &textColor);
+        setTextColor(textColor, states);
     }
 
-    void Control::overrideThemedProperties(Properties* properties, unsigned char states)
+    if (properties->exists("textAlignment"))
     {
-        GP_ASSERT(properties);
-        GP_ASSERT(_style);
-        GP_ASSERT(_style->_theme);
+        setTextAlignment(Font::getJustify(properties->getString("textAlignment")), states);
+    }
 
-        Theme::ImageList* imageList = NULL;
-        Theme::ThemeImage* cursor = NULL;
-        Theme::Skin* skin = NULL;
-        _style->_theme->lookUpSprites(properties, &imageList, &cursor, &skin);
+    if (properties->exists("rightToLeft"))
+    {
+        setTextRightToLeft(properties->getBool("rightToLeft"), states);
+    }
 
-        if (imageList)
-        {
-            setImageList(imageList, states);
-        }
+    if (properties->exists("opacity"))
+    {
+        setOpacity(properties->getFloat("opacity"), states);
+    }
+}
 
-        if (cursor)
-        {
-            setCursor(cursor, states);
-        }
+void Control::setImageList(Theme::ImageList* imageList, unsigned char states)
+{
+    overrideStyle();
+    Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
+    getOverlays(states, overlays);
 
-        if (skin)
-        {
-            setSkin(skin, states);
-        }
+    for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
+    {
+        overlays[i]->setImageList(imageList);
+    }
 
-        if (properties->exists("font"))
-        {
-            Font* font = Font::create(properties->getString("font"));
-            setFont(font, states);
-            font->release();
-        }
+    _dirty = true;
+}
 
-        if (properties->exists("fontSize"))
-        {
-            setFontSize(properties->getInt("fontSize"), states);
-        }
+void Control::setCursor(Theme::ThemeImage* cursor, unsigned char states)
+{
+    overrideStyle();
+    Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
+    getOverlays(states, overlays);
 
-        if (properties->exists("textColor"))
-        {
-            Vector4 textColor(0, 0, 0, 1);
-            properties->getColor("textColor", &textColor);
-            setTextColor(textColor, states);
-        }
+    for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
+    {
+        overlays[i]->setCursor(cursor);
+    }
 
-        if (properties->exists("textAlignment"))
-        {
-            setTextAlignment(Font::getJustify(properties->getString("textAlignment")), states);
-        }
+    _dirty = true;
+}
 
-        if (properties->exists("rightToLeft"))
-        {
-            setTextRightToLeft(properties->getBool("rightToLeft"), states);
-        }
+void Control::setSkin(Theme::Skin* skin, unsigned char states)
+{
+    overrideStyle();
+    Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
+    getOverlays(states, overlays);
 
-        if (properties->exists("opacity"))
-        {
-            setOpacity(properties->getFloat("opacity"), states);
-        }
+    for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
+    {
+        overlays[i]->setSkin(skin);
     }
 
-    void Control::setImageList(Theme::ImageList* imageList, unsigned char states)
-    {
-        overrideStyle();
-        Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
-        getOverlays(states, overlays);
+    _dirty = true;
+}
 
-        for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
-        {
-            overlays[i]->setImageList(imageList);
-        }
+Theme::Skin* Control::getSkin(State state)
+{
+    Theme::Style::Overlay* overlay = getOverlay(state);
+    GP_ASSERT(overlay);
+    return overlay->getSkin();
+}
 
-        _dirty = true;
+Control::Alignment Control::getAlignment(const char* alignment)
+{
+    if (!alignment)
+    {
+        return Control::ALIGN_TOP_LEFT;
     }
 
-    void Control::setCursor(Theme::ThemeImage* cursor, unsigned char states)
+    if (strcmp(alignment, "ALIGN_LEFT") == 0)
     {
-        overrideStyle();
-        Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
-        getOverlays(states, overlays);
-
-        for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
-        {
-            overlays[i]->setCursor(cursor);
-        }
-
-        _dirty = true;
+        return Control::ALIGN_LEFT;
     }
-
-    void Control::setSkin(Theme::Skin* skin, unsigned char states)
+    else if (strcmp(alignment, "ALIGN_HCENTER") == 0)
     {
-        overrideStyle();
-        Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
-        getOverlays(states, overlays);
-
-        for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
-        {
-            overlays[i]->setSkin(skin);
-        }
-
-        _dirty = true;
+        return Control::ALIGN_HCENTER;
     }
-
-    Theme::Skin* Control::getSkin(State state)
+    else if (strcmp(alignment, "ALIGN_RIGHT") == 0)
     {
-        Theme::Style::Overlay* overlay = getOverlay(state);
-        GP_ASSERT(overlay);
-        return overlay->getSkin();
+        return Control::ALIGN_RIGHT;
     }
-
-    Control::Alignment Control::getAlignment(const char* alignment)
+    else if (strcmp(alignment, "ALIGN_TOP") == 0)
+    {
+        return Control::ALIGN_TOP;
+    }
+    else if (strcmp(alignment, "ALIGN_VCENTER") == 0)
+    {
+        return Control::ALIGN_VCENTER;
+    }
+    else if (strcmp(alignment, "ALIGN_BOTTOM") == 0)
+    {
+        return Control::ALIGN_BOTTOM;
+    }
+    else if (strcmp(alignment, "ALIGN_TOP_LEFT") == 0)
     {
-        if (!alignment)
-        {
-            return Control::ALIGN_TOP_LEFT;
-        }
-
-        if (strcmp(alignment, "ALIGN_LEFT") == 0)
-        {
-            return Control::ALIGN_LEFT;
-        }
-        else if (strcmp(alignment, "ALIGN_HCENTER") == 0)
-        {
-            return Control::ALIGN_HCENTER;
-        }
-        else if (strcmp(alignment, "ALIGN_RIGHT") == 0)
-        {
-            return Control::ALIGN_RIGHT;
-        }
-        else if (strcmp(alignment, "ALIGN_TOP") == 0)
-        {
-            return Control::ALIGN_TOP;
-        }
-        else if (strcmp(alignment, "ALIGN_VCENTER") == 0)
-        {
-            return Control::ALIGN_VCENTER;
-        }
-        else if (strcmp(alignment, "ALIGN_BOTTOM") == 0)
-        {
-            return Control::ALIGN_BOTTOM;
-        }
-        else if (strcmp(alignment, "ALIGN_TOP_LEFT") == 0)
-        {
-            return Control::ALIGN_TOP_LEFT;
-        }
-        else if (strcmp(alignment, "ALIGN_VCENTER_LEFT") == 0)
-        {
-            return Control::ALIGN_VCENTER_LEFT;
-        }
-        else if (strcmp(alignment, "ALIGN_BOTTOM_LEFT") == 0)
-        {
-            return Control::ALIGN_BOTTOM_LEFT;
-        }
-        else if (strcmp(alignment, "ALIGN_TOP_HCENTER") == 0)
-        {
-            return Control::ALIGN_TOP_HCENTER;
-        }
-        else if (strcmp(alignment, "ALIGN_VCENTER_HCENTER") == 0)
-        {
-            return Control::ALIGN_VCENTER_HCENTER;
-        }
-        else if (strcmp(alignment, "ALIGN_BOTTOM_HCENTER") == 0)
-        {
-            return Control::ALIGN_BOTTOM_HCENTER;
-        }
-        else if (strcmp(alignment, "ALIGN_TOP_RIGHT") == 0)
-        {
-            return Control::ALIGN_TOP_RIGHT;
-        }
-        else if (strcmp(alignment, "ALIGN_VCENTER_RIGHT") == 0)
-        {
-            return Control::ALIGN_VCENTER_RIGHT;
-        }
-        else if (strcmp(alignment, "ALIGN_BOTTOM_RIGHT") == 0)
-        {
-            return Control::ALIGN_BOTTOM_RIGHT;
-        }
-        else
-        {
-            GP_ERROR("Failed to get corresponding control alignment for unsupported value \'%s\'.", alignment);
-        }
-
-        // Default.
         return Control::ALIGN_TOP_LEFT;
     }
+    else if (strcmp(alignment, "ALIGN_VCENTER_LEFT") == 0)
+    {
+        return Control::ALIGN_VCENTER_LEFT;
+    }
+    else if (strcmp(alignment, "ALIGN_BOTTOM_LEFT") == 0)
+    {
+        return Control::ALIGN_BOTTOM_LEFT;
+    }
+    else if (strcmp(alignment, "ALIGN_TOP_HCENTER") == 0)
+    {
+        return Control::ALIGN_TOP_HCENTER;
+    }
+    else if (strcmp(alignment, "ALIGN_VCENTER_HCENTER") == 0)
+    {
+        return Control::ALIGN_VCENTER_HCENTER;
+    }
+    else if (strcmp(alignment, "ALIGN_BOTTOM_HCENTER") == 0)
+    {
+        return Control::ALIGN_BOTTOM_HCENTER;
+    }
+    else if (strcmp(alignment, "ALIGN_TOP_RIGHT") == 0)
+    {
+        return Control::ALIGN_TOP_RIGHT;
+    }
+    else if (strcmp(alignment, "ALIGN_VCENTER_RIGHT") == 0)
+    {
+        return Control::ALIGN_VCENTER_RIGHT;
+    }
+    else if (strcmp(alignment, "ALIGN_BOTTOM_RIGHT") == 0)
+    {
+        return Control::ALIGN_BOTTOM_RIGHT;
+    }
+    else
+    {
+        GP_ERROR("Failed to get corresponding control alignment for unsupported value \'%s\'.", alignment);
+    }
+
+    // Default.
+    return Control::ALIGN_TOP_LEFT;
+}
+
 }

+ 27 - 12
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:
 
@@ -735,14 +736,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);
 
@@ -750,6 +753,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);
 
@@ -784,8 +788,9 @@ protected:
     /**
      * Get a Theme::ThemeImage from its ID, for a given state.
      *
-     * @param id The ID of the image to retrieve
+     * @param id The ID of the image to retrieve.
      * @param state The state to get this image from.
+     *
      * @return The requested Theme::ThemeImage, or NULL if none was found.
      */
     Theme::ThemeImage* getImage(const char* id, State state);
@@ -819,25 +824,32 @@ protected:
      * Position, relative to parent container's clipping window, and desired size.
      */
     Rectangle _bounds;
-    
+
     /**
-     * The position and size of this control, relative to parent container's bounds, including border and padding, after clipping.
+     * Position, relative to parent container's clipping window, including border and padding, after clipping.
      */
     Rectangle _clipBounds;
-    
+
     /**
-     * The position and size of this control's text area, before clipping.  Used for text alignment.
+     * Absolute bounds, including border and padding, before clipping.
      */
-    Rectangle _textBounds;
-    
+    Rectangle _absoluteBounds;
+
     /**
-     * Clipping window of this control's content, after clipping.
+     * Absolute bounds, including border and padding, after clipping.
      */
-    Rectangle _clip;
-    
+    Rectangle _absoluteClipBounds;
+
     /**
-     * Flag for whether the Control is dirty.
+     * Absolute bounds of content area (i.e. without border and padding), before clipping.
      */
+    Rectangle _viewportBounds;
+
+    /**
+     * Absolute bounds of content area (i.e. without border and padding), after clipping.
+     */
+    Rectangle _viewportClipBounds;
+
     bool _dirty;
     
     /**
@@ -907,9 +919,12 @@ private:
      * @param clip The clipping rectangle of this control's parent container.
      */
     virtual void drawBorder(SpriteBatch* spriteBatch, const Rectangle& clip);
+
+    virtual void draw(SpriteBatch* spriteBatch, const Rectangle& clip, bool needsClear, float targetHeight);
     
     bool _styleOverridden;
     Theme::Skin* _skin;
+    Rectangle _clearBounds;         // Previous frame's absolute clip bounds, to be cleared if necessary.
 };
 
 }

+ 4 - 3
gameplay/src/FlowLayout.cpp

@@ -42,8 +42,7 @@ Layout::Type FlowLayout::getType()
 void FlowLayout::update(const Container* container)
 {
     GP_ASSERT(container);
-
-    const Rectangle& containerBounds = container->getClipBounds();
+    const Rectangle& containerBounds = container->getBounds();
     const Theme::Border& containerBorder = container->getBorder(container->getState());
     const Theme::Padding& containerPadding = container->getPadding();
 
@@ -62,6 +61,8 @@ void FlowLayout::update(const Container* container)
         Control* control = controls.at(i);
         GP_ASSERT(control);
 
+        //align(control, container);
+
         const Rectangle& bounds = control->getBounds();
         const Theme::Margin& margin = control->getMargin();
 
@@ -79,7 +80,7 @@ void FlowLayout::update(const Container* container)
         control->setPosition(xPosition, yPosition);
         if (control->isDirty() || control->isContainer())
         {
-            control->update(container->getClip());
+            control->update(container->getClip(), Vector2::zero());
         }
 
         xPosition += bounds.width + margin.right;

+ 0 - 3
gameplay/src/FlowLayout.h

@@ -6,9 +6,6 @@
 namespace gameplay
 {
 
-/**
- *  Defines a layout that arranges components in a left-to-right or right-to-left flow. 
- */
 class FlowLayout : public Layout
 {
     friend class Form;

+ 228 - 39
gameplay/src/Font.cpp

@@ -173,7 +173,13 @@ void Font::begin()
 {
     _batch->begin();
 }
+/*
+Font::TextBatch* Font::getTextBatch(const char* text, const Rectangle& area, const Vector4& color, unsigned int size, Justify justify,
+    bool wrap, bool rightToLeft, const Rectangle* clip)
+{
 
+}
+*/
 void Font::drawText(const char* text, int x, int y, const Vector4& color, unsigned int size, bool rightToLeft)
 {
     if (size == 0)
@@ -207,7 +213,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 +221,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 +262,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 +270,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 +278,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;
                 }
             }
@@ -311,8 +317,6 @@ void Font::drawText(const char* text, const Rectangle& area, const Vector4& colo
         hAlign = ALIGN_LEFT;
     }
 
-    token = text;
-
     // For alignments other than top-left, need to calculate the y position to begin drawing from
     // and the starting x position of each line.  For right-to-left text, need to determine
     // the number of characters on each line.
@@ -343,7 +347,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 +364,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 +392,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 +443,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 +533,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 +591,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 +733,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 +769,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 +1014,187 @@ 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,
+void Font::getMeasurementInfo(const char* text, const Rectangle& area, unsigned int size, Justify justify, bool wrap, bool rightToLeft,
+        std::vector<int>* xPositions, int* yPosition, std::vector<unsigned int>* lineLengths)
+{
+    float scale = (float)size / _size;
+
+    Justify vAlign = static_cast<Justify>(justify & 0xF0);
+    if (vAlign == 0)
+    {
+        vAlign = ALIGN_TOP;
+    }
+
+    Justify hAlign = static_cast<Justify>(justify & 0x0F);
+    if (hAlign == 0)
+    {
+        hAlign = ALIGN_LEFT;
+    }
+
+    const char* token = text;
+    const float areaHeight = area.height - size;
+
+    // For alignments other than top-left, need to calculate the y position to begin drawing from
+    // and the starting x position of each line.  For right-to-left text, need to determine
+    // the number of characters on each line.
+    if (vAlign != ALIGN_TOP || hAlign != ALIGN_LEFT || rightToLeft)
+    {
+        int lineWidth = 0;
+        int delimWidth = 0;
+
+        if (wrap)
+        {
+            // Go a word at a time.
+            bool reachedEOF = false;
+            unsigned int lineLength = 0;
+            while (token[0] != 0)
+            {
+                unsigned int tokenWidth = 0;
+
+                // Handle delimiters until next token.
+                char delimiter = token[0];
+                while (delimiter == ' ' ||
+                       delimiter == '\t' ||
+                       delimiter == '\r' ||
+                       delimiter == '\n' ||
+                       delimiter == 0)
+                {
+                    switch (delimiter)
+                    {
+                        case ' ':
+                            delimWidth += size >> 1;
+                            lineLength++;
+                            break;
+                        case '\r':
+                        case '\n':
+                            *yPosition += size;
+
+                            if (lineWidth > 0)
+                            {
+                                addLineInfo(area, lineWidth, lineLength, hAlign, xPositions, lineLengths, rightToLeft);
+                            }
+
+                            lineWidth = 0;
+                            lineLength = 0;
+                            delimWidth = 0;
+                            break;
+                        case '\t':
+                            delimWidth += (size >> 1)*4;
+                            lineLength++;
+                            break;
+                        case 0:
+                            reachedEOF = true;
+                            break;
+                    }
+
+                    if (reachedEOF)
+                    {
+                        break;
+                    }
+
+                    token++;
+                    delimiter = token[0];
+                }
+
+                if (reachedEOF || token == NULL)
+                {
+                    break;
+                }
+
+                unsigned int tokenLength = strcspn(token, " \r\n\t");
+                tokenWidth += getTokenWidth(token, tokenLength, size, scale);
+
+                // Wrap if necessary.
+                if (lineWidth + tokenWidth + delimWidth > area.width)
+                {
+                    *yPosition += size;
+
+                    // Push position of current line.
+                    if (lineLength)
+                    {
+                        addLineInfo(area, lineWidth, lineLength-1, hAlign, xPositions, lineLengths, rightToLeft);
+                    }
+                    else
+                    {
+                        addLineInfo(area, lineWidth, tokenLength, hAlign, xPositions, lineLengths, rightToLeft);
+                    }
+
+                    // Move token to the next line.
+                    lineWidth = 0;
+                    lineLength = 0;
+                    delimWidth = 0;
+                }
+                else
+                {
+                    lineWidth += delimWidth;
+                    delimWidth = 0;
+                }
+
+                lineWidth += tokenWidth;
+                lineLength += tokenLength;
+                token += tokenLength;
+            }
+
+            // Final calculation of vertical position.
+            int textHeight = *yPosition - area.y;
+            int vWhiteSpace = areaHeight - textHeight;
+            if (vAlign == ALIGN_VCENTER)
+            {
+                *yPosition = area.y + vWhiteSpace / 2;
+            }
+            else if (vAlign == ALIGN_BOTTOM)
+            {
+                *yPosition = area.y + vWhiteSpace;
+            }
+
+            // Calculation of final horizontal position.
+            addLineInfo(area, lineWidth, lineLength, hAlign, xPositions, lineLengths, rightToLeft);
+        }
+        else
+        {
+            // Go a line at a time.
+            while (token[0] != 0)
+            {
+                char delimiter = token[0];
+                while (delimiter == '\n')
+                {
+                    *yPosition += size;
+                    ++token;
+                    delimiter = token[0];
+                }
+
+                unsigned int tokenLength = strcspn(token, "\n");
+                if (tokenLength == 0)
+                {
+                    tokenLength = strlen(token);
+                }
+
+                int lineWidth = getTokenWidth(token, tokenLength, size, scale);
+                addLineInfo(area, lineWidth, tokenLength, hAlign, xPositions, lineLengths, rightToLeft);
+
+                token += tokenLength;
+            }
+
+            int textHeight = *yPosition - area.y;
+            int vWhiteSpace = areaHeight - textHeight;
+            if (vAlign == ALIGN_VCENTER)
+            {
+                *yPosition = area.y + vWhiteSpace / 2;
+            }
+            else if (vAlign == ALIGN_BOTTOM)
+            {
+                *yPosition = area.y + vWhiteSpace;
+            }
+        }
+
+        if (vAlign == ALIGN_TOP)
+        {
+            *yPosition = area.y;
+        }
+    }
+}
+
+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 +1206,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 +1262,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 +1279,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 +1432,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 +1441,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 +1473,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 +1514,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 +1522,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 +1593,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 +1617,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 +1670,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 +1681,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 +1713,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)
                 {

+ 23 - 2
gameplay/src/Font.h

@@ -76,6 +76,13 @@ public:
         float uvs[4];
     };
 
+    class TextBatch
+    {
+    public:
+        SpriteBatch::SpriteVertex* _vertices;
+        unsigned int _vertexCount;
+    };
+
     /**
      * Creates a font from the given bundle.
      *
@@ -125,6 +132,17 @@ public:
      */
     void end();
 
+    /**
+     * Compute vertex coordinates and UVs for a given string.
+     */
+    TextBatch* getTextBatch(const char* text, const Rectangle& area, const Vector4& color, unsigned int size = 0,
+        Justify justify = ALIGN_TOP_LEFT, bool wrap = true, bool rightToLeft = false, const Rectangle* clip = NULL);
+
+    /**
+     * Draw a string from a precomputed StringBatch.
+     */
+    void drawTextBatch(TextBatch* textBatch);
+
     /**
      * Draws the specified text in a solid color, with a scaling factor.
      *
@@ -181,7 +199,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 +243,10 @@ private:
      */
     ~Font();
 
-    unsigned int getIndexOrLocation(const char* text, const Rectangle& clip, unsigned int size, const Vector2& inLocation, Vector2* outLocation,
+    void getMeasurementInfo(const char* text, const Rectangle& area, unsigned int size, Justify justify, bool wrap, bool rightToLeft,
+        std::vector<int>* xPositions, int* yPosition, std::vector<unsigned int>* lineLengths);
+
+    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);

+ 373 - 309
gameplay/src/Form.cpp

@@ -1,6 +1,8 @@
 #include "Base.h"
 #include "Form.h"
 #include "AbsoluteLayout.h"
+#include "FlowLayout.h"
+#include "ScrollLayout.h"
 #include "VerticalLayout.h"
 #include "Game.h"
 #include "Theme.h"
@@ -11,405 +13,467 @@
 
 namespace gameplay
 {
-    static std::vector<Form*> __forms;
 
-    Form::Form() : _theme(NULL), _quad(NULL), _node(NULL), _frameBuffer(NULL)
-    {
-    }
+static std::vector<Form*> __forms;
+
+Form::Form() : _theme(NULL), _quad(NULL), _node(NULL), _frameBuffer(NULL), _spriteBatch(NULL)
+{
+}
 
-    Form::Form(const Form& copy)
+Form::Form(const Form& copy)
+{
+}
+
+Form::~Form()
+{
+    SAFE_RELEASE(_quad);
+    SAFE_RELEASE(_node);
+    SAFE_RELEASE(_frameBuffer);
+    SAFE_RELEASE(_theme);
+    SAFE_DELETE(_spriteBatch);
+
+    // Remove this Form from the global list.
+    std::vector<Form*>::iterator it = std::find(__forms.begin(), __forms.end(), this);
+    if (it != __forms.end())
     {
+        __forms.erase(it);
     }
+}
 
-    Form::~Form()
+Form* Form::create(const char* url)
+{
+    // Load Form from .form file.
+    Properties* properties = Properties::create(url);
+    if (properties == NULL)
     {
-        SAFE_RELEASE(_quad);
-        SAFE_RELEASE(_node);
-        SAFE_RELEASE(_frameBuffer);
-        SAFE_RELEASE(_theme);
-
-        // Remove this Form from the global list.
-        std::vector<Form*>::iterator it = std::find(__forms.begin(), __forms.end(), this);
-        if (it != __forms.end())
-        {
-            __forms.erase(it);
-        }
+        GP_ASSERT(properties);
+        return NULL;
     }
 
-    Form* Form::create(const char* url)
+    // Check if the Properties is valid and has a valid namespace.
+    Properties* formProperties = (strlen(properties->getNamespace()) > 0) ? properties : properties->getNextNamespace();
+    assert(formProperties);
+    if (!formProperties || !(strcmp(formProperties->getNamespace(), "form") == 0))
     {
-        // Load Form from .form file.
-        Properties* properties = Properties::create(url);
-        if (properties == NULL)
-        {
-            GP_ASSERT(properties);
-            return NULL;
-        }
-
-        // Check if the Properties is valid and has a valid namespace.
-        Properties* formProperties = (strlen(properties->getNamespace()) > 0) ? properties : properties->getNextNamespace();
-        if (!formProperties || !(strcmp(formProperties->getNamespace(), "form") == 0))
-        {
-            GP_ASSERT(formProperties);
-            SAFE_DELETE(properties);
-            return NULL;
-        }
+        GP_ASSERT(formProperties);
+        SAFE_DELETE(properties);
+        return NULL;
+    }
 
-        // Create new form with given ID, theme and layout.
-        const char* themeFile = formProperties->getString("theme");
-        const char* layoutString = formProperties->getString("layout");
+    // Create new form with given ID, theme and layout.
+    const char* themeFile = formProperties->getString("theme");
+    const char* layoutString = formProperties->getString("layout");
         
-        Layout* layout;
-        switch (getLayoutType(layoutString))
-        {
-        case Layout::LAYOUT_ABSOLUTE:
-            layout = AbsoluteLayout::create();
-            break;
-        case Layout::LAYOUT_FLOW:
-            break;
-        case Layout::LAYOUT_VERTICAL:
-            layout = VerticalLayout::create();
-            break;
-        default:
-            GP_ERROR("Unsupported layout type \'%d\'.", getLayoutType(layoutString));
-        }
-
-        Theme* theme = Theme::create(themeFile);
-        GP_ASSERT(theme);
-
-        Form* form = new Form();
-        form->_layout = layout;
-        form->_theme = theme;
-
-        const char* styleName = formProperties->getString("style");
-        form->initialize(theme->getStyle(styleName), formProperties);
-
-        if (form->_autoWidth)
-        {
-            form->_bounds.width = Game::getInstance()->getWidth();
-        }
-
-        if (form->_autoHeight)
-        {
-            form->_bounds.height = Game::getInstance()->getHeight();
-        }
+    Layout* layout;
+    switch (getLayoutType(layoutString))
+    {
+    case Layout::LAYOUT_ABSOLUTE:
+        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;
+    default:
+        GP_ERROR("Unsupported layout type \'%d\'.", getLayoutType(layoutString));
+    }
 
-        // Add all the controls to the form.
-        form->addControls(theme, formProperties);
+    Theme* theme = Theme::create(themeFile);
+    GP_ASSERT(theme);
 
-        SAFE_DELETE(properties);
+    Form* form = new Form();
+    form->_layout = layout;
+    form->_theme = theme;
 
-        __forms.push_back(form);
+    const char* styleName = formProperties->getString("style");
+    form->initialize(theme->getStyle(styleName), formProperties);
 
-        return form;
+    if (form->_autoWidth)
+    {
+        form->_bounds.width = Game::getInstance()->getWidth();
     }
 
-    Form* Form::getForm(const char* id)
+    if (form->_autoHeight)
     {
-        std::vector<Form*>::const_iterator it;
-        for (it = __forms.begin(); it < __forms.end(); it++)
-        {
-            Form* f = *it;
-            GP_ASSERT(f);
-            if (strcmp(id, f->getID()) == 0)
-            {
-                return f;
-            }
-        }
-        
-        return NULL;
+        form->_bounds.height = Game::getInstance()->getHeight();
     }
 
-    void Form::setQuad(const Vector3& p1, const Vector3& p2, const Vector3& p3, const Vector3& p4)
+    // Add all the controls to the form.
+    form->addControls(theme, formProperties);
+
+        
+    // Width and height must be powers of two to create a texture.
+    int w = (int)form->_bounds.width;
+    int h = (int)form->_bounds.height;
+
+    if (!((w & (w - 1)) == 0))
     {
-        Mesh* mesh = Mesh::createQuad(p1, p2, p3, p4);
-        initializeQuad(mesh);
-        SAFE_RELEASE(mesh);
+        w = nextHighestPowerOfTwo(w);
     }
 
-    void Form::setQuad(float x, float y, float width, float height)
+    if (!((h & (h - 1)) == 0))
     {
-        Mesh* mesh = Mesh::createQuad(x, y, width, height);
-        initializeQuad(mesh);
-        SAFE_RELEASE(mesh);
+        h = nextHighestPowerOfTwo(h);
     }
 
-    void Form::setNode(Node* node)
-    {
-        _node = node;
+    form->_u2 = form->_bounds.width / (float)w;
+    form->_v1 = form->_bounds.height / (float)h;
 
-        if (_node && !_quad)
-        {
-            // Set this Form up to be 3D by initializing a quad, projection matrix and viewport.
-            setQuad(0.0f, 0.0f, _bounds.width, _bounds.height);
+    // Create the frame buffer.
+    form->_frameBuffer = FrameBuffer::create(form->_id.c_str());
+    RenderTarget* rt = RenderTarget::create(form->_id.c_str(), w, h);
+    GP_ASSERT(rt);
+    form->_frameBuffer->setRenderTarget(rt);
+    SAFE_RELEASE(rt);
 
-            Matrix::createOrthographicOffCenter(0, _bounds.width, _bounds.height, 0, 0, 1, &_projectionMatrix);
-            _theme->setProjectionMatrix(_projectionMatrix);
-            
-            _node->setModel(_quad);
-        }
-    }
+    Matrix::createOrthographicOffCenter(0, form->_bounds.width, form->_bounds.height, 0, 0, 1, &form->_projectionMatrix);
+    Game* game = Game::getInstance();
+    Matrix::createOrthographicOffCenter(0, game->getWidth(), game->getHeight(), 0, 0, 1, &form->_defaultProjectionMatrix);
+
+    SAFE_DELETE(properties);
 
-    void Form::update()
+    __forms.push_back(form);
+
+    return form;
+}
+
+Form* Form::getForm(const char* id)
+{
+    std::vector<Form*>::const_iterator it;
+    for (it = __forms.begin(); it < __forms.end(); it++)
     {
-        if (isDirty())
+        Form* f = *it;
+        GP_ASSERT(f);
+        if (strcmp(id, f->getID()) == 0)
         {
-            Container::update(Rectangle(0, 0, _bounds.width, _bounds.height));
+            return f;
         }
     }
+        
+    return NULL;
+}
 
-    void Form::draw()
-    {
-        // If this form has a node then it's a 3D form.  The contents will be rendered
-        // into a framebuffer which will be used to texture a quad.  The quad will be
-        // given the same dimensions as the form and must be transformed appropriately
-        // by the user, unless they call setQuad() themselves.
+void Form::setQuad(const Vector3& p1, const Vector3& p2, const Vector3& p3, const Vector3& p4)
+{
+    Mesh* mesh = Mesh::createQuad(p1, p2, p3, p4);
 
-        // On the other hand, if this form has not been set on a node it will render
-        // directly to the display.
+    initializeQuad(mesh);
+    SAFE_RELEASE(mesh);
+}
 
-        if (_node)
-        {
-            // Check whether this form has changed since the last call to draw()
-            // and if so, render into the framebuffer.
-            if (isDirty())
-            {
-                GP_ASSERT(_frameBuffer);
-                _frameBuffer->bind();
+void Form::setQuad(float x, float y, float width, float height)
+{
+    float x2 = x + width;
+    float y2 = y + height;
 
-                Game* game = Game::getInstance();
-                Rectangle prevViewport = game->getViewport();
-                
-                game->setViewport(Rectangle(_bounds.x, _bounds.y, _bounds.width, _bounds.height));
+    float vertices[] =
+    {
+        x, y2, 0,   0, _v1,
+        x, y, 0,    0, 0,
+        x2, y2, 0,  _u2, _v1,
+        x2, y, 0,   _u2, 0
+    };
 
-                GP_ASSERT(_theme);
-                draw(_theme->getSpriteBatch(), _clip);
+    VertexFormat::Element elements[] =
+    {
+        VertexFormat::Element(VertexFormat::POSITION, 3),
+        VertexFormat::Element(VertexFormat::TEXCOORD0, 2)
+    };
+    Mesh* mesh = Mesh::createMesh(VertexFormat(elements, 2), 4, false);
+    assert(mesh);
 
-                // Rebind the default framebuffer and game viewport.
-                FrameBuffer::bindDefault();
+    mesh->setPrimitiveType(Mesh::TRIANGLE_STRIP);
+    mesh->setVertexData(vertices, 0, 4);
 
-                // restore the previous game viewport
-                game->setViewport(prevViewport);
-            }
+    initializeQuad(mesh);
+    SAFE_RELEASE(mesh);
+}
 
-            GP_ASSERT(_quad);
-            _quad->draw();
-        }
-        else
-        {
-            GP_ASSERT(_theme);
-            draw(_theme->getSpriteBatch(), _clip);
-        }
+void Form::setNode(Node* node)
+{
+    _node = node;
+        
+    if (_node)
+    {
+        // Set this Form up to be 3D by initializing a quad.
+        setQuad(0.0f, 0.0f, _bounds.width, _bounds.height);
+        _node->setModel(_quad);
     }
+}
 
-    void Form::draw(SpriteBatch* spriteBatch, const Rectangle& clip)
+void Form::update()
+{
+    if (isDirty())
     {
-        GP_ASSERT(spriteBatch);
+        Container::update(Rectangle(0, 0, _bounds.width, _bounds.height), Vector2::zero());
+    }
+}
 
-        std::vector<Control*>::const_iterator it;
+void Form::draw()
+{
+    /*
+    The first time a form is drawn, its contents are rendered into a framebuffer.
+    The framebuffer will only be drawn into again when the contents of the form change.
 
-        // Batch for all themed border and background sprites.
-        spriteBatch->begin();
+    If this form has a node then it's a 3D form and the framebuffer 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, unless they call setQuad() themselves.
 
-        // Batch each font individually.
-        std::set<Font*>::const_iterator fontIter;
-        for (fontIter = _theme->_fonts.begin(); fontIter != _theme->_fonts.end(); fontIter++)
-        {
-            Font* font = *fontIter;
-            if (font)
-            {
-                font->begin();
-            }
-        }
+    On the other hand, if this form has not been set on a node, SpriteBatch will be used
+    to render the contents of the frambuffer directly to the display.
+    */
 
-        // Draw the form's border and background.
-        // We don't pass the form's position to itself or it will be applied twice!
-        Control::drawBorder(spriteBatch, Rectangle(0, 0, _bounds.width, _bounds.height));
+    // Check whether this form has changed since the last call to draw()
+    // and if so, render into the framebuffer.
+    if (isDirty())
+    {
+        GP_ASSERT(_frameBuffer);
+        _frameBuffer->bind();
 
-        for (it = _controls.begin(); it < _controls.end(); it++)
-        {
-            Control* control = *it;
-            GP_ASSERT(control);
+        Game* game = Game::getInstance();
+        Rectangle prevViewport = game->getViewport();
+        game->setViewport(Rectangle(_bounds.x, _bounds.y, _bounds.width, _bounds.height));
 
-            // Draw this control's border and background.
-            control->drawBorder(spriteBatch, clip);
+        GP_ASSERT(_theme);
+        _theme->setProjectionMatrix(_projectionMatrix);
+        draw(_theme->getSpriteBatch(), _viewportClipBounds);
+        _theme->setProjectionMatrix(_defaultProjectionMatrix);
 
-            // Add all themed foreground sprites (checkboxes etc.) to the same batch.
-            control->drawImages(spriteBatch, clip);
+        // Rebind the default framebuffer and game viewport.
+        FrameBuffer::bindDefault();
 
-            control->drawText(clip);
-        }
-
-        // Done all batching.
-        spriteBatch->end();
+        // restore the previous game viewport
+        game->setViewport(prevViewport);
+    }
 
-        for (fontIter = _theme->_fonts.begin(); fontIter != _theme->_fonts.end(); fontIter++)
+    if (_node)
+    {
+        GP_ASSERT(_quad);
+        _quad->draw();
+    }
+    else
+    {
+        if (!_spriteBatch)
         {
-            Font* font = *fontIter;
-            if (font)
-            {
-                font->end();
-            }
+            _spriteBatch = SpriteBatch::create(_frameBuffer->getRenderTarget()->getTexture());
+            GP_ASSERT(_spriteBatch);
         }
 
-        _dirty = false;
+        _spriteBatch->begin();
+        _spriteBatch->draw(_bounds.x, _bounds.y, 0, _bounds.width, _bounds.height, 0, _v1, _u2, 0, Vector4::one());
+        _spriteBatch->end();
     }
+}
 
-    void Form::initializeQuad(Mesh* mesh)
-    {
-        // Release current model.
-        SAFE_RELEASE(_quad);
+void Form::draw(SpriteBatch* spriteBatch, const Rectangle& clip)
+{
+    GP_ASSERT(spriteBatch);
+
+    std::vector<Control*>::const_iterator it;
 
-        // Create the model
-        _quad = Model::create(mesh);
+    // Batch each font individually.
+    std::set<Font*>::const_iterator fontIter;
+    for (fontIter = _theme->_fonts.begin(); fontIter != _theme->_fonts.end(); fontIter++)
+    {
+        Font* font = *fontIter;
+        if (font)
+        {
+            font->begin();
+        }
+    }
 
-        // Create the material
-        Material* material = _quad->setMaterial("res/shaders/textured.vsh", "res/shaders/textured.fsh");
-        GP_ASSERT(material);
+    // Batch for all themed border and background sprites.
+    spriteBatch->begin();
 
-        // Set the common render state block for the material
-        GP_ASSERT(_theme);
-        GP_ASSERT(_theme->getSpriteBatch());
-        RenderState::StateBlock* stateBlock = _theme->getSpriteBatch()->getStateBlock();
-        GP_ASSERT(stateBlock);
-        stateBlock->setDepthWrite(true);
-        material->setStateBlock(stateBlock);
+    // Draw the form's border and background.
+    // We don't pass the form's position to itself or it will be applied twice!
+    Control::drawBorder(spriteBatch, Rectangle(0, 0, _bounds.width, _bounds.height));
 
-        // Bind the WorldViewProjection matrix
-        material->setParameterAutoBinding("u_worldViewProjectionMatrix", RenderState::WORLD_VIEW_PROJECTION_MATRIX);
+    Rectangle boundsUnion = Rectangle::empty();
+    for (it = _controls.begin(); it < _controls.end(); it++)
+    {
+        Control* control = *it;
+        GP_ASSERT(control);
 
-        // Create a FrameBuffer if necessary.
-        if (!_frameBuffer)
+        if (_skin || control->isDirty() || control->_clearBounds.intersects(boundsUnion))
         {
-            _frameBuffer = FrameBuffer::create(_id.c_str());
+            control->draw(spriteBatch, clip, _skin == NULL, _bounds.height);
+            Rectangle::combine(control->_clearBounds, boundsUnion, &boundsUnion);
         }
+    }
+
+    // Done all batching.
+    spriteBatch->end();
 
-        // Use the FrameBuffer to texture the quad.
-        if (!_frameBuffer->getRenderTarget())
+    for (fontIter = _theme->_fonts.begin(); fontIter != _theme->_fonts.end(); fontIter++)
+    {
+        Font* font = *fontIter;
+        if (font)
         {
-            RenderTarget* rt = RenderTarget::create(_id.c_str(), _bounds.width, _bounds.height);
-            GP_ASSERT(rt);
-            _frameBuffer->setRenderTarget(rt);
-            SAFE_RELEASE(rt);
+            font->end();
         }
-
-        Texture::Sampler* sampler = Texture::Sampler::create(_frameBuffer->getRenderTarget()->getTexture());
-        GP_ASSERT(sampler);
-        sampler->setWrapMode(Texture::CLAMP, Texture::CLAMP);
-        material->getParameter("u_texture")->setValue(sampler);
-        material->getParameter("u_textureRepeat")->setValue(Vector2::one());
-        material->getParameter("u_textureTransform")->setValue(Vector2::zero());
-
-        SAFE_RELEASE(sampler);
     }
 
-    bool Form::touchEventInternal(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+    _dirty = false;
+}
+
+void Form::initializeQuad(Mesh* mesh)
+{
+    // Release current model.
+    SAFE_RELEASE(_quad);
+
+    // Create the model.
+    _quad = Model::create(mesh);
+
+    // Create the material.
+    Material* material = _quad->setMaterial("res/shaders/textured.vsh", "res/shaders/textured.fsh");
+    GP_ASSERT(material);
+
+    // Set the common render state block for the material.
+    GP_ASSERT(_theme);
+    GP_ASSERT(_theme->getSpriteBatch());
+    RenderState::StateBlock* stateBlock = _theme->getSpriteBatch()->getStateBlock();
+    GP_ASSERT(stateBlock);
+    stateBlock->setDepthWrite(true);
+    material->setStateBlock(stateBlock);
+
+    // Bind the WorldViewProjection matrix.
+    material->setParameterAutoBinding("u_worldViewProjectionMatrix", RenderState::WORLD_VIEW_PROJECTION_MATRIX);
+
+    // Bind the texture.
+    Texture::Sampler* sampler = Texture::Sampler::create(_frameBuffer->getRenderTarget()->getTexture());
+    GP_ASSERT(sampler);
+    sampler->setWrapMode(Texture::CLAMP, Texture::CLAMP);
+    material->getParameter("u_diffuseTexture")->setValue(sampler);
+    material->getParameter("u_diffuseColor")->setValue(Vector4::one());
+
+    SAFE_RELEASE(sampler);
+}
+
+bool Form::touchEventInternal(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+{
+    // Check for a collision with each Form in __forms.
+    // Pass the event on.
+    std::vector<Form*>::const_iterator it;
+    for (it = __forms.begin(); it < __forms.end(); it++)
     {
-        // Check for a collision with each Form in __forms.
-        // Pass the event on.
-        std::vector<Form*>::const_iterator it;
-        for (it = __forms.begin(); it < __forms.end(); it++)
-        {
-            Form* form = *it;
-            GP_ASSERT(form);
+        Form* form = *it;
+        GP_ASSERT(form);
 
-            if (form->isEnabled())
+        if (form->isEnabled())
+        {
+            Node* node = form->_node;
+            if (node)
             {
-                Node* node = form->_node;
-                if (node)
-                {
-                    Scene* scene = node->getScene();
-                    GP_ASSERT(scene);
-                    Camera* camera = scene->getActiveCamera();
+                Scene* scene = node->getScene();
+                GP_ASSERT(scene);
+                Camera* camera = scene->getActiveCamera();
 
-                    if (camera)
+                if (camera)
+                {
+                    // Get info about the form's position.
+                    Matrix m = node->getMatrix();
+                    Vector3 min(0, 0, 0);
+                    m.transformPoint(&min);
+
+                    // Unproject point into world space.
+                    Ray ray;
+                    camera->pickRay(Game::getInstance()->getViewport(), x, y, &ray);
+
+                    // Find the quad's plane.
+                    // We know its normal is the quad's forward vector.
+                    Vector3 normal = node->getForwardVectorWorld();
+
+                    // To get the plane's distance from the origin,
+                    // we'll find the distance from the plane defined
+                    // by the quad's forward vector and one of its points
+                    // to the plane defined by the same vector and the origin.
+                    const float& a = normal.x; const float& b = normal.y; const float& c = normal.z;
+                    const float d = -(a*min.x) - (b*min.y) - (c*min.z);
+                    const float distance = abs(d) /  sqrt(a*a + b*b + c*c);
+                    Plane plane(normal, -distance);
+
+                    // Check for collision with plane.
+                    float collisionDistance = ray.intersects(plane);
+                    if (collisionDistance != Ray::INTERSECTS_NONE)
                     {
-                        // Get info about the form's position.
-                        Matrix m = node->getMatrix();
-                        Vector3 min(0, 0, 0);
-                        m.transformPoint(&min);
-
-                        // Unproject point into world space.
-                        Ray ray;
-                        camera->pickRay(Game::getInstance()->getViewport(), x, y, &ray);
-
-                        // Find the quad's plane.
-                        // We know its normal is the quad's forward vector.
-                        Vector3 normal = node->getForwardVectorWorld();
-
-                        // To get the plane's distance from the origin,
-                        // we'll find the distance from the plane defined
-                        // by the quad's forward vector and one of its points
-                        // to the plane defined by the same vector and the origin.
-                        const float& a = normal.x; const float& b = normal.y; const float& c = normal.z;
-                        const float d = -(a*min.x) - (b*min.y) - (c*min.z);
-                        const float distance = abs(d) /  sqrt(a*a + b*b + c*c);
-                        Plane plane(normal, -distance);
-
-                        // Check for collision with plane.
-                        float collisionDistance = ray.intersects(plane);
-                        if (collisionDistance != Ray::INTERSECTS_NONE)
+                        // Multiply the ray's direction vector by collision distance
+                        // and add that to the ray's origin.
+                        Vector3 point = ray.getOrigin() + collisionDistance*ray.getDirection();
+
+                        // Project this point into the plane.
+                        m.invert();
+                        m.transformPoint(&point);
+
+                        // Pass the touch event on.
+                        const Rectangle& bounds = form->getBounds();
+                        if (form->getState() == Control::FOCUS ||
+                            (evt == Touch::TOUCH_PRESS &&
+                                point.x >= bounds.x &&
+                                point.x <= bounds.x + bounds.width &&
+                                point.y >= bounds.y &&
+                                point.y <= bounds.y + bounds.height))
                         {
-                            // Multiply the ray's direction vector by collision distance
-                            // and add that to the ray's origin.
-                            Vector3 point = ray.getOrigin() + collisionDistance*ray.getDirection();
-
-                            // Project this point into the plane.
-                            m.invert();
-                            m.transformPoint(&point);
-
-                            // Pass the touch event on.
-                            const Rectangle& bounds = form->getClipBounds();
-                            if (form->getState() == Control::FOCUS ||
-                                (evt == Touch::TOUCH_PRESS &&
-                                 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 - bounds.x, bounds.height - point.y - bounds.y, contactIndex))
                             {
-                               if (form->touchEvent(evt, point.x - bounds.x, bounds.height - point.y - bounds.y, contactIndex))
-                               {
-                                   return true;
-                               }
+                                return true;
                             }
                         }
                     }
                 }
-                else
+            }
+            else
+            {
+                // Simply compare with the form's bounds.
+                const Rectangle& bounds = form->getBounds();
+                if (form->getState() == Control::FOCUS ||
+                    (evt == Touch::TOUCH_PRESS &&
+                        x >= bounds.x &&
+                        x <= bounds.x + bounds.width &&
+                        y >= bounds.y &&
+                        y <= bounds.y + bounds.height))
                 {
-                    // Simply compare with the form's bounds.
-                    const Rectangle& bounds = form->getClipBounds();
-                    if (form->getState() == Control::FOCUS ||
-                        (evt == Touch::TOUCH_PRESS &&
-                         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 - bounds.x, y - bounds.y, contactIndex))
                     {
-                        // Pass on the event's position relative to the form.
-                        if (form->touchEvent(evt, x - bounds.x, y - bounds.y, contactIndex))
-                        {
-                            return true;
-                        }
+                        return true;
                     }
                 }
             }
         }
-
-        return false;
     }
 
-    void Form::keyEventInternal(Keyboard::KeyEvent evt, int key)
+    return false;
+}
+
+void Form::keyEventInternal(Keyboard::KeyEvent evt, int key)
+{
+    std::vector<Form*>::const_iterator it;
+    for (it = __forms.begin(); it < __forms.end(); it++)
     {
-        std::vector<Form*>::const_iterator it;
-        for (it = __forms.begin(); it < __forms.end(); it++)
+        Form* form = *it;
+        GP_ASSERT(form);
+        if (form->isEnabled())
         {
-            Form* form = *it;
-            GP_ASSERT(form);
-            if (form->isEnabled())
-            {
-                form->keyEvent(evt, key);
-            }
+            form->keyEvent(evt, key);
         }
     }
-}
+}
+
+int Form::nextHighestPowerOfTwo(int x)
+{
+    x--;
+    x |= x >> 1;
+    x |= x >> 2;
+    x |= x >> 4;
+    x |= x >> 8;
+    x |= x >> 16;
+    return x + 1;
+}
+
+}

+ 7 - 0
gameplay/src/Form.h

@@ -157,11 +157,18 @@ private:
      */
     static void keyEventInternal(Keyboard::KeyEvent evt, int key);
 
+    static int nextHighestPowerOfTwo(int x);
+
     Theme* _theme;              // The Theme applied to this Form.
     Model* _quad;               // Quad for rendering this Form in world-space.
     Node* _node;                // Node for transforming this Form in world-space.
     FrameBuffer* _frameBuffer;  // FBO the Form is rendered into for texturing the quad.
     Matrix _projectionMatrix;   // Orthographic projection matrix to be set on SpriteBatch objects when rendering into the FBO.
+    Matrix _defaultProjectionMatrix;
+    SpriteBatch* _spriteBatch;
+
+    float _u2;
+    float _v1;
 };
 
 }

+ 26 - 22
gameplay/src/Frustum.cpp

@@ -74,6 +74,24 @@ void Frustum::getCorners(Vector3* corners) const
     Plane::intersection(_far, _left, _top, &corners[7]);
 }
 
+bool Frustum::intersects(const Vector3& point) const
+{
+    if (_near.distance(point) <= 0)
+        return false;
+    if (_far.distance(point) <= 0)
+        return false;
+    if (_left.distance(point) <= 0)
+        return false;
+    if (_right.distance(point) <= 0)
+        return false;
+    if (_top.distance(point) <= 0)
+        return false;
+    if (_bottom.distance(point) <= 0)
+        return false;
+
+    return true;
+}
+
 bool Frustum::intersects(const BoundingSphere& sphere) const
 {
     return sphere.intersects(*this);
@@ -105,23 +123,14 @@ void Frustum::set(const Frustum& frustum)
     _matrix.set(frustum._matrix);
 }
 
-void updatePlane(const Matrix& matrix, Plane* dst)
+void Frustum::updatePlanes()
 {
-    GP_ASSERT(dst);
-
-    dst->setNormal(Vector3(matrix.m[3] + matrix.m[2], matrix.m[7] + matrix.m[6], matrix.m[11] + matrix.m[10]));
-    dst->setDistance(matrix.m[15] + matrix.m[14]);
-
-    Vector3 normal = dst->getNormal();
-    if (!normal.isZero())
-    {
-        float normalizeFactor = 1.0f / sqrt(normal.x * normal.x + normal.y * normal.y + normal.z * normal.z);
-        if (normalizeFactor != 1.0f)
-        {
-            dst->setNormal(Vector3(normal.x * normalizeFactor, normal.y * normalizeFactor, normal.z * normalizeFactor));
-            dst->setDistance(dst->getDistance() * normalizeFactor);
-        }
-    }
+    _near.set(Vector3(_matrix.m[3] + _matrix.m[2], _matrix.m[7] + _matrix.m[6], _matrix.m[11] + _matrix.m[10]), _matrix.m[15] + _matrix.m[14]);
+    _far.set(Vector3(_matrix.m[3] - _matrix.m[2], _matrix.m[7] - _matrix.m[6], _matrix.m[11] - _matrix.m[10]), _matrix.m[15] - _matrix.m[14]);
+    _bottom.set(Vector3(_matrix.m[3] + _matrix.m[1], _matrix.m[7] + _matrix.m[5], _matrix.m[11] + _matrix.m[9]), _matrix.m[15] + _matrix.m[13]);
+    _top.set(Vector3(_matrix.m[3] - _matrix.m[1], _matrix.m[7] - _matrix.m[5], _matrix.m[11] - _matrix.m[9]), _matrix.m[15] - _matrix.m[13]);
+    _left.set(Vector3(_matrix.m[3] + _matrix.m[0], _matrix.m[7] + _matrix.m[4], _matrix.m[11] + _matrix.m[8]), _matrix.m[15] + _matrix.m[12]);
+    _right.set(Vector3(_matrix.m[3] - _matrix.m[0], _matrix.m[7] - _matrix.m[4], _matrix.m[11] - _matrix.m[8]), _matrix.m[15] - _matrix.m[12]);
 }
 
 void Frustum::set(const Matrix& matrix)
@@ -129,12 +138,7 @@ void Frustum::set(const Matrix& matrix)
     _matrix.set(matrix);
 
     // Update the planes.
-    updatePlane(matrix, &_near);
-    updatePlane(matrix, &_far);
-    updatePlane(matrix, &_bottom);
-    updatePlane(matrix, &_top);
-    updatePlane(matrix, &_left);
-    updatePlane(matrix, &_right);
+    updatePlanes();
 }
 
 }

+ 9 - 0
gameplay/src/Frustum.h

@@ -113,6 +113,15 @@ public:
      */
     void getCorners(Vector3* corners) const;
 
+    /**
+     * Tests whether this frustum instersects the specified point.
+     *
+     * @param point The point to test intersection with.
+     *
+     * @return true if the specified point intersects this frustum; false otherwise.
+     */
+    bool intersects(const Vector3& point) const;
+
     /**
      * Tests whether this frustum intersects the specified bounding sphere.
      *

+ 69 - 62
gameplay/src/Label.cpp

@@ -3,86 +3,93 @@
 
 namespace gameplay
 {
-    Label::Label() : _text(""), _font(NULL)
-    {
-    }
 
-    Label::Label(const Label& copy)
-    {
-    }
+Label::Label() : _text(""), _font(NULL)
+{
+}
 
-    Label::~Label()
-    {
-    }
+Label::Label(const Label& copy)
+{
+}
 
-    Label* Label::create(Theme::Style* style, Properties* properties)
-    {
-        Label* label = new Label();
-        label->initialize(style, properties);
+Label::~Label()
+{
+}
 
-        return label;
-    }
+Label* Label::create(Theme::Style* style, Properties* properties)
+{
+    Label* label = new Label();
+    label->initialize(style, properties);
+    label->_consumeTouchEvents = false;
 
-    void Label::initialize(Theme::Style* style, Properties* properties)
-    {
-        GP_ASSERT(properties);
+    return label;
+}
 
-        Control::initialize(style, properties);
+void Label::initialize(Theme::Style* style, Properties* properties)
+{
+    GP_ASSERT(properties);
 
-        const char* text = properties->getString("text");
-        if (text)
-        {
-            _text = text;
-        }
-    }
+    Control::initialize(style, properties);
 
-    void Label::addListener(Control::Listener* listener, int eventFlags)
+    const char* text = properties->getString("text");
+    if (text)
     {
-        if ((eventFlags & Listener::TEXT_CHANGED) == Listener::TEXT_CHANGED)
-        {
-            GP_ERROR("TEXT_CHANGED event is not applicable to this control.");
-        }
-        if ((eventFlags & Listener::VALUE_CHANGED) == Listener::VALUE_CHANGED)
-        {
-            GP_ERROR("VALUE_CHANGED event is not applicable to this control.");
-        }
-
-        Control::addListener(listener, eventFlags);
+        _text = text;
     }
-    
-    void Label::setText(const char* text)
+}
+
+void Label::addListener(Control::Listener* listener, int eventFlags)
+{
+    if ((eventFlags & Listener::TEXT_CHANGED) == Listener::TEXT_CHANGED)
     {
-        if (text)
-        {
-            _text = text;
-        }
+        GP_ERROR("TEXT_CHANGED event is not applicable to this control.");
     }
-
-    const char* Label::getText()
+    if ((eventFlags & Listener::VALUE_CHANGED) == Listener::VALUE_CHANGED)
     {
-        return _text.c_str();
+        GP_ERROR("VALUE_CHANGED event is not applicable to this control.");
     }
 
-    void Label::update(const Rectangle& clip)
-    {
-        Control::update(clip);
+    _consumeTouchEvents = true;
 
-        _font = getFont(_state);
-        _textColor = getTextColor(_state);
-        _textColor.w *= getOpacity(_state);
-    }
+    Control::addListener(listener, eventFlags);
+}
+    
+void Label::setText(const char* text)
+{
+    assert(text);
 
-    void Label::drawText(const Rectangle& clip)
-    {
-        if (_text.size() <= 0)
-            return;
+    _text = text;
+    _dirty = true;
+}
 
-        // Draw the text.
-        if (_font)
-        {
-            _font->drawText(_text.c_str(), _textBounds, _textColor, getFontSize(_state), getTextAlignment(_state), true, getTextRightToLeft(_state), &_clip);
-        }
+const char* Label::getText()
+{
+    return _text.c_str();
+}
 
-        _dirty = false;
+void Label::update(const Rectangle& clip, const Vector2& offset)
+{
+    Control::update(clip, offset);
+
+    _textBounds.set(_viewportBounds);
+
+    _font = getFont(_state);
+    _textColor = getTextColor(_state);
+    _textColor.w *= getOpacity(_state);
+}
+
+void Label::drawText(const Rectangle& clip)
+{
+    if (_text.size() <= 0)
+        return;
+
+    // Draw the text.
+    if (_font)
+    {
+        _font->drawText(_text.c_str(), _textBounds, _textColor, getFontSize(_state), getTextAlignment(_state), true, getTextRightToLeft(_state), &_viewportClipBounds);
     }
+
+    _dirty = false;
+}
+
 }

+ 3 - 1
gameplay/src/Label.h

@@ -91,8 +91,9 @@ protected:
      * properties, such as its text viewport.
      *
      * @param clip The clipping rectangle of this label's parent container.
+     * @param offset The scroll offset of this label's parent container.
      */
-    void update(const Rectangle& clip);
+    void update(const Rectangle& clip, const Vector2& offset);
 
     /**
      * Draw this label's text.
@@ -115,6 +116,7 @@ protected:
      * The text color being used to display the label.
      */
     Vector4 _textColor;
+    Rectangle _textBounds;  // The position and size of this control's text area, before clipping.  Used for text alignment.
 
 private:
 

+ 53 - 45
gameplay/src/Layout.cpp

@@ -5,53 +5,61 @@
 
 namespace gameplay
 {
-    void Layout::align(Control* control, const Container* container)
-    {
-        GP_ASSERT(control);
-        GP_ASSERT(container);
 
-        if (control->_alignment != Control::ALIGN_TOP_LEFT ||
-            control->_autoWidth || control->_autoHeight)
+void Layout::align(Control* control, const Container* container)
+{
+    GP_ASSERT(control);
+    GP_ASSERT(container);
+
+    if (control->_alignment != Control::ALIGN_TOP_LEFT ||
+        control->_autoWidth || control->_autoHeight)
+    {
+        Rectangle controlBounds = control->getBounds();
+        const Theme::Margin& controlMargin = control->getMargin();
+        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;
+
+        if (control->_autoWidth)
         {
-            Rectangle controlBounds = control->getBounds();
-            const Rectangle& containerBounds = container->getClipBounds();
-            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;
-
-            if (control->_autoWidth)
-            {
-                controlBounds.width = clipWidth;
-            }
-
-            if (control->_autoHeight)
-            {
-                controlBounds.height = clipHeight;
-            }
-
-            // Vertical alignment
-            if ((control->_alignment & Control::ALIGN_BOTTOM) == Control::ALIGN_BOTTOM)
-            {
-                controlBounds.y = clipHeight - controlBounds.height;
-            }
-            else if ((control->_alignment & Control::ALIGN_VCENTER) == Control::ALIGN_VCENTER)
-            {
-                controlBounds.y = clipHeight * 0.5f - controlBounds.height * 0.5f;
-            }
-
-            // Horizontal alignment
-            if ((control->_alignment & Control::ALIGN_RIGHT) == Control::ALIGN_RIGHT)
-            {
-                controlBounds.x = clipWidth - controlBounds.width;
-            }
-            else if ((control->_alignment & Control::ALIGN_HCENTER) == Control::ALIGN_HCENTER)
-            {
-                controlBounds.x = clipWidth * 0.5f - controlBounds.width * 0.5f;
-            }
-
-            control->setBounds(controlBounds);
+            controlBounds.width = clipWidth;
         }
+
+        if (control->_autoHeight)
+        {
+            controlBounds.height = clipHeight;
+        }
+
+        // Vertical alignment
+        if ((control->_alignment & Control::ALIGN_BOTTOM) == Control::ALIGN_BOTTOM)
+        {
+            controlBounds.y = clipHeight - controlBounds.height;
+        }
+        else if ((control->_alignment & Control::ALIGN_VCENTER) == Control::ALIGN_VCENTER)
+        {
+            controlBounds.y = clipHeight * 0.5f - controlBounds.height * 0.5f;
+        }
+
+        // Horizontal alignment
+        if ((control->_alignment & Control::ALIGN_RIGHT) == Control::ALIGN_RIGHT)
+        {
+            controlBounds.x = clipWidth - controlBounds.width - controlMargin.right;
+        }
+        else if ((control->_alignment & Control::ALIGN_HCENTER) == Control::ALIGN_HCENTER)
+        {
+            controlBounds.x = clipWidth * 0.5f - controlBounds.width * 0.5f;
+        }
+
+        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);
 };
 
 }

+ 25 - 14
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()
@@ -53,7 +48,7 @@ ParticleEmitter* ParticleEmitter::create(const char* textureFile, TextureBlendin
     GP_ASSERT(textureFile);
 
     Texture* texture = NULL;
-    texture = Texture::create(textureFile, true);    
+    texture = Texture::create(textureFile, false);
 
     if (!texture)
     {
@@ -282,6 +277,7 @@ void ParticleEmitter::emit(unsigned int particleCount)
     for (unsigned int i = 0; i < particleCount; i++)
     {
         Particle* p = &_particles[_particleCount];
+        p->_visible = true;
 
         generateColor(_colorStart, _colorStartVar, &p->_colorStart);
         generateColor(_colorEnd, _colorEndVar, &p->_colorEnd);
@@ -784,14 +780,19 @@ void ParticleEmitter::update(long elapsedTime)
         // How many particles should we emit this frame?
         unsigned int emitCount = _timeRunning / _timePerEmission;
             
-        if ((int)_timePerEmission > 0)
+        if (emitCount)
         {
-            _timeRunning %= (int)_timePerEmission;
-        }
+            if ((int)_timePerEmission > 0)
+            {
+                _timeRunning %= (int)_timePerEmission;
+            }
 
-        emit(emitCount);
+            emit(emitCount);
+        }
     }
 
+    const Frustum& frustum = _node->getScene()->getActiveCamera()->getFrustum();
+
     // Now update all currently living particles.
     for (unsigned int particlesIndex = 0; particlesIndex < _particleCount; ++particlesIndex)
     {
@@ -817,6 +818,12 @@ void ParticleEmitter::update(long elapsedTime)
             p->_position.y += p->_velocity.y * elapsedSecs;
             p->_position.z += p->_velocity.z * elapsedSecs;
 
+            if (!frustum.intersects(p->_position))
+            {
+                p->_visible = false;
+                continue;
+            }
+
             p->_angle += p->_rotationPerParticleSpeed * elapsedSecs;
 
             // Simple linear interpolation of color and size.
@@ -896,10 +903,11 @@ void ParticleEmitter::draw()
         _spriteBatch->begin();
 
         // 2D Rotation.
-        Vector2 pivot(0.5f, 0.5f);
+        static const Vector2 pivot(0.5f, 0.5f);
 
         // 3D Rotation so that particles always face the camera.
         const Matrix& cameraWorldMatrix = _node->getScene()->getActiveCamera()->getNode()->getWorldMatrix();
+
         Vector3 right;
         cameraWorldMatrix.getRightVector(&right);
         Vector3 up;
@@ -909,9 +917,12 @@ void ParticleEmitter::draw()
         {
             Particle* p = &_particles[i];
 
-            _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);
+            if (p->_visible)
+            {
+                _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);
+            }
         }
 
         // Render.

+ 8 - 7
gameplay/src/ParticleEmitter.h

@@ -615,6 +615,13 @@ public:
      */
     void draw();
 
+    /**
+     * Gets a BlendMode enum from a corresponding string.
+     */
+    static TextureBlending getTextureBlendingFromString(const char* src);
+
+    void setTextureBlending(TextureBlending blending);
+
 private:
 
     /**
@@ -656,13 +663,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.
      */
@@ -687,6 +687,7 @@ private:
         float _size;
         unsigned int _frame;
         float _timeOnCurrentFrame;
+        bool _visible;
     };
 
     unsigned int _particleCountMax;

+ 2 - 2
gameplay/src/Plane.cpp

@@ -251,7 +251,7 @@ void Plane::transform(const Matrix& matrix)
         float nx = _normal.x * inverted.m[0] + _normal.y * inverted.m[1] + _normal.z * inverted.m[2] + _distance * inverted.m[3];
         float ny = _normal.x * inverted.m[4] + _normal.y * inverted.m[5] + _normal.z * inverted.m[6] + _distance * inverted.m[7];
         float nz = _normal.x * inverted.m[8] + _normal.y * inverted.m[9] + _normal.z * inverted.m[10] + _distance * inverted.m[11];
-        float d = _normal.x * inverted.m[12]+ _normal.y * inverted.m[13] + _normal.z * inverted.m[14]+ _distance * inverted.m[15];
+        float d = _normal.x * inverted.m[12]+ _normal.y * inverted.m[13] + _normal.z * inverted.m[14] + _distance * inverted.m[15];
         float factor = 1.0f / sqrt(nx * nx + ny * ny + nz * nz);
 
         _normal.x = nx * factor;
@@ -271,7 +271,7 @@ void Plane::normalize()
 
     if (normalizeFactor != 1.0f)
     {
-        _normal.x*= normalizeFactor;
+        _normal.x *= normalizeFactor;
         _normal.y *= normalizeFactor;
         _normal.z *= normalizeFactor;
         _distance *= normalizeFactor;

+ 7 - 11
gameplay/src/RadioButton.cpp

@@ -88,8 +88,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)
                     {
@@ -122,9 +122,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())
@@ -165,10 +165,7 @@ void RadioButton::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
     GP_ASSERT(_image);
 
     // 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();
@@ -184,10 +181,9 @@ 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);
+    spriteBatch->draw(pos.x, pos.y, size.x, size.y, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color, _viewportClipBounds);
 }
 
 }

+ 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

+ 17 - 18
gameplay/src/Slider.cpp

@@ -3,7 +3,7 @@
 namespace gameplay
 {
 
-Slider::Slider()
+Slider::Slider() : _minImage(NULL), _maxImage(NULL), _trackImage(NULL), _markerImage(NULL)
 {
 }
 
@@ -88,21 +88,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)
             {
@@ -142,9 +141,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);
@@ -189,23 +188,23 @@ 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, _viewportClipBounds);
 
     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);
+    spriteBatch->draw(pos.x, pos.y, minCapRegion.width, minCapRegion.height, minCap.u1, minCap.v1, minCap.u2, minCap.v2, minCapColor, _viewportClipBounds);
         
-    pos.x = clip.x + _clipBounds.x + _clipBounds.width - border.right - padding.right - maxCapRegion.width * 0.5f;
-    spriteBatch->draw(pos.x, pos.y, maxCapRegion.width, maxCapRegion.height, maxCap.u1, maxCap.v1, maxCap.u2, maxCap.v2, maxCapColor, _clip);
+    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, _viewportClipBounds);
 
     // 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);
+    spriteBatch->draw(pos.x, pos.y, markerRegion.width, markerRegion.height, marker.u1, marker.v1, marker.u2, marker.v2, markerColor, _viewportClipBounds);
 }
 
 }

+ 3 - 2
gameplay/src/Slider.h

@@ -154,9 +154,10 @@ protected:
      * properties, such as its text viewport.
      *
      * @param clip The clipping rectangle of this slider's parent container.
+     * @param offset The scroll offset of this slider's parent container.
      */
-    void update(const Rectangle& clip); 
-
+    void update(const Rectangle& clip, const Vector2& offset);
+
     /**
      * The minimum value for the Slider.
      */

+ 69 - 67
gameplay/src/SpriteBatch.cpp

@@ -45,57 +45,6 @@
 namespace gameplay
 {
 
-/**
- * Sprite vertex structure used for batching.
- */
-struct SpriteVertex
-{
-    /**
-     * The x coordinate of the vertex.
-     */
-    float x;
-    
-    /**
-     * The y coordinate of the vertex.
-     */
-    float y;
-    
-    /**
-     * The z coordinate of the vertex.
-     */
-    float z;
-
-    /**
-     * The u component of the (u, v) texture coordinates for the vertex.
-     */
-    float u;
-    
-    /**
-     * The v component of the (u, v) texture coordinates for the vertex.
-     */
-    float v;
-
-    /**
-     * The red color component of the vertex.
-     */
-    float r;
-    
-    /**
-     * The green color component of the vertex.
-     */
-    float g;
-    
-    /**
-     * The blue color component of the vertex.
-     */
-    float b;
-    
-    /**
-     * The alpha component of the vertex.
-     */
-    float a;
-};
-
 // Shared sprite effects
 static Effect* __spriteEffect = NULL;
 
@@ -303,31 +252,87 @@ void SpriteBatch::draw(const Vector3& position, const Vector3& right, const Vect
     float u1, float v1, float u2, float v2, const Vector4& color, const Vector2& rotationPoint, float rotationAngle)
 {
     // Calculate the vertex positions.
-    Vector3 p[4];
+    //static Vector3 p[4];
+
+    // Pre-optimized:
+    /*
     p[0] = position - 0.5f * width * right - 0.5f * height * forward;
     p[1] = position + 0.5f * width * right - 0.5f * height * forward;
     p[2] = p[0] + height * forward;
     p[3] = p[1] + height * forward;
+    */
+    
+    // Optimized:
+    Vector3 tRight(right);
+    tRight *= width * 0.5f;
+    Vector3 tForward(forward);
+    tForward *= height * 0.5f;
+    
+    Vector3 p0 = position;
+    p0 -= tRight;
+    p0 -= tForward;
+
+    Vector3 p1 = position;
+    p1 += tRight;
+    p1 -= tForward;
+
+    tForward = forward;
+    tForward *= height;
+    Vector3 p2 = p0;
+    p2 += tForward;
+    Vector3 p3 = p1;
+    p3 += tForward;
 
     // Calculate the rotation point.
-    Vector3 rp = p[0] + (rotationPoint.x * width * right) + (rotationPoint.y * height * forward);
+    
+    // Pre-optimized:
+    //Vector3 rp = p[0] + (rotationPoint.x * width * right) + (rotationPoint.y * height * forward);
+
+    // Optimized:
+    Vector3 rp = p0;
+    tRight = right;
+    tRight *= width * rotationPoint.x;
+    tForward *= rotationPoint.y;
+    rp += tRight;
+    rp += tForward;
 
     // Rotate all points the specified amount about the given point (about the up vector).
-    Vector3 u;
+    static Vector3 u;
     Vector3::cross(right, forward, &u);
-    Matrix rotation;
+    static Matrix rotation;
     Matrix::createRotation(u, rotationAngle, &rotation);
+
+    // Pre-optimized:
+    /*
     p[0] = (rotation * (p[0] - rp)) + rp;
     p[1] = (rotation * (p[1] - rp)) + rp;
     p[2] = (rotation * (p[2] - rp)) + rp;
     p[3] = (rotation * (p[3] - rp)) + rp;
+    */
+
+    // Optimized:
+    p0 -= rp;
+    p0 *= rotation;
+    p0 += rp;
+
+    p1 -= rp;
+    p1 *= rotation;
+    p1 += rp;
+
+    p2 -= rp;
+    p2 *= rotation;
+    p2 += rp;
+
+    p3 -= rp;
+    p3 *= rotation;
+    p3 += rp;
 
     // Add the sprite vertex data to the batch.
     static SpriteVertex v[4];
-    ADD_SPRITE_VERTEX(v[0], p[0].x, p[0].y, p[0].z, u1, v1, color.x, color.y, color.z, color.w);
-    ADD_SPRITE_VERTEX(v[1], p[1].x, p[1].y, p[1].z, u2, v1, color.x, color.y, color.z, color.w);
-    ADD_SPRITE_VERTEX(v[2], p[2].x, p[2].y, p[2].z, u1, v2, color.x, color.y, color.z, color.w);
-    ADD_SPRITE_VERTEX(v[3], p[3].x, p[3].y, p[3].z, u2, v2, color.x, color.y, color.z, color.w);
+    ADD_SPRITE_VERTEX(v[0], p0.x, p0.y, p0.z, u1, v1, color.x, color.y, color.z, color.w);
+    ADD_SPRITE_VERTEX(v[1], p1.x, p1.y, p1.z, u2, v1, color.x, color.y, color.z, color.w);
+    ADD_SPRITE_VERTEX(v[2], p2.x, p2.y, p2.z, u1, v2, color.x, color.y, color.z, color.w);
+    ADD_SPRITE_VERTEX(v[3], p3.x, p3.y, p3.z, u2, v2, color.x, color.y, color.z, color.w);
     
     static const unsigned short indices[4] = { 0, 1, 2, 3 };
     _batch->add(v, 4, const_cast<unsigned short*>(indices), 4);
@@ -340,14 +345,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.
@@ -364,7 +362,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;
     }
 
@@ -372,7 +372,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;
     }
 

+ 51 - 0
gameplay/src/SpriteBatch.h

@@ -28,6 +28,57 @@ class SpriteBatch
 
 public:
 
+    /**
+     * Sprite vertex structure used for batching.
+     */
+    struct SpriteVertex
+    {
+        /**
+         * The x coordinate of the vertex.
+         */
+        float x;
+    
+        /**
+         * The y coordinate of the vertex.
+         */
+        float y;
+    
+        /**
+         * The z coordinate of the vertex.
+         */
+        float z;
+
+        /**
+         * The u component of the (u, v) texture coordinates for the vertex.
+         */
+        float u;
+    
+        /**
+         * The v component of the (u, v) texture coordinates for the vertex.
+         */
+        float v;
+
+        /**
+         * The red color component of the vertex.
+         */
+        float r;
+    
+        /**
+         * The green color component of the vertex.
+         */
+        float g;
+    
+        /**
+         * The blue color component of the vertex.
+         */
+        float b;
+    
+        /**
+         * The alpha component of the vertex.
+         */
+        float a;
+    };
+
     /**
      * Creates a new SpriteBatch for drawing sprites with the given texture.
      *

+ 59 - 47
gameplay/src/TextBox.cpp

@@ -56,8 +56,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);
@@ -67,8 +73,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;
@@ -76,14 +82,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;
     }
 
@@ -105,7 +118,7 @@ void TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
                         // TODO: Move cursor to beginning of line.
                         // This only works for left alignment...
                         
-                        //_caretLocation.x = _clip.x;
+                        //_caretLocation.x = _viewportClipBounds.x;
                         //_dirty = true;
                         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);
@@ -139,9 +153,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;
@@ -154,9 +168,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;
@@ -170,7 +184,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;
@@ -184,7 +198,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;
@@ -201,7 +215,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)
@@ -212,7 +226,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;
@@ -228,18 +242,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.
@@ -249,13 +263,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.
@@ -277,22 +291,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);
-        GP_ASSERT(font);
-        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);
@@ -302,9 +303,8 @@ void TextBox::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
 {
     if (_state == FOCUS)
     {
-        GP_ASSERT(_caretImage);
-
         // Draw the cursor at its current location.
+        GP_ASSERT(_caretImage);
         const Rectangle& region = _caretImage->getRegion();
         if (!region.isEmpty())
         {
@@ -313,7 +313,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, _viewportClipBounds);
         }
     }
 
@@ -322,11 +322,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);
+
+    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);
 
-    _caretLocation.set(x - border.left - padding.left + _clip.x,
-                       y - border.top - padding.top + _clip.y);
+    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.
@@ -126,6 +126,7 @@ protected:
      * The current position of the TextBox's caret.
      */
     Vector2 _caretLocation;
+    Vector2 _prevCaretLocation;
 
     /**
      * The index into the TextBox's string that the caret is.

+ 655 - 651
gameplay/src/Theme.cpp

@@ -4,806 +4,810 @@
 
 namespace gameplay
 {
-    static std::vector<Theme*> __themeCache;
 
-    Theme::Theme()
+static std::vector<Theme*> __themeCache;
+
+Theme::Theme()
+{
+}
+
+Theme::Theme(const Theme& theme)
+{
+}
+
+Theme::~Theme()
+{
+    // Destroy all the cursors, styles and , fonts.
+    for (unsigned int i = 0, count = _styles.size(); i < count; ++i)
     {
+        Style* style = _styles[i];
+        SAFE_DELETE(style);
     }
 
-    Theme::Theme(const Theme& theme)
+    for (unsigned int i = 0, count = _images.size(); i < count; ++i)
     {
+        ThemeImage* image = _images[i];
+        SAFE_RELEASE(image);
     }
 
-    Theme::~Theme()
+    for (unsigned int i = 0, count = _imageLists.size(); i < count; ++i)
     {
-        // Destroy all the cursors, styles and , fonts.
-        for (unsigned int i = 0, count = _styles.size(); i < count; ++i)
-        {
-            Style* style = _styles[i];
-            SAFE_DELETE(style);
-        }
+        ImageList* imageList = _imageLists[i];
+        SAFE_RELEASE(imageList);
+    }
 
-        for (unsigned int i = 0, count = _images.size(); i < count; ++i)
-        {
-            ThemeImage* image = _images[i];
-            SAFE_RELEASE(image);
-        }
+    for (unsigned int i = 0, count = _skins.size(); i < count; ++i)
+    {
+        Skin* skin = _skins[i];
+        SAFE_RELEASE(skin);
+    }
 
-        for (unsigned int i = 0, count = _imageLists.size(); i < count; ++i)
-        {
-            ImageList* imageList = _imageLists[i];
-            SAFE_RELEASE(imageList);
-        }
+    SAFE_DELETE(_spriteBatch);
+    SAFE_RELEASE(_texture);
 
-        for (unsigned int i = 0, count = _skins.size(); i < count; ++i)
-        {
-            Skin* skin = _skins[i];
-            SAFE_RELEASE(skin);
-        }
+    // Remove ourself from the theme cache.
+    std::vector<Theme*>::iterator itr = std::find(__themeCache.begin(), __themeCache.end(), this);
+    if (itr != __themeCache.end())
+    {
+        __themeCache.erase(itr);
+    }
+}
 
-        SAFE_DELETE(_spriteBatch);
-        SAFE_RELEASE(_texture);
+Theme* Theme::create(const char* url)
+{
+    GP_ASSERT(url);
 
-        // Remove ourself from the theme cache.
-        std::vector<Theme*>::iterator itr = std::find(__themeCache.begin(), __themeCache.end(), this);
-        if (itr != __themeCache.end())
+    // Search theme cache first.
+    for (unsigned int i = 0, count = __themeCache.size(); i < count; ++i)
+    {
+        Theme* t = __themeCache[i];
+        if (t->_url == url)
         {
-            __themeCache.erase(itr);
+            // Found a match.
+            t->addRef();
+
+            return t;
         }
     }
 
-    Theme* Theme::create(const char* url)
+    // Load theme properties from file path.
+    Properties* properties = Properties::create(url);
+    GP_ASSERT(properties);
+    if (properties == NULL)
     {
-        GP_ASSERT(url);
+        return NULL;
+    }
 
-        // Search theme cache first.
-        for (unsigned int i = 0, count = __themeCache.size(); i < count; ++i)
-        {
-            Theme* t = __themeCache[i];
-            if (t->_url == url)
-            {
-                // Found a match.
-                t->addRef();
+    // Check if the Properties is valid and has a valid namespace.
+    Properties* themeProperties = (strlen(properties->getNamespace()) > 0) ? properties : properties->getNextNamespace();
+    GP_ASSERT(themeProperties);
+    if (!themeProperties || !(strcmp(themeProperties->getNamespace(), "theme") == 0))
+    {
+        SAFE_DELETE(properties);
+        return NULL;
+    }
 
-                return t;
-            }
-        }
+    // Create a new theme.
+    Theme* theme = new Theme();
+    theme->_url = url;
+        
+    // Parse the Properties object and set up the theme.
+    const char* textureFile = themeProperties->getString("texture");
+    theme->_texture = Texture::create(textureFile, false);
+    GP_ASSERT(theme->_texture);
+    theme->_spriteBatch = SpriteBatch::create(theme->_texture);
+    GP_ASSERT(theme->_spriteBatch);
 
-        // Load theme properties from file path.
-        Properties* properties = Properties::create(url);
-        GP_ASSERT(properties);
-        if (properties == NULL)
+    float tw = 1.0f / theme->_texture->getWidth();
+    float th = 1.0f / theme->_texture->getHeight();
+
+    Properties* space = themeProperties->getNextNamespace();
+    while (space != NULL)
+    {
+        // First load all cursors, checkboxes etc. that can be referred to by styles.
+        const char* spacename = space->getNamespace();
+            
+        if (strcmp(spacename, "image") == 0)
         {
-            return NULL;
+            theme->_images.push_back(ThemeImage::create(tw, th, space, Vector4::one()));
         }
-
-        // Check if the Properties is valid and has a valid namespace.
-        Properties* themeProperties = (strlen(properties->getNamespace()) > 0) ? properties : properties->getNextNamespace();
-        GP_ASSERT(themeProperties);
-        if (!themeProperties || !(strcmp(themeProperties->getNamespace(), "theme") == 0))
+        else if (strcmp(spacename, "imageList") == 0)
         {
-            SAFE_DELETE(properties);
-            return NULL;
+            theme->_imageLists.push_back(ImageList::create(tw, th, space));
         }
-
-        // Create a new theme.
-        Theme* theme = new Theme();
-        theme->_url = url;
-        
-        // Parse the Properties object and set up the theme.
-        const char* textureFile = themeProperties->getString("texture");
-        theme->_texture = Texture::create(textureFile, false);
-        GP_ASSERT(theme->_texture);
-        theme->_spriteBatch = SpriteBatch::create(theme->_texture);
-        GP_ASSERT(theme->_spriteBatch);
-
-        float tw = 1.0f / theme->_texture->getWidth();
-        float th = 1.0f / theme->_texture->getHeight();
-
-        Properties* space = themeProperties->getNextNamespace();
-        while (space != NULL)
+        else if (strcmp(spacename, "skin") == 0)
         {
-            // First load all cursors, checkboxes etc. that can be referred to by styles.
-            const char* spacename = space->getNamespace();
-            
-            if (strcmp(spacename, "image") == 0)
+            Theme::Border border;
+            Properties* innerSpace = space->getNextNamespace();
+            if (innerSpace)
             {
-                theme->_images.push_back(ThemeImage::create(tw, th, space, Vector4::one()));
+                const char* innerSpacename = innerSpace->getNamespace();
+                if (strcmp(innerSpacename, "border") == 0)
+                {
+                    border.top = innerSpace->getFloat("top");
+                    border.bottom = innerSpace->getFloat("bottom");
+                    border.left = innerSpace->getFloat("left");
+                    border.right = innerSpace->getFloat("right");
+                }
             }
-            else if (strcmp(spacename, "imageList") == 0)
+
+            Vector4 regionVector;
+            space->getVector4("region", &regionVector);
+            const Rectangle region(regionVector.x, regionVector.y, regionVector.z, regionVector.w);
+
+            Vector4 color(1, 1, 1, 1);
+            if (space->exists("color"))
             {
-                theme->_imageLists.push_back(ImageList::create(tw, th, space));
+                space->getColor("color", &color);
             }
-            else if (strcmp(spacename, "skin") == 0)
+
+            Skin* skin = Skin::create(space->getId(), tw, th, region, border, color);
+            GP_ASSERT(skin);
+            theme->_skins.push_back(skin);
+        }
+
+        space = themeProperties->getNextNamespace();
+    }
+
+    themeProperties->rewind();
+    space = themeProperties->getNextNamespace();
+    while (space != NULL)
+    {
+        const char* spacename = space->getNamespace();
+        if (strcmp(spacename, "style") == 0)
+        {
+            // Each style contains up to MAX_OVERLAYS overlays,
+            // as well as Border and Padding namespaces.
+            Theme::Margin margin;
+            Theme::Padding padding;
+            Theme::Style::Overlay* normal = NULL;
+            Theme::Style::Overlay* focus = NULL;
+            Theme::Style::Overlay* active = NULL;
+            Theme::Style::Overlay* disabled = NULL;
+
+            // Need to load OVERLAY_NORMAL first so that the other overlays can inherit from it.
+            Properties* innerSpace = space->getNextNamespace();
+            while (innerSpace != NULL)
             {
-                Theme::Border border;
-                Properties* innerSpace = space->getNextNamespace();
-                if (innerSpace)
+                const char* innerSpacename = innerSpace->getNamespace();
+                if (strcmp(innerSpacename, "stateNormal") == 0)
                 {
-                    const char* innerSpacename = innerSpace->getNamespace();
-                    if (strcmp(innerSpacename, "border") == 0)
+                    Vector4 textColor(0, 0, 0, 1);
+                    if (innerSpace->exists("textColor"))
                     {
-                        border.top = innerSpace->getFloat("top");
-                        border.bottom = innerSpace->getFloat("bottom");
-                        border.left = innerSpace->getFloat("left");
-                        border.right = innerSpace->getFloat("right");
+                        innerSpace->getColor("textColor", &textColor);
                     }
-                }
 
-                Vector4 regionVector;
-                space->getVector4("region", &regionVector);
-                const Rectangle region(regionVector.x, regionVector.y, regionVector.z, regionVector.w);
+                    const char* fontPath = innerSpace->getString("font");
+                    Font* font = NULL;
+                    if (fontPath)
+                    {
+                        font = Font::create(fontPath);
+                    }
+                    unsigned int fontSize = innerSpace->getInt("fontSize");
+                    const char* textAlignmentString = innerSpace->getString("textAlignment");
+                    Font::Justify textAlignment = Font::ALIGN_TOP_LEFT;
+                    if (textAlignmentString)
+                    {
+                        textAlignment = Font::getJustify(textAlignmentString);
+                    }
+                    bool rightToLeft = innerSpace->getBool("rightToLeft");
 
-                Vector4 color(1, 1, 1, 1);
-                if (space->exists("color"))
-                {
-                    space->getColor("color", &color);
+                    float opacity = 1.0f;
+                    if (innerSpace->exists("opacity"))
+                    {
+                        opacity = innerSpace->getFloat("opacity");
+                    }
+
+                    ImageList* imageList = NULL;
+                    ThemeImage* cursor = NULL;
+                    Skin* skin = NULL;
+                    theme->lookUpSprites(innerSpace, &imageList, &cursor, &skin);
+
+                    normal = Theme::Style::Overlay::create();
+                    GP_ASSERT(normal);
+                    normal->setSkin(skin);
+                    normal->setCursor(cursor);
+                    normal->setImageList(imageList);
+                    normal->setTextColor(textColor);
+                    normal->setFont(font);
+                    normal->setFontSize(fontSize);
+                    normal->setTextAlignment(textAlignment);
+                    normal->setTextRightToLeft(rightToLeft);
+                    normal->setOpacity(opacity);
+
+                    if (font)
+                    {
+                        theme->_fonts.insert(font);
+                        font->release();
+                    }
+
+                    // Done with this pass.
+                    break;
                 }
 
-                Skin* skin = Skin::create(space->getId(), tw, th, region, border, color);
-                GP_ASSERT(skin);
-                theme->_skins.push_back(skin);
+                innerSpace = space->getNextNamespace();
             }
 
-            space = themeProperties->getNextNamespace();
-        }
+            // At least the OVERLAY_NORMAL is required.
+            if (!normal)
+                GP_ERROR("All themes require the normal state overlay to be defined.");
 
-        themeProperties->rewind();
-        space = themeProperties->getNextNamespace();
-        while (space != NULL)
-        {
-            const char* spacename = space->getNamespace();
-            if (strcmp(spacename, "style") == 0)
+            space->rewind();
+            innerSpace = space->getNextNamespace();
+            while (innerSpace != NULL)
             {
-                // Each style contains up to MAX_OVERLAYS overlays,
-                // as well as Border and Padding namespaces.
-                Theme::Margin margin;
-                Theme::Padding padding;
-                Theme::Style::Overlay* normal = NULL;
-                Theme::Style::Overlay* focus = NULL;
-                Theme::Style::Overlay* active = NULL;
-                Theme::Style::Overlay* disabled = NULL;
-
-                // Need to load OVERLAY_NORMAL first so that the other overlays can inherit from it.
-                Properties* innerSpace = space->getNextNamespace();
-                while (innerSpace != NULL)
+                const char* innerSpacename = innerSpace->getNamespace();
+                if (strcmp(innerSpacename, "margin") == 0)
+                {
+                    margin.top = innerSpace->getFloat("top");
+                    margin.bottom = innerSpace->getFloat("bottom");
+                    margin.left = innerSpace->getFloat("left");
+                    margin.right = innerSpace->getFloat("right");
+                }
+                else if (strcmp(innerSpacename, "padding") == 0)
                 {
-                    const char* innerSpacename = innerSpace->getNamespace();
-                    if (strcmp(innerSpacename, "stateNormal") == 0)
+                    padding.top = innerSpace->getFloat("top");
+                    padding.bottom = innerSpace->getFloat("bottom");
+                    padding.left = innerSpace->getFloat("left");
+                    padding.right = innerSpace->getFloat("right");
+                }
+                else if (strcmp(innerSpacename, "stateNormal") != 0)
+                {
+                    // Either OVERLAY_FOCUS or OVERLAY_ACTIVE.
+                    // If a property isn't specified, it inherits from OVERLAY_NORMAL.
+                    Vector4 textColor;
+                    if (!innerSpace->getColor("textColor", &textColor))
                     {
-                        Vector4 textColor(0, 0, 0, 1);
-                        if (innerSpace->exists("textColor"))
-                        {
-                            innerSpace->getColor("textColor", &textColor);
-                        }
-
-                        const char* fontPath = innerSpace->getString("font");
-                        Font* font = NULL;
-                        if (fontPath)
-                        {
-                            font = Font::create(fontPath);
-                        }
-                        unsigned int fontSize = innerSpace->getInt("fontSize");
-                        const char* textAlignmentString = innerSpace->getString("textAlignment");
-                        Font::Justify textAlignment = Font::ALIGN_TOP_LEFT;
-                        if (textAlignmentString)
-                        {
-                            textAlignment = Font::getJustify(textAlignmentString);
-                        }
-                        bool rightToLeft = innerSpace->getBool("rightToLeft");
-
-                        float opacity = 1.0f;
-                        if (innerSpace->exists("opacity"))
-                        {
-                            opacity = innerSpace->getFloat("opacity");
-                        }
-
-                        ImageList* imageList = NULL;
-                        ThemeImage* cursor = NULL;
-                        Skin* skin = NULL;
-                        theme->lookUpSprites(innerSpace, &imageList, &cursor, &skin);
-
-                        normal = Theme::Style::Overlay::create();
-                        GP_ASSERT(normal);
-                        normal->setSkin(skin);
-                        normal->setCursor(cursor);
-                        normal->setImageList(imageList);
-                        normal->setTextColor(textColor);
-                        normal->setFont(font);
-                        normal->setFontSize(fontSize);
-                        normal->setTextAlignment(textAlignment);
-                        normal->setTextRightToLeft(rightToLeft);
-                        normal->setOpacity(opacity);
+                        textColor.set(normal->getTextColor());
+                    }
 
-                        if (font)
-                        {
-                            theme->_fonts.insert(font);
-                            font->release();
-                        }
+                    const char* fontPath = innerSpace->getString("font");
+                    Font* font = NULL;
+                    if (fontPath)
+                    {
+                        font = Font::create(fontPath);
+                    }
+                    if (!font)
+                    {
+                        font = normal->getFont();
+                    }
 
-                        // Done with this pass.
-                        break;
+                    unsigned int fontSize;
+                    if (innerSpace->exists("fontSize"))
+                    {
+                        fontSize = innerSpace->getInt("fontSize");
+                    }
+                    else
+                    {
+                        fontSize = normal->getFontSize();
                     }
 
-                    innerSpace = space->getNextNamespace();
-                }
+                    const char* textAlignmentString = innerSpace->getString("textAlignment");
+                    Font::Justify textAlignment;
+                    if (textAlignmentString)
+                    {
+                        textAlignment = Font::getJustify(textAlignmentString);
+                    }
+                    else
+                    {
+                        textAlignment = normal->getTextAlignment();
+                    }
 
-                // At least the OVERLAY_NORMAL is required.
-                if (!normal)
-                    GP_ERROR("All themes require the normal state overlay to be defined.");
+                    bool rightToLeft;
+                    if (innerSpace->exists("rightToLeft"))
+                    {
+                        rightToLeft = innerSpace->getBool("rightToLeft");
+                    }
+                    else
+                    {
+                        rightToLeft = normal->getTextRightToLeft();
+                    }
 
-                space->rewind();
-                innerSpace = space->getNextNamespace();
-                while (innerSpace != NULL)
-                {
-                    const char* innerSpacename = innerSpace->getNamespace();
-                    if (strcmp(innerSpacename, "margin") == 0)
+                    float opacity;
+                    if (innerSpace->exists("opacity"))
+                    {
+                        opacity = innerSpace->getFloat("opacity");
+                    }
+                    else
                     {
-                        margin.top = innerSpace->getFloat("top");
-                        margin.bottom = innerSpace->getFloat("bottom");
-                        margin.left = innerSpace->getFloat("left");
-                        margin.right = innerSpace->getFloat("right");
+                        opacity = normal->getOpacity();
                     }
-                    else if (strcmp(innerSpacename, "padding") == 0)
+
+                    ImageList* imageList = NULL;
+                    ThemeImage* cursor = NULL;
+                    Skin* skin = NULL;
+                    theme->lookUpSprites(innerSpace, &imageList, &cursor, &skin);
+
+                    if (!imageList)
                     {
-                        padding.top = innerSpace->getFloat("top");
-                        padding.bottom = innerSpace->getFloat("bottom");
-                        padding.left = innerSpace->getFloat("left");
-                        padding.right = innerSpace->getFloat("right");
+                        imageList = normal->getImageList();
                     }
-                    else if (strcmp(innerSpacename, "stateNormal") != 0)
+
+                    if (!cursor)
                     {
-                        // Either OVERLAY_FOCUS or OVERLAY_ACTIVE.
-                        // If a property isn't specified, it inherits from OVERLAY_NORMAL.
-                        Vector4 textColor;
-                        if (!innerSpace->getColor("textColor", &textColor))
-                        {
-                            textColor.set(normal->getTextColor());
-                        }
-
-                        const char* fontPath = innerSpace->getString("font");
-                        Font* font = NULL;
-                        if (fontPath)
-                        {
-                            font = Font::create(fontPath);
-                        }
-                        if (!font)
-                        {
-                            font = normal->getFont();
-                        }
-
-                        unsigned int fontSize;
-                        if (innerSpace->exists("fontSize"))
-                        {
-                            fontSize = innerSpace->getInt("fontSize");
-                        }
-                        else
-                        {
-                            fontSize = normal->getFontSize();
-                        }
-
-                        const char* textAlignmentString = innerSpace->getString("textAlignment");
-                        Font::Justify textAlignment;
-                        if (textAlignmentString)
-                        {
-                            textAlignment = Font::getJustify(textAlignmentString);
-                        }
-                        else
-                        {
-                            textAlignment = normal->getTextAlignment();
-                        }
-
-                        bool rightToLeft;
-                        if (innerSpace->exists("rightToLeft"))
-                        {
-                            rightToLeft = innerSpace->getBool("rightToLeft");
-                        }
-                        else
-                        {
-                            rightToLeft = normal->getTextRightToLeft();
-                        }
-
-                        float opacity;
-                        if (innerSpace->exists("opacity"))
-                        {
-                            opacity = innerSpace->getFloat("opacity");
-                        }
-                        else
-                        {
-                            opacity = normal->getOpacity();
-                        }
-
-                        ImageList* imageList = NULL;
-                        ThemeImage* cursor = NULL;
-                        Skin* skin = NULL;
-                        theme->lookUpSprites(innerSpace, &imageList, &cursor, &skin);
-
-                        if (!imageList)
-                        {
-                            imageList = normal->getImageList();
-                        }
-
-                        if (!cursor)
-                        {
-                            cursor = normal->getCursor();
-                        }
+                        cursor = normal->getCursor();
+                    }
                         
-                        if (!skin)
-                        {
-                            skin = normal->getSkin();
-                        }
-
-                        if (strcmp(innerSpacename, "stateFocus") == 0)
-                        {
-                            focus = Theme::Style::Overlay::create();
-                            GP_ASSERT(focus);
-                            focus->setSkin(skin);
-                            focus->setCursor(cursor);
-                            focus->setImageList(imageList);
-                            focus->setTextColor(textColor);
-                            focus->setFont(font);
-                            focus->setFontSize(fontSize);
-                            focus->setTextAlignment(textAlignment);
-                            focus->setTextRightToLeft(rightToLeft);
-                            focus->setOpacity(opacity);
-
-                            if (font)
-                                theme->_fonts.insert(font);
-                        }
-                        else if (strcmp(innerSpacename, "stateActive") == 0)
-                        {
-                            active = Theme::Style::Overlay::create();
-                            GP_ASSERT(active);
-                            active->setSkin(skin);
-                            active->setCursor(cursor);
-                            active->setImageList(imageList);
-                            active->setTextColor(textColor);
-                            active->setFont(font);
-                            active->setFontSize(fontSize);
-                            active->setTextAlignment(textAlignment);
-                            active->setTextRightToLeft(rightToLeft);
-                            active->setOpacity(opacity);
-
-                            if (font)
-                                theme->_fonts.insert(font);
-                        }
-                        else if (strcmp(innerSpacename, "stateDisabled") == 0)
-                        {
-                            disabled = Theme::Style::Overlay::create();
-                            GP_ASSERT(disabled);
-                            disabled->setSkin(skin);
-                            disabled->setCursor(cursor);
-                            disabled->setImageList(imageList);
-                            disabled->setTextColor(textColor);
-                            disabled->setFont(font);
-                            disabled->setFontSize(fontSize);
-                            disabled->setTextAlignment(textAlignment);
-                            disabled->setTextRightToLeft(rightToLeft);
-                            disabled->setOpacity(opacity);
-
-                            if (font)
-                                theme->_fonts.insert(font);
-                        }
+                    if (!skin)
+                    {
+                        skin = normal->getSkin();
                     }
 
-                    innerSpace = space->getNextNamespace();
-                }
-                
-                if (!focus)
-                {
-                    focus = normal;
-                    focus->addRef();
-                }
+                    if (strcmp(innerSpacename, "stateFocus") == 0)
+                    {
+                        focus = Theme::Style::Overlay::create();
+                        GP_ASSERT(focus);
+                        focus->setSkin(skin);
+                        focus->setCursor(cursor);
+                        focus->setImageList(imageList);
+                        focus->setTextColor(textColor);
+                        focus->setFont(font);
+                        focus->setFontSize(fontSize);
+                        focus->setTextAlignment(textAlignment);
+                        focus->setTextRightToLeft(rightToLeft);
+                        focus->setOpacity(opacity);
 
-                if (!active)
-                {
-                    active = normal;
-                    active->addRef();
-                }
+                        if (font)
+                            theme->_fonts.insert(font);
+                    }
+                    else if (strcmp(innerSpacename, "stateActive") == 0)
+                    {
+                        active = Theme::Style::Overlay::create();
+                        GP_ASSERT(active);
+                        active->setSkin(skin);
+                        active->setCursor(cursor);
+                        active->setImageList(imageList);
+                        active->setTextColor(textColor);
+                        active->setFont(font);
+                        active->setFontSize(fontSize);
+                        active->setTextAlignment(textAlignment);
+                        active->setTextRightToLeft(rightToLeft);
+                        active->setOpacity(opacity);
 
-                if (!disabled)
-                {
-                    disabled = normal;
-                    disabled->addRef();
+                        if (font)
+                            theme->_fonts.insert(font);
+                    }
+                    else if (strcmp(innerSpacename, "stateDisabled") == 0)
+                    {
+                        disabled = Theme::Style::Overlay::create();
+                        GP_ASSERT(disabled);
+                        disabled->setSkin(skin);
+                        disabled->setCursor(cursor);
+                        disabled->setImageList(imageList);
+                        disabled->setTextColor(textColor);
+                        disabled->setFont(font);
+                        disabled->setFontSize(fontSize);
+                        disabled->setTextAlignment(textAlignment);
+                        disabled->setTextRightToLeft(rightToLeft);
+                        disabled->setOpacity(opacity);
+
+                        if (font)
+                            theme->_fonts.insert(font);
+                    }
                 }
 
-                Theme::Style* s = new Theme::Style(theme, space->getId(), tw, th, margin, padding, normal, focus, active, disabled);
-                GP_ASSERT(s);
-                theme->_styles.push_back(s);
+                innerSpace = space->getNextNamespace();
+            }
+                
+            if (!focus)
+            {
+                focus = normal;
+                focus->addRef();
             }
 
-            space = themeProperties->getNextNamespace();
-        }
+            if (!active)
+            {
+                active = normal;
+                active->addRef();
+            }
 
-        // Add this theme to the cache.
-        __themeCache.push_back(theme);
+            if (!disabled)
+            {
+                disabled = normal;
+                disabled->addRef();
+            }
 
-        SAFE_DELETE(properties);
+            Theme::Style* s = new Theme::Style(theme, space->getId(), tw, th, margin, padding, normal, focus, active, disabled);
+            GP_ASSERT(s);
+            theme->_styles.push_back(s);
+        }
 
-        return theme;
+        space = themeProperties->getNextNamespace();
     }
 
-    Theme::Style* Theme::getStyle(const char* name) const
-    {
-        GP_ASSERT(name);
+    // Add this theme to the cache.
+    __themeCache.push_back(theme);
 
-        for (unsigned int i = 0, count = _styles.size(); i < count; ++i)
-        {
-            GP_ASSERT(_styles[i]);
-            if (strcmp(name, _styles[i]->getId()) == 0)
-            {
-                return _styles[i];
-            }
-        }
+    SAFE_DELETE(properties);
 
-        return NULL;
-    }
+    return theme;
+}
 
-    void Theme::setProjectionMatrix(const Matrix& matrix)
-    {
-        GP_ASSERT(_spriteBatch);
-        _spriteBatch->setProjectionMatrix(matrix);
+Theme::Style* Theme::getStyle(const char* name) const
+{
+    GP_ASSERT(name);
 
-        // Set the matrix on each Font used by the style.
-        std::set<Font*>::const_iterator it;
-        for (it = _fonts.begin(); it != _fonts.end(); ++it)
+    for (unsigned int i = 0, count = _styles.size(); i < count; ++i)
+    {
+        GP_ASSERT(_styles[i]);
+        if (strcmp(name, _styles[i]->getId()) == 0)
         {
-            GP_ASSERT(*it);
-            GP_ASSERT((*it)->getSpriteBatch());
-            (*it)->getSpriteBatch()->setProjectionMatrix(matrix);
+            return _styles[i];
         }
     }
 
-    SpriteBatch* Theme::getSpriteBatch() const
-    {
-        return _spriteBatch;
-    }
+    return NULL;
+}
 
-    /**************
-     * Theme::UVs *
-     **************/
-    Theme::UVs::UVs()
-        : u1(0), v1(0), u2(0), v2(0)
-    {
-    }
+void Theme::setProjectionMatrix(const Matrix& matrix)
+{
+    GP_ASSERT(_spriteBatch);
+    _spriteBatch->setProjectionMatrix(matrix);
 
-    Theme::UVs::UVs(float u1, float v1, float u2, float v2)
-        : u1(u1), v1(v1), u2(u2), v2(v2)
+    // Set the matrix on each Font used by the style.
+    std::set<Font*>::const_iterator it;
+    for (it = _fonts.begin(); it != _fonts.end(); ++it)
     {
+        Font* font = *it;
+        GP_ASSERT(font);
+        GP_ASSERT(font->getSpriteBatch());
+        font->getSpriteBatch()->setProjectionMatrix(matrix);
     }
+}
 
-    const Theme::UVs& Theme::UVs::empty()
-    {
-        static UVs empty(0, 0, 0, 0);
-        return empty;
-    }
+SpriteBatch* Theme::getSpriteBatch() const
+{
+    return _spriteBatch;
+}
 
-    /**********************
-     * Theme::SideRegions *
-     **********************/
-    const Theme::SideRegions& Theme::SideRegions::empty()
-    {
-        static SideRegions empty;
-        return empty;
-    }
+/**************
+ * Theme::UVs *
+ **************/
+Theme::UVs::UVs()
+    : u1(0), v1(0), u2(0), v2(0)
+{
+}
 
-    /****************
-     * Theme::ThemeImage *
-     ****************/
-    Theme::ThemeImage::ThemeImage(float tw, float th, const Rectangle& region, const Vector4& color)
-        : _region(region), _color(color)
-    {
-        generateUVs(tw, th, region.x, region.y, region.width, region.height, &_uvs);
-    }
+Theme::UVs::UVs(float u1, float v1, float u2, float v2)
+    : u1(u1), v1(v1), u2(u2), v2(v2)
+{
+}
 
-    Theme::ThemeImage::~ThemeImage()
-    {
-    }
+const Theme::UVs& Theme::UVs::empty()
+{
+    static UVs empty(0, 0, 0, 0);
+    return empty;
+}
 
-    Theme::ThemeImage* Theme::ThemeImage::create(float tw, float th, Properties* properties, const Vector4& defaultColor)
-    {
-        GP_ASSERT(properties);
+/**********************
+ * Theme::SideRegions *
+ **********************/
+const Theme::SideRegions& Theme::SideRegions::empty()
+{
+    static SideRegions empty;
+    return empty;
+}
 
-        Vector4 regionVector;                
-        properties->getVector4("region", &regionVector);
-        const Rectangle region(regionVector.x, regionVector.y, regionVector.z, regionVector.w);
+/*********************
+ * Theme::ThemeImage *
+ *********************/
+Theme::ThemeImage::ThemeImage(float tw, float th, const Rectangle& region, const Vector4& color)
+    : _region(region), _color(color)
+{
+    generateUVs(tw, th, region.x, region.y, region.width, region.height, &_uvs);
+}
 
-        Vector4 color;
-        if (properties->exists("color"))
-        {
-            properties->getColor("color", &color);
-        }
-        else
-        {
-            color.set(defaultColor);
-        }
+Theme::ThemeImage::~ThemeImage()
+{
+}
 
-        ThemeImage* image = new ThemeImage(tw, th, region, color);
-        const char* id = properties->getId();
-        if (id)
-        {
-            image->_id = id;
-        }
+Theme::ThemeImage* Theme::ThemeImage::create(float tw, float th, Properties* properties, const Vector4& defaultColor)
+{
+    GP_ASSERT(properties);
 
-        return image;
-    }
+    Vector4 regionVector;                
+    properties->getVector4("region", &regionVector);
+    const Rectangle region(regionVector.x, regionVector.y, regionVector.z, regionVector.w);
 
-    const char* Theme::ThemeImage::getId() const
+    Vector4 color;
+    if (properties->exists("color"))
     {
-        return _id.c_str();
+        properties->getColor("color", &color);
     }
-
-    const Theme::UVs& Theme::ThemeImage::getUVs() const
+    else
     {
-        return _uvs;
+        color.set(defaultColor);
     }
 
-    const Rectangle& Theme::ThemeImage::getRegion() const
+    ThemeImage* image = new ThemeImage(tw, th, region, color);
+    const char* id = properties->getId();
+    if (id)
     {
-        return _region;
+        image->_id = id;
     }
 
-    const Vector4& Theme::ThemeImage::getColor() const
-    {
-        return _color;
-    }
+    return image;
+}
 
-    /********************
-     * Theme::ImageList *
-     ********************/
-    Theme::ImageList::ImageList(const Vector4& color) : _color(color)
-    {
-    }
+const char* Theme::ThemeImage::getId() const
+{
+    return _id.c_str();
+}
 
-    Theme::ImageList::ImageList(const ImageList& copy)
-    {
-        _id = copy._id;
-        _color = copy._color;
+const Theme::UVs& Theme::ThemeImage::getUVs() const
+{
+    return _uvs;
+}
 
-        std::vector<ThemeImage*>::const_iterator it;
-        for (it = copy._images.begin(); it != copy._images.end(); it++)
-        {
-            ThemeImage* image = *it;
-            GP_ASSERT(image);
-            _images.push_back(new ThemeImage(*image));
-        }
-    }
+const Rectangle& Theme::ThemeImage::getRegion() const
+{
+    return _region;
+}
 
-    Theme::ImageList::~ImageList()
+const Vector4& Theme::ThemeImage::getColor() const
+{
+    return _color;
+}
+
+/********************
+ * Theme::ImageList *
+ ********************/
+Theme::ImageList::ImageList(const Vector4& color) : _color(color)
+{
+}
+
+Theme::ImageList::ImageList(const ImageList& copy)
+{
+    _id = copy._id;
+    _color = copy._color;
+
+    std::vector<ThemeImage*>::const_iterator it;
+    for (it = copy._images.begin(); it != copy._images.end(); it++)
     {
-        std::vector<ThemeImage*>::const_iterator it;
-        for (it = _images.begin(); it != _images.end(); it++)
-        {
-            ThemeImage* image = *it;
-            SAFE_RELEASE(image);
-        }
+        ThemeImage* image = *it;
+        GP_ASSERT(image);
+        _images.push_back(new ThemeImage(*image));
     }
+}
 
-    Theme::ImageList* Theme::ImageList::create(float tw, float th, Properties* properties)
+Theme::ImageList::~ImageList()
+{
+    std::vector<ThemeImage*>::const_iterator it;
+    for (it = _images.begin(); it != _images.end(); it++)
     {
-        GP_ASSERT(properties);
+        ThemeImage* image = *it;
+        SAFE_RELEASE(image);
+    }
+}
 
-        Vector4 color(1, 1, 1, 1);
-        if (properties->exists("color"))
-        {
-            properties->getColor("color", &color);
-        }
+Theme::ImageList* Theme::ImageList::create(float tw, float th, Properties* properties)
+{
+    GP_ASSERT(properties);
 
-        ImageList* imageList = new ImageList(color);
+    Vector4 color(1, 1, 1, 1);
+    if (properties->exists("color"))
+    {
+        properties->getColor("color", &color);
+    }
 
-        const char* id = properties->getId();
-        if (id)
-        {
-            imageList->_id = id;
-        }
+    ImageList* imageList = new ImageList(color);
 
-        Properties* space = properties->getNextNamespace();
-        while (space != NULL)
-        {
-            ThemeImage* image = ThemeImage::create(tw, th, space, color);
-            GP_ASSERT(image);
-            imageList->_images.push_back(image);
-            space = properties->getNextNamespace();
-        }
-
-        return imageList;
+    const char* id = properties->getId();
+    if (id)
+    {
+        imageList->_id = id;
     }
 
-    const char* Theme::ImageList::getId() const
+    Properties* space = properties->getNextNamespace();
+    while (space != NULL)
     {
-        return _id.c_str();
+        ThemeImage* image = ThemeImage::create(tw, th, space, color);
+        GP_ASSERT(image);
+        imageList->_images.push_back(image);
+        space = properties->getNextNamespace();
     }
 
-    Theme::ThemeImage* Theme::ImageList::getImage(const char* imageId) const
-    {
-        GP_ASSERT(imageId);
+    return imageList;
+}
 
-        std::vector<ThemeImage*>::const_iterator it;
-        for (it = _images.begin(); it != _images.end(); it++)
-        {
-            ThemeImage* image = *it;
-            GP_ASSERT(image);
-            GP_ASSERT(image->getId());
-            if (strcmp(image->getId(), imageId) == 0)
-            {
-                return image;
-            }
-        }
+const char* Theme::ImageList::getId() const
+{
+    return _id.c_str();
+}
 
-        return NULL;
-    }
+Theme::ThemeImage* Theme::ImageList::getImage(const char* imageId) const
+{
+    GP_ASSERT(imageId);
 
-    /***************
-     * Theme::Skin *
-     ***************/
-    Theme::Skin* Theme::Skin::create(const char* id, float tw, float th, const Rectangle& region, const Theme::Border& border, const Vector4& color)
+    std::vector<ThemeImage*>::const_iterator it;
+    for (it = _images.begin(); it != _images.end(); it++)
     {
-        Skin* skin = new Skin(tw, th, region, border, color);
-
-        if (id)
+        ThemeImage* image = *it;
+        GP_ASSERT(image);
+        GP_ASSERT(image->getId());
+        if (strcmp(image->getId(), imageId) == 0)
         {
-            skin->_id = id;
+            return image;
         }
-
-        return skin;
     }
 
-    Theme::Skin::Skin(float tw, float th, const Rectangle& region, const Theme::Border& border, const Vector4& color)
-        : _border(border), _color(color), _region(region)
-    {
-        setRegion(region, tw, th);
-    }
+    return NULL;
+}
 
-    Theme::Skin::~Skin()
-    {
-    }
+/***************
+ * Theme::Skin *
+ ***************/
+Theme::Skin* Theme::Skin::create(const char* id, float tw, float th, const Rectangle& region, const Theme::Border& border, const Vector4& color)
+{
+    Skin* skin = new Skin(tw, th, region, border, color);
 
-    const char* Theme::Skin::getId() const
+    if (id)
     {
-        return _id.c_str();
+        skin->_id = id;
     }
 
-    const Theme::Border& Theme::Skin::getBorder() const
-    {
-        return _border;
-    }
+    return skin;
+}
 
-    const Rectangle& Theme::Skin::getRegion() const
-    {
-        return _region;
-    }
+Theme::Skin::Skin(float tw, float th, const Rectangle& region, const Theme::Border& border, const Vector4& color)
+    : _border(border), _color(color), _region(region)
+{
+    setRegion(region, tw, th);
+}
 
-    void Theme::Skin::setRegion(const Rectangle& region, float tw, float th)
-    {
-        // Can calculate all measurements in advance.
-        float leftEdge = region.x * tw;
-        float rightEdge = (region.x + region.width) * tw;
-        float leftBorder = (region.x + _border.left) * tw;
-        float rightBorder = (region.x + region.width - _border.right) * tw;
-
-        float topEdge = 1.0f - (region.y * th);
-        float bottomEdge = 1.0f - ((region.y + region.height) * th);
-        float topBorder = 1.0f - ((region.y + _border.top) * th);
-        float bottomBorder = 1.0f - ((region.y + region.height - _border.bottom) * th);
-
-        // There are 9 sets of UVs to set.
-        _uvs[TOP_LEFT].u1 = leftEdge;
-        _uvs[TOP_LEFT].v1 = topEdge;
-        _uvs[TOP_LEFT].u2 = leftBorder;
-        _uvs[TOP_LEFT].v2 = topBorder;
-
-        _uvs[TOP].u1 = leftBorder;
-        _uvs[TOP].v1 = topEdge;
-        _uvs[TOP].u2 = rightBorder;
-        _uvs[TOP].v2 = topBorder;
-
-        _uvs[TOP_RIGHT].u1 = rightBorder;
-        _uvs[TOP_RIGHT].v1 = topEdge;
-        _uvs[TOP_RIGHT].u2 = rightEdge;
-        _uvs[TOP_RIGHT].v2 = topBorder;
-
-        _uvs[LEFT].u1 = leftEdge;
-        _uvs[LEFT].v1 = topBorder;
-        _uvs[LEFT].u2 = leftBorder;
-        _uvs[LEFT].v2 = bottomBorder;
-
-        _uvs[CENTER].u1 = leftBorder;
-        _uvs[CENTER].v1 = topBorder;
-        _uvs[CENTER].u2 = rightBorder;
-        _uvs[CENTER].v2 = bottomBorder;
-
-        _uvs[RIGHT].u1 = rightBorder;
-        _uvs[RIGHT].v1 = topBorder;
-        _uvs[RIGHT].u2 = rightEdge;
-        _uvs[RIGHT].v2 = bottomBorder;
-
-        _uvs[BOTTOM_LEFT].u1 = leftEdge;
-        _uvs[BOTTOM_LEFT].v1 = bottomBorder;
-        _uvs[BOTTOM_LEFT].u2 = leftBorder;
-        _uvs[BOTTOM_LEFT].v2 = bottomEdge;
-
-        _uvs[BOTTOM].u1 = leftBorder;
-        _uvs[BOTTOM].v1 = bottomBorder;
-        _uvs[BOTTOM].u2 = rightBorder;
-        _uvs[BOTTOM].v2 = bottomEdge;
-
-        _uvs[BOTTOM_RIGHT].u1 = rightBorder;
-        _uvs[BOTTOM_RIGHT].v1 = bottomBorder;
-        _uvs[BOTTOM_RIGHT].u2 = rightEdge;
-        _uvs[BOTTOM_RIGHT].v2 = bottomEdge;
-    }
+Theme::Skin::~Skin()
+{
+}
 
-    const Theme::UVs& Theme::Skin::getUVs(SkinArea area) const
-    {
-        return _uvs[area];
-    }
+const char* Theme::Skin::getId() const
+{
+    return _id.c_str();
+}
 
-    const Vector4& Theme::Skin::getColor() const
-    {
-        return _color;
-    }
+const Theme::Border& Theme::Skin::getBorder() const
+{
+    return _border;
+}
+
+const Rectangle& Theme::Skin::getRegion() const
+{
+    return _region;
+}
+
+void Theme::Skin::setRegion(const Rectangle& region, float tw, float th)
+{
+    // Can calculate all measurements in advance.
+    float leftEdge = region.x * tw;
+    float rightEdge = (region.x + region.width) * tw;
+    float leftBorder = (region.x + _border.left) * tw;
+    float rightBorder = (region.x + region.width - _border.right) * tw;
+
+    float topEdge = 1.0f - (region.y * th);
+    float bottomEdge = 1.0f - ((region.y + region.height) * th);
+    float topBorder = 1.0f - ((region.y + _border.top) * th);
+    float bottomBorder = 1.0f - ((region.y + region.height - _border.bottom) * th);
+
+    // There are 9 sets of UVs to set.
+    _uvs[TOP_LEFT].u1 = leftEdge;
+    _uvs[TOP_LEFT].v1 = topEdge;
+    _uvs[TOP_LEFT].u2 = leftBorder;
+    _uvs[TOP_LEFT].v2 = topBorder;
+
+    _uvs[TOP].u1 = leftBorder;
+    _uvs[TOP].v1 = topEdge;
+    _uvs[TOP].u2 = rightBorder;
+    _uvs[TOP].v2 = topBorder;
+
+    _uvs[TOP_RIGHT].u1 = rightBorder;
+    _uvs[TOP_RIGHT].v1 = topEdge;
+    _uvs[TOP_RIGHT].u2 = rightEdge;
+    _uvs[TOP_RIGHT].v2 = topBorder;
+
+    _uvs[LEFT].u1 = leftEdge;
+    _uvs[LEFT].v1 = topBorder;
+    _uvs[LEFT].u2 = leftBorder;
+    _uvs[LEFT].v2 = bottomBorder;
+
+    _uvs[CENTER].u1 = leftBorder;
+    _uvs[CENTER].v1 = topBorder;
+    _uvs[CENTER].u2 = rightBorder;
+    _uvs[CENTER].v2 = bottomBorder;
+
+    _uvs[RIGHT].u1 = rightBorder;
+    _uvs[RIGHT].v1 = topBorder;
+    _uvs[RIGHT].u2 = rightEdge;
+    _uvs[RIGHT].v2 = bottomBorder;
+
+    _uvs[BOTTOM_LEFT].u1 = leftEdge;
+    _uvs[BOTTOM_LEFT].v1 = bottomBorder;
+    _uvs[BOTTOM_LEFT].u2 = leftBorder;
+    _uvs[BOTTOM_LEFT].v2 = bottomEdge;
+
+    _uvs[BOTTOM].u1 = leftBorder;
+    _uvs[BOTTOM].v1 = bottomBorder;
+    _uvs[BOTTOM].u2 = rightBorder;
+    _uvs[BOTTOM].v2 = bottomEdge;
+
+    _uvs[BOTTOM_RIGHT].u1 = rightBorder;
+    _uvs[BOTTOM_RIGHT].v1 = bottomBorder;
+    _uvs[BOTTOM_RIGHT].u2 = rightEdge;
+    _uvs[BOTTOM_RIGHT].v2 = bottomEdge;
+}
+
+const Theme::UVs& Theme::Skin::getUVs(SkinArea area) const
+{
+    return _uvs[area];
+}
+
+const Vector4& Theme::Skin::getColor() const
+{
+    return _color;
+}
     
-    /**
-     * Theme utility methods.
-     */
-    void Theme::generateUVs(float tw, float th, float x, float y, float width, float height, UVs* uvs)
-    {
-        GP_ASSERT(uvs);
-        uvs->u1 = x * tw;
-        uvs->u2 = (x + width) * tw;
-        uvs->v1 = 1.0f - (y * th);
-        uvs->v2 = 1.0f - ((y + height) * th);
-    }
+/**
+ * Theme utility methods.
+ */
+void Theme::generateUVs(float tw, float th, float x, float y, float width, float height, UVs* uvs)
+{
+    GP_ASSERT(uvs);
+    uvs->u1 = x * tw;
+    uvs->u2 = (x + width) * tw;
+    uvs->v1 = 1.0f - (y * th);
+    uvs->v2 = 1.0f - ((y + height) * th);
+}
 
-    void Theme::lookUpSprites(const Properties* overlaySpace, ImageList** imageList, ThemeImage** cursor, Skin** skin)
+void Theme::lookUpSprites(const Properties* overlaySpace, ImageList** imageList, ThemeImage** cursor, Skin** skin)
+{
+    GP_ASSERT(overlaySpace);
+
+    const char* imageListString = overlaySpace->getString("imageList");
+    if (imageListString)
     {
-        GP_ASSERT(overlaySpace);
-        const char* imageListString = overlaySpace->getString("imageList");
-        if (imageListString)
+        for (unsigned int i = 0; i < _imageLists.size(); ++i)
         {
-            for (unsigned int i = 0; i < _imageLists.size(); ++i)
+            GP_ASSERT(_imageLists[i]);
+            GP_ASSERT(_imageLists[i]->getId());
+            if (strcmp(_imageLists[i]->getId(), imageListString) == 0)
             {
-                GP_ASSERT(_imageLists[i]);
-                GP_ASSERT(_imageLists[i]->getId());
-                if (strcmp(_imageLists[i]->getId(), imageListString) == 0)
-                {
-                    GP_ASSERT(imageList);
-                    *imageList = _imageLists[i];
-                    break;
-                }
+                GP_ASSERT(imageList);
+                *imageList = _imageLists[i];
+                break;
             }
         }
+    }
 
-        const char* cursorString = overlaySpace->getString("cursor");
-        if (cursorString)
+    const char* cursorString = overlaySpace->getString("cursor");
+    if (cursorString)
+    {
+        for (unsigned int i = 0; i < _images.size(); ++i)
         {
-            for (unsigned int i = 0; i < _images.size(); ++i)
+            GP_ASSERT(_images[i]);
+            GP_ASSERT(_images[i]->getId());
+            if (strcmp(_images[i]->getId(), cursorString) == 0)
             {
-                GP_ASSERT(_images[i]);
-                GP_ASSERT(_images[i]->getId());
-                if (strcmp(_images[i]->getId(), cursorString) == 0)
-                {
-                    GP_ASSERT(cursor);
-                    *cursor = _images[i];
-                    break;
-                }
+                GP_ASSERT(cursor);
+                *cursor = _images[i];
+                break;
             }
         }
+    }
 
-        const char* skinString = overlaySpace->getString("skin");
-        if (skinString)
+    const char* skinString = overlaySpace->getString("skin");
+    if (skinString)
+    {
+        for (unsigned int i = 0; i < _skins.size(); ++i)
         {
-            for (unsigned int i = 0; i < _skins.size(); ++i)
+            GP_ASSERT(_skins[i]);
+            GP_ASSERT(_skins[i]->getId());
+            if (strcmp(_skins[i]->getId(), skinString) == 0)
             {
-                GP_ASSERT(_skins[i]);
-                GP_ASSERT(_skins[i]->getId());
-                if (strcmp(_skins[i]->getId(), skinString) == 0)
-                {
-                    GP_ASSERT(skin);
-                    *skin = _skins[i];
-                    break;
-                }
+                GP_ASSERT(skin);
+                *skin = _skins[i];
+                break;
             }
         }
     }
 }
+
+}

+ 65 - 65
gameplay/src/VerticalLayout.cpp

@@ -3,91 +3,91 @@
 
 namespace gameplay
 {
-    static VerticalLayout* __instance;
 
-    VerticalLayout::VerticalLayout() : _bottomToTop(false)
-    {
-    }
+static VerticalLayout* __instance;
 
-    VerticalLayout::VerticalLayout(const VerticalLayout& copy)
-    {
-    }
+VerticalLayout::VerticalLayout() : _bottomToTop(false)
+{
+}
 
-    VerticalLayout::~VerticalLayout()
-    {
-    }
+VerticalLayout::VerticalLayout(const VerticalLayout& copy)
+{
+}
 
-    VerticalLayout* VerticalLayout::create()
-    {
-        if (!__instance)
-        {
-            __instance = new VerticalLayout();
-        }
-        else
-        {
-            __instance->addRef();
-        }
-
-        return __instance;
-    }
+VerticalLayout::~VerticalLayout()
+{
+    __instance = NULL;
+}
 
-    void VerticalLayout::setBottomToTop(bool bottomToTop)
+VerticalLayout* VerticalLayout::create()
+{
+    if (!__instance)
     {
-        _bottomToTop = bottomToTop;
+        __instance = new VerticalLayout();
     }
-
-    Layout::Type VerticalLayout::getType()
+    else
     {
-        return Layout::LAYOUT_VERTICAL;
+        __instance->addRef();
     }
 
-    void VerticalLayout::update(const Container* container)
-    {
-        GP_ASSERT(container);
+    return __instance;
+}
+
+void VerticalLayout::setBottomToTop(bool bottomToTop)
+{
+    _bottomToTop = bottomToTop;
+}
 
-        // Need border, padding.
-        Theme::Border border = container->getBorder(container->getState());
-        Theme::Padding padding = container->getPadding();
+Layout::Type VerticalLayout::getType()
+{
+    return Layout::LAYOUT_VERTICAL;
+}
+
+void VerticalLayout::update(const Container* container)
+{
+    GP_ASSERT(container);
 
-        float yPosition = 0;
+    // Need border, padding.
+    Theme::Border border = container->getBorder(container->getState());
+    Theme::Padding padding = container->getPadding();
 
-        std::vector<Control*> controls = container->getControls();
+    float yPosition = 0;
 
-        unsigned int i, end, iter;
-        if (_bottomToTop)
-        {
-            i = controls.size() - 1;
-            end = -1;
-            iter = -1;
-        }
-        else
-        {
-            i = 0;
-            end = controls.size();
-            iter = 1;
-        }
+    std::vector<Control*> controls = container->getControls();
 
-        while (i != end)
-        {
-            Control* control = controls.at(i);
-            GP_ASSERT(control);
+    unsigned int i, end, iter;
+    if (_bottomToTop)
+    {
+        i = controls.size() - 1;
+        end = -1;
+        iter = -1;
+    }
+    else
+    {
+        i = 0;
+        end = controls.size();
+        iter = 1;
+    }
 
-            align(control, container);
+    while (i != end)
+    {
+        Control* control = controls.at(i);
+        GP_ASSERT(control);
+            
+        align(control, container);
 
-            const Rectangle& bounds = control->getClipBounds();
-            const Theme::Margin& margin = control->getMargin();
+        const Rectangle& bounds = control->getBounds();
+        const Theme::Margin& margin = control->getMargin();
 
-            yPosition += margin.top;
+        yPosition += margin.top;
 
-            control->setPosition(0, yPosition);
-            if (control->isDirty() || control->isContainer())
-            {
-                control->update(container->getClip());
-            }
+        control->setPosition(0, yPosition);
+        control->update(container->getClip(), Vector2::zero());
 
-            yPosition += bounds.height + margin.bottom;
+        yPosition += bounds.height + margin.bottom;
 
-            i += iter;
-        }
+        i += iter;
     }
+}
+
 }