Jelajahi Sumber

Added more flexible sizing and positioning for UI controls:
- x, y, width and height can now be specified as either absolute or percentage-based values, allowing controls to be positioned and sized relative to their parent Container
- changed autoWidth and autoHeight control properties to an enumeration to support the existing STRETCH functionality (where controls stretch to 100% of their parent container) and a new FIT option. Setting autoWidth or autoHeight to AUTO_SIZE_FIT causes the control to automatically size itself to tightly fit its contents.

sgrenier 12 tahun lalu
induk
melakukan
7a8ca49182

+ 13 - 3
gameplay/src/CheckBox.cpp

@@ -145,11 +145,21 @@ void CheckBox::update(const Control* container, const Vector2& offset)
     {
         size.set(_imageSize);
     }
-    float iconWidth = size.x;
+    
+    if (_autoWidth == Control::AUTO_SIZE_FIT)
+    {
+        // Text-only width was already measured in Label::update - append image
+        setWidth(size.x + _bounds.width + 5);
+    }
 
-    _textBounds.x += iconWidth + 5;
-    _textBounds.width -= iconWidth + 5;
+    if (_autoHeight == Control::AUTO_SIZE_FIT)
+    {
+        // Text-only width was already measured in Label::update - append image
+        setHeight(std::max(getHeight(), size.y));
+    }
 
+    _textBounds.x += size.x + 5;
+    
     if (_checked)
     {
         _image = getImage("checked", _state);

+ 85 - 8
gameplay/src/Container.cpp

@@ -60,7 +60,7 @@ Container::Container()
       _lastFrameTime(0), _focusChangeRepeat(false),
       _focusChangeStartTime(0), _focusChangeRepeatDelay(FOCUS_CHANGE_REPEAT_DELAY), _focusChangeCount(0),
       _totalWidth(0), _totalHeight(0),
-      _initializedWithScroll(false), _scrollWheelRequiresFocus(false)
+      _initializedWithScroll(false), _scrollWheelRequiresFocus(false), _allowRelayout(true)
 {
 	clearContacts();
 }
@@ -469,6 +469,7 @@ void Container::update(const Control* container, const Vector2& offset)
 
         GP_ASSERT(_scrollBarLeftCap && _scrollBarHorizontal && _scrollBarRightCap);
 
+        _viewportBounds.height -= _scrollBarHorizontal->getRegion().height;
         _viewportClipBounds.height -= _scrollBarHorizontal->getRegion().height;
     }
 
@@ -479,7 +480,8 @@ void Container::update(const Control* container, const Vector2& offset)
         _scrollBarBottomCap = getImage("scrollBarBottomCap", _state);
 
         GP_ASSERT(_scrollBarTopCap && _scrollBarVertical && _scrollBarBottomCap);
-        
+
+        _viewportBounds.width -= _scrollBarVertical->getRegion().width;
         _viewportClipBounds.width -= _scrollBarVertical->getRegion().width;
     }
 
@@ -492,6 +494,81 @@ void Container::update(const Control* container, const Vector2& offset)
     {
         _layout->update(this, Vector2::zero());
     }
+
+    // Handle automatically sizing based on our children
+    if (_autoWidth == Control::AUTO_SIZE_FIT || _autoHeight == Control::AUTO_SIZE_FIT)
+    {
+        Vector2 oldSize(_bounds.width, _bounds.height);
+        bool sizeChanged = false;
+        bool relayout = false;
+
+        if (_autoWidth == Control::AUTO_SIZE_FIT)
+        {
+            // Size ourself to tightly fit the width of our children
+            float width = 0;
+            for (std::vector<Control*>::const_iterator it = _controls.begin(); it < _controls.end(); ++it)
+            {
+                Control* ctrl = *it;
+                if (ctrl->isXPercentage() || ctrl->isWidthPercentage())
+                {
+                    // We (this control's parent) are resizing and our child's layout
+                    // depends on our size, so we need to dirty it
+                    ctrl->_dirty = true;
+                    relayout = _allowRelayout;
+                }
+                else
+                {
+                    float w = ctrl->getX() + ctrl->getWidth();
+                    if (width < w)
+                        width = w;
+                }
+            }
+            width += getBorder(_state).left + getBorder(_state).right + getPadding().left + getPadding().right;
+            if (width != oldSize.x)
+            {
+                setWidth(width);
+                sizeChanged = true;
+            }
+        }
+
+        if (_autoHeight == Control::AUTO_SIZE_FIT)
+        {
+            // Size ourself to tightly fit the height of our children
+            float height = 0;
+            for (std::vector<Control*>::const_iterator it = _controls.begin(); it < _controls.end(); ++it)
+            {
+                Control* ctrl = *it;
+                if (ctrl->isYPercentage() || ctrl->isHeightPercentage())
+                {
+                    // We (this control's parent) are resizing and our child's layout
+                    // depends on our size, so we need to dirty it
+                    ctrl->_dirty = true;
+                    relayout = _allowRelayout;
+                }
+                else
+                {
+                    float h = ctrl->getY() + ctrl->getHeight();
+                    if (height < h)
+                        height = h;
+                }
+            }
+            height += getBorder(_state).top + getBorder(_state).bottom + getPadding().top + getPadding().bottom;
+            if (height != oldSize.y)
+            {
+                setHeight(height);
+                sizeChanged = true;
+            }
+        }
+
+        if (sizeChanged && relayout)
+        {
+            // Our size changed and as a result we need to force another layout.
+            // Prevent infinitely recursive layouts by disabling relayout for the next call.
+            _allowRelayout = false;
+            update(container, offset);
+            _allowRelayout = true;
+        }
+    }
 }
 
 void Container::draw(SpriteBatch* spriteBatch, const Rectangle& clip, bool needsClear, bool cleared, float targetHeight)
@@ -560,7 +637,7 @@ void Container::draw(SpriteBatch* spriteBatch, const Rectangle& clip, bool needs
 
             clipRegion.width += verticalRegion.width;
 
-            Rectangle bounds(_viewportBounds.x + _viewportBounds.width - verticalRegion.width, _viewportBounds.y + _scrollBarBounds.y, topRegion.width, topRegion.height);
+            Rectangle bounds(_viewportBounds.x + _viewportBounds.width, _viewportBounds.y + _scrollBarBounds.y, topRegion.width, topRegion.height);
             spriteBatch->draw(bounds.x, bounds.y, bounds.width, bounds.height, topUVs.u1, topUVs.v1, topUVs.u2, topUVs.v2, topColor, clipRegion);
 
             bounds.y += topRegion.height;
@@ -591,7 +668,7 @@ void Container::draw(SpriteBatch* spriteBatch, const Rectangle& clip, bool needs
 
             clipRegion.height += horizontalRegion.height;
         
-            Rectangle bounds(_viewportBounds.x + _scrollBarBounds.x, _viewportBounds.y + _viewportBounds.height - horizontalRegion.height, leftRegion.width, leftRegion.height);
+            Rectangle bounds(_viewportBounds.x + _scrollBarBounds.x, _viewportBounds.y + _viewportBounds.height, leftRegion.width, leftRegion.height);
             spriteBatch->draw(bounds.x, bounds.y, bounds.width, bounds.height, leftUVs.u1, leftUVs.v1, leftUVs.u2, leftUVs.v2, leftColor, clipRegion);
 
             bounds.x += leftRegion.width;
@@ -1337,8 +1414,8 @@ void Container::updateScroll()
 
     float vWidth = getImageRegion("verticalScrollBar", _state).width;
     float hHeight = getImageRegion("horizontalScrollBar", _state).height;
-    float clipWidth = _bounds.width - containerBorder.left - containerBorder.right - containerPadding.left - containerPadding.right - vWidth;
-    float clipHeight = _bounds.height - containerBorder.top - containerBorder.bottom - containerPadding.top - containerPadding.bottom - hHeight;
+    float clipWidth = _absoluteBounds.width - containerBorder.left - containerBorder.right - containerPadding.left - containerPadding.right - vWidth;
+    float clipHeight = _absoluteBounds.height - containerBorder.top - containerBorder.bottom - containerPadding.top - containerPadding.bottom - hHeight;
 
     // Apply and dampen inertia.
     if (!_scrollingVelocity.isZero())
@@ -1577,7 +1654,7 @@ bool Container::mouseEventScroll(Mouse::MouseEvent evt, int x, int y, int wheelD
             if (_scrollBarVertical)
             {
                 float vWidth = _scrollBarVertical->getRegion().width;
-                Rectangle vBounds(_viewportBounds.x + _viewportBounds.width - vWidth,
+                Rectangle vBounds(_viewportBounds.x + _viewportBounds.width,
                                  _scrollBarBounds.y,
                                  vWidth, _scrollBarBounds.height);
 
@@ -1605,7 +1682,7 @@ bool Container::mouseEventScroll(Mouse::MouseEvent evt, int x, int y, int wheelD
             {
                 float hHeight = _scrollBarHorizontal->getRegion().height;
                 Rectangle hBounds(_scrollBarBounds.x,
-                                  _viewportBounds.y + _viewportBounds.height - hHeight,
+                                  _viewportBounds.y + _viewportBounds.height,
                                   _scrollBarBounds.width, hHeight);
             
                 if (y + _viewportBounds.y >= hBounds.y &&

+ 1 - 0
gameplay/src/Container.h

@@ -631,6 +631,7 @@ private:
     bool _contactIndices[MAX_CONTACT_INDICES];
     bool _initializedWithScroll;
     bool _scrollWheelRequiresFocus;
+    bool _allowRelayout;
 };
 
 }

+ 287 - 121
gameplay/src/Control.cpp

@@ -2,12 +2,50 @@
 #include "Game.h"
 #include "Control.h"
 
+#define BOUNDS_X_PERCENTAGE_BIT 1
+#define BOUNDS_Y_PERCENTAGE_BIT 2
+#define BOUNDS_WIDTH_PERCENTAGE_BIT 4
+#define BOUNDS_HEIGHT_PERCENTAGE_BIT 8
+
 namespace gameplay
 {
 
+static std::string toString(float v)
+{
+    std::ostringstream s;
+    s << v;
+    return s.str();
+}
+
+static float parseCoord(const char* s, bool* isPercentage)
+{
+    const char* p;
+    if ((p = strchr(s, '%')) != NULL)
+    {
+        std::string value(s, (std::string::size_type)(p - s));
+        *isPercentage = true;
+        return (float)(atof(value.c_str()) * 0.01);
+    }
+    *isPercentage = false;
+    return (float)atof(s);
+}
+
+static bool parseCoordPair(const char* s, float* v1, float* v2, bool* v1Percentage, bool* v2Percentage)
+{
+    size_t len = strlen(s);
+    const char* s2 = strchr(s, ',');
+    if (s2 == NULL)
+        return false;
+    std::string v1Str(s, (std::string::size_type)(s2 - s));
+    std::string v2Str(s2 + 1);
+    *v1 = parseCoord(v1Str.c_str(), v1Percentage);
+    *v2 = parseCoord(v2Str.c_str(), v2Percentage);
+    return true;
+}
+
 Control::Control()
-    : _id(""), _state(Control::NORMAL), _bounds(Rectangle::empty()), _clipBounds(Rectangle::empty()), _viewportClipBounds(Rectangle::empty()),
-    _clearBounds(Rectangle::empty()), _dirty(true), _consumeInputEvents(false), _alignment(ALIGN_TOP_LEFT), _isAlignmentSet(false), _autoWidth(false), _autoHeight(false), _listeners(NULL), _visible(true),
+    : _id(""), _state(Control::NORMAL), _boundsBits(0), _dirty(true), _consumeInputEvents(false),
+    _alignment(ALIGN_TOP_LEFT), _isAlignmentSet(false), _autoWidth(AUTO_SIZE_NONE), _autoHeight(AUTO_SIZE_NONE), _listeners(NULL), _visible(true),
     _zIndex(-1), _contactIndex(INVALID_CONTACT_INDEX), _focusIndex(-1), _parent(NULL), _styleOverridden(false), _skin(NULL), _previousState(NORMAL)
 {
     addScriptEvent("controlEvent", "<Control>[Control::Listener::EventType]");
@@ -31,6 +69,17 @@ Control::~Control()
     }
 }
 
+static Control::AutoSize parseAutoSize(const char* str)
+{
+    if (str == NULL)
+        return Control::AUTO_SIZE_NONE;
+    if (strcmp(str, "AUTO_SIZE_STRETCH") == 0 || strcmp(str, "true") == 0) // left for backwards compatibility
+        return Control::AUTO_SIZE_STRETCH;
+    if (strcmp(str, "AUTO_SIZE_FIT") == 0)
+        return Control::AUTO_SIZE_FIT;
+    return Control::AUTO_SIZE_NONE;
+}
+
 void Control::initialize(Theme::Style* style, Properties* properties)
 {
     GP_ASSERT(properties);
@@ -41,8 +90,9 @@ void Control::initialize(Theme::Style* style, Properties* properties)
 
     _isAlignmentSet = alignmentString != NULL;
     _alignment = getAlignment(alignmentString);
-    _autoWidth = properties->getBool("autoWidth");
-    _autoHeight = properties->getBool("autoHeight");
+
+    _autoWidth = parseAutoSize(properties->getString("autoWidth"));
+    _autoHeight = parseAutoSize(properties->getString("autoHeight"));
 
     _consumeInputEvents = properties->getBool("consumeInputEvents", false);
 
@@ -66,28 +116,31 @@ void Control::initialize(Theme::Style* style, Properties* properties)
         _focusIndex = -1;
     }
 
-    Vector2 position;
-    Vector2 size;
+    float bounds[4];
+    bool boundsBits[4];
     if (properties->exists("position"))
     {
-        properties->getVector2("position", &position);
+        parseCoordPair(properties->getString("position", "0,0"), &bounds[0], &bounds[1], &boundsBits[0], &boundsBits[1]);
     }
     else
     {
-        position.x = properties->getFloat("x");
-        position.y = properties->getFloat("y");
+        bounds[0] = parseCoord(properties->getString("x", "0"), &boundsBits[0]);
+        bounds[1] = parseCoord(properties->getString("y", "0"), &boundsBits[1]);
     }
-        
+
     if (properties->exists("size"))
     {
-        properties->getVector2("size", &size);
+        parseCoordPair(properties->getString("size", "0,0"), &bounds[2], &bounds[3], &boundsBits[2], &boundsBits[3]);
     }
     else
     {
-        size.x = properties->getFloat("width");
-        size.y = properties->getFloat("height");
+        bounds[2] = parseCoord(properties->getString("width", "0"), &boundsBits[2]);
+        bounds[3] = parseCoord(properties->getString("height", "0"), &boundsBits[3]);
     }
-    setBounds(Rectangle(position.x, position.y, size.x, size.y));
+    setX(bounds[0], boundsBits[0]);
+    setY(bounds[1], boundsBits[1]);
+    setWidth(bounds[2], boundsBits[2]);
+    setHeight(bounds[3], boundsBits[3]);
 
     const char* id = properties->getId();
     if (id)
@@ -98,6 +151,10 @@ void Control::initialize(Theme::Style* style, Properties* properties)
         setEnabled(properties->getBool("enabled"));
     }
 
+    // Register script listeners for control events
+    if (properties->exists("listener"))
+        addScriptCallback("controlEvent", properties->getString("listener"));
+
     // Potentially override themed properties for all states.
     overrideThemedProperties(properties, STATE_ALL);
 
@@ -147,81 +204,156 @@ const char* Control::getId() const
     return _id.c_str();
 }
 
-void Control::setPosition(float x, float y)
+float Control::getX() const
 {
-    if (x != _bounds.x || y != _bounds.y)
-    {
-        _bounds.x = x;
-        _bounds.y = y;
-        _dirty = true;
-    }
+    return _bounds.x;
 }
 
-void Control::setSize(float width, float height)
+void Control::setX(float x, bool percentage)
 {
-    if (width != _bounds.width || height != _bounds.height)
+    if (_relativeBounds.x != x || percentage != ((_boundsBits & BOUNDS_X_PERCENTAGE_BIT) != 0))
     {
-        _bounds.width = width;
-        _bounds.height = height;
+        _relativeBounds.x = x;
+        if (percentage)
+        {
+            _boundsBits |= BOUNDS_X_PERCENTAGE_BIT;
+        }
+        else
+        {
+            _boundsBits &= ~BOUNDS_X_PERCENTAGE_BIT;
+            _bounds.x = x;
+        }
         _dirty = true;
     }
 }
 
-void Control::setWidth(float width)
+bool Control::isXPercentage() const
 {
-    if (width != _bounds.width)
+    return (_boundsBits & BOUNDS_X_PERCENTAGE_BIT) != 0;
+}
+
+float Control::getY() const
+{
+    return _bounds.y;
+}
+
+void Control::setY(float y, bool percentage)
+{
+    if (_relativeBounds.y != y || percentage != ((_boundsBits & BOUNDS_Y_PERCENTAGE_BIT) != 0))
     {
-        _bounds.width = width;
+        _relativeBounds.y = y;
+        if (percentage)
+        {
+            _boundsBits |= BOUNDS_Y_PERCENTAGE_BIT;
+        }
+        else
+        {
+            _boundsBits &= ~BOUNDS_Y_PERCENTAGE_BIT;
+            _bounds.y = y;
+        }
         _dirty = true;
     }
 }
 
-void Control::setHeight(float height)
+bool Control::isYPercentage() const
+{
+    return (_boundsBits & BOUNDS_Y_PERCENTAGE_BIT) != 0;
+}
+
+float Control::getWidth() const
+{
+    return _bounds.width;
+}
+
+void Control::setWidth(float width, bool percentage)
 {
-    if (height != _bounds.height)
+    if (_relativeBounds.width != width || percentage != ((_boundsBits & BOUNDS_WIDTH_PERCENTAGE_BIT) != 0))
     {
-        _bounds.height = height;
+        _relativeBounds.width = width;
+        if (percentage)
+        {
+            _boundsBits |= BOUNDS_WIDTH_PERCENTAGE_BIT;
+        }
+        else
+        {
+            _boundsBits &= ~BOUNDS_WIDTH_PERCENTAGE_BIT;
+            _bounds.width = width;
+        }
         _dirty = true;
     }
 }
 
-void Control::setBounds(const Rectangle& bounds)
+bool Control::isWidthPercentage() const
 {
-    if (bounds != _bounds)
+    return (_boundsBits & BOUNDS_WIDTH_PERCENTAGE_BIT) != 0;
+}
+
+float Control::getHeight() const
+{
+    return _bounds.height;
+}
+
+void Control::setHeight(float height, bool percentage)
+{
+    if (_relativeBounds.height != height || percentage != ((_boundsBits & BOUNDS_HEIGHT_PERCENTAGE_BIT) != 0))
     {
-        _bounds.set(bounds);
+        _relativeBounds.height = height;
+        if (percentage)
+        {
+            _boundsBits |= BOUNDS_HEIGHT_PERCENTAGE_BIT;
+        }
+        else
+        {
+            _boundsBits &= ~BOUNDS_HEIGHT_PERCENTAGE_BIT;
+            _bounds.height = height;
+        }
         _dirty = true;
     }
 }
 
-const Rectangle& Control::getBounds() const
+bool Control::isHeightPercentage() const
 {
-    return _bounds;
+    return (_boundsBits & BOUNDS_HEIGHT_PERCENTAGE_BIT) != 0;
 }
 
-const Rectangle& Control::getAbsoluteBounds() const
+void Control::setPosition(float x, float y)
 {
-    return _absoluteBounds;
+    setX(x);
+    setY(y);
 }
 
-float Control::getX() const
+void Control::setSize(float width, float height)
 {
-    return _bounds.x;
+    setWidth(width);
+    setHeight(height);
 }
 
-float Control::getY() const
+const Rectangle& Control::getBounds() const
 {
-    return _bounds.y;
+    return _bounds;
 }
 
-float Control::getWidth() const
+void Control::setBounds(const Rectangle& bounds)
 {
-    return _bounds.width;
+    setX(bounds.x);
+    setY(bounds.y);
+    setWidth(bounds.width);
+    setHeight(bounds.height);
 }
 
-float Control::getHeight() const
+const Rectangle& Control::getAbsoluteBounds() const
 {
-    return _bounds.height;
+    return _absoluteBounds;
+}
+
+const Rectangle& Control::getClipBounds() const
+{
+    return _clipBounds;
+}
+
+const Rectangle& Control::getClip() const
+{
+    return _viewportClipBounds;
 }
 
 void Control::setAlignment(Alignment alignment)
@@ -236,32 +368,42 @@ Control::Alignment Control::getAlignment() const
     return _alignment;
 }
 
+Control::AutoSize Control::getAutoWidth() const
+{
+    return _autoWidth;
+}
+
 void Control::setAutoWidth(bool autoWidth)
 {
-    if (_autoWidth != autoWidth)
+    setAutoWidth(autoWidth ? AUTO_SIZE_STRETCH : AUTO_SIZE_NONE);
+}
+
+void Control::setAutoWidth(AutoSize mode)
+{
+    if (_autoWidth != mode)
     {
-        _autoWidth = autoWidth;
+        _autoWidth = mode;
         _dirty = true;
     }
 }
 
-bool Control::getAutoWidth() const
+Control::AutoSize Control::getAutoHeight() const
 {
-    return _autoWidth;
+    return _autoHeight;
 }
 
 void Control::setAutoHeight(bool autoHeight)
 {
-    if (_autoHeight != autoHeight)
-    {
-        _autoHeight = autoHeight;
-        _dirty = true;
-    }
+    setAutoHeight(autoHeight ? AUTO_SIZE_STRETCH : AUTO_SIZE_NONE);
 }
 
-bool Control::getAutoHeight() const
+void Control::setAutoHeight(AutoSize mode)
 {
-    return _autoHeight;
+    if (_autoHeight != mode)
+    {
+        _autoHeight = mode;
+        _dirty = true;
+    }
 }
 
 void Control::setVisible(bool visible)
@@ -633,16 +775,6 @@ bool Control::getTextRightToLeft(State state) const
     return overlay->getTextRightToLeft();
 }
 
-const Rectangle& Control::getClipBounds() const
-{
-    return _clipBounds;
-}
-
-const Rectangle& Control::getClip() const
-{
-    return _viewportClipBounds;
-}
-
 void Control::setStyle(Theme::Style* style)
 {
     if (style != _style)
@@ -956,24 +1088,51 @@ void Control::notifyListeners(Control::Listener::EventType eventType)
 
 void Control::update(const Control* container, const Vector2& offset)
 {
-    const Rectangle& clip = container->getClip();
-    const Rectangle& absoluteViewport = container->_viewportBounds;
+    Game* game = Game::getInstance();
+    const Rectangle parentAbsoluteBounds = container ? container->_viewportBounds : Rectangle(0, 0, game->getViewport().width, game->getViewport().height);
+    const Rectangle parentAbsoluteClip = container ? container->getClip() : parentAbsoluteBounds;
 
+    // Store previous absolute clip bounds
     _clearBounds.set(_absoluteClipBounds);
 
-    // Calculate the clipped bounds.
-    float x = _bounds.x + offset.x;
-    float y = _bounds.y + offset.y;
-    float width = _bounds.width;
-    float height = _bounds.height;
-
-    float clipX2 = clip.x + clip.width;
-    float x2 = clip.x + x + width;
+    // Calculate local un-clipped bounds.
+    _bounds.set(_relativeBounds);
+    if (isXPercentage())
+        _bounds.x *= parentAbsoluteBounds.width;
+    if (isYPercentage())
+        _bounds.y *= parentAbsoluteBounds.height;
+    if (_autoWidth == AUTO_SIZE_STRETCH)
+        _bounds.width = parentAbsoluteBounds.width;
+    else if (isWidthPercentage())
+        _bounds.width *= parentAbsoluteBounds.width;
+    if (_autoHeight == AUTO_SIZE_STRETCH)
+        _bounds.height = parentAbsoluteBounds.height;
+    else if (isHeightPercentage())
+        _bounds.height *= parentAbsoluteBounds.height;
+
+    float x, y, width, height, clipX2, x2, clipY2, y2;
+
+    // Calculate the local clipped bounds
+    width = _bounds.width;
+    height = _bounds.height;
+    if (container)
+    {
+        x = _bounds.x + offset.x;
+        y = _bounds.y + offset.y;
+        x2 = parentAbsoluteClip.x + x + width;
+        y2 = parentAbsoluteClip.y + y + height;
+    }
+    else
+    {
+        x = 0;
+        y = 0;
+        x2 = width;
+        y2 = height;
+    }
+    clipX2 = parentAbsoluteClip.x + parentAbsoluteClip.width;
+    clipY2 = parentAbsoluteClip.y + parentAbsoluteClip.height;
     if (x2 > clipX2)
         width -= x2 - clipX2;
-
-    float clipY2 = clip.y + clip.height;
-    float y2 = clip.y + y + height;
     if (y2 > clipY2)
         height -= y2 - clipY2;
 
@@ -999,50 +1158,63 @@ void Control::update(const Control* container, const Vector2& offset)
 
     _clipBounds.set(x, y, width, height);
 
-    // Calculate the absolute bounds.
-    x = _bounds.x + offset.x + absoluteViewport.x;
-    y = _bounds.y + offset.y + absoluteViewport.y;
+    // Calculate absolute bounds un-clipped bounds
+    if (container)
+    {
+        x = _bounds.x + offset.x + parentAbsoluteBounds.x;
+        y = _bounds.y + offset.y + parentAbsoluteBounds.y;
+    }
+    else
+    {
+        x = 0;
+        y = 0;
+    }
     _absoluteBounds.set(x, y, _bounds.width, _bounds.height);
 
-    // Calculate the absolute viewport bounds.
+    // Calculate the absolute viewport bounds (content area, which does not include border and padding)
     // Absolute bounds minus border and padding.
     const Theme::Border& border = getBorder(_state);
     const Theme::Padding& padding = getPadding();
-
     x += border.left + padding.left;
     y += border.top + padding.top;
     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);
 
     // Calculate the clip area.
     // Absolute bounds, minus border and padding,
     // clipped to the parent container's clip area.
-    clipX2 = clip.x + clip.width;
+    if (container)
+    {
+        clipX2 = parentAbsoluteClip.x + parentAbsoluteClip.width;
+        clipY2 = parentAbsoluteClip.y + parentAbsoluteClip.height;
+    }
+    else
+    {
+        clipX2 = parentAbsoluteClip.width;
+        clipY2 = parentAbsoluteClip.height;
+    }
     x2 = x + width;
     if (x2 > clipX2)
         width = clipX2 - x;
-
-    clipY2 = clip.y + clip.height;
     y2 = y + height;
     if (y2 > clipY2)
         height = clipY2 - y;
 
-    if (x < clip.x)
+    if (x < parentAbsoluteClip.x)
     {
-        float dx = clip.x - x;
+        float dx = parentAbsoluteClip.x - x;
         width -= dx;
-        x = clip.x;
+        x = parentAbsoluteClip.x;
     }
 
-    if (y < clip.y)
+    if (y < parentAbsoluteClip.y)
     {
-        float dy = clip.y - y;
+        float dy = parentAbsoluteClip.y - y;
         height -= dy;
-        y = clip.y;
+        y = parentAbsoluteClip.y;
     }
- 
+
     _viewportClipBounds.set(x, y, width, height);
 
     width += border.left + padding.left + border.right + padding.right;
@@ -1057,12 +1229,14 @@ void Control::update(const Control* container, const Vector2& offset)
     _skin = getSkin(_state);
 
     // Current opacity should be multiplied by that of the parent container.
-    _opacity = getOpacity(_state) * container->_opacity;
+    _opacity = getOpacity(_state);
+    if (container)
+        _opacity *= container->_opacity;
 }
 
 void Control::drawBorder(SpriteBatch* spriteBatch, const Rectangle& clip)
 {
-    if (!spriteBatch || !_skin || _bounds.width <= 0 || _bounds.height <= 0)
+    if (!spriteBatch || !_skin || _absoluteBounds.width <= 0 || _absoluteBounds.height <= 0)
         return;
 
     // Get the border and background images for this control's current state.
@@ -1082,18 +1256,18 @@ void Control::drawBorder(SpriteBatch* spriteBatch, const Rectangle& clip)
     Vector4 skinColor = _skin->getColor();
     skinColor.w *= _opacity;
 
-    float midWidth = _bounds.width - border.left - border.right;
-    float midHeight = _bounds.height - border.top - border.bottom;
+    float midWidth = _absoluteBounds.width - border.left - border.right;
+    float midHeight = _absoluteBounds.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;
+    float rightX = _absoluteBounds.x + _absoluteBounds.width - border.right;
+    float bottomY = _absoluteBounds.y + _absoluteBounds.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);
+        spriteBatch->draw(_absoluteBounds.x, _absoluteBounds.y, _absoluteBounds.width, _absoluteBounds.height, center.u1, center.v1, center.u2, center.v2, skinColor, clip);
     }
     else
     {
@@ -1107,7 +1281,7 @@ void Control::drawBorder(SpriteBatch* spriteBatch, const Rectangle& clip)
             spriteBatch->draw(_absoluteBounds.x, midY, border.left, midHeight, left.u1, left.v1, left.u2, left.v2, skinColor, clip);
 
         // Always draw the background.
-        spriteBatch->draw(_absoluteBounds.x + border.left, _absoluteBounds.y + border.top, _bounds.width - border.left - border.right, _bounds.height - border.top - border.bottom,
+        spriteBatch->draw(_absoluteBounds.x + border.left, _absoluteBounds.y + border.top, _absoluteBounds.width - border.left - border.right, _absoluteBounds.height - border.top - border.bottom,
             center.u1, center.v1, center.u2, center.v2, skinColor, clip);
 
         if (border.right)
@@ -1237,7 +1411,7 @@ void Control::getAnimationPropertyValue(int propertyId, AnimationValue* value)
 {
     GP_ASSERT(value);
 
-    switch(propertyId)
+    switch (propertyId)
     {
     case ANIMATE_POSITION:
         value->setFloat(0, _bounds.x);
@@ -1274,38 +1448,30 @@ void Control::setAnimationPropertyValue(int propertyId, AnimationValue* value, f
     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;
+        setX(Curve::lerp(blendWeight, _bounds.x, value->getFloat(0)), isXPercentage());
+        setY(Curve::lerp(blendWeight, _bounds.y, value->getFloat(1)), isYPercentage());
         break;
     case ANIMATE_POSITION_X:
-        _bounds.x = Curve::lerp(blendWeight, _bounds.x, value->getFloat(0));
-        _dirty = true;
+        setX(Curve::lerp(blendWeight, _bounds.x, value->getFloat(0)), isXPercentage());
         break;
     case ANIMATE_POSITION_Y:
-        _bounds.y = Curve::lerp(blendWeight, _bounds.y, value->getFloat(0));
-        _dirty = true;
+        setY(Curve::lerp(blendWeight, _bounds.y, value->getFloat(0)), isYPercentage());
         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;
+        setWidth(Curve::lerp(blendWeight, _bounds.width, value->getFloat(0)), isWidthPercentage());
+        setHeight(Curve::lerp(blendWeight, _bounds.height, value->getFloat(1)), isHeightPercentage());
         break;
     case ANIMATE_SIZE_WIDTH:
-        _bounds.width = Curve::lerp(blendWeight, _bounds.width, value->getFloat(0));
-        _dirty = true;
+        setWidth(Curve::lerp(blendWeight, _bounds.width, value->getFloat(0)), isWidthPercentage());
         break;
     case ANIMATE_SIZE_HEIGHT:
-        _bounds.height = Curve::lerp(blendWeight, _bounds.height, value->getFloat(0));
-        _dirty = true;
+        setHeight(Curve::lerp(blendWeight, _bounds.height, value->getFloat(0)), isHeightPercentage());
         break;
     case ANIMATE_OPACITY:
         setOpacity(Curve::lerp(blendWeight, _opacity, value->getFloat(0)));
-        _dirty = true;
         break;
     }
 }
-    
 
 Theme::Style::Overlay** Control::getOverlays(unsigned char overlayTypes, Theme::Style::Overlay** overlays)
 {

+ 193 - 67
gameplay/src/Control.h

@@ -91,6 +91,29 @@ public:
         ALIGN_BOTTOM_RIGHT = ALIGN_BOTTOM | ALIGN_RIGHT
     };
 
+    /**
+     * Defines supported auto sizing modes for controls.
+     */
+    enum AutoSize
+    {
+        /**
+         * No auto sizing is applied.
+         */
+        AUTO_SIZE_NONE = 0x00,
+
+        /**
+         * The control's size is stretched to fill the content area of its parent container.
+         */
+        AUTO_SIZE_STRETCH = 0x01,
+
+        /**
+         * The control's size is set to tightly fit its contents.
+         *
+         * Not all controls support this auto sizing mode.
+         */
+        AUTO_SIZE_FIT = 0x02
+    };
+
     /**
      * Implement Control::Listener and call Control::addListener()
      * in order to listen for events on controls.
@@ -215,42 +238,128 @@ public:
     const char* getId() const;
 
     /**
-     * Set the position of this control relative to its parent container.
+     * Get the x coordinate of this control.
      *
-     * @param x The x coordinate.
-     * @param y The y coordinate.
+     * @return The x coordinate of this control.
      */
-    void setPosition(float x, float y);
+    float getX() const;
 
     /**
-     * Set the desired size of this control, including its border and padding, before clipping.
+     * Sets the X coordinate for the control.
      *
-     * @param width The width.
-     * @param height The height.
+     * If the value is passed as a percentage of its parent container's clip region, it is interpreted as a value
+     * between 0-1, where 1 equals the full size of it's parent.
+     *
+     * @param x The new X coordinate.
+     * @param percentage True if the value should be interpreted as a percentage (0-1), false if it is regular number.
      */
-    virtual void setSize(float width, float height);
+    void setX(float x, bool percentage = false);
 
-    /** 
+    /**
+     * Determines if the X coordinate of this control computed as a percentage of its parent container.
+     *
+     * @return True if the X value is computed as a percentage of its parent container.
+     */
+    bool isXPercentage() const;
+
+    /**
+     * Get the y coordinate of this control.
+     *
+     * @return The y coordinate of this control.
+     */
+    float getY() const;
+
+    /**
+     * Sets the Y coordinate for the control.
+     *
+     * If the value is passed as a percentage of its parent container's clip region, it is interpreted as a value
+     * between 0-1, where 1 equals the full size of it's parent.
+     *
+     * @param y The new Y coordinate.
+     * @param percentage True if the value should be interpreted as a percentage (0-1), false if it is regular number.
+     */
+    void setY(float y, bool percentage = false);
+
+    /**
+     * Determines if the Y coordinate of this control is computed as a percentage of its parent container.
+     *
+     * @return True if the Y value is computed as a percentage of its parent container.
+     */
+    bool isYPercentage() const;
+
+    /**
+     * Get the width of this control.
+     *
+     * @return The width of this control.
+     */
+    float getWidth() const;
+
+    /**
      * Set the desired width of the control, including it's border and padding, before clipping.
      *
+     * If the value is passed as a percentage of its parent container's clip region, it is interpreted as a value
+     * between 0-1, where 1 equals the full size of it's parent.
+     *
      * @param width The width.
+     * @param percentage True if the value should be interpreted as a percentage (0-1), false if it is regular number.
      */
-    virtual void setWidth(float width);
+    void setWidth(float width, bool percentage = false);
 
-    /** 
+    /**
+     * Determines if the width of this control is computed as a percentage of its parent container.
+     *
+     * @return True if the width is computed as a percentage of its parent container.
+     */
+    bool isWidthPercentage() const;
+
+    /**
+     * Get the height of this control.
+     *
+     * @return The height of this control.
+     */
+    float getHeight() const;
+
+    /**
      * Set the desired height of the control, including it's border and padding, before clipping.
      *
+     * If the value is passed as a percentage of its parent container's clip region, it is interpreted as a value
+     * between 0-1, where 1 equals the full size of it's parent.
+     *
      * @param height The height.
+     * @param percentage True if the value should be interpreted as a percentage (0-1), false if it is regular number.
      */
-    virtual void setHeight(float height);
+    void setHeight(float height, bool percentage = false);
 
     /**
-     * Set the bounds of this control, relative to its parent container and including its
-     * border and padding, before clipping.
+     * Determines if the height of this control is computed as a percentage of its parent container.
      *
-     * @param bounds The new bounds to set.
+     * @return True if the height is computed as a percentage of its parent container.
      */
-    virtual void setBounds(const Rectangle& bounds);
+    bool isHeightPercentage() const;
+
+    /**
+     * Set the position of this control relative to its parent container.
+     *
+     * This method sets the local position of the control, relative to its container.
+     * Setting percetage values is not supported with this method, use setX
+     * and setY instead.
+     *
+     * @param x The x coordinate.
+     * @param y The y coordinate.
+     */
+    void setPosition(float x, float y);
+
+    /**
+     * Set the desired size of this control, including its border and padding, before clipping.
+     *
+     * This method sets the size of the control, relative to its container.
+     * Setting percetage values is not supported with this method, use setWidth
+     * and setHeight instead.
+     *
+     * @param width The width.
+     * @param height The height.
+     */
+    void setSize(float width, float height);
 
     /**
      * Get the bounds of this control, relative to its parent container and including its
@@ -260,85 +369,106 @@ public:
      */
     const Rectangle& getBounds() const;
 
+    /**
+     * Set the bounds of this control, relative to its parent container and including its
+     * border and padding, before clipping.
+     *
+     * This method sets the local bounds of the control, relative to its container.
+     * Setting percetage values is not supported with this method, use setX,
+     * setY, setWidth and setHeight instead.
+     *
+     * @param bounds The new bounds to set.
+     */
+    void setBounds(const Rectangle& bounds);
+
     /**
      * Get the absolute bounds of this control, in pixels, including border and padding,
      * before clipping.
      *
+     * The absolute bounds of a control represents its final computed bounds after all 
+     * alignment, auto sizing, relative position and sizing has been computed. The
+     * returned bounds is in absolute coordinates, relative to the control's top-most
+     * parent container (usually its form).
+     *
      * @return The absolute bounds of this control.
      */
     const Rectangle& getAbsoluteBounds() const;
 
     /**
-     * Get the x coordinate of this control's bounds.
+     * Get the bounds of this control, relative to its parent container, after clipping.
      *
-     * @return The x coordinate of this control's bounds.
+     * @return The bounds of this control.
      */
-    float getX() const;
-    
+    const Rectangle& getClipBounds() const;
+
     /**
-     * Get the y coordinate of this control's bounds.
+     * Get the content area of this control, in screen coordinates, after clipping.
      *
-     * @return The y coordinate of this control's bounds.
+     * @return The clipping area of this control.
      */
-    float getY() const;
+    const Rectangle& getClip() const;
 
     /**
-     * Get the width of this control's bounds.
+     * Returns the auto sizing mode for this control's width.
      *
-     * @return The width of this control's bounds.
+     * @return The auto size mode for this control's width.
      */
-    float getWidth() const;
+    AutoSize getAutoWidth() const;
 
     /**
-     * Get the height of this control's bounds.
+     * Enables or disables auto sizing for this control's width.
      *
-     * @return The height of this control's bounds.
+     * This method is a simplified version of setAutoWidth(AutoSize) left intact for legacy reasons.
+     * It enables or disables the AUTO_SIZE_STRETCH mode for the control's width.
+     *
+     * @param autoWidth True to enable AUTO_SIZE_STRETCH for this control's width.
      */
-    float getHeight() const;
+    void setAutoWidth(bool autoWidth);
 
     /**
-     * Set the alignment of this control within its parent container.
+     * Sets the auto size mode for this control's width.
      *
-     * @param alignment This control's alignment.
+     * @param mode The new auto size mode for this control's width.
      */
-    void setAlignment(Alignment alignment);
+    void setAutoWidth(AutoSize mode);
 
     /**
-     * Get the alignment of this control within its parent container.
+     * Returns the auto sizing mode for this control's height.
      *
-     * @return The alignment of this control within its parent container.
+     * @return The auto size mode for this control's height.
      */
-    Alignment getAlignment() const;
+    AutoSize getAutoHeight() const;
 
     /**
-     * Set this control to fit horizontally within its parent container.
+     * Enables or disables auto sizing for this control's height.
+     *
+     * This method is a simplified version of setAutoHeight(AutoSize) left intact for legacy reasons.
+     * It enables or disables the AUTO_SIZE_STRETCH mode for the control's height.
      *
-     * @param autoWidth Whether to size this control to fit horizontally within its parent container.
+     * @param autoWidth True to enable AUTO_SIZE_STRETCH for this control's height.
      */
-    virtual void setAutoWidth(bool autoWidth);
+    void setAutoHeight(bool autoHeight);
 
     /**
-     * Get whether this control's width is set to automatically adjust to
-     * fit horizontally within its parent container.
+     * Sets the auto size mode for this control's height.
      *
-     * @return Whether this control's width is set to automatically adjust.
+     * @param mode The new auto size mode for this control's height.
      */
-    bool getAutoWidth() const;
+    void setAutoHeight(AutoSize mode);
 
     /**
-     * Set this control to fit vertically within its parent container.
+     * Set the alignment of this control within its parent container.
      *
-     * @param autoHeight Whether to size this control to fit vertically within its parent container.
+     * @param alignment This control's alignment.
      */
-    virtual void setAutoHeight(bool autoHeight);
+    void setAlignment(Alignment alignment);
 
     /**
-     * Get whether this control's height is set to automatically adjust to
-     * fit vertically within its parent container.
+     * Get the alignment of this control within its parent container.
      *
-     * @return Whether this control's height is set to automatically adjust.
+     * @return The alignment of this control within its parent container.
      */
-    bool getAutoHeight() const;
+    Alignment getAlignment() const;
 
     /**
      * Set the size of this control's border.
@@ -661,20 +791,6 @@ public:
      */
     bool isEnabled() const;
 
-    /**
-     * Get the bounds of this control, relative to its parent container, after clipping.
-     *
-     * @return The bounds of this control.
-     */
-    const Rectangle& getClipBounds() const;
-
-    /**
-     * Get the content area of this control, in screen coordinates, after clipping.
-     *
-     * @return The clipping area of this control.
-     */
-    const Rectangle& getClip() const;
-
     /**
      * Change this control's state.
      *
@@ -985,12 +1101,22 @@ protected:
     State _state;
 
     /**
-     * Position, relative to parent container's clipping window, and desired size.
+     * Bits indicating whether bounds values are absolute values or percentages.
+     */
+    int _boundsBits;
+
+    /**
+     * Local bounds, relative to parent container's clipping window, possibly stored as percentages (see _boundsBits).
+     */
+    Rectangle _relativeBounds;
+
+    /**
+     * Local bounds, relative to parent container's clipping window, and desired size.
      */
     Rectangle _bounds;
 
     /**
-     * Position, relative to parent container's clipping window, including border and padding, after clipping.
+     * Local bounds, relative to parent container's clipping window, including border and padding, after clipping.
      */
     Rectangle _clipBounds;
 
@@ -1042,12 +1168,12 @@ protected:
     /**
      * Whether the Control's width is auto-sized.
      */
-    bool _autoWidth;
+    AutoSize _autoWidth;
     
     /**
      * Whether the Control's height is auto-sized.
      */
-    bool _autoHeight;
+    AutoSize _autoHeight;
     
     /**
      * Listeners map of EventType's to a list of Listeners.

+ 79 - 226
gameplay/src/Form.cpp

@@ -77,11 +77,7 @@ Form* Form::create(const char* id, Theme::Style* style, Layout::Type layoutType)
     form->_theme = style->getTheme();
     form->_theme->addRef();
 
-    // Get default projection matrix.
-    Game* game = Game::getInstance();
-    Matrix::createOrthographicOffCenter(0, game->getWidth(), game->getHeight(), 0, 0, 1, &form->_defaultProjectionMatrix);
-
-    form->updateBounds();
+    form->updateFrameBuffer();
 
     __forms.push_back(form);
 
@@ -109,7 +105,8 @@ Form* Form::create(const char* url)
     }
 
     // Create new form with given ID, theme and layout.
-    const char* themeFile = formProperties->getString("theme");
+    std::string themeFile;
+    formProperties->getPath("theme", &themeFile);
     const char* layoutString = formProperties->getString("layout");
         
     Layout* layout;
@@ -129,17 +126,13 @@ Form* Form::create(const char* url)
         break;
     }
 
-    Theme* theme = Theme::create(themeFile);
+    Theme* theme = Theme::create(themeFile.c_str());
     GP_ASSERT(theme);
 
     Form* form = new Form();
     form->_layout = layout;
     form->_theme = theme;
 
-    // Get default projection matrix.
-    Game* game = Game::getInstance();
-    Matrix::createOrthographicOffCenter(0, game->getWidth(), game->getHeight(), 0, 0, 1, &form->_defaultProjectionMatrix);
-
     Theme::Style* style = NULL;
     const char* styleName = formProperties->getString("style");
     if (styleName)
@@ -154,25 +147,6 @@ Form* Form::create(const char* url)
 
     form->_consumeInputEvents = formProperties->getBool("consumeInputEvents", false);
 
-    // Alignment
-    if ((form->_alignment & Control::ALIGN_BOTTOM) == Control::ALIGN_BOTTOM)
-    {
-        form->_bounds.y = Game::getInstance()->getHeight() - form->_bounds.height;
-    }
-    else if ((form->_alignment & Control::ALIGN_VCENTER) == Control::ALIGN_VCENTER)
-    {
-        form->_bounds.y = Game::getInstance()->getHeight() * 0.5f - form->_bounds.height * 0.5f;
-    }
-
-    if ((form->_alignment & Control::ALIGN_RIGHT) == Control::ALIGN_RIGHT)
-    {
-        form->_bounds.x = Game::getInstance()->getWidth() - form->_bounds.width;
-    }
-    else if ((form->_alignment & Control::ALIGN_HCENTER) == Control::ALIGN_HCENTER)
-    {
-        form->_bounds.x = Game::getInstance()->getWidth() * 0.5f - form->_bounds.width * 0.5f;
-    }
-
     form->_scroll = getScroll(formProperties->getString("scroll"));
     form->_scrollBarsAutoHide = formProperties->getBool("scrollBarsAutoHide");
     if (form->_scrollBarsAutoHide)
@@ -185,7 +159,7 @@ Form* Form::create(const char* url)
 
     SAFE_DELETE(properties);
     
-    form->updateBounds();
+    form->updateFrameBuffer();
 
     __forms.push_back(form);
 
@@ -212,42 +186,37 @@ Theme* Form::getTheme() const
     return _theme;
 }
 
-void Form::setSize(float width, float height)
+void Form::updateFrameBuffer()
 {
-    if (_autoWidth)
-    {
-        width = Game::getInstance()->getWidth();
-    }
+    float width = _absoluteClipBounds.width;
+    float height = _absoluteClipBounds.height;
 
-    if (_autoHeight)
-    {
-        height = Game::getInstance()->getHeight();
-    }
+    SAFE_RELEASE(_frameBuffer);
+    SAFE_DELETE(_spriteBatch);
 
-    if (width != 0.0f && height != 0.0f &&
-        (width != _bounds.width || height != _bounds.height))
+    if (width != 0.0f && height != 0.0f)
     {
         // Width and height must be powers of two to create a texture.
         unsigned int w = nextPowerOfTwo(width);
         unsigned int h = nextPowerOfTwo(height);
         _u2 = width / (float)w;
         _v1 = height / (float)h;
-
-        // Create framebuffer if necessary. TODO: Use pool to cache.
-        if (_frameBuffer)
-            SAFE_RELEASE(_frameBuffer)
         
         _frameBuffer = FrameBuffer::create(_id.c_str(), w, h);
         GP_ASSERT(_frameBuffer);
 
-        // Re-create projection matrix.
+        // Re-create projection matrix for drawing onto framebuffer
         Matrix::createOrthographicOffCenter(0, width, height, 0, 0, 1, &_projectionMatrix);
 
-        // Re-create sprite batch.
-        SAFE_DELETE(_spriteBatch);
+        // Re-create sprite batch
         _spriteBatch = SpriteBatch::create(_frameBuffer->getRenderTarget()->getTexture());
         GP_ASSERT(_spriteBatch);
 
+        // Compute full-viewport ortho matrix for drawing frame buffer onto screen
+        Matrix viewportProjection;
+        Matrix::createOrthographicOffCenter(0, Game::getInstance()->getViewport().width, Game::getInstance()->getViewport().height, 0, 0, 1, &viewportProjection);
+        _spriteBatch->setProjectionMatrix(viewportProjection);
+
         // Clear the framebuffer black
         Game* game = Game::getInstance();
         FrameBuffer* previousFrameBuffer = _frameBuffer->bind();
@@ -256,57 +225,12 @@ void Form::setSize(float width, float height)
         game->setViewport(Rectangle(0, 0, width, height));
         _theme->setProjectionMatrix(_projectionMatrix);
         game->clear(Game::CLEAR_COLOR, Vector4::zero(), 1.0, 0);
-        _theme->setProjectionMatrix(_defaultProjectionMatrix);
 
         previousFrameBuffer->bind();
         game->setViewport(previousViewport);
-    }
-    _bounds.width = width;
-    _bounds.height = height;
-    _dirty = true;
-}
-
-void Form::setBounds(const Rectangle& bounds)
-{
-    setPosition(bounds.x, bounds.y);
-    setSize(bounds.width, bounds.height);
-}
 
-void Form::setWidth(float width)
-{
-    setSize(width, _bounds.height);
-}
-
-void Form::setHeight(float height)
-{
-    setSize(_bounds.width, height);
-}
-
-void Form::setAutoWidth(bool autoWidth)
-{
-    if (_autoWidth != autoWidth)
-    {
-        _autoWidth = autoWidth;
-        _dirty = true;
-
-        if (_autoWidth)
-        {
-            setSize(_bounds.width, Game::getInstance()->getWidth());
-        }
-    }
-}
-
-void Form::setAutoHeight(bool autoHeight)
-{
-    if (_autoHeight != autoHeight)
-    {
-        _autoHeight = autoHeight;
-        _dirty = true;
-
-        if (_autoHeight)
-        {
-            setSize(_bounds.width, Game::getInstance()->getHeight());
-        }
+        // Force any attached node to be updated
+        setNode(_node);
     }
 }
 
@@ -332,12 +256,20 @@ static Effect* createEffect()
 
 void Form::setNode(Node* node)
 {
-    // If the user wants a custom node then we need to create a 3D quad
-    if (node && node != _node)
+    // If we were already attached to a node, remove ourself from it
+    if (_node)
+    {
+        _node->setModel(NULL);
+        _nodeQuad = NULL;
+        _nodeMaterial = NULL;
+        _node = NULL;
+    }
+
+    if (node)
     {
         // Set this Form up to be 3D by initializing a quad.
-        float x2 = _bounds.width;
-        float y2 = _bounds.height;
+        float x2 = _absoluteBounds.width;
+        float y2 = _absoluteBounds.height;
         float vertices[] =
         {
             0, y2, 0,   0, _v1,
@@ -374,11 +306,14 @@ void Form::setNode(Node* node)
         _nodeMaterial->setParameterAutoBinding("u_worldViewProjectionMatrix", RenderState::WORLD_VIEW_PROJECTION_MATRIX);
 
         // Bind the texture from the framebuffer and set the texture to clamp
-        Texture::Sampler* sampler = Texture::Sampler::create(_frameBuffer->getRenderTarget()->getTexture());
-        GP_ASSERT(sampler);
-        sampler->setWrapMode(Texture::CLAMP, Texture::CLAMP);
-        _nodeMaterial->getParameter("u_texture")->setValue(sampler);
-        sampler->release();
+        if (_frameBuffer)
+        {
+            Texture::Sampler* sampler = Texture::Sampler::create(_frameBuffer->getRenderTarget()->getTexture());
+            GP_ASSERT(sampler);
+            sampler->setWrapMode(Texture::CLAMP, Texture::CLAMP);
+            _nodeMaterial->getParameter("u_texture")->setValue(sampler);
+            sampler->release();
+        }
 
         RenderState::StateBlock* rsBlock = _nodeMaterial->getStateBlock();
         rsBlock->setDepthWrite(true);
@@ -386,14 +321,15 @@ void Form::setNode(Node* node)
         rsBlock->setBlendSrc(RenderState::BLEND_SRC_ALPHA);
         rsBlock->setBlendDst(RenderState::BLEND_ONE_MINUS_SRC_ALPHA);
     }
+
     _node = node;
 }
 
 void Form::update(float elapsedTime)
 {
-    if (isDirty())
+    if (true)//isDirty())
     {
-        updateBounds();
+        update(NULL, Vector2::zero());
 
         // Cache themed attributes for performance.
         _skin = getSkin(_state);
@@ -411,122 +347,26 @@ void Form::update(float elapsedTime)
     }
 }
 
-void Form::updateBounds()
-{   
-    _clearBounds.set(_absoluteClipBounds);
-
-    // Calculate the clipped bounds.
-    float x = 0;
-    float y = 0;
-    float width = _bounds.width;
-    float height = _bounds.height;
-
-    Rectangle clip(0, 0, _bounds.width, _bounds.height);
-
-    float clipX2 = clip.x + clip.width;
-    float x2 = clip.x + x + width;
-    if (x2 > clipX2)
-        width -= x2 - clipX2;
-
-    float clipY2 = clip.y + clip.height;
-    float y2 = clip.y + y + height;
-    if (y2 > clipY2)
-        height -= y2 - clipY2;
-
-    if (x < 0)
-    {
-        width += x;
-        x = -x;
-    }
-    else
-    {
-        x = 0;
-    }
-
-    if (y < 0)
-    {
-        height += y;
-        y = -y;
-    }
-    else
-    {
-        y = 0;
-    }
-
-    _clipBounds.set(x, y, width, height);
-
-    // Calculate the absolute bounds.
-    x = 0;
-    y = 0;
-    _absoluteBounds.set(x, y, _bounds.width, _bounds.height);
-
-    // Calculate the absolute viewport bounds. Absolute bounds minus border and padding.
-    const Theme::Border& border = getBorder(_state);
-    const Theme::Padding& padding = getPadding();
-
-    x += border.left + padding.left;
-    y += border.top + padding.top;
-    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);
-
-    // 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;
-
-    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 (y < clip.y)
-    {
-        float dy = clip.y - y;
-        height -= dy;
-        y = clip.y;
-    }
- 
-    _viewportClipBounds.set(x, y, width, height);
-    _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);
-    }
+void Form::update(const Control* container, const Vector2& offset)
+{
+    // Store previous absolute bounds
+    Rectangle oldAbsoluteClipBounds = _absoluteClipBounds;
 
-    // Get scrollbar images and diminish clipping bounds to make room for scrollbars.
-    if ((_scroll & SCROLL_HORIZONTAL) == SCROLL_HORIZONTAL)
-    {
-        _scrollBarLeftCap = getImage("scrollBarLeftCap", _state);
-        _scrollBarHorizontal = getImage("horizontalScrollBar", _state);
-        _scrollBarRightCap = getImage("scrollBarRightCap", _state);
+    _layout->align(this, NULL);
 
-        _viewportClipBounds.height -= _scrollBarHorizontal->getRegion().height;
-    }
+    Container::update(container, offset);
 
-    if ((_scroll & SCROLL_VERTICAL) == SCROLL_VERTICAL)
+    if (_absoluteClipBounds.width != oldAbsoluteClipBounds.width || _absoluteClipBounds.height != oldAbsoluteClipBounds.height)
     {
-        _scrollBarTopCap = getImage("scrollBarTopCap", _state);
-        _scrollBarVertical = getImage("verticalScrollBar", _state);
-        _scrollBarBottomCap = getImage("scrollBarBottomCap", _state);
-        
-        _viewportClipBounds.width -= _scrollBarVertical->getRegion().width;
+        updateFrameBuffer();
     }
 }
 
 void Form::draw()
 {
+    if (!_visible || !_frameBuffer)
+        return;
+
     // 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.
     // If this form has a node then it's a 3D form and the framebuffer will be used
@@ -536,25 +376,22 @@ void Form::draw()
     // to render the contents of the framebuffer directly to the display.
 
     // Check whether this form has changed since the last call to draw() and if so, render into the framebuffer.
-    if (isDirty())
+    if (true)//isDirty())
     {
-        GP_ASSERT(_frameBuffer);
         FrameBuffer* previousFrameBuffer = _frameBuffer->bind();
 
         Game* game = Game::getInstance();
         Rectangle prevViewport = game->getViewport();
-        game->setViewport(Rectangle(0, 0, _bounds.width, _bounds.height));
+        game->setViewport(Rectangle(0, 0, _absoluteClipBounds.width, _absoluteClipBounds.height));
 
         GP_ASSERT(_theme);
         _theme->setProjectionMatrix(_projectionMatrix);
-        
+
         // By setting needsClear to true here, an optimization meant to clear and redraw only areas of the form
         // that have changed is disabled.  Currently, repositioning controls can result in areas of the screen being cleared
         // after another control has been drawn there.  This should probably be done in two passes -- one to clear areas where
         // dirty controls were last frame, and another to draw them where they are now.
-        Container::draw(_theme->getSpriteBatch(), Rectangle(0, 0, _bounds.width, _bounds.height),
-                        /*_skin != NULL*/ true, false, _bounds.height);
-        _theme->setProjectionMatrix(_defaultProjectionMatrix);
+        Container::draw(_theme->getSpriteBatch(), _absoluteClipBounds, /*_skin != NULL*/ true, false, _absoluteClipBounds.height);
 
         // Restore the previous game viewport.
         game->setViewport(prevViewport);
@@ -571,11 +408,6 @@ void Form::draw()
     else
     {
         // Otherwise we draw the framebuffer in ortho space with a spritebatch.
-        if (!_spriteBatch)
-        {
-            _spriteBatch = SpriteBatch::create(_frameBuffer->getRenderTarget()->getTexture());
-            GP_ASSERT(_spriteBatch);
-        }
         _spriteBatch->start();
         _spriteBatch->draw(_bounds.x, _bounds.y, 0, _bounds.width, _bounds.height, 0, _v1, _u2, 0, Vector4::one());
         _spriteBatch->finish();
@@ -740,6 +572,27 @@ void Form::gamepadEventInternal(Gamepad::GamepadEvent evt, Gamepad* gamepad, uns
     }
 }
 
+void Form::resizeEventInternal(unsigned int width, unsigned int height)
+{
+    for (size_t i = 0; i < __forms.size(); ++i)
+    {
+        Form* form = __forms[i];
+        if (form)
+        {
+            if (form->_spriteBatch)
+            {
+                // Update viewport projection matrix
+                Matrix viewportProjection;
+                Matrix::createOrthographicOffCenter(0, Game::getInstance()->getViewport().width, Game::getInstance()->getViewport().height, 0, 0, 1, &viewportProjection);
+                form->_spriteBatch->setProjectionMatrix(viewportProjection);
+            }
+
+            // Dirty the form
+            form->_dirty = true;
+        }
+    }
+}
+
 bool Form::projectPoint(int x, int y, Vector3* point)
 {
     Scene* scene = _node->getScene();

+ 20 - 49
gameplay/src/Form.h

@@ -92,49 +92,6 @@ public:
      */
     Theme* getTheme() const;
 
-    /**
-     * Set the desired size of this form.
-     *
-     * @param width The width.
-     * @param height The height.
-     */
-    virtual void setSize(float width, float height);
-
-    /**
-     * Set the bounds of this form.
-     *
-     * @param bounds The new bounds to set.
-     */
-    virtual void setBounds(const Rectangle& bounds);
-
-    /** 
-     * Set the desired width of the form.
-     *
-     * @param width The width.
-     */
-    virtual void setWidth(float width);
-
-    /** 
-     * Set the desired height of the form.
-     *
-     * @param height The height.
-     */
-    virtual void setHeight(float height);
-
-    /**
-     * Set this form's width to that of the display.
-     *
-     * @param autoWidth Whether to set this form's width to that of the display.
-     */
-    virtual void setAutoWidth(bool autoWidth);
-
-    /**
-     * Set this form's height to that of the display.
-     *
-     * @param autoHeight Whether to set this form's height to that of the display.
-     */
-    virtual void setAutoHeight(bool autoHeight);
-
     /**
      * Attach this form to a node.
      *
@@ -162,6 +119,13 @@ public:
      */
     const char* getType() const;
 
+protected:
+
+    /**
+     * @see Control::update
+     */
+    void update(const Control* container, const Vector2& offset);
+
 private:
     
     /**
@@ -186,11 +150,6 @@ private:
      */
     void initializeQuad(Mesh* mesh);
 
-    /**
-     * Update this form's bounds.
-     */
-    void updateBounds();
-
     /**
      * Updates all visible, enabled forms.
      */
@@ -226,6 +185,14 @@ private:
      */
     static void gamepadEventInternal(Gamepad::GamepadEvent evt, Gamepad* gamepad, unsigned int analogIndex);
 
+    /**
+     * Fired by the platform when the game window resizes.
+     *
+     * @param width The new window width.
+     * @param height The new window height.
+     */
+    static void resizeEventInternal(unsigned int width, unsigned int height);
+
     /**
      * Get the next highest power of two of an integer.  Used when creating framebuffers.
      *
@@ -246,6 +213,11 @@ private:
      */
     bool projectPoint(int x, int y, Vector3* point);
 
+    /**
+     * Called when the form is resized to update its internal frame buffer.
+     */
+    void updateFrameBuffer();
+
     Theme* _theme;                      // The Theme applied to this Form.
     FrameBuffer* _frameBuffer;          // FBO the Form is rendered into for texturing the quad. 
     SpriteBatch* _spriteBatch;
@@ -255,7 +227,6 @@ private:
     float _u2;
     float _v1;
     Matrix _projectionMatrix;           // Orthographic projection matrix to be set on SpriteBatch objects when rendering into the FBO.
-    Matrix _defaultProjectionMatrix;
     bool _isGamepad;
 };
 

+ 19 - 1
gameplay/src/ImageControl.cpp

@@ -150,4 +150,22 @@ void ImageControl::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
     spriteBatch->start();
 }
 
-}
+void ImageControl::update(const Control* container, const Vector2& offset)
+{
+    Button::update(container, offset);
+
+    if (_batch)
+    {
+        if (_autoWidth == Control::AUTO_SIZE_FIT)
+        {
+            setWidth(_batch->getSampler()->getTexture()->getWidth());
+        }
+
+        if (_autoHeight == Control::AUTO_SIZE_FIT)
+        {
+            setHeight(_batch->getSampler()->getTexture()->getWidth());
+        }
+    }
+}
+
+}

+ 5 - 0
gameplay/src/ImageControl.h

@@ -123,6 +123,11 @@ protected:
 
     void drawImages(SpriteBatch* spriteBatch, const Rectangle& clip);
 
+    /**
+     * @see Control#update(const Control*, const Vector2&)
+     */
+    void update(const Control* container, const Vector2& offset);
+
     // Source region.
     Rectangle _srcRegion;
     // Destination region.

+ 12 - 1
gameplay/src/Label.cpp

@@ -87,11 +87,22 @@ void Label::update(const Control* container, const Vector2& offset)
 {
     Control::update(container, offset);
 
-    _textBounds.set(_viewportBounds);
+    _textBounds.set((int)_viewportBounds.x, (int)_viewportBounds.y, _viewportBounds.width, _viewportBounds.height);
 
     _font = getFont(_state);
     _textColor = getTextColor(_state);
     _textColor.w *= _opacity;
+
+    Font* font = getFont(_state);
+    if ((_autoWidth == Control::AUTO_SIZE_FIT || _autoHeight == Control::AUTO_SIZE_FIT) && font)
+    {
+        unsigned int w, h;
+        font->measureText(_text.c_str(), getFontSize(_state), &w, &h);
+        if (_autoWidth == Control::AUTO_SIZE_FIT)
+            setWidth(w + getBorder(_state).left + getBorder(_state).right + getPadding().left + getPadding().right);
+        if (_autoHeight == Control::AUTO_SIZE_FIT)
+            setHeight(h + getBorder(_state).top + getBorder(_state).bottom + getPadding().top + getPadding().bottom);
+    }
 }
 
 void Label::drawText(const Rectangle& clip)

+ 15 - 28
gameplay/src/Layout.cpp

@@ -2,6 +2,7 @@
 #include "Layout.h"
 #include "Control.h"
 #include "Container.h"
+#include "Game.h"
 
 namespace gameplay
 {
@@ -9,21 +10,19 @@ namespace gameplay
 void Layout::align(Control* control, const Container* container)
 {
     GP_ASSERT(control);
-    GP_ASSERT(container);
 
     if (control->_alignment != Control::ALIGN_TOP_LEFT ||
-        control->_isAlignmentSet ||
-        control->_autoWidth || control->_autoHeight)
+        control->_isAlignmentSet)
     {
-        Rectangle controlBounds = control->getBounds();
+        const 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();
+        const Rectangle& containerBounds = container ? container->getBounds() : Game::getInstance()->getViewport();
+        const Theme::Border& containerBorder = container ? container->getBorder(container->getState()) : Theme::Border::empty();
+        const Theme::Padding& containerPadding = container ? container->getPadding() : Theme::Padding::empty();
 
         float clipWidth;
         float clipHeight; 
-        if (container->getScroll() != Container::SCROLL_NONE)
+        if (container && (container->getScroll() != Container::SCROLL_NONE))
         {
             const Rectangle& verticalScrollBarBounds = container->getImageRegion("verticalScrollBar", container->getState());
             const Rectangle& horizontalScrollBarBounds = container->getImageRegion("horizontalScrollBar", container->getState());
@@ -36,51 +35,39 @@ void Layout::align(Control* control, const Container* container)
             clipHeight = containerBounds.height - containerBorder.top - containerBorder.bottom - containerPadding.top - containerPadding.bottom;
         }
 
-        if (control->_autoWidth)
-        {
-            controlBounds.width = clipWidth - controlMargin.left - controlMargin.right;
-        }
-
-        if (control->_autoHeight)
-        {
-            controlBounds.height = clipHeight - controlMargin.top - controlMargin.bottom;
-        }
-
         // Vertical alignment
-        if(control->_isAlignmentSet || control->_autoHeight)
+        if (control->_isAlignmentSet || control->_autoHeight)
         {
             if ((control->_alignment & Control::ALIGN_BOTTOM) == Control::ALIGN_BOTTOM)
             {
-                controlBounds.y = clipHeight - controlBounds.height - controlMargin.bottom;
+                control->setY(clipHeight - controlBounds.height - controlMargin.bottom);
             }
             else if ((control->_alignment & Control::ALIGN_VCENTER) == Control::ALIGN_VCENTER)
             {
-                controlBounds.y = clipHeight * 0.5f - controlBounds.height * 0.5f;
+                control->setY(clipHeight * 0.5f - controlBounds.height * 0.5f);
             }
             else if ((control->_alignment & Control::ALIGN_TOP) == Control::ALIGN_TOP)
             {
-                controlBounds.y = controlMargin.top;
+                control->setY(controlMargin.top);
             }
         }
 
         // Horizontal alignment
-        if(control->_isAlignmentSet || control->_autoWidth)
+        if (control->_isAlignmentSet)
         {
             if ((control->_alignment & Control::ALIGN_RIGHT) == Control::ALIGN_RIGHT)
             {
-                controlBounds.x = clipWidth - controlBounds.width - controlMargin.right;
+                control->setX(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->setX(clipWidth * 0.5f - controlBounds.width * 0.5f);
             }
             else if ((control->_alignment & Control::ALIGN_LEFT) == Control::ALIGN_LEFT)
             {
-                controlBounds.x = controlMargin.left;
+                control->setX(controlMargin.left);
             }
         }
-
-        control->setBounds(controlBounds);
     }
 }
 

+ 2 - 0
gameplay/src/Platform.cpp

@@ -74,6 +74,8 @@ void Platform::resizeEventInternal(unsigned int width, unsigned int height)
         game->resizeEvent(width, height);
         game->getScriptController()->resizeEvent(width, height);
     }
+
+    Form::resizeEventInternal(width, height);
 }
 
 void Platform::gamepadEventInternal(Gamepad::GamepadEvent evt, Gamepad* gamepad, unsigned int analogIndex)

+ 2 - 2
gameplay/src/Properties.cpp

@@ -704,7 +704,7 @@ Properties::Type Properties::getType(const char* name) const
     }
 }
 
-const char* Properties::getString(const char* name) const
+const char* Properties::getString(const char* name, const char* defaultValue) const
 {
     if (name)
     {
@@ -722,7 +722,7 @@ const char* Properties::getString(const char* name) const
         }
     }
 
-    return NULL;
+    return defaultValue;
 }
 
 bool Properties::getBool(const char* name, bool defaultValue) const

+ 2 - 1
gameplay/src/Properties.h

@@ -233,10 +233,11 @@ public:
      * whatever the intended type of the property.
      *
      * @param name The name of the property to interpret, or NULL to return the current property's value.
+     * @param defaultValue The default value to return if the specified property does not exist.
      * 
      * @return The value of the given property as a string, or the empty string if no property with that name exists.
      */
-    const char* getString(const char* name = NULL) const;
+    const char* getString(const char* name = NULL, const char* defaultValue = NULL) const;
 
     /**
      * Interpret the value of the given property as a boolean.

+ 13 - 3
gameplay/src/RadioButton.cpp

@@ -196,11 +196,21 @@ void RadioButton::update(const Control* container, const Vector2& offset)
     {
         size.set(_imageSize);
     }
-    float iconWidth = size.x;
 
-    _textBounds.x += iconWidth + 5;
-    _textBounds.width -= iconWidth + 5;
+    if (_autoWidth == Control::AUTO_SIZE_FIT)
+    {
+        // Text-only width was already measured in Label::update - append image
+        setWidth(size.x + _bounds.width + 5);
+    }
+
+    if (_autoHeight == Control::AUTO_SIZE_FIT)
+    {
+        // Text-only width was already measured in Label::update - append image
+        setHeight(std::max(getHeight(), size.y));
+    }
 
+    _textBounds.x += size.x + 5;
+    
     if (_selected)
     {
         _image = getImage("selected", _state);

+ 17 - 5
gameplay/src/ScriptController.cpp

@@ -431,12 +431,20 @@ void ScriptController::loadScript(const char* path, bool forceReload)
     std::set<std::string>::iterator iter = _loadedScripts.find(path);
     if (iter == _loadedScripts.end() || forceReload)
     {
+        bool success = false;
+        if (iter == _loadedScripts.end())
+            _loadedScripts.insert(path); // insert before loading script to prevent load recursion
+
 #ifdef __ANDROID__
         const char* scriptContents = FileSystem::readAll(path);
         if (luaL_dostring(_lua, scriptContents))
         {
             GP_WARN("Failed to run Lua script with error: '%s'.", lua_tostring(_lua, -1));
         }
+        else
+        {
+            success = true;
+        }
         SAFE_DELETE_ARRAY(scriptContents);
 #else
         std::string fullPath;
@@ -449,10 +457,15 @@ void ScriptController::loadScript(const char* path, bool forceReload)
         {
             GP_WARN("Failed to run Lua script with error: '%s'.", lua_tostring(_lua, -1));
         }
+        else
+        {
+            success = true;
+        }
 #endif
-        if (iter == _loadedScripts.end())
+        if (!success && (iter == _loadedScripts.end()))
         {
-            _loadedScripts.insert(path);
+            iter = _loadedScripts.find(path);
+            _loadedScripts.erase(iter);
         }
     }
 }
@@ -463,11 +476,10 @@ std::string ScriptController::loadUrl(const char* url)
     std::string id;
     splitURL(url, &file, &id);
 
-    // Make sure the function isn't empty.
     if (id.size() <= 0)
     {
-        GP_ERROR("Got an empty function name when parsing function url '%s'.", url);
-        return std::string();
+        // The url does not reference a script - only a function
+        return file;
     }
 
     // Ensure the script is loaded.

+ 13 - 1
gameplay/src/Slider.cpp

@@ -495,7 +495,7 @@ void Slider::update(const Control* container, const Vector2& offset)
         {
             _value += (total * GAMEPAD_FRACTION) * _delta;
         }
-            
+
         if (_value > _max)
             _value = _max;
         else if (_value < _min)
@@ -506,6 +506,18 @@ void Slider::update(const Control* container, const Vector2& offset)
             notifyListeners(Control::Listener::VALUE_CHANGED);
         }
     }
+
+    if (_autoHeight == Control::AUTO_SIZE_FIT)
+    {
+        float height = _minImage->getRegion().height;
+        height = std::max(height, _maxImage->getRegion().height);
+        height = std::max(height, _markerImage->getRegion().height);
+        height = std::max(height, _trackImage->getRegion().height);
+        height += _bounds.height;
+        if (_valueTextVisible && _font)
+            height += getFontSize(_state);
+        setHeight(height);
+    }
 }
 
 void Slider::draw(SpriteBatch* spriteBatch, const Rectangle& clip, bool needsClear, bool cleared, float targetHeight)

+ 1 - 1
gameplay/src/SpriteBatch.cpp

@@ -133,7 +133,7 @@ SpriteBatch* SpriteBatch::create(Texture* texture,  Effect* effect, unsigned int
 
 	// Bind an ortho projection to the material by default (user can override with setProjectionMatrix)
 	Game* game = Game::getInstance();
-	Matrix::createOrthographicOffCenter(0, game->getWidth(), game->getHeight(), 0, 0, 1, &batch->_projectionMatrix);
+    Matrix::createOrthographicOffCenter(0, game->getViewport().width, game->getViewport().height, 0, 0, 1, &batch->_projectionMatrix);
 	material->getParameter("u_projectionMatrix")->bindValue(batch, &SpriteBatch::getProjectionMatrix);
 	
     return batch;

+ 10 - 9
gameplay/src/Theme.cpp

@@ -88,12 +88,13 @@ Theme* Theme::create(const char* url)
     theme->_url = url;
         
     // Parse the Properties object and set up the theme.
-    const char* textureFile = themeProperties->getString("texture");
-    theme->_texture = Texture::create(textureFile, false);
+    std::string textureFile;
+    themeProperties->getPath("texture", &textureFile);
+    theme->_texture = Texture::create(textureFile.c_str(), false);
     GP_ASSERT(theme->_texture);
     theme->_spriteBatch = SpriteBatch::create(theme->_texture);
     GP_ASSERT(theme->_spriteBatch);
-    theme->_spriteBatch->getSampler()->setFilterMode(Texture::NEAREST, Texture::NEAREST);
+    theme->_spriteBatch->getSampler()->setFilterMode(Texture::LINEAR, Texture::LINEAR);
 
     float tw = 1.0f / theme->_texture->getWidth();
     float th = 1.0f / theme->_texture->getHeight();
@@ -176,11 +177,11 @@ Theme* Theme::create(const char* url)
                         innerSpace->getColor("textColor", &textColor);
                     }
 
-                    const char* fontPath = innerSpace->getString("font");
                     Font* font = NULL;
-                    if (fontPath)
+                    std::string fontPath;
+                    if (innerSpace->getPath("font", &fontPath))
                     {
-                        font = Font::create(fontPath);
+                        font = Font::create(fontPath.c_str());
                     }
                     unsigned int fontSize = innerSpace->getInt("fontSize");
                     const char* textAlignmentString = innerSpace->getString("textAlignment");
@@ -260,11 +261,11 @@ Theme* Theme::create(const char* url)
                         textColor.set(normal->getTextColor());
                     }
 
-                    const char* fontPath = innerSpace->getString("font");
                     Font* font = NULL;
-                    if (fontPath)
+                    std::string fontPath;
+                    if (innerSpace->getPath("font", &fontPath))
                     {
-                        font = Font::create(fontPath);
+                        font = Font::create(fontPath.c_str());
                     }
                     if (!font)
                     {

+ 1 - 1
samples/browser/res/common/forms/formSelect.form

@@ -14,7 +14,7 @@ form formSelect
 		text = Basic Controls
 		group = formSelection
 		size = 180, 60
-		textAlignment = ALIGN_VCENTER_HCENTER
+		textAlignment = ALIGN_VCENTER_LEFT
 		selected = true
 	}
 

+ 8 - 7
samples/browser/src/LightSample.cpp

@@ -91,6 +91,10 @@ void LightSample::initialize()
 	_pointLightNode->setTranslation(0.0f, 0.0f, 8.0f);
 	_scene->addNode(_pointLightNode);
 
+    // Create and initialize lights and materials for lights
+	_lighting = Material::create("res/common/light.material");
+	_model->setMaterial(_lighting);
+
     // Create and initialize ui form
 	_form = Form::create("res/common/light.form");
     _properties = static_cast<Container*>(_form->getControl("lightProperties"));
@@ -115,13 +119,6 @@ void LightSample::initialize()
 	_addBumped = static_cast<CheckBox*>(_form->getControl("bumpedCheckBox"));
 	_addBumped->addListener(this, Control::Listener::VALUE_CHANGED);
 
-    _properties->setEnabled(false);
-    _noLight->setSelected(true);
-	_form->setConsumeInputEvents(false);
-	
-    // Create and initialize lights and materials for lights
-	_lighting = Material::create("res/common/light.material");
-	_model->setMaterial(_lighting);
 	initializeDirectionalTechnique("directional");
 	initializeDirectionalTechnique("directionalSpecular");
 	initializeDirectionalTechnique("directionalBumped");
@@ -135,6 +132,10 @@ void LightSample::initialize()
 	initializePointTechnique("pointBumped");
 	initializePointTechnique("pointSpecularBumped");
 
+    _properties->setEnabled(false);
+    _noLight->setSelected(true);
+	_form->setConsumeInputEvents(false);
+
 	setSpecularValue(_specularSlider->getValue());
 }