Просмотр исходного кода

Layout optimizations continued.

sgrenier 12 лет назад
Родитель
Сommit
17a8763cb4

+ 2 - 15
gameplay/src/AbsoluteLayout.cpp

@@ -36,22 +36,9 @@ Layout::Type AbsoluteLayout::getType()
     return Layout::LAYOUT_ABSOLUTE;
 }
 
-void AbsoluteLayout::update(const Container* container, const Vector2& offset)
+void AbsoluteLayout::update(const Container* container)
 {
-    GP_ASSERT(container);
-
-    // An AbsoluteLayout does nothing to modify the layout of Controls.
-    const std::vector<Control*>& controls = container->getControls();
-    for (size_t i = 0, count = controls.size(); i < count; ++i)
-    {
-        Control* control = controls[i];
-        GP_ASSERT(control);
-
-        if (control->isVisible())
-        {
-            control->updateBounds(offset);
-        }
-    }
+    // Nothing to do for absolute layout
 }
 
 }

+ 1 - 2
gameplay/src/AbsoluteLayout.h

@@ -33,9 +33,8 @@ protected:
      * It simply calls update() on any control that is dirty.
      *
      * @param container The container to update.
-     * @param offset The layout offset.
      */
-    void update(const Container* container, const Vector2& offset);
+    void update(const Container* container);
 
 private:
     

+ 2 - 4
gameplay/src/CheckBox.cpp

@@ -110,15 +110,13 @@ void CheckBox::updateBounds(const Vector2& offset)
         // Text-only width was already measured in Label::update - append image
         const Theme::Border& border = getBorder(state);
         const Theme::Border& padding = getPadding();
-        setHeight(std::max(_bounds.height, size.y + border.top + border.bottom + padding.top + padding.bottom));
-        _autoSize = (AutoSize)(_autoSize | AUTO_SIZE_HEIGHT);
+        setHeightInternal(std::max(_bounds.height, size.y + border.top + border.bottom + padding.top + padding.bottom));
     }
 
     if (_autoSize & AUTO_SIZE_WIDTH)
     {
         // Text-only width was already measured in Label::update - append image
-        setWidth(_viewportBounds.height + 5 + _bounds.width);
-        _autoSize = (AutoSize)(_autoSize | AUTO_SIZE_WIDTH);
+        setWidthInternal(_viewportBounds.height + 5 + _bounds.width);
     }
 
     _textBounds.x += _viewportBounds.height + 5;

+ 83 - 53
gameplay/src/Container.cpp

@@ -57,7 +57,7 @@ Container::Container()
       _scrollingMouseVertically(false), _scrollingMouseHorizontally(false),
       _scrollBarOpacityClip(NULL), _zIndexDefault(0),
       _selectButtonDown(false), _lastFrameTime(0), _totalWidth(0), _totalHeight(0),
-      _initializedWithScroll(false), _scrollWheelRequiresFocus(false), _allowRelayout(true)
+      _initializedWithScroll(false), _scrollWheelRequiresFocus(false)
 {
 	clearContacts();
 }
@@ -128,7 +128,6 @@ void Container::initialize(const char* typeName, Theme::Style* style, Properties
 			_scrollWheelSpeed = properties->getFloat("scrollWheelSpeed");
 
 		addControls(properties);
-		_layout->update(this, _scrollPosition);
 
 		const char* activeControl = properties->getString("activeControl");
 		if (activeControl)
@@ -191,7 +190,6 @@ void Container::setLayout(Layout::Type type)
 
 		_layout = createLayout(type);
         setDirty(Control::DIRTY_BOUNDS);
-		_layout->update(this, _scrollPosition);
 	}
 }
 
@@ -503,8 +501,27 @@ void Container::setActiveControl(Control* control)
     }
 }
 
+void Container::update(float elapsedTime)
+{
+    Control::update(elapsedTime);
+
+    for (size_t i = 0, count = _controls.size(); i < count; ++i)
+    {
+        _controls[i]->update(elapsedTime);
+    }
+}
+
 void Container::updateBounds(const Vector2& offset)
 {
+    if ((_dirtyBits & DIRTY_BOUNDS) == 0)
+    {
+        // We're not dirty, but our children might be.
+        updateChildBounds();
+        return;
+    }
+
+    Rectangle oldAbsoluteBounds(_absoluteBounds);
+
     Control::updateBounds(offset);
 
     Control::State state = getState();
@@ -534,31 +551,27 @@ void Container::updateBounds(const Vector2& offset)
         _viewportClipBounds.width -= _scrollBarVertical->getRegion().width;
     }
 
-    GP_ASSERT(_layout);
+    // Update scroll position and scrollbars
     updateScroll();
-    _layout->update(this, _scrollPosition);
+
+    // Update layout
+    GP_ASSERT(_layout);
+    _layout->update(this);
+
+    // Update bounds of our children
+    updateChildBounds();
 
     // Handle automatically sizing based on our children
     if (_autoSize != AUTO_SIZE_NONE)
     {
-        Vector2 oldSize(_bounds.width, _bounds.height);
-        bool sizeChanged = false;
-        bool relayout = false;
-
         if (_autoSize & AUTO_SIZE_WIDTH)
         {
             // 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)
+            for (size_t i = 0, count = _controls.size(); i < count; ++i)
             {
-                Control* ctrl = *it;
-                if (ctrl->isXPercentage() || ctrl->isWidthPercentage())
-                {
-                    // Our child's layout depends on our size, so we need to dirty it
-                    ctrl->setDirty(DIRTY_BOUNDS);
-                    relayout = _allowRelayout;
-                }
-                else
+                Control* ctrl = _controls[i];
+                if (!ctrl->isXPercentage() && !ctrl->isWidthPercentage())
                 {
                     float w = ctrl->getX() + ctrl->getWidth();
                     if (width < w)
@@ -566,28 +579,17 @@ void Container::updateBounds(const Vector2& offset)
                 }
             }
             width += getBorder(state).left + getBorder(state).right + getPadding().left + getPadding().right;
-            if (width != oldSize.x)
-            {
-                setWidth(width);
-                _autoSize = (AutoSize)(_autoSize | AUTO_SIZE_WIDTH);
-                sizeChanged = true;
-            }
+            setWidthInternal(width);
         }
 
         if (_autoSize & AUTO_SIZE_HEIGHT)
         {
             // 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)
+            for (size_t i = 0, count = _controls.size(); i < count; ++i)
             {
-                Control* ctrl = *it;
-                if (ctrl->isYPercentage() || ctrl->isHeightPercentage())
-                {
-                    // Our child's layout depends on our size, so we need to dirty it
-                    ctrl->setDirty(DIRTY_BOUNDS);
-                    relayout = _allowRelayout;
-                }
-                else
+                Control* ctrl = _controls[i];
+                if (!ctrl->isYPercentage() && !ctrl->isHeightPercentage())
                 {
                     float h = ctrl->getY() + ctrl->getHeight();
                     if (height < h)
@@ -595,21 +597,42 @@ void Container::updateBounds(const Vector2& offset)
                 }
             }
             height += getBorder(state).top + getBorder(state).bottom + getPadding().top + getPadding().bottom;
-            if (height != oldSize.y)
-            {
-                setHeight(height);
-                _autoSize = (AutoSize)(_autoSize | AUTO_SIZE_HEIGHT);
-                sizeChanged = true;
-            }
+            setHeightInternal(height);
         }
+    }
 
-        if (sizeChanged && relayout)
+    // If our bounds have changed, dirty our entire child hierarchy since at a minimum their absolute bounds
+    // will need to be recomputed to be properly offset from our new bounds.
+    if (_absoluteBounds != oldAbsoluteBounds)
+    {
+        setDirty(DIRTY_BOUNDS);
+    }
+}
+
+void Container::updateChildBounds()
+{
+    for (size_t i = 0, count = _controls.size(); i < count; ++i)
+    {
+        Control* ctrl = _controls[i];
+        GP_ASSERT(ctrl);
+
+        if (ctrl->isVisible() && (ctrl->isContainer() || ctrl->_dirtyBits & DIRTY_BOUNDS))
         {
-            // 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;
-            updateBounds(offset);
-            _allowRelayout = true;
+            Rectangle oldBounds(ctrl->_absoluteBounds);
+
+            ctrl->updateBounds(_scrollPosition);
+
+            // If the child bounds have changed, dirty our bounds and all of our
+            // parent bounds so that our layout and/or bounds are recomputed.
+            if (ctrl->_absoluteBounds != oldBounds)
+            {
+                Control* parent = this;
+                while (parent)
+                {
+                    parent->setDirty(DIRTY_BOUNDS);
+                    parent = parent->_parent;
+                }
+            }
         }
     }
 }
@@ -1045,9 +1068,9 @@ void Container::updateScroll()
     // Calculate total width and height.
     _totalWidth = _totalHeight = 0.0f;
     std::vector<Control*> controls = getControls();
-    for (size_t i = 0, controlsCount = controls.size(); i < controlsCount; i++)
+    for (size_t i = 0, count = controls.size(); i < count; ++i)
     {
-        Control* control = controls.at(i);
+        Control* control = _controls[i];
 
         const Rectangle& bounds = control->getBounds();
         const Theme::Margin& margin = control->getMargin();
@@ -1090,6 +1113,8 @@ void Container::updateScroll()
             if (fabs(_scrollingVelocity.y) < 100.0f)
                 _scrollingVelocity.y = 0.0f;
         }
+
+        setDirty(DIRTY_BOUNDS);
     }
 
     // Stop scrolling when the far edge is reached.
@@ -1177,7 +1202,7 @@ bool Container::touchEventScroll(Touch::TouchEvent evt, int x, int y, unsigned i
         break;
 
     case Touch::TOUCH_MOVE:
-        if (_scrolling && _contactIndex == (int) contactIndex)
+        if (_scrolling && _contactIndex == (int)contactIndex)
         {
             double gameTime = Game::getAbsoluteTime();
 
@@ -1235,7 +1260,7 @@ bool Container::touchEventScroll(Touch::TouchEvent evt, int x, int y, unsigned i
 
             _scrollingLastTime = gameTime;
             setDirty(DIRTY_BOUNDS);
-            return _consumeInputEvents;
+            return false;
         }
         break;
 
@@ -1250,8 +1275,7 @@ bool Container::touchEventScroll(Touch::TouchEvent evt, int x, int y, unsigned i
             {
                 _scrollingVelocity.set(0, 0);
                 _scrollingMouseVertically = _scrollingMouseHorizontally = false;
-                setDirty(DIRTY_BOUNDS);
-                return _consumeInputEvents;
+                return false;
             }
 
             int dx = _scrollingLastX - _scrollingFirstX;
@@ -1288,7 +1312,7 @@ bool Container::touchEventScroll(Touch::TouchEvent evt, int x, int y, unsigned i
 
             _scrollingMouseVertically = _scrollingMouseHorizontally = false;
             setDirty(DIRTY_BOUNDS);
-            return _consumeInputEvents;
+            return false;
         }
         break;
     }
@@ -1347,11 +1371,17 @@ bool Container::mouseEventScroll(Mouse::MouseEvent evt, int x, int y, int wheelD
                 {
                     // We're within the vertical bounds of the horizontal scrollbar.
                     if (x < hBounds.x)
+                    {
                         _scrollPosition.x += _totalWidth / 5.0f;
+                    }
                     else if (x > hBounds.x + hBounds.width)
+                    {
                         _scrollPosition.x -= _totalWidth / 5.0f;
+                    }
                     else
+                    {
                         _scrollingMouseHorizontally = true;
+                    }
                 }
             }
 
@@ -1381,7 +1411,7 @@ bool Container::mouseEventScroll(Mouse::MouseEvent evt, int x, int y, int wheelD
             }
             _scrollBarOpacity = 1.0f;
             setDirty(DIRTY_BOUNDS);
-            return _consumeInputEvents;
+            return false;
         }
     }
 

+ 10 - 1
gameplay/src/Container.h

@@ -304,11 +304,21 @@ protected:
      */
     void initialize(const char* typeName, Theme::Style* style, Properties* properties);
 
+    /**
+     * @see Control::update
+     */
+    void update(float elapsedTime);
+
     /**
      * @see Control::updateBounds
      */
     void updateBounds(const Vector2& offset);
 
+    /**
+     * Updates the bounds for this container's child controls.
+     */
+    void updateChildBounds();
+
     /**
      * Gets a Layout::Type enum from a matching string.
      *
@@ -553,7 +563,6 @@ private:
     bool _contactIndices[MAX_CONTACT_INDICES];
     bool _initializedWithScroll;
     bool _scrollWheelRequiresFocus;
-    bool _allowRelayout;
 };
 
 }

+ 95 - 74
gameplay/src/Control.cpp

@@ -137,6 +137,10 @@ void Control::initialize(const char* typeName, Theme::Style* style, Properties*
 
     if (properties)
     {
+        const char* id = properties->getId();
+        if (id)
+            _id = id;
+
 		// Properties not defined by the style.
 		const char* alignmentString = properties->getString("alignment");
 
@@ -222,10 +226,6 @@ void Control::initialize(const char* typeName, Theme::Style* style, Properties*
         // Parse the auto-size mode for the control (this overrides explicit sizes and legacy autoWidth/autoHeight)
         _autoSize = parseAutoSize(properties->getString("autoSize"));
 
-		const char* id = properties->getId();
-		if (id)
-			_id = id;
-
 		if (properties->exists("enabled"))
 		{
 			setEnabled(properties->getBool("enabled"));
@@ -299,20 +299,25 @@ void Control::setX(float x, bool percentage)
 {
     if (_relativeBounds.x != x || percentage != ((_boundsBits & BOUNDS_X_PERCENTAGE_BIT) != 0))
     {
-        _relativeBounds.x = x;
-        if (percentage)
-        {
-            _boundsBits |= BOUNDS_X_PERCENTAGE_BIT;
-        }
-        else
-        {
-            _boundsBits &= ~BOUNDS_X_PERCENTAGE_BIT;
-            _bounds.x = x;
-        }
+        setXInternal(x, percentage);
         setDirty(DIRTY_BOUNDS);
     }
 }
 
+void Control::setXInternal(float x, bool percentage)
+{
+    _relativeBounds.x = x;
+    if (percentage)
+    {
+        _boundsBits |= BOUNDS_X_PERCENTAGE_BIT;
+    }
+    else
+    {
+        _boundsBits &= ~BOUNDS_X_PERCENTAGE_BIT;
+        _bounds.x = x;
+    }
+}
+
 bool Control::isXPercentage() const
 {
     return (_boundsBits & BOUNDS_X_PERCENTAGE_BIT) != 0;
@@ -327,20 +332,25 @@ void Control::setY(float y, bool percentage)
 {
     if (_relativeBounds.y != y || percentage != ((_boundsBits & BOUNDS_Y_PERCENTAGE_BIT) != 0))
     {
-        _relativeBounds.y = y;
-        if (percentage)
-        {
-            _boundsBits |= BOUNDS_Y_PERCENTAGE_BIT;
-        }
-        else
-        {
-            _boundsBits &= ~BOUNDS_Y_PERCENTAGE_BIT;
-            _bounds.y = y;
-        }
+        setYInternal(y, percentage);
         setDirty(DIRTY_BOUNDS);
     }
 }
 
+void Control::setYInternal(float y, bool percentage)
+{
+    _relativeBounds.y = y;
+    if (percentage)
+    {
+        _boundsBits |= BOUNDS_Y_PERCENTAGE_BIT;
+    }
+    else
+    {
+        _boundsBits &= ~BOUNDS_Y_PERCENTAGE_BIT;
+        _bounds.y = y;
+    }
+}
+
 bool Control::isYPercentage() const
 {
     return (_boundsBits & BOUNDS_Y_PERCENTAGE_BIT) != 0;
@@ -357,20 +367,25 @@ void Control::setWidth(float width, bool percentage)
 
     if (_relativeBounds.width != width || percentage != ((_boundsBits & BOUNDS_WIDTH_PERCENTAGE_BIT) != 0))
     {
-        _relativeBounds.width = width;
-        if (percentage)
-        {
-            _boundsBits |= BOUNDS_WIDTH_PERCENTAGE_BIT;
-        }
-        else
-        {
-            _boundsBits &= ~BOUNDS_WIDTH_PERCENTAGE_BIT;
-            _bounds.width = width;
-        }
+        setWidthInternal(width, percentage);
         setDirty(DIRTY_BOUNDS);
     }
 }
 
+void Control::setWidthInternal(float width, bool percentage)
+{
+    _relativeBounds.width = width;
+    if (percentage)
+    {
+        _boundsBits |= BOUNDS_WIDTH_PERCENTAGE_BIT;
+    }
+    else
+    {
+        _boundsBits &= ~BOUNDS_WIDTH_PERCENTAGE_BIT;
+        _bounds.width = width;
+    }
+}
+
 bool Control::isWidthPercentage() const
 {
     return (_boundsBits & BOUNDS_WIDTH_PERCENTAGE_BIT) != 0;
@@ -387,20 +402,25 @@ void Control::setHeight(float height, bool percentage)
 
     if (_relativeBounds.height != height || percentage != ((_boundsBits & BOUNDS_HEIGHT_PERCENTAGE_BIT) != 0))
     {
-        _relativeBounds.height = height;
-        if (percentage)
-        {
-            _boundsBits |= BOUNDS_HEIGHT_PERCENTAGE_BIT;
-        }
-        else
-        {
-            _boundsBits &= ~BOUNDS_HEIGHT_PERCENTAGE_BIT;
-            _bounds.height = height;
-        }
+        setHeightInternal(height, percentage);
         setDirty(DIRTY_BOUNDS);
     }
 }
 
+void Control::setHeightInternal(float height, bool percentage)
+{
+    _relativeBounds.height = height;
+    if (percentage)
+    {
+        _boundsBits |= BOUNDS_HEIGHT_PERCENTAGE_BIT;
+    }
+    else
+    {
+        _boundsBits &= ~BOUNDS_HEIGHT_PERCENTAGE_BIT;
+        _bounds.height = height;
+    }
+}
+
 bool Control::isHeightPercentage() const
 {
     return (_boundsBits & BOUNDS_HEIGHT_PERCENTAGE_BIT) != 0;
@@ -480,7 +500,9 @@ void Control::setVisible(bool visible)
     {
         _visible = visible;
 
-        if (!_visible)
+        if (_visible)
+            setDirty(DIRTY_BOUNDS);
+        else
             Form::controlDisabled(this);
     }
 }
@@ -538,6 +560,10 @@ void Control::setOpacity(float opacity, unsigned char states)
         if (overlays[i])
             overlays[i]->setOpacity(opacity);
     }
+
+    // Opacity is currently pre-multiplied in updateBounds, so we need to 
+    // dirty our bounds when it changes.
+    setDirty(DIRTY_BOUNDS);
 }
 
 float Control::getOpacity(State state) const
@@ -555,6 +581,10 @@ void Control::setEnabled(bool enabled)
 
         if (!_enabled)
             Form::controlDisabled(this);
+
+        // The enabled flag modifies control state which can be themed, so we need to assume
+        // that the bounds may change as a result of this.
+        setDirty(DIRTY_BOUNDS);
     }
 }
 
@@ -1091,16 +1121,15 @@ void Control::setDirty(int bits)
 {
     _dirtyBits |= bits;
 
-    // Propogate dirty bits to parent if needed
-    if (_parent)
+    if (bits & DIRTY_BOUNDS)
     {
-        if (bits & DIRTY_BOUNDS)
+        // When a container's bounds are dirty, all children controls also need
+        // to be dirtied so that their absolute bounds are updated correctly.
+        if (isContainer())
         {
-            // If our parent's size depends on children size, mark it dirty
-            if (_parent->_autoSize != AUTO_SIZE_NONE)
-            {
-                _parent->setDirty(DIRTY_BOUNDS);
-            }
+            Container* container = static_cast<Container*>(this);
+            for (unsigned int i = 0, count = container->getControlCount(); i < count; ++i)
+                container->getControl(i)->setDirty(DIRTY_BOUNDS);
         }
     }
 }
@@ -1110,12 +1139,16 @@ bool Control::isDirty(int bit) const
     return (_dirtyBits & bit) == bit;
 }
 
+void Control::update(float elapsedTime)
+{
+}
+
 void Control::updateBounds(const Vector2& offset)
 {
-    Game* game = Game::getInstance();
+    // Clear our dirty bounds bit
+    _dirtyBits &= ~DIRTY_BOUNDS;
 
-    // Store old bounds so we can determine if our bounds change as a result of this layout
-    Rectangle oldBounds(_bounds);
+    Game* game = Game::getInstance();
 
     const Rectangle parentAbsoluteBounds = _parent ? _parent->_viewportBounds : Rectangle(0, 0, game->getViewport().width, game->getViewport().height);
     const Rectangle parentAbsoluteClip = _parent ? _parent->_viewportClipBounds : parentAbsoluteBounds;
@@ -1155,29 +1188,29 @@ void Control::updateBounds(const Vector2& offset)
         // Vertical alignment
         if ((_alignment & Control::ALIGN_BOTTOM) == Control::ALIGN_BOTTOM)
         {
-            setY(clipHeight - _bounds.height - margin.bottom);
+            setYInternal(clipHeight - _bounds.height - margin.bottom);
         }
         else if ((_alignment & Control::ALIGN_VCENTER) == Control::ALIGN_VCENTER)
         {
-            setY(clipHeight * 0.5f - _bounds.height * 0.5f);
+            setYInternal(clipHeight * 0.5f - _bounds.height * 0.5f);
         }
         else if ((_alignment & Control::ALIGN_TOP) == Control::ALIGN_TOP)
         {
-            setY(margin.top);
+            setYInternal(margin.top);
         }
 
         // Horizontal alignment
         if ((_alignment & Control::ALIGN_RIGHT) == Control::ALIGN_RIGHT)
         {
-            setX(clipWidth - _bounds.width - margin.right);
+            setXInternal(clipWidth - _bounds.width - margin.right);
         }
         else if ((_alignment & Control::ALIGN_HCENTER) == Control::ALIGN_HCENTER)
         {
-            setX(clipWidth * 0.5f - _bounds.width * 0.5f);
+            setXInternal(clipWidth * 0.5f - _bounds.width * 0.5f);
         }
         else if ((_alignment & Control::ALIGN_LEFT) == Control::ALIGN_LEFT)
         {
-            setX(margin.left);
+            setXInternal(margin.left);
         }
     }
 
@@ -1227,18 +1260,6 @@ void Control::updateBounds(const Vector2& offset)
     _opacity = getOpacity(getState());
     if (_parent)
         _opacity *= _parent->_opacity;
-
-    // Clear our dirty bounds bit
-    _dirtyBits &= ~DIRTY_BOUNDS;
-
-    // If our bounds have changed and our parent's size depends on ours,
-    // then dirty our parent bounds it's recomputed on the next layout pass
-    bool boundsChanged = _bounds != oldBounds;
-    if (boundsChanged && _parent && _parent->_autoSize != AUTO_SIZE_NONE && 
-        !((_boundsBits & BOUNDS_WIDTH_PERCENTAGE_BIT) && (_boundsBits & BOUNDS_HEIGHT_PERCENTAGE_BIT)))
-    {
-        _parent->setDirty(DIRTY_BOUNDS);
-    }
 }
 
 void Control::startBatch(Form* form, SpriteBatch* batch)

+ 71 - 8
gameplay/src/Control.h

@@ -27,10 +27,6 @@ class Control : public Ref, public AnimationTarget, public ScriptTarget
 {
     friend class Form;
     friend class Container;
-    friend class Layout;
-    friend class AbsoluteLayout;
-    friend class VerticalLayout;
-    friend class FlowLayout;
 
 public:
 
@@ -312,7 +308,9 @@ public:
      * 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.
+     * Explicitly setting the width of a control clears the AUTO_SIZE_WIDTH bit, if set.
+     *
+     * @param width The new width.
      * @param percentage True if the value should be interpreted as a percentage (0-1), false if it is regular number.
      */
     void setWidth(float width, bool percentage = false);
@@ -337,7 +335,9 @@ public:
      * 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.
+     * Explicitly setting the height of a control clears the AUTO_SIZE_HEIGHT bit, if set.
+     *
+     * @param height The new height.
      * @param percentage True if the value should be interpreted as a percentage (0-1), false if it is regular number.
      */
     void setHeight(float height, bool percentage = false);
@@ -368,8 +368,10 @@ public:
      * Setting percetage values is not supported with this method, use setWidth
      * and setHeight instead.
      *
-     * @param width The width.
-     * @param height The height.
+     * Explicitly setting the size of a control clears the AutoSize bits, if set.
+     *
+     * @param width The new width.
+     * @param height The new height.
      */
     void setSize(float width, float height);
 
@@ -389,6 +391,8 @@ public:
      * Setting percetage values is not supported with this method, use setX,
      * setY, setWidth and setHeight instead.
      *
+     * Explicitly setting the bounds of a control clears the AutoSize bits, if set.
+     *
      * @param bounds The new bounds to set.
      */
     void setBounds(const Rectangle& bounds);
@@ -991,6 +995,52 @@ protected:
      */
     Control& operator=(const Control&);
 
+    /**
+     * Internal method for setting the X position of the control.
+     *
+     * This method is meant for internal use by the Control or descendent classes
+     * who need to modify the position of the control during bounds computation.
+     *
+     * @see setX(float, bool)
+     */
+    void setXInternal(float x, bool percentage = false);
+
+    /**
+     * Internal method for setting the Y position of the control.
+     *
+     * This method is meant for internal use by the Control or descendent classes
+     * who need to modify the position of the control during bounds computation.
+     *
+     * @see setY(float, bool)
+     */
+    void setYInternal(float x, bool percentage = false);
+
+    /**
+     * Internal method for setting the width of the control.
+     *
+     * The width of the control is set without modifying the existing AutoSize
+     * rules for the control.
+     *
+     * This method is meant for internal use by the Control or descendent classes
+     * who need to modify the size of the control during bounds computation.
+     *
+     * @see setWidth(float, bool)
+     */
+    void setWidthInternal(float width, bool percentage = false);
+
+    /**
+     * Internal method for setting the height of the control.
+     *
+     * The height of the control is set without modifying the existing AutoSize
+     * rules for the control.
+     *
+     * This method is meant for internal use by the Control or descendent classes
+     * who need to modify the size of the control during bounds computation.
+     *
+     * @see setHeight(float, bool)
+     */
+    void setHeightInternal(float height, bool percentage = false);
+
     /**
      * Get the overlay type corresponding to this control's current state.
      *
@@ -1049,9 +1099,22 @@ protected:
      */
     virtual bool gamepadEvent(Gamepad::GamepadEvent evt, Gamepad* gamepad, unsigned int analogIndex);
 
+    /**
+     * Called each frame to update this control and its children.
+     *
+     * Any logic that must be performed every frame should be implemented here,
+     * such as custom animation or interpolation that depends on time.
+     * Layout logic should not be implemented here, but in updateBounds instead.
+     *
+     * @param elapsedTime Time since last frame.
+     */
+    virtual void update(float elapsedTime);
+
     /**
      * Updates the bounds for this control and its children.
      *
+     * Child controls that need to customize their bounds calculation should override this method.
+     *
      * @param offset Positioning offset to add to the control's position (most often used for scrolling).
      */
     virtual void updateBounds(const Vector2& offset);

+ 1 - 2
gameplay/src/FlowLayout.cpp

@@ -52,7 +52,7 @@ void FlowLayout::setSpacing(int horizontalSpacing, int verticalSpacing)
     _verticalSpacing = verticalSpacing;
 }
 
-void FlowLayout::update(const Container* container, const Vector2& offset)
+void FlowLayout::update(const Container* container)
 {
     GP_ASSERT(container);
     const Rectangle& containerBounds = container->getBounds();
@@ -92,7 +92,6 @@ void FlowLayout::update(const Container* container, const Vector2& offset)
         yPosition = rowY + margin.top;
 
         control->setPosition(xPosition, yPosition);
-        control->updateBounds(offset);
 
         xPosition += bounds.width + margin.right + _horizontalSpacing;
 

+ 1 - 2
gameplay/src/FlowLayout.h

@@ -53,9 +53,8 @@ protected:
      * Update the controls contained by the specified container.
      *
      * @param container The container to update.
-     * @param offset The offset position.
      */
-    void update(const Container* container, const Vector2& offset);
+    void update(const Container* container);
 
     /**
      * Horizontal spacing between controls.

+ 14 - 2
gameplay/src/Form.cpp

@@ -119,6 +119,10 @@ void Form::initialize(const char* typeName, Theme::Style* style, Properties* pro
     Container::initialize(typeName, style, properties);
 
     __forms.push_back(this);
+
+    // After creation, update our bounds once so code that runs immediately after form
+    // creation has access to up-to-date bounds.
+    updateBounds(Vector2::zero());
 }
 
 Form* Form::getForm(const char* id)
@@ -180,12 +184,20 @@ static unsigned int nextPowerOfTwo(unsigned int v)
 }
 
 void Form::update(float elapsedTime)
+{
+    Container::update(elapsedTime);
+
+    // Update our bounds if needed
+    updateBounds(Vector2::zero());
+}
+
+void Form::updateBounds(const Vector2& offset)
 {
     // Two pass bounds update:
     // 1. First pass computes child/leaf control sizes.
     // 2. Second pass fits parent sizes to that of the children (and does relative sizing of children)
-    updateBounds(Vector2::zero());
-    //updateBounds(Vector2::zero());
+    Container::updateBounds(offset);
+    Container::updateBounds(offset);
 }
 
 void Form::startBatch(SpriteBatch* batch)

+ 6 - 1
gameplay/src/Form.h

@@ -102,10 +102,15 @@ public:
     void setNode(Node* node);
 
     /**
-     * Updates each control within this form, and positions them according to its layout.
+     * @see Control::update
      */
     void update(float elapsedTime);
 
+    /**
+    * @see Control::updateBounds
+    */
+    void updateBounds(const Vector2& offset);
+
     /**
      * Draws this form.
      *

+ 2 - 4
gameplay/src/ImageControl.cpp

@@ -146,14 +146,12 @@ void ImageControl::updateBounds(const Vector2& offset)
     {
         if (_autoSize & AUTO_SIZE_WIDTH)
         {
-            setWidth(_batch->getSampler()->getTexture()->getWidth());
-            _autoSize = (AutoSize)(_autoSize | AUTO_SIZE_WIDTH);
+            setWidthInternal(_batch->getSampler()->getTexture()->getWidth());
         }
 
         if (_autoSize & AUTO_SIZE_HEIGHT)
         {
-            setHeight(_batch->getSampler()->getTexture()->getWidth());
-            _autoSize = (AutoSize)(_autoSize | AUTO_SIZE_HEIGHT);
+            setHeightInternal(_batch->getSampler()->getTexture()->getWidth());
         }
     }
 }

+ 2 - 4
gameplay/src/Label.cpp

@@ -90,13 +90,11 @@ void Label::updateBounds(const Vector2& offset)
         font->measureText(_text.c_str(), getFontSize(state), &w, &h);
         if (_autoSize & AUTO_SIZE_WIDTH)
         {
-            setWidth(w + getBorder(state).left + getBorder(state).right + getPadding().left + getPadding().right);
-            _autoSize = (AutoSize)(_autoSize | AUTO_SIZE_WIDTH);
+            setWidthInternal(w + getBorder(state).left + getBorder(state).right + getPadding().left + getPadding().right);
         }
         if (_autoSize & AUTO_SIZE_HEIGHT)
         {
-            setHeight(h + getBorder(state).top + getBorder(state).bottom + getPadding().top + getPadding().bottom);
-            _autoSize = (AutoSize)(_autoSize | AUTO_SIZE_HEIGHT);
+            setHeightInternal(h + getBorder(state).top + getBorder(state).bottom + getPadding().top + getPadding().bottom);
         }
     }
 }

+ 2 - 2
gameplay/src/Layout.h

@@ -64,9 +64,8 @@ protected:
      * Position, resize, and update the controls within a container.
      *
      * @param container The container to update.
-     * @param offset The update offset.
      */
-    virtual void update(const Container* container, const Vector2& offset) = 0;
+    virtual void update(const Container* container) = 0;
 
     /**
      * Touch callback on touch events.  Coordinates are given relative to the container's
@@ -80,6 +79,7 @@ protected:
      * @see Touch::TouchEvent
      */
     virtual bool touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
+
 };
 
 }

+ 2 - 4
gameplay/src/RadioButton.cpp

@@ -154,15 +154,13 @@ void RadioButton::updateBounds(const Vector2& offset)
         // Text-only width was already measured in Label::update - append image
         const Theme::Border& border = getBorder(state);
         const Theme::Border& padding = getPadding();
-        setHeight(std::max(_bounds.height, size.y + border.top + border.bottom + padding.top + padding.bottom));
-        _autoSize = (AutoSize)(_autoSize | AUTO_SIZE_HEIGHT);
+        setHeightInternal(std::max(_bounds.height, size.y + border.top + border.bottom + padding.top + padding.bottom));
     }
 
     if (_autoSize & AUTO_SIZE_WIDTH)
     {
         // Text-only width was already measured in Label::update - append image
-        setWidth(_viewportBounds.height + 5 + _bounds.width);
-        _autoSize = (AutoSize)(_autoSize | AUTO_SIZE_WIDTH);
+        setWidthInternal(_viewportBounds.height + 5 + _bounds.width);
     }
 
     _textBounds.x += _viewportBounds.height + 5;

+ 46 - 56
gameplay/src/Slider.cpp

@@ -54,6 +54,9 @@ void Slider::initialize(const char* typeName, Theme::Style* style, Properties* p
             _valueTextAlignment = Font::getJustify(properties->getString("valueTextAlignment"));
         }
     }
+
+    // Force value text to be updated
+    setValue(_value);
 }
 
 void Slider::setMin(float min)
@@ -93,8 +96,21 @@ float Slider::getValue() const
 
 void Slider::setValue(float value)
 {
-    float oldValue = _value;
-    _value = MATH_CLAMP(value, _min, _max);
+    value = MATH_CLAMP(value, _min, _max);
+
+    if (value != _value)
+    {
+        _value = value;
+        notifyListeners(Control::Listener::VALUE_CHANGED);
+    }
+
+    // Always update value text if it's visible
+    if (_valueTextVisible)
+    {
+        char s[32];
+        sprintf(s, "%.*f", _valueTextPrecision, _value);
+        _valueText = s;
+    }
 }
 
 void Slider::setValueTextVisible(bool valueTextVisible)
@@ -164,19 +180,14 @@ void Slider::updateValue(int x, int y)
         markerPosition = 0.0f;
     }
 
-    float oldValue = _value;
-    _value = (markerPosition * (_max - _min)) + _min;
+    float value = (markerPosition * (_max - _min)) + _min;
     if (_step > 0.0f)
     {            
-        int numSteps = round(_value / _step);
-        _value = _step * numSteps;
+        int numSteps = round(value / _step);
+        value = _step * numSteps;
     }
 
-    // Call the callback if our value changed.
-    if (_value != oldValue)
-    {
-        notifyListeners(Control::Listener::VALUE_CHANGED);
-    }
+    setValue(value);
 }
 
 bool Slider::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
@@ -219,25 +230,15 @@ bool Slider::mouseEvent(Mouse::MouseEvent evt, int x, int y, int wheelDelta)
             if (hasFocus() && !isScrollable(_parent))
             {
                 float total = _max - _min;
-                float oldValue = _value;
-                _value += (total * SCROLLWHEEL_FRACTION) * wheelDelta;
-            
-                if (_value > _max)
-                    _value = _max;
-                else if (_value < _min)
-                    _value = _min;
+                float value = _value + (total * SCROLLWHEEL_FRACTION) * wheelDelta;
 
                 if (_step > 0.0f)
                 {            
-                    int numSteps = round(_value / _step);
-                    _value = _step * numSteps;
-                }
-
-                if (_value != oldValue)
-                {
-                    notifyListeners(Control::Listener::VALUE_CHANGED);
+                    int numSteps = round(value / _step);
+                    value = _step * numSteps;
                 }
 
+                setValue(value);
                 return true;
             }
             break;
@@ -283,22 +284,22 @@ bool Slider::keyEvent(Keyboard::KeyEvent evt, int key)
         case Keyboard::KEY_LEFT_ARROW:
             if (_step > 0.0f)
             {
-                _value = std::max(_value - _step, _min);
+                setValue(std::max(_value - _step, _min));
             }
             else
             {
-                _value = std::max(_value - (_max - _min) * MOVE_FRACTION, _min);
+                setValue(std::max(_value - (_max - _min) * MOVE_FRACTION, _min));
             }
             return true;
 
         case Keyboard::KEY_RIGHT_ARROW:
             if (_step > 0.0f)
             {
-                _value = std::min(_value + _step, _max);
+                setValue(std::min(_value + _step, _max));
             }
             else
             {
-                _value = std::min(_value + (_max - _min) * MOVE_FRACTION, _max);
+                setValue(std::min(_value + (_max - _min) * MOVE_FRACTION, _max));
             }
             return true;
         }
@@ -308,47 +309,37 @@ bool Slider::keyEvent(Keyboard::KeyEvent evt, int key)
     return Control::keyEvent(evt, key);
 }
 
-void Slider::updateBounds(const Vector2& offset)
+void Slider::update(float elapsedTime)
 {
-    Label::updateBounds(offset);
-
-    Control::State state = getState();
-
-    _minImage = getImage("minCap", state);
-    _maxImage = getImage("maxCap", state);
-    _markerImage = getImage("marker", state);
-    _trackImage = getImage("track", state);
-
-    char s[32];
-    sprintf(s, "%.*f", _valueTextPrecision, _value);
-    _valueText = s;
+    Label::update(elapsedTime);
 
     if (_delta != 0.0f)
     {
-        float oldValue = _value;
         float total = _max - _min;
 
         if (_step > 0.0f)
         {
             _gamepadValue += (total * MOVE_FRACTION) * _delta;
             int numSteps = round(_gamepadValue / _step);
-            _value = _step * numSteps;
+            setValue(_step * numSteps);
         }
         else
         {
-            _value += (total * MOVE_FRACTION) * _delta;
+            setValue(_value + (total * MOVE_FRACTION) * _delta);
         }
+    }
+}
 
-        if (_value > _max)
-            _value = _max;
-        else if (_value < _min)
-            _value = _min;
+void Slider::updateBounds(const Vector2& offset)
+{
+    Label::updateBounds(offset);
 
-        if (_value != oldValue)
-        {
-            notifyListeners(Control::Listener::VALUE_CHANGED);
-        }
-    }
+    Control::State state = getState();
+
+    _minImage = getImage("minCap", state);
+    _maxImage = getImage("maxCap", state);
+    _markerImage = getImage("marker", state);
+    _trackImage = getImage("track", state);
 
     // Compute height of track (max of track, min/max and marker
     _trackHeight = _minImage->getRegion().height;
@@ -361,8 +352,7 @@ void Slider::updateBounds(const Vector2& offset)
         float height = _bounds.height + _trackHeight;
         if (_valueTextVisible)
             height += getFontSize(state);
-        setHeight(height);
-        _autoSize = (AutoSize)(_autoSize | AUTO_SIZE_HEIGHT);
+        setHeightInternal(height);
     }
 }
 

+ 5 - 0
gameplay/src/Slider.h

@@ -232,6 +232,11 @@ protected:
      */
     unsigned int drawText(Form* form, const Rectangle& clip);
 
+    /**
+     * @see Control::update
+     */
+    void update(float elapsedTime);
+
     /**
      * @see Control::updateBounds
      */

+ 1 - 2
gameplay/src/VerticalLayout.cpp

@@ -42,7 +42,7 @@ void VerticalLayout::setSpacing(int spacing)
     _spacing = spacing;
 }
 
-void VerticalLayout::update(const Container* container, const Vector2& offset)
+void VerticalLayout::update(const Container* container)
 {
     GP_ASSERT(container);
 
@@ -81,7 +81,6 @@ void VerticalLayout::update(const Container* container, const Vector2& offset)
             yPosition += margin.top;
 
             control->setPosition(margin.left, yPosition);
-            control->updateBounds(offset);
 
             yPosition += bounds.height + margin.bottom + _spacing;
         }

+ 1 - 2
gameplay/src/VerticalLayout.h

@@ -75,9 +75,8 @@ protected:
      * the bottom-most edge of the container is reached.
      *
      * @param container The container to update.
-     * @param offset Positioning offset to add to the control's position.
      */
-    void update(const Container* container, const Vector2& offset);
+    void update(const Container* container);
 
     /**
      * Flag determining whether this layout will start laying out controls from the bottom of the container.