Pārlūkot izejas kodu

Merge pull request #925 from ablake/next

Enables interaction with UI forms using gamepads.
Sean Paul Taylor 12 gadi atpakaļ
vecāks
revīzija
cea4968d8c
56 mainītis faili ar 1756 papildinājumiem un 313 dzēšanām
  1. 1 1
      gameplay/src/AbsoluteLayout.cpp
  2. 8 0
      gameplay/src/AnimationClip.cpp
  3. 3 1
      gameplay/src/AnimationController.cpp
  4. 37 7
      gameplay/src/Button.cpp
  5. 7 0
      gameplay/src/Button.h
  6. 20 0
      gameplay/src/CheckBox.cpp
  7. 7 0
      gameplay/src/CheckBox.h
  8. 554 51
      gameplay/src/Container.cpp
  9. 54 4
      gameplay/src/Container.h
  10. 70 30
      gameplay/src/Control.cpp
  11. 37 17
      gameplay/src/Control.h
  12. 119 119
      gameplay/src/Form.cpp
  13. 7 0
      gameplay/src/Form.h
  14. 7 7
      gameplay/src/Game.cpp
  15. 6 4
      gameplay/src/Game.h
  16. 3 3
      gameplay/src/Gamepad.cpp
  17. 1 1
      gameplay/src/Image.h
  18. 2 0
      gameplay/src/ImageControl.h
  19. 6 8
      gameplay/src/Joystick.cpp
  20. 9 7
      gameplay/src/Label.cpp
  21. 9 0
      gameplay/src/Platform.cpp
  22. 10 2
      gameplay/src/Platform.h
  23. 5 3
      gameplay/src/PlatformBlackBerry.cpp
  24. 15 14
      gameplay/src/PlatformMacOSX.mm
  25. 8 12
      gameplay/src/PlatformWindows.cpp
  26. 24 3
      gameplay/src/RadioButton.cpp
  27. 7 0
      gameplay/src/RadioButton.h
  28. 1 1
      gameplay/src/ScriptController.cpp
  29. 3 2
      gameplay/src/ScriptController.h
  30. 146 11
      gameplay/src/Slider.cpp
  31. 25 0
      gameplay/src/Slider.h
  32. 5 3
      gameplay/src/TextBox.cpp
  33. 1 1
      gameplay/src/VerticalLayout.cpp
  34. 45 0
      gameplay/src/lua/lua_Button.cpp
  35. 1 0
      gameplay/src/lua/lua_Button.h
  36. 45 0
      gameplay/src/lua/lua_CheckBox.cpp
  37. 1 0
      gameplay/src/lua/lua_CheckBox.h
  38. 45 0
      gameplay/src/lua/lua_Container.cpp
  39. 1 0
      gameplay/src/lua/lua_Container.h
  40. 45 0
      gameplay/src/lua/lua_Control.cpp
  41. 1 0
      gameplay/src/lua/lua_Control.h
  42. 45 0
      gameplay/src/lua/lua_Form.cpp
  43. 1 0
      gameplay/src/lua/lua_Form.h
  44. 33 1
      gameplay/src/lua/lua_Game.cpp
  45. 45 0
      gameplay/src/lua/lua_ImageControl.cpp
  46. 1 0
      gameplay/src/lua/lua_ImageControl.h
  47. 45 0
      gameplay/src/lua/lua_Joystick.cpp
  48. 1 0
      gameplay/src/lua/lua_Joystick.h
  49. 45 0
      gameplay/src/lua/lua_Label.cpp
  50. 1 0
      gameplay/src/lua/lua_Label.h
  51. 45 0
      gameplay/src/lua/lua_RadioButton.cpp
  52. 1 0
      gameplay/src/lua/lua_RadioButton.h
  53. 45 0
      gameplay/src/lua/lua_Slider.cpp
  54. 1 0
      gameplay/src/lua/lua_Slider.h
  55. 45 0
      gameplay/src/lua/lua_TextBox.cpp
  56. 1 0
      gameplay/src/lua/lua_TextBox.h

+ 1 - 1
gameplay/src/AbsoluteLayout.cpp

@@ -41,7 +41,7 @@ void AbsoluteLayout::update(const Container* container, const Vector2& offset)
     GP_ASSERT(container);
 
     // An AbsoluteLayout does nothing to modify the layout of Controls.
-    std::vector<Control*> controls = container->getControls();
+    const std::vector<Control*>& controls = container->getControls();
     for (size_t i = 0, count = controls.size(); i < count; i++)
     {
         Control* control = controls[i];

+ 8 - 0
gameplay/src/AnimationClip.cpp

@@ -556,6 +556,8 @@ bool AnimationClip::update(float elapsedTime)
 
 void AnimationClip::onBegin()
 {
+    addRef();
+
     // Initialize animation to play.
     setClipStateBit(CLIP_IS_STARTED_BIT);
     if (_speed >= 0)
@@ -584,10 +586,14 @@ void AnimationClip::onBegin()
             listener++;
         }
     }
+
+    release();
 }
 
 void AnimationClip::onEnd()
 {
+    addRef();
+
     _blendWeight = 1.0f;
     resetClipStateBit(CLIP_ALL_BITS);
 
@@ -602,6 +608,8 @@ void AnimationClip::onEnd()
             listener++;
         }
     }
+
+    release();
 }
 
 bool AnimationClip::isClipStateBitSet(unsigned char bit) const

+ 3 - 1
gameplay/src/AnimationController.cpp

@@ -106,6 +106,7 @@ void AnimationController::update(float elapsedTime)
     {
         AnimationClip* clip = (*clipIter);
         GP_ASSERT(clip);
+        clip->addRef();
         if (clip->isClipStateBitSet(AnimationClip::CLIP_IS_RESTARTED_BIT))
         {   // If the CLIP_IS_RESTARTED_BIT is set, we should end the clip and 
             // move it from where it is in the running clips list to the back.
@@ -116,13 +117,14 @@ void AnimationController::update(float elapsedTime)
         }
         else if (clip->update(elapsedTime))
         {
-            SAFE_RELEASE(clip);
+            clip->release();
             clipIter = _runningClips.erase(clipIter);
         }
         else
         {
             clipIter++;
         }
+        clip->release();
     }
 
     Transform::resumeTransformChanged();

+ 37 - 7
gameplay/src/Button.cpp

@@ -50,11 +50,8 @@ bool Button::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contac
                 y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height)
             {
                 _contactIndex = (int) contactIndex;
-
                 setState(Control::ACTIVE);
-
-                notifyListeners(Listener::PRESS);
-
+                notifyListeners(Control::Listener::PRESS);
                 return _consumeInputEvents;
             }
             else
@@ -68,14 +65,13 @@ bool Button::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contac
         if (_contactIndex == (int) contactIndex)
         {
             _contactIndex = INVALID_CONTACT_INDEX;
-            notifyListeners(Listener::RELEASE);
+            notifyListeners(Control::Listener::RELEASE);
             if (!_parent->isScrolling() &&
                 x > _clipBounds.x && x <= _clipBounds.x + _clipBounds.width &&
                 y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height)
             {
                 setState(Control::FOCUS);
-
-                notifyListeners(Listener::CLICK);
+                notifyListeners(Control::Listener::CLICK);
             }
             else
             {
@@ -93,6 +89,40 @@ bool Button::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contac
     return false;
 }
 
+bool Button::gamepadEvent(Gamepad::GamepadEvent evt, Gamepad* gamepad, unsigned int analogIndex)
+{
+    switch (evt)
+    {
+    case Gamepad::BUTTON_EVENT:
+        if (_state == Control::FOCUS)
+        {
+            if (gamepad->isButtonDown(Gamepad::BUTTON_A) ||
+                gamepad->isButtonDown(Gamepad::BUTTON_X))
+            {
+                notifyListeners(Control::Listener::PRESS);
+                setState(Control::ACTIVE);
+                return _consumeInputEvents;
+            }
+        }
+        else if (_state == Control::ACTIVE)
+        {
+            if (!gamepad->isButtonDown(Gamepad::BUTTON_A) &&
+                !gamepad->isButtonDown(Gamepad::BUTTON_X))
+            {
+                notifyListeners(Control::Listener::RELEASE);
+                notifyListeners(Control::Listener::CLICK);
+                setState(Control::FOCUS);
+                return _consumeInputEvents;
+            }
+        }
+        break;
+    default:
+        break;
+    }
+
+    return false;
+}
+
 const char* Button::getType() const
 {
     return "button";

+ 7 - 0
gameplay/src/Button.h

@@ -85,6 +85,13 @@ protected:
      */
     bool touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
 
+    /**
+     * Gamepad callback on gamepad events.
+     *
+     * @see Control::gamepadEvent
+     */
+    virtual bool gamepadEvent(Gamepad::GamepadEvent evt, Gamepad* gamepad, unsigned int analogIndex);
+
     /**
      * @see Control::getType
      */

+ 20 - 0
gameplay/src/CheckBox.cpp

@@ -94,6 +94,26 @@ bool CheckBox::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int cont
     return Button::touchEvent(evt, x, y, contactIndex);
 }
 
+bool CheckBox::gamepadEvent(Gamepad::GamepadEvent evt, Gamepad* gamepad, unsigned int analogIndex)
+{
+    switch (evt)
+    {
+    case Gamepad::BUTTON_EVENT:
+        if (_state == Control::ACTIVE)
+        {
+            if (!gamepad->isButtonDown(Gamepad::BUTTON_A) &&
+                !gamepad->isButtonDown(Gamepad::BUTTON_X))
+            {
+                _checked = !_checked;
+                notifyListeners(Control::Listener::VALUE_CHANGED);   
+            }
+        }
+        break;
+    }
+
+    return Button::gamepadEvent(evt, gamepad, analogIndex);
+}
+
 void CheckBox::update(const Control* container, const Vector2& offset)
 {
     Label::update(container, offset);

+ 7 - 0
gameplay/src/CheckBox.h

@@ -131,6 +131,13 @@ protected:
      */
     bool touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
 
+    /**
+     * Gamepad callback on gamepad events.
+     *
+     * @see Control::gamepadEvent
+     */
+    bool gamepadEvent(Gamepad::GamepadEvent evt, Gamepad* gamepad, unsigned int analogIndex);
+
     /**
      * Called when a control's properties change.  Updates this control's internal rendering
      * properties, such as its text viewport.

+ 554 - 51
gameplay/src/Container.cpp

@@ -10,8 +10,8 @@
 #include "RadioButton.h"
 #include "Slider.h"
 #include "TextBox.h"
-#include "ImageControl.h"
 #include "Joystick.h"
+#include "ImageControl.h"
 #include "Game.h"
 
 namespace gameplay
@@ -23,6 +23,12 @@ static const long SCROLL_INERTIA_DELAY = 100L;
 static const float SCROLL_FRICTION_FACTOR = 5.0f;
 // Distance that must be scrolled before isScrolling() will return true, used e.g. to cancel button-click events.
 static const float SCROLL_THRESHOLD = 10.0f;
+// Distance a joystick must be pushed in order to trigger focus-change and/or scrolling.
+static const float JOYSTICK_THRESHOLD = 0.75f;
+// Scroll speed when using a DPad -- max scroll speed when using a joystick.
+static const float GAMEPAD_SCROLL_SPEED = 500.0f;
+// If the DPad or joystick is held down, this is the initial delay in milliseconds between focus change events.
+static const float FOCUS_CHANGE_REPEAT_DELAY = 300.0f;
 
 /**
  * Sort function for use with _controls.sort(), based on Z-Order.
@@ -43,7 +49,11 @@ Container::Container()
       _scrollingVelocity(Vector2::zero()), _scrollingFriction(1.0f),
       _scrollingRight(false), _scrollingDown(false),
       _scrollingMouseVertically(false), _scrollingMouseHorizontally(false),
-      _scrollBarOpacityClip(NULL), _zIndexDefault(0), _focusIndexDefault(0), _focusIndexMax(0), _totalWidth(0), _totalHeight(0),
+      _scrollBarOpacityClip(NULL), _zIndexDefault(0), _focusIndexDefault(0), _focusIndexMax(0),
+      _focusPressed(0), _selectButtonDown(false),
+      _lastFrameTime(0), _focusChangeRepeat(false),
+      _focusChangeStartTime(0), _focusChangeRepeatDelay(FOCUS_CHANGE_REPEAT_DELAY), _focusChangeCount(0),
+      _totalWidth(0), _totalHeight(0),
       _contactIndices(0), _initializedWithScroll(false)
 {
 }
@@ -182,20 +192,6 @@ void Container::addControls(Theme* theme, Properties* properties)
         {
             addControl(control);
             control->release();
-
-            if (control->getZIndex() == -1)
-            {
-                control->setZIndex(_zIndexDefault++);
-            }
-
-            if (control->getFocusIndex() == -1)
-            {
-                control->setFocusIndex(_focusIndexDefault++);
-            }
-
-            int focusIndex = control->getFocusIndex();
-            if (focusIndex > _focusIndexMax)
-                _focusIndexMax = focusIndex;
         }
 
         // Get the next control.
@@ -203,7 +199,7 @@ void Container::addControls(Theme* theme, Properties* properties)
     }
 
     // Sort controls by Z-Order.
-    std::sort(_controls.begin(), _controls.end(), &sortControlsByZOrder);
+    sortControls();
 }
 
 Layout* Container::getLayout()
@@ -220,11 +216,26 @@ unsigned int Container::addControl(Control* control)
         control->_parent->removeControl(control);
     }
 
+    if (control->getZIndex() == -1)
+    {
+        control->setZIndex(_zIndexDefault++);
+    }
+
+    if (control->getFocusIndex() == -1)
+    {
+        control->setFocusIndex(_focusIndexDefault++);
+    }
+
+    int focusIndex = control->getFocusIndex();
+    if (focusIndex > _focusIndexMax)
+        _focusIndexMax = focusIndex;
+
     if (control->_parent != this)
     {
         _controls.push_back(control);
         control->addRef();
         control->_parent = this;
+        sortControls();
         return (unsigned int)(_controls.size() - 1);
     }
     else
@@ -438,12 +449,6 @@ void Container::update(const Control* container, const Vector2& offset)
         _viewportClipBounds.width -= _scrollBarVertical->getRegion().width;
     }
 
-    // Sort controls by Z-Order.
-    if (_layout->getType() == Layout::LAYOUT_ABSOLUTE)
-    {
-        std::sort(_controls.begin(), _controls.end(), &sortControlsByZOrder);
-    }
-
     GP_ASSERT(_layout);
     if (_scroll != SCROLL_NONE)
     {
@@ -623,40 +628,524 @@ bool Container::keyEvent(Keyboard::KeyEvent evt, int key)
             continue;
         }
 
-        if (control->getState() == Control::FOCUS)
+        if (control->getState() == Control::FOCUS && control->keyEvent(evt, key))
         {
-            if (control->keyEvent(evt, key))
+            release();
+            return true;
+        }
+    }
+
+    // If we made it this far, no control handled the event.
+    if (evt == Keyboard::KEY_CHAR && key == Keyboard::KEY_TAB)
+    {
+        moveFocus(NEXT);
+        return _consumeInputEvents;
+    }
+
+    release();
+    return false;
+}
+
+void Container::guaranteeFocus(Control* inFocus)
+{
+    std::vector<Control*>::const_iterator it;
+    for (it = _controls.begin(); it < _controls.end(); it++)
+    {
+        Control* control = *it;
+        GP_ASSERT(control);
+        if (control == inFocus)
+            continue;
+
+        if (control->isContainer())
+        {
+            ((Container*)control)->guaranteeFocus(inFocus);
+        }
+        else if (control->getState() == Control::FOCUS)
+        {
+            control->setState(NORMAL);
+            return;
+        }
+    }
+}
+
+bool Container::moveFocus(Direction direction, Control* outsideControl)
+{
+    _direction = direction;
+
+    Control* start = outsideControl;
+    if (!start)
+    {
+        std::vector<Control*>::const_iterator it;
+        for (it = _controls.begin(); it < _controls.end(); it++)
+        {
+            Control* control = *it;
+            GP_ASSERT(control);
+            if (control->getState() == Control::FOCUS)
             {
-                release();
+                start = control;
+                break;
+            }
+        }
+    }
+
+    int focusIndex = 0;
+    Control* next = NULL;
+    if (start)
+    {
+        const Rectangle& startBounds = start->getAbsoluteBounds();
+        Vector2 vStart, vNext;
+        float distance = FLT_MAX;
+        start->setState(Control::NORMAL);
+
+        switch(direction)
+        {
+        case UP:
+            vStart.set(startBounds.x + startBounds.width * 0.5f,
+                        startBounds.y);
+            break;
+        case DOWN:
+            vStart.set(startBounds.x + startBounds.width * 0.5f,
+                        startBounds.bottom());
+            break;
+        case LEFT:
+            vStart.set(startBounds.x,
+                        startBounds.y + startBounds.height * 0.5f);
+            break;
+        case RIGHT:
+            vStart.set(startBounds.right(),
+                        startBounds.y + startBounds.height * 0.5f);
+            break;
+        case NEXT:
+            break;
+        }
+
+        if (direction != NEXT)
+        {
+            std::vector<Control*>::const_iterator itt;
+            for (itt = _controls.begin(); itt < _controls.end(); itt++)
+            {
+                Control* nextControl = *itt;
+
+                if (nextControl == start || nextControl->getFocusIndex() < 0 ||
+                    !nextControl->isEnabled() || !nextControl->isVisible())
+                {
+                    // Control is not focusable.
+                    continue;
+                }
+
+                const Rectangle& nextBounds = nextControl->getAbsoluteBounds();
+                switch(direction)
+                {
+                case UP:
+                    vNext.set(nextBounds.x + nextBounds.width * 0.5f,
+                              nextBounds.bottom());
+                    if (vNext.y > vStart.y) continue;
+                    break;
+                case DOWN:
+                    vNext.set(nextBounds.x + nextBounds.width * 0.5f,
+                              nextBounds.y);
+                    if (vNext.y < vStart.y) continue;
+                    break;
+                case LEFT:
+                    vNext.set(nextBounds.right(),
+                              nextBounds.y + nextBounds.height * 0.5f);
+                    if (vNext.x > vStart.x) continue;
+                    break;
+                case RIGHT:
+                    vNext.set(nextBounds.x,
+                              nextBounds.y + nextBounds.height * 0.5f);
+                    if (vNext.x < vStart.x) continue;
+                    break;
+                }
+
+                float nextDistance = vStart.distance(vNext);
+                if (abs(nextDistance) < distance)
+                {
+                    distance = nextDistance;
+                    next = nextControl;
+                }
+            }
+        }
+
+        if (!next)
+        {
+            // Check for controls in the given direction in our parent container.
+            if (!outsideControl && _parent && _parent->moveFocus(direction, start))
+            {
+                setState(NORMAL);
+                _focusChangeRepeat = false;
                 return true;
             }
-            else if (evt == Keyboard::KEY_CHAR && key == Keyboard::KEY_TAB)
+            
+            // No control is in the given direction.  Move to the next control in the focus order.
+            int focusDelta;
+            switch(direction)
+            {
+            case UP:
+            case LEFT:
+                focusDelta = -1;
+                break;
+            case DOWN:
+            case RIGHT:
+            case NEXT:
+                focusDelta = 1;
+                break;
+            }
+
+            // Find the index to search for.
+            if (outsideControl)
+            {
+                focusIndex = outsideControl->_parent->getFocusIndex() + focusDelta;
+            }
+            else
+            {
+                focusIndex = start->getFocusIndex() + focusDelta;
+            }
+
+            if (focusIndex > _focusIndexMax)
+            {
+                focusIndex = 0;
+            }
+            else if (focusIndex < 0)
+            {
+                focusIndex = _focusIndexMax;
+            }
+        }
+    }
+
+    if (!next)
+    {
+        std::vector<Control*>::const_iterator itt;
+        for (itt = _controls.begin(); itt < _controls.end(); itt++)
+        {
+            Control* nextControl = *itt;
+            if (nextControl->getFocusIndex() == focusIndex)
+            {
+                next = nextControl;
+            }
+        }
+    }
+
+    // If we haven't found next by now, then there are no focusable controls in this container.
+    if (next)
+    {
+        next->setState(Control::FOCUS);
+        _dirty = true;
+
+        if (next->isContainer())
+        {
+            if (((Container*)next)->moveFocus(direction, start))
             {
-                // Shift focus to next control.
-                int focusIndex = control->getFocusIndex() + 1; // Index to search for.
-                if (focusIndex > _focusIndexMax)
+                _focusChangeRepeat = false;
+                return true;
+            }
+        }
+
+        // If the next control is not fully visible, scroll the container so that it is.
+        const Rectangle& bounds = next->getBounds();
+        if (bounds.x < _scrollPosition.x)
+        {
+            // Control is to the left of the scrolled viewport.
+            _scrollPosition.x = -bounds.x;
+        }
+        else if (bounds.x + bounds.width > _scrollPosition.x + _viewportBounds.width)
+        {
+            // Control is off to the right.
+            _scrollPosition.x = -(bounds.x + bounds.width - _viewportBounds.width);
+        }
+
+        if (bounds.y < _viewportBounds.y - _scrollPosition.y)
+        {
+            // Control is above the viewport.
+            _scrollPosition.y = -bounds.y;
+        }
+        else if (bounds.y + bounds.height > _viewportBounds.height - _scrollPosition.y)
+        {
+            // Control is below the viewport.
+            _scrollPosition.y = -(bounds.y + bounds.height - _viewportBounds.height);
+        }
+
+        if (outsideControl && outsideControl->_parent)
+        {
+            _focusPressed = outsideControl->_parent->_focusPressed;
+            _focusChangeCount = outsideControl->_parent->_focusChangeCount;
+            _focusChangeRepeatDelay = outsideControl->_parent->_focusChangeRepeatDelay;
+            outsideControl->_parent->guaranteeFocus(next);
+        }
+
+        _focusChangeStartTime = Game::getAbsoluteTime();
+        _focusChangeRepeat = true;
+        addRef();
+        Game::getInstance()->schedule(_focusChangeRepeatDelay, this);
+
+        return true;
+    }
+
+    return false;
+}
+
+void Container::timeEvent(long timeDiff, void* cookie)
+{
+    double time = Game::getAbsoluteTime();
+    if (_focusChangeRepeat && _state == FOCUS && _focusPressed &&
+        abs(time - timeDiff - _focusChangeRepeatDelay - _focusChangeStartTime) < 50)
+    {
+        ++_focusChangeCount;
+        if (_focusChangeCount == 5)
+        {
+            _focusChangeRepeatDelay *= 0.5;
+        }
+        moveFocus(_direction);
+    }
+    else
+    {
+        _focusChangeCount = 0;
+        _focusChangeRepeatDelay = FOCUS_CHANGE_REPEAT_DELAY;
+    }
+
+    release();
+}
+
+void Container::startScrolling(float x, float y, bool resetTime)
+{
+    _scrollingVelocity.set(-x, y);
+    _scrolling = true;
+    _scrollBarOpacity = 1.0f;
+    _dirty = true;
+
+    if (_scrollBarOpacityClip && _scrollBarOpacityClip->isPlaying())
+    {
+        _scrollBarOpacityClip->stop();
+        _scrollBarOpacityClip = NULL;
+    }
+
+    if (resetTime)
+    {
+        _lastFrameTime = Game::getGameTime();
+    }
+}
+
+void Container::stopScrolling()
+{
+    _scrollingVelocity.set(0, 0);
+    _scrolling = false;
+    _dirty = true;
+}
+
+bool Container::gamepadEvent(Gamepad::GamepadEvent evt, Gamepad* gamepad, unsigned int analogIndex)
+{
+    addRef();
+
+    bool eventConsumed = false;
+
+    // Pass the event to any control that is active or in focus.
+    std::vector<Control*>::const_iterator it;
+    for (it = _controls.begin(); it < _controls.end(); it++)
+    {
+        Control* control = *it;
+        GP_ASSERT(control);
+        if (control->getState() == Control::FOCUS || control->getState() == Control::ACTIVE)
+        {
+            eventConsumed |= control->gamepadEvent(evt, gamepad, analogIndex);
+            break;
+        }
+    }
+
+    // First check if a selection button is down.
+    if (!_selectButtonDown)
+    {
+        if (gamepad->isButtonDown(Gamepad::BUTTON_A) ||
+            gamepad->isButtonDown(Gamepad::BUTTON_X))
+        {
+            _selectButtonDown = true;
+            _focusChangeRepeat = false;
+            eventConsumed |= _consumeInputEvents;
+        }
+    }
+    else
+    {
+        if (!gamepad->isButtonDown(Gamepad::BUTTON_A) &&
+            !gamepad->isButtonDown(Gamepad::BUTTON_X))
+        {
+            _selectButtonDown = false;
+        }
+    }
+
+    Vector2 joystick;
+    gamepad->getJoystickValues(analogIndex, &joystick);
+
+    // Don't allow focus changes or scrolling while a selection button is down.
+    if (!_selectButtonDown && !eventConsumed)
+    {
+        switch (evt)
+        {
+            case Gamepad::BUTTON_EVENT:
+            {
+                // Shift focus forward or backward when the DPad is used.
+                if (!(_focusPressed & DOWN) &&
+                    gamepad->isButtonDown(Gamepad::BUTTON_DOWN))
                 {
-                    focusIndex = 0;
+                    _focusPressed |= DOWN;
+                    eventConsumed |= _consumeInputEvents;
+                    if (moveFocus(DOWN))
+                        break;
+                    else
+                        startScrolling(0, -GAMEPAD_SCROLL_SPEED);
+                }
+                    
+                if (!(_focusPressed & RIGHT) &&
+                    gamepad->isButtonDown(Gamepad::BUTTON_RIGHT))
+                {
+                    _focusPressed |= RIGHT;
+                    eventConsumed |= _consumeInputEvents;
+                    if (moveFocus(RIGHT))
+                        break;
+                    else
+                        startScrolling(GAMEPAD_SCROLL_SPEED, 0);
                 }
-                control->setState(Control::NORMAL);
 
-                std::vector<Control*>::const_iterator itt;
-                for (itt = _controls.begin(); itt < _controls.end(); itt++)
+                if (!(_focusPressed & UP) &&
+                    gamepad->isButtonDown(Gamepad::BUTTON_UP))
+                {
+                    _focusPressed |= UP;
+                    eventConsumed |= _consumeInputEvents;
+                    if (moveFocus(UP))
+                        break;
+                    else
+                        startScrolling(0, GAMEPAD_SCROLL_SPEED);
+                }
+
+                if (!(_focusPressed & LEFT) &&
+                    gamepad->isButtonDown(Gamepad::BUTTON_LEFT))
+                {
+                    _focusPressed |= LEFT;
+                    eventConsumed |= _consumeInputEvents;
+                    if (moveFocus(LEFT))
+                        break;
+                    else
+                        startScrolling(-GAMEPAD_SCROLL_SPEED, 0);
+                }
+                break;
+            }
+            case Gamepad::JOYSTICK_EVENT:
+            {
+                switch (analogIndex)
                 {
-                    Control* nextControl = *itt;
-                    if (nextControl->getFocusIndex() == focusIndex)
+                case 0:
+                    // The left analog stick can be used in the same way as the DPad.
+                    eventConsumed |= _consumeInputEvents;
+                    if (!(_focusPressed & RIGHT) &&
+                        joystick.x > JOYSTICK_THRESHOLD)
+                    {
+                        _focusPressed |= RIGHT;
+                        if (moveFocus(RIGHT))
+                            break;
+                        else
+                            startScrolling(GAMEPAD_SCROLL_SPEED * joystick.x, 0);
+                    }
+
+                    if (!(_focusPressed & DOWN) &&
+                        joystick.y < -JOYSTICK_THRESHOLD)
+                    {
+                        _focusPressed |= DOWN;
+                        if (moveFocus(DOWN))
+                            break;
+                        else
+                            startScrolling(0, GAMEPAD_SCROLL_SPEED * joystick.y);
+                    }
+
+                    if (!(_focusPressed & LEFT) &&
+                        joystick.x < -JOYSTICK_THRESHOLD)
+                    {
+                        _focusPressed |= LEFT;
+                        if (moveFocus(LEFT))
+                            break;
+                        else
+                            startScrolling(GAMEPAD_SCROLL_SPEED * joystick.x, 0);
+                    }
+                        
+                    if (!(_focusPressed & UP) &&
+                        joystick.y > JOYSTICK_THRESHOLD)
+                    {
+                        _focusPressed |= UP;
+                        if (moveFocus(UP))
+                            break;
+                        else
+                            startScrolling(0, GAMEPAD_SCROLL_SPEED * joystick.y);
+                    }
+                    break;
+
+                case 1:
+                    // The right analog stick can be used to scroll.
+                    if (_scroll != SCROLL_NONE)
                     {
-                        nextControl->setState(Control::FOCUS);
+                        if (_scrolling)
+                        {
+                            if (joystick.isZero())
+                            {
+                                stopScrolling();
+                            }
+                            else
+                            {
+                                startScrolling(GAMEPAD_SCROLL_SPEED * joystick.x, GAMEPAD_SCROLL_SPEED * joystick.y, false);
+                            }
+                        }
+                        else
+                        {
+                            startScrolling(GAMEPAD_SCROLL_SPEED * joystick.x, GAMEPAD_SCROLL_SPEED * joystick.y);
+                        }
                         release();
                         return _consumeInputEvents;
                     }
+                    break;
                 }
             }
         }
     }
 
+    if ((evt == Gamepad::BUTTON_EVENT || evt == Gamepad::JOYSTICK_EVENT) &&
+        analogIndex == 0)
+    {
+        if ((_focusPressed & DOWN) &&
+            !gamepad->isButtonDown(Gamepad::BUTTON_DOWN) &&
+            joystick.y > -JOYSTICK_THRESHOLD)
+        {
+            _focusPressed &= ~DOWN;
+            eventConsumed |= _consumeInputEvents;
+        }
+
+        if ((_focusPressed & RIGHT) &&
+            !gamepad->isButtonDown(Gamepad::BUTTON_RIGHT) &&
+            joystick.x < JOYSTICK_THRESHOLD)
+        {
+            _focusPressed &= ~RIGHT;
+            eventConsumed |= _consumeInputEvents;
+        }
+    
+        if ((_focusPressed & UP) &&
+            !gamepad->isButtonDown(Gamepad::BUTTON_UP) &&
+            joystick.y < JOYSTICK_THRESHOLD)
+        {
+            _focusPressed &= ~UP;
+            eventConsumed |= _consumeInputEvents;
+        }
+
+        if ((_focusPressed & LEFT) &&
+            !gamepad->isButtonDown(Gamepad::BUTTON_LEFT) &&
+            joystick.x > -JOYSTICK_THRESHOLD)
+        {
+            _focusPressed &= ~LEFT;
+            eventConsumed |= _consumeInputEvents;
+        }
+    }
+
+    if (!_focusPressed && _scrolling)
+    {
+        stopScrolling();
+    }
+
     release();
-    return false;
+    return eventConsumed;
 }
 
 bool Container::isContainer() const
@@ -700,11 +1189,14 @@ void Container::updateScroll()
         _layout->update(this, _scrollPosition);
     }
 
-    // Update Time.
-    static double lastFrameTime = Game::getGameTime();
+    // Update time.
+    if (!_lastFrameTime)
+    {
+        _lastFrameTime = Game::getGameTime();
+    }
     double frameTime = Game::getGameTime();
-    float elapsedTime = (float)(frameTime - lastFrameTime);
-    lastFrameTime = frameTime;
+    float elapsedTime = (float)(frameTime - _lastFrameTime);
+    _lastFrameTime = frameTime;
 
     const Theme::Border& containerBorder = getBorder(_state);
     const Theme::Padding& containerPadding = getPadding();
@@ -737,7 +1229,7 @@ void Container::updateScroll()
     float clipHeight = _bounds.height - containerBorder.top - containerBorder.bottom - containerPadding.top - containerPadding.bottom - hHeight;
 
     // Apply and dampen inertia.
-    if (!_scrolling && !_scrollingVelocity.isZero())
+    if (!_scrollingVelocity.isZero())
     {
         // Calculate the time passed since last update.
         float elapsedSecs = (float)elapsedTime * 0.001f;
@@ -745,14 +1237,17 @@ void Container::updateScroll()
         _scrollPosition.x += _scrollingVelocity.x * elapsedSecs;
         _scrollPosition.y += _scrollingVelocity.y * elapsedSecs;
 
-        float dampening = 1.0f - _scrollingFriction * SCROLL_FRICTION_FACTOR * elapsedSecs;
-        _scrollingVelocity.x *= dampening;
-        _scrollingVelocity.y *= dampening;
-
-        if (fabs(_scrollingVelocity.x) < 100.0f)
-            _scrollingVelocity.x = 0.0f;
-        if (fabs(_scrollingVelocity.y) < 100.0f)
-            _scrollingVelocity.y = 0.0f;
+        if (!_scrolling)
+        {
+            float dampening = 1.0f - _scrollingFriction * SCROLL_FRICTION_FACTOR * elapsedSecs;
+            _scrollingVelocity.x *= dampening;
+            _scrollingVelocity.y *= dampening;
+
+            if (fabs(_scrollingVelocity.x) < 100.0f)
+                _scrollingVelocity.x = 0.0f;
+            if (fabs(_scrollingVelocity.y) < 100.0f)
+                _scrollingVelocity.y = 0.0f;
+        }
     }
 
     // Stop scrolling when the far edge is reached.
@@ -809,6 +1304,14 @@ void Container::updateScroll()
     _layout->update(this, _scrollPosition);
 }
 
+void Container::sortControls()
+{
+    if (_layout->getType() == Layout::LAYOUT_ABSOLUTE)
+    {
+        std::sort(_controls.begin(), _controls.end(), &sortControlsByZOrder);
+    }
+}
+
 bool Container::touchEventScroll(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
 {
     switch(evt)

+ 54 - 4
gameplay/src/Container.h

@@ -3,6 +3,7 @@
 
 #include "Control.h"
 #include "Layout.h"
+#include "TimeListener.h"
 
 namespace gameplay
 {
@@ -44,7 +45,7 @@ namespace gameplay
     }
  @endverbatim
  */
-class Container : public Control
+class Container : public Control, TimeListener
 {
 
 public:
@@ -212,6 +213,13 @@ public:
      */
     virtual void setAnimationPropertyValue(int propertyId, AnimationValue* value, float blendWeight = 1.0f);
 
+    /**
+     * @see TimeListener::timeEvent
+     *
+     * @script{ignore}
+     */
+    void timeEvent(long timeDiff, void* cookie);
+
 protected:
 
     /**
@@ -291,10 +299,17 @@ protected:
      *
      * @return True if the mouse event is consumed or false if it is not consumed.
      *
-     * @see Mouse::MouseEvent
+     * @see Mouse::mouseEvent
      */
     virtual bool mouseEvent(Mouse::MouseEvent evt, int x, int y, int wheelDelta);
 
+    /**
+     * Gamepad callback on gamepad events.
+     *
+     * @see Control::gamepadEvent
+     */
+    virtual bool gamepadEvent(Gamepad::GamepadEvent evt, Gamepad* gamepad, unsigned int analogIndex);
+
     /**
      * Gets a Layout::Type enum from a matching string.
      *
@@ -334,6 +349,13 @@ protected:
      */
     void updateScroll();
 
+    /**
+     * Sorts controls by Z-Order (for absolute layouts only).
+     * This method is used by controls to notify their parent container when
+     * their Z-Index changes.
+     */
+    void sortControls();
+
     /**
      * Applies touch events to scroll state.
      *
@@ -513,16 +535,44 @@ private:
      */
     Container(const Container& copy);
 
+    enum Direction
+    {
+        UP = 0x01,
+        DOWN = 0x02,
+        LEFT = 0x04,
+        RIGHT = 0x08,
+        NEXT = 0x10
+    };
+
+    // Returns true on success; false if there are no controls to focus on,
+    // in which case scrolling can be initiated.
+    bool moveFocus(Direction direction, Control* outsideControl = NULL);
+
+    void guaranteeFocus(Control* inFocus);
+
+    // Starts scrolling at the given horizontal and vertical speeds.
+    void startScrolling(float x, float y, bool resetTime = true);
+
+    void stopScrolling();
+
     AnimationClip* _scrollBarOpacityClip;
     int _zIndexDefault;
     int _focusIndexDefault;
     int _focusIndexMax;
+    unsigned int _focusPressed;
+    bool _selectButtonDown;
+    double _lastFrameTime;
+
+    // Timing information for repeating focus changes.
+    bool _focusChangeRepeat;
+    double _focusChangeStartTime;
+    double _focusChangeRepeatDelay;
+    unsigned int _focusChangeCount;
+    Direction _direction;
 
     float _totalWidth;
     float _totalHeight;
-
     int _contactIndices;
-
     bool _initializedWithScroll;
 };
 

+ 70 - 30
gameplay/src/Control.cpp

@@ -8,7 +8,7 @@ namespace gameplay
 Control::Control()
     : _id(""), _state(Control::NORMAL), _bounds(Rectangle::empty()), _clipBounds(Rectangle::empty()), _viewportClipBounds(Rectangle::empty()),
     _clearBounds(Rectangle::empty()), _dirty(true), _consumeInputEvents(true), _alignment(ALIGN_TOP_LEFT), _isAlignmentSet(false), _autoWidth(false), _autoHeight(false), _listeners(NULL), _visible(true),
-    _contactIndex(INVALID_CONTACT_INDEX), _focusIndex(0), _parent(NULL), _styleOverridden(false), _skin(NULL)
+    _zIndex(-1), _contactIndex(INVALID_CONTACT_INDEX), _focusIndex(-1), _parent(NULL), _styleOverridden(false), _skin(NULL)
 {
     addScriptEvent("controlEvent", "<Control>[Control::Listener::EventType]");
 }
@@ -17,9 +17,9 @@ Control::~Control()
 {
     if (_listeners)
     {
-        for (std::map<Listener::EventType, std::list<Listener*>*>::const_iterator itr = _listeners->begin(); itr != _listeners->end(); ++itr)
+        for (std::map<Control::Listener::EventType, std::list<Control::Listener*>*>::const_iterator itr = _listeners->begin(); itr != _listeners->end(); ++itr)
         {
-            std::list<Listener*>* list = itr->second;
+            std::list<Control::Listener*>* list = itr->second;
             SAFE_DELETE(list);
         }
         SAFE_DELETE(_listeners);
@@ -187,6 +187,11 @@ const Rectangle& Control::getBounds() const
     return _bounds;
 }
 
+const Rectangle& Control::getAbsoluteBounds() const
+{
+    return _absoluteBounds;
+}
+
 float Control::getX() const
 {
     return _bounds.x;
@@ -691,29 +696,29 @@ void Control::addListener(Control::Listener* listener, int eventFlags)
 {
     GP_ASSERT(listener);
 
-    if ((eventFlags & Listener::PRESS) == Listener::PRESS)
+    if ((eventFlags & Control::Listener::PRESS) == Control::Listener::PRESS)
     {
-        addSpecificListener(listener, Listener::PRESS);
+        addSpecificListener(listener, Control::Listener::PRESS);
     }
 
-    if ((eventFlags & Listener::RELEASE) == Listener::RELEASE)
+    if ((eventFlags & Control::Listener::RELEASE) == Control::Listener::RELEASE)
     {
-        addSpecificListener(listener, Listener::RELEASE);
+        addSpecificListener(listener, Control::Listener::RELEASE);
     }
 
-    if ((eventFlags & Listener::CLICK) == Listener::CLICK)
+    if ((eventFlags & Control::Listener::CLICK) == Control::Listener::CLICK)
     {
-        addSpecificListener(listener, Listener::CLICK);
+        addSpecificListener(listener, Control::Listener::CLICK);
     }
 
-    if ((eventFlags & Listener::VALUE_CHANGED) == Listener::VALUE_CHANGED)
+    if ((eventFlags & Control::Listener::VALUE_CHANGED) == Control::Listener::VALUE_CHANGED)
     {
-        addSpecificListener(listener, Listener::VALUE_CHANGED);
+        addSpecificListener(listener, Control::Listener::VALUE_CHANGED);
     }
 
-    if ((eventFlags & Listener::TEXT_CHANGED) == Listener::TEXT_CHANGED)
+    if ((eventFlags & Control::Listener::TEXT_CHANGED) == Control::Listener::TEXT_CHANGED)
     {
-        addSpecificListener(listener, Listener::TEXT_CHANGED);
+        addSpecificListener(listener, Control::Listener::TEXT_CHANGED);
     }
 }
 
@@ -722,13 +727,13 @@ void Control::removeListener(Control::Listener* listener)
     if (_listeners == NULL || listener == NULL)
         return;
 
-    for (std::map<Listener::EventType, std::list<Listener*>*>::iterator itr = _listeners->begin(); itr != _listeners->end();)
+    for (std::map<Control::Listener::EventType, std::list<Control::Listener*>*>::iterator itr = _listeners->begin(); itr != _listeners->end();)
     {
         itr->second->remove(listener);
 
         if(itr->second->empty())
         {
-            std::list<Listener*>* list = itr->second;
+            std::list<Control::Listener*>* list = itr->second;
             _listeners->erase(itr++);
             SAFE_DELETE(list);
         }
@@ -740,23 +745,23 @@ void Control::removeListener(Control::Listener* listener)
         SAFE_DELETE(_listeners);
 }
 
-void Control::addSpecificListener(Control::Listener* listener, Listener::EventType eventType)
+void Control::addSpecificListener(Control::Listener* listener, Control::Listener::EventType eventType)
 {
     GP_ASSERT(listener);
 
     if (!_listeners)
     {
-        _listeners = new std::map<Listener::EventType, std::list<Listener*>*>();
+        _listeners = new std::map<Control::Listener::EventType, std::list<Control::Listener*>*>();
     }
 
-    std::map<Listener::EventType, std::list<Listener*>*>::const_iterator itr = _listeners->find(eventType);
+    std::map<Control::Listener::EventType, std::list<Control::Listener*>*>::const_iterator itr = _listeners->find(eventType);
     if (itr == _listeners->end())
     {
-        _listeners->insert(std::make_pair(eventType, new std::list<Listener*>()));
+        _listeners->insert(std::make_pair(eventType, new std::list<Control::Listener*>()));
         itr = _listeners->find(eventType);
     }
 
-    std::list<Listener*>* listenerList = itr->second;
+    std::list<Control::Listener*>* listenerList = itr->second;
     listenerList->push_back(listener);
 }
 
@@ -773,7 +778,7 @@ bool Control::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int conta
         {
             _contactIndex = (int) contactIndex;
 
-            notifyListeners(Listener::PRESS);
+            notifyListeners(Control::Listener::PRESS);
 
             return _consumeInputEvents;
         }
@@ -789,15 +794,15 @@ bool Control::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int conta
         {
             _contactIndex = INVALID_CONTACT_INDEX;
 
-            // Always trigger Listener::RELEASE
-            notifyListeners(Listener::RELEASE);
+            // Always trigger Control::Listener::RELEASE
+            notifyListeners(Control::Listener::RELEASE);
 
-            // Only trigger Listener::CLICK if both PRESS and RELEASE took place within the control's bounds.
+            // Only trigger Control::Listener::CLICK if both PRESS and RELEASE took place within the control's bounds.
             if (x > _clipBounds.x && x <= _clipBounds.x + _clipBounds.width &&
                 y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height)
             {
                 // Leave this control in the FOCUS state.
-                notifyListeners(Listener::CLICK);
+                notifyListeners(Control::Listener::CLICK);
             }
 
             return _consumeInputEvents;
@@ -834,7 +839,42 @@ bool Control::mouseEvent(Mouse::MouseEvent evt, int x, int y, int wheelDelta)
     return false;
 }
 
-void Control::notifyListeners(Listener::EventType eventType)
+bool Control::gamepadEvent(Gamepad::GamepadEvent evt, Gamepad* gamepad, unsigned int analogIndex)
+{
+    // Default behavior for gamepad events.
+    switch (evt)
+    {
+    case Gamepad::BUTTON_EVENT:
+        if (_state == Control::FOCUS)
+        {
+            if (gamepad->isButtonDown(Gamepad::BUTTON_A) ||
+                gamepad->isButtonDown(Gamepad::BUTTON_X))
+            {
+                notifyListeners(Control::Listener::PRESS);
+                return _consumeInputEvents;
+            }
+        }
+        else if (_state == Control::ACTIVE)
+        {
+            if (!gamepad->isButtonDown(Gamepad::BUTTON_A) &&
+                !gamepad->isButtonDown(Gamepad::BUTTON_X))
+            {
+                notifyListeners(Control::Listener::RELEASE);
+                notifyListeners(Control::Listener::CLICK);
+                return _consumeInputEvents;
+            }
+        }
+        break;
+    case Gamepad::JOYSTICK_EVENT:
+        break;
+    case Gamepad::TRIGGER_EVENT:
+        break;
+    }
+
+    return false;
+}
+
+void Control::notifyListeners(Control::Listener::EventType eventType)
 {
     // This method runs untrusted code by notifying listeners of events.
     // If the user calls exit() or otherwise releases this control, we
@@ -843,11 +883,11 @@ void Control::notifyListeners(Listener::EventType eventType)
 
     if (_listeners)
     {
-        std::map<Listener::EventType, std::list<Listener*>*>::const_iterator itr = _listeners->find(eventType);
+        std::map<Control::Listener::EventType, std::list<Control::Listener*>*>::const_iterator itr = _listeners->find(eventType);
         if (itr != _listeners->end())
         {
-            std::list<Listener*>* listenerList = itr->second;
-            for (std::list<Listener*>::iterator listenerItr = listenerList->begin(); listenerItr != listenerList->end(); ++listenerItr)
+            std::list<Control::Listener*>* listenerList = itr->second;
+            for (std::list<Control::Listener*>::iterator listenerItr = listenerList->begin(); listenerItr != listenerList->end(); ++listenerItr)
             {
                 GP_ASSERT(*listenerItr);
                 (*listenerItr)->controlEvent(this, eventType);
@@ -1011,7 +1051,7 @@ void Control::drawBorder(SpriteBatch* spriteBatch, const Rectangle& clip)
             spriteBatch->draw(rightX, _absoluteBounds.y, border.right, border.top, topRight.u1, topRight.v1, topRight.u2, topRight.v2, skinColor, clip);
         if (border.left)
             spriteBatch->draw(_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,
             center.u1, center.v1, center.u2, center.v2, skinColor, clip);

+ 37 - 17
gameplay/src/Control.h

@@ -11,6 +11,7 @@
 #include "Keyboard.h"
 #include "Mouse.h"
 #include "ScriptTarget.h"
+#include "Gamepad.h"
 
 namespace gameplay
 {
@@ -85,12 +86,6 @@ public:
         ALIGN_BOTTOM_RIGHT = ALIGN_BOTTOM | ALIGN_RIGHT
     };
 
-    /**
-     * @script{ignore}
-     * A constant used for setting themed attributes on all control states simultaneously.
-     */
-    static const unsigned char STATE_ALL = NORMAL | FOCUS | ACTIVE | DISABLED;
-
     /**
      * Implement Control::Listener and call Control::addListener()
      * in order to listen for events on controls.
@@ -156,6 +151,12 @@ public:
         virtual void controlEvent(Control* control, EventType evt) = 0;
     };
 
+    /**
+     * @script{ignore}
+     * A constant used for setting themed attributes on all control states simultaneously.
+     */
+    static const unsigned char STATE_ALL = NORMAL | FOCUS | ACTIVE | DISABLED;
+
     /**
      * Position animation property. Data = x, y
      */
@@ -244,6 +245,14 @@ public:
      */
     const Rectangle& getBounds() const;
 
+    /**
+     * Get the absolute bounds of this control, in pixels, including border and padding,
+     * before clipping.
+     *
+     * @return The absolute bounds of this control.
+     */
+    const Rectangle& getAbsoluteBounds() const;
+
     /**
      * Get the x coordinate of this control's bounds.
      *
@@ -841,6 +850,15 @@ protected:
      */
     virtual bool mouseEvent(Mouse::MouseEvent evt, int x, int y, int wheelDelta);
 
+    /**
+     * Gamepad callback on gamepad events.
+     *
+     * @param gamepad The gamepad whose state changed.
+     * @param evt The gamepad event that occurred.
+     * @param analogIndex If evt is JOYSTICK_EVENT or TRIGGER_EVENT, this will be the index of the corresponding control.
+     */
+    virtual bool gamepadEvent(Gamepad::GamepadEvent evt, Gamepad* gamepad, unsigned int analogIndex);
+
     /**
      * Called when a control's properties change.  Updates this control's internal rendering
      * properties, such as its text viewport.
@@ -850,6 +868,14 @@ protected:
      */
     virtual void update(const Control* container, const Vector2& offset);
 
+    /**
+     * Draws the themed border and background of a control.
+     *
+     * @param spriteBatch The sprite batch containing this control's border images.
+     * @param clip The clipping rectangle of this control's parent container.
+     */
+    virtual void drawBorder(SpriteBatch* spriteBatch, const Rectangle& clip);
+
     /**
      * Draw the images associated with this control.
      *
@@ -915,7 +941,8 @@ protected:
      *
      * @param eventType The event to trigger.
      */
-    void notifyListeners(Listener::EventType eventType);
+    //void notifyListeners(Listener::EventType eventType);
+    void notifyListeners(Control::Listener::EventType eventType);
 
     /**
      * Gets the Alignment by string.
@@ -1003,7 +1030,8 @@ protected:
     /**
      * Listeners map of EventType's to a list of Listeners.
      */
-    std::map<Listener::EventType, std::list<Listener*>*>* _listeners;
+    //std::map<Listener::EventType, std::list<Listener*>*>* _listeners;
+    std::map<Control::Listener::EventType, std::list<Control::Listener*>*>* _listeners;
     
     /**
      * The Control's Theme::Style.
@@ -1063,15 +1091,7 @@ private:
 
     Theme::Skin* getSkin(State state);
 
-    void addSpecificListener(Control::Listener* listener, Listener::EventType eventType);
-    
-    /**
-     * Draws the themed border and background of a control.
-     *
-     * @param spriteBatch The sprite batch containing this control's border images.
-     * @param clip The clipping rectangle of this control's parent container.
-     */
-    virtual void drawBorder(SpriteBatch* spriteBatch, const Rectangle& clip);
+    void addSpecificListener(Control::Listener* listener, Control::Listener::EventType eventType);
     
     bool _styleOverridden;
     Theme::Skin* _skin;

+ 119 - 119
gameplay/src/Form.cpp

@@ -389,138 +389,138 @@ void Form::setNode(Node* node)
 }
 
 void Form::update(float elapsedTime)
-{
-    updateBounds();
-}
-
-void Form::updateBounds()
 {
     if (isDirty())
     {
-        _clearBounds.set(_absoluteClipBounds);
+        updateBounds();
 
-        // 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;
+        // Cache themed attributes for performance.
+        _skin = getSkin(_state);
+        _opacity = getOpacity(_state);
 
-        if (x < 0)
+        GP_ASSERT(_layout);
+        if (_scroll != SCROLL_NONE)
         {
-            width += x;
-            x = -x;
+            updateScroll();
         }
         else
         {
-            x = 0;
+            _layout->update(this, Vector2::zero());
         }
+    }
+}
 
-        if (y < 0)
-        {
-            height += y;
-            y = -y;
-        }
-        else
-        {
-            y = 0;
-        }
+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);
 
-        _clipBounds.set(x, y, width, height);
+    float clipX2 = clip.x + clip.width;
+    float x2 = clip.x + x + width;
+    if (x2 > clipX2)
+        width -= x2 - clipX2;
 
-        // Calculate the absolute bounds.
+    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;
-        _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();
+    _clipBounds.set(x, y, width, height);
 
-        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;
+    // Calculate the absolute bounds.
+    x = 0;
+    y = 0;
+    _absoluteBounds.set(x, y, _bounds.width, _bounds.height);
 
-        _viewportBounds.set(x, y, width, height);
+    // Calculate the absolute viewport bounds. Absolute bounds minus border and padding.
+    const Theme::Border& border = getBorder(_state);
+    const Theme::Padding& padding = getPadding();
 
-        // Calculate the 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;
+    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;
 
-        clipY2 = clip.y + clip.height;
-        y2 = y + height;
-        if (y2 > clipY2)
-            height = clipY2 - y;
+    _viewportBounds.set(x, y, width, height);
 
-        if (x < clip.x)
-        {
-            float dx = clip.x - x;
-            width -= dx;
-            x = clip.x;
-        }
+    // 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;
 
-        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);
-        }
+    clipY2 = clip.y + clip.height;
+    y2 = y + height;
+    if (y2 > clipY2)
+        height = clipY2 - y;
 
-        // Cache themed attributes for performance.
-        _skin = getSkin(_state);
-        _opacity = getOpacity(_state);
+    if (x < clip.x)
+    {
+        float dx = clip.x - x;
+        width -= dx;
+        x = clip.x;
+    }
 
-        // 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);
+    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);
+    }
 
-            _viewportClipBounds.height -= _scrollBarHorizontal->getRegion().height;
-        }
+    // 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);
 
-        if ((_scroll & SCROLL_VERTICAL) == SCROLL_VERTICAL)
-        {
-            _scrollBarTopCap = getImage("scrollBarTopCap", _state);
-            _scrollBarVertical = getImage("verticalScrollBar", _state);
-            _scrollBarBottomCap = getImage("scrollBarBottomCap", _state);
-        
-            _viewportClipBounds.width -= _scrollBarVertical->getRegion().width;
-        }
+        _viewportClipBounds.height -= _scrollBarHorizontal->getRegion().height;
+    }
 
-        GP_ASSERT(_layout);
-        if (_scroll != SCROLL_NONE)
-        {
-            updateScroll();
-        }
-        else
-        {
-            _layout->update(this, Vector2::zero());
-        }
+    if ((_scroll & SCROLL_VERTICAL) == SCROLL_VERTICAL)
+    {
+        _scrollBarTopCap = getImage("scrollBarTopCap", _state);
+        _scrollBarVertical = getImage("verticalScrollBar", _state);
+        _scrollBarBottomCap = getImage("scrollBarBottomCap", _state);
+        
+        _viewportClipBounds.width -= _scrollBarVertical->getRegion().width;
     }
 }
 
@@ -605,7 +605,6 @@ bool Form::touchEventInternal(Touch::TouchEvent evt, int x, int y, unsigned int
 {
     // Check for a collision with each Form in __forms.
     // Pass the event on.
-    bool eventConsumed = false;
     size_t size = __forms.size();
     for (size_t i = 0; i < size; ++i)
     {
@@ -627,7 +626,8 @@ bool Form::touchEventInternal(Touch::TouchEvent evt, int x, int y, unsigned int
                          point.y >= bounds.y &&
                          point.y <= bounds.y + bounds.height))
                     {
-                        eventConsumed |= form->touchEvent(evt, point.x - bounds.x, bounds.height - point.y - bounds.y, contactIndex);
+                        if (form->touchEvent(evt, point.x - bounds.x, bounds.height - point.y - bounds.y, contactIndex))
+                            return true;
                     }
                 }
             }
@@ -643,12 +643,13 @@ bool Form::touchEventInternal(Touch::TouchEvent evt, int x, int y, unsigned int
                         y <= bounds.y + bounds.height))
                 {
                     // Pass on the event's position relative to the form.
-                    eventConsumed |= form->touchEvent(evt, x - bounds.x, y - bounds.y, contactIndex);
+                    if (form->touchEvent(evt, x - bounds.x, y - bounds.y, contactIndex))
+                        return true;
                 }
             }
         }
     }
-    return eventConsumed;
+    return false;
 }
 
 bool Form::keyEventInternal(Keyboard::KeyEvent evt, int key)
@@ -669,8 +670,6 @@ bool Form::keyEventInternal(Keyboard::KeyEvent evt, int key)
 
 bool Form::mouseEventInternal(Mouse::MouseEvent evt, int x, int y, int wheelDelta)
 {
-    bool eventConsumed = false;
-
     for (size_t i = 0; i < __forms.size(); ++i)
     {
         Form* form = __forms[i];
@@ -694,7 +693,8 @@ bool Form::mouseEventInternal(Mouse::MouseEvent evt, int x, int y, int wheelDelt
                          point.y >= bounds.y &&
                          point.y <= bounds.y + bounds.height))
                     {
-                        eventConsumed |= form->mouseEvent(evt, point.x - bounds.x, bounds.height - point.y - bounds.y, wheelDelta);
+                        if (form->mouseEvent(evt, point.x - bounds.x, bounds.height - point.y - bounds.y, wheelDelta))
+                            return true;
                     }
                 }
             }
@@ -713,30 +713,30 @@ bool Form::mouseEventInternal(Mouse::MouseEvent evt, int x, int y, int wheelDelt
                         y <= bounds.y + bounds.height))
                 {
                     // Pass on the event's position relative to the form.
-                    eventConsumed |= form->mouseEvent(evt, x - bounds.x, y - bounds.y, wheelDelta);
+                    if (form->mouseEvent(evt, x - bounds.x, y - bounds.y, wheelDelta))
+                        return true;
                 }
             }
         }
     }
-    return eventConsumed;
+    return false;
 }
 
 bool Form::gamepadEventInternal(Gamepad::GamepadEvent evt, Gamepad* gamepad, unsigned int analogIndex)
 {
-    bool eventConsumed = false;
-
     for (size_t i = 0; i < __forms.size(); ++i)
     {
         Form* form = __forms[i];
         GP_ASSERT(form);
 
-        if (form->isEnabled() && form->isVisible())
+        if (form->isEnabled() && form->isVisible() && form->getState() == FOCUS)
         {
-            eventConsumed |= form->gamepadEventInternal(evt, gamepad, analogIndex);
+            if (form->gamepadEvent(evt, gamepad, analogIndex))
+                return true;
         }
     }
 
-    return eventConsumed;
+    return false;
 }
 
 bool Form::projectPoint(int x, int y, Vector3* point)

+ 7 - 0
gameplay/src/Form.h

@@ -219,6 +219,13 @@ private:
      */
     static bool mouseEventInternal(Mouse::MouseEvent evt, int x, int y, int wheelDelta);
 
+    /**
+     * Propagate gamepad events to enabled forms.
+     *
+     * @return True if the gamepad event is consumed, false otherwise.
+     *
+     * @see Control::gamepadEvent
+     */
     static bool gamepadEventInternal(Gamepad::GamepadEvent evt, Gamepad* gamepad, unsigned int analogIndex);
 
     /**

+ 7 - 7
gameplay/src/Game.cpp

@@ -317,12 +317,12 @@ void Game::frame()
         // Update gamepads.
         Gamepad::updateInternal(elapsedTime);
 
-        // Update forms.
-        Form::updateInternal(elapsedTime);
-
         // Application Update.
         update(elapsedTime);
 
+        // Update forms.
+        Form::updateInternal(elapsedTime);
+
         // Run script update.
         _scriptController->update(elapsedTime);
 
@@ -349,12 +349,12 @@ void Game::frame()
         // Update gamepads.
         Gamepad::updateInternal(0);
 
-        // Update forms.
-        Form::updateInternal(0);
-
         // Application Update.
         update(0);
 
+        // Update forms.
+        Form::updateInternal(0);
+
         // Script update.
         _scriptController->update(0);
 
@@ -509,7 +509,7 @@ void Game::gestureTapEvent(int x, int y)
 {
 }
 
-void Game::gamepadEvent(Gamepad::GamepadEvent evt, Gamepad* gamepad)
+void Game::gamepadEvent(Gamepad::GamepadEvent evt, Gamepad* gamepad, unsigned int analogIndex)
 {
 }
 

+ 6 - 4
gameplay/src/Game.h

@@ -443,13 +443,15 @@ public:
     virtual void gestureTapEvent(int x, int y);
 
     /**
-     * Gamepad callback on gamepad events. Override to receive Gamepad::CONNECTED_EVENT 
-     * and Gamepad::DISCONNECTED_EVENT.
+     * Gamepad callback on gamepad events.  Override to receive Gamepad::CONNECTED_EVENT 
+     * and Gamepad::DISCONNECTED_EVENT, and store the Gamepad* in order to poll it from update().
+     * Or, handle all gamepad input through BUTTON, JOYSTICK and TRIGGER events.
      *
      * @param evt The gamepad event that occurred.
-     * @param gamepad the gamepad the event occurred on
+     * @param gamepad The gamepad that generated the event.
+     * @param analogIndex If this is a JOYSTICK_EVENT or TRIGGER_EVENT, the index of the joystick or trigger whose value changed.
      */
-    virtual void gamepadEvent(Gamepad::GamepadEvent evt, Gamepad* gamepad);
+    virtual void gamepadEvent(Gamepad::GamepadEvent evt, Gamepad* gamepad, unsigned int analogIndex = 0);
 
     /**
      * Gets the current number of gamepads currently connected to the system.

+ 3 - 3
gameplay/src/Gamepad.cpp

@@ -384,7 +384,7 @@ void Gamepad::setButtons(unsigned int buttons)
     if (buttons != _buttons)
     {
         _buttons = buttons;
-        Form::gamepadEventInternal(BUTTON_EVENT, this, 0);
+        Platform::gamepadEventInternal(BUTTON_EVENT, this);
     }
 }
 
@@ -393,7 +393,7 @@ void Gamepad::setJoystickValue(unsigned int index, float x, float y)
     if (_joysticks[index].x != x || _joysticks[index].y != y)
     {
         _joysticks[index].set(x, y);
-        Form::gamepadEventInternal(JOYSTICK_EVENT, this, index);
+        Platform::gamepadEventInternal(JOYSTICK_EVENT, this, index);
     }
 }
 
@@ -402,7 +402,7 @@ void Gamepad::setTriggerValue(unsigned int index, float value)
     if (_triggers[index] != value)
     {
         _triggers[index] = value;
-        Form::gamepadEventInternal(TRIGGER_EVENT, this, index);
+        Platform::gamepadEventInternal(TRIGGER_EVENT, this, index);
     }
 }
 

+ 1 - 1
gameplay/src/Image.h

@@ -66,7 +66,7 @@ private:
      * Constructor.
      */
     Image();
-        
+
     /**
      * Destructor.
      */

+ 2 - 0
gameplay/src/ImageControl.h

@@ -52,6 +52,8 @@ public:
 
     /**
      * Set the path of the image for this ImageControl to display.
+     *
+     * @param path The path to the image.
      */
     void setImage(const char* path);
 

+ 6 - 8
gameplay/src/Joystick.cpp

@@ -110,7 +110,7 @@ void Joystick::initialize(Theme::Style* style, Properties* properties)
 
 void Joystick::addListener(Control::Listener* listener, int eventFlags)
 {
-    if ((eventFlags & Listener::TEXT_CHANGED) == Listener::TEXT_CHANGED)
+    if ((eventFlags & Control::Listener::TEXT_CHANGED) == Control::Listener::TEXT_CHANGED)
     {
         GP_ERROR("TEXT_CHANGED event is not applicable to this control.");
     }
@@ -130,7 +130,7 @@ bool Joystick::touchEvent(Touch::TouchEvent touchEvent, int x, int y, unsigned i
                 float dy = 0.0f;
 
                 _contactIndex = (int) contactIndex;
-                notifyListeners(Listener::PRESS);
+                notifyListeners(Control::Listener::PRESS);
 
                 // Get the displacement of the touch from the centre.
                 if (!_relative)
@@ -168,7 +168,7 @@ bool Joystick::touchEvent(Touch::TouchEvent touchEvent, int x, int y, unsigned i
                 {
                     _value.set(value);
                     _dirty = true;
-                    notifyListeners(Listener::VALUE_CHANGED);
+                    notifyListeners(Control::Listener::VALUE_CHANGED);
                 }
 
                 _state = ACTIVE;
@@ -203,7 +203,7 @@ bool Joystick::touchEvent(Touch::TouchEvent touchEvent, int x, int y, unsigned i
                 {
                     _value.set(value);
                     _dirty = true;
-                    notifyListeners(Listener::VALUE_CHANGED);
+                    notifyListeners(Control::Listener::VALUE_CHANGED);
                 }
 
                 return _consumeInputEvents;
@@ -216,7 +216,7 @@ bool Joystick::touchEvent(Touch::TouchEvent touchEvent, int x, int y, unsigned i
             {
                 _contactIndex = INVALID_CONTACT_INDEX;
 
-                notifyListeners(Listener::RELEASE);
+                notifyListeners(Control::Listener::RELEASE);
 
                 // Reset displacement and direction vectors.
                 _displacement.set(0.0f, 0.0f);
@@ -225,7 +225,7 @@ bool Joystick::touchEvent(Touch::TouchEvent touchEvent, int x, int y, unsigned i
                 {
                     _value.set(value);
                     _dirty = true;
-                    notifyListeners(Listener::VALUE_CHANGED);
+                    notifyListeners(Control::Listener::VALUE_CHANGED);
                 }
 
                 _state = NORMAL;
@@ -242,7 +242,6 @@ bool Joystick::touchEvent(Touch::TouchEvent touchEvent, int x, int y, unsigned i
 void Joystick::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
 {
     GP_ASSERT(spriteBatch);
-    spriteBatch->start();
 
     // If the joystick is not absolute, then only draw if it is active.
     if (!_relative || (_relative && _state == ACTIVE))
@@ -284,7 +283,6 @@ void Joystick::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
                 spriteBatch->draw(position.x, position.y, _innerSize->x, _innerSize->y, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color, _viewportClipBounds);
         }
     }
-    spriteBatch->finish();
 }
 
 const char* Joystick::getType() const

+ 9 - 7
gameplay/src/Label.cpp

@@ -21,6 +21,12 @@ Label* Label::create(const char* id, Theme::Style* style)
         label->_id = id;
     label->setStyle(style);
 
+    // Labels don't consume input events by default like other controls.
+    label->_consumeInputEvents = false;
+
+    // Ensure that labels cannot receive focus.
+    label->_focusIndex = -2;
+
     return label;
 }
 
@@ -28,9 +34,8 @@ Label* Label::create(Theme::Style* style, Properties* properties)
 {
     Label* label = new Label();
     label->initialize(style, properties);
+
     label->_consumeInputEvents = false;
-    
-    // Ensure that labels cannot receive focus.
     label->_focusIndex = -2;
 
     return label;
@@ -41,7 +46,6 @@ void Label::initialize(Theme::Style* style, Properties* properties)
     GP_ASSERT(properties);
 
     Control::initialize(style, properties);
-
     const char* text = properties->getString("text");
     if (text)
     {
@@ -51,11 +55,11 @@ void Label::initialize(Theme::Style* style, Properties* properties)
 
 void Label::addListener(Control::Listener* listener, int eventFlags)
 {
-    if ((eventFlags & Listener::TEXT_CHANGED) == Listener::TEXT_CHANGED)
+    if ((eventFlags & Control::Listener::TEXT_CHANGED) == Control::Listener::TEXT_CHANGED)
     {
         GP_ERROR("TEXT_CHANGED event is not applicable to this control.");
     }
-    if ((eventFlags & Listener::VALUE_CHANGED) == Listener::VALUE_CHANGED)
+    if ((eventFlags & Control::Listener::VALUE_CHANGED) == Control::Listener::VALUE_CHANGED)
     {
         GP_ERROR("VALUE_CHANGED event is not applicable to this control.");
     }
@@ -102,8 +106,6 @@ void Label::drawText(const Rectangle& clip)
         _font->drawText(_text.c_str(), _textBounds, _textColor, getFontSize(_state), getTextAlignment(_state), true, getTextRightToLeft(_state), &_viewportClipBounds);
         _font->finish();
     }
-
-    _dirty = false;
 }
 
 const char* Label::getType() const

+ 9 - 0
gameplay/src/Platform.cpp

@@ -55,6 +55,15 @@ void Platform::resizeEventInternal(unsigned int width, unsigned int height)
     }
 }
 
+void Platform::gamepadEventInternal(Gamepad::GamepadEvent evt, Gamepad* gamepad, unsigned int analogIndex)
+{
+    if (!Form::gamepadEventInternal(evt, gamepad, analogIndex))
+    {
+        Game::getInstance()->gamepadEvent(evt, gamepad, analogIndex);
+        Game::getInstance()->getScriptController()->gamepadEvent(evt, gamepad, analogIndex);
+    }
+}
+
 void Platform::gamepadEventConnectedInternal(GamepadHandle handle,  unsigned int buttonCount, unsigned int joystickCount, unsigned int triggerCount,
                                              unsigned int vendorId, unsigned int productId, const char* vendorString, const char* productString)
 {

+ 10 - 2
gameplay/src/Platform.h

@@ -300,7 +300,14 @@ public:
      */
     static void resizeEventInternal(unsigned int width, unsigned int height);
 
-   /**
+    /**
+     * Internal method used only from static code in various platform implementation.
+     *
+     * @script{ignore}
+     */
+    static void gamepadEventInternal(Gamepad::GamepadEvent evt, Gamepad* gamepad, unsigned int analogIndex = 0);
+
+    /**
      * Internal method used only from static code in various platform implementation.
      *
      * @script{ignore}
@@ -308,7 +315,8 @@ public:
     static void gamepadEventConnectedInternal(GamepadHandle handle, unsigned int buttonCount, unsigned int joystickCount, unsigned int triggerCount,
                                               unsigned int vendorId, unsigned int productId, 
                                               const char* vendorString, const char* productString);
-   /**
+
+    /**
      * Internal method used only from static code in various platform implementation.
      *
      * @script{ignore}

+ 5 - 3
gameplay/src/PlatformBlackBerry.cpp

@@ -575,7 +575,9 @@ void queryGamepad(GamepadHandle handle, int* buttonCount, int* joystickCount, in
 
 void Platform::pollGamepadState(Gamepad* gamepad)
 {
-    screen_get_device_property_iv(gamepad->_handle, SCREEN_PROPERTY_BUTTONS, (int*)&gamepad->_buttons);
+	unsigned int buttons;
+    screen_get_device_property_iv(gamepad->_handle, SCREEN_PROPERTY_BUTTONS, (int*)&buttons);
+	gamepad->setButtons(buttons);
 
     unsigned int i;
     for (i = 0; i < gamepad->_joystickCount; ++i)
@@ -604,7 +606,7 @@ void Platform::pollGamepadState(Gamepad* gamepad)
         x *= (x < 0) ? 0.0078125f : 0.0078740157480315f;
         y *= (y > 0) ? 0.0078125f : 0.0078740157480315f;
 
-        gamepad->_joysticks[i].set(x, y);        
+		gamepad->setJoystickValue(i, x, y);
     }
 
     for (i = 0; i < gamepad->_triggerCount; ++i)
@@ -623,7 +625,7 @@ void Platform::pollGamepadState(Gamepad* gamepad)
         }
 
         float value = (float)analog[2] * 0.0078125f;
-        gamepad->_triggers[i] = value;
+		gamepad->setTriggerValue(i, value);
     }
 }
 #else

+ 15 - 14
gameplay/src/PlatformMacOSX.mm

@@ -2046,7 +2046,7 @@ void Platform::pollGamepadState(Gamepad* gamepad)
             }
         }
         
-        gamepad->_buttons = 0;
+        unsigned int buttons = 0;
         for (int i = 0; i < [gp numberOfButtons]; ++i)
         {
             HIDGamepadButton* b = [gp buttonAtIndex: i];
@@ -2056,11 +2056,11 @@ void Platform::pollGamepadState(Gamepad* gamepad)
                 if (mapping)
                 {
                     if (mapping[i] >= 0)
-                        gamepad->_buttons |= (1 << mapping[i]);
+                        buttons |= (1 << mapping[i]);
                 }
                 else
                 {
-                    gamepad->_buttons |= (1 << i);
+                    buttons |= (1 << i);
                 }
             }
         }
@@ -2074,32 +2074,34 @@ void Platform::pollGamepadState(Gamepad* gamepad)
                 case -1:
                     break;
                 case 0:
-                    gamepad->_buttons |= (1 << Gamepad::BUTTON_UP);
+                    buttons |= (1 << Gamepad::BUTTON_UP);
                     break;
                 case 1:
-                    gamepad->_buttons |= (1 << Gamepad::BUTTON_UP) | (1 << Gamepad::BUTTON_RIGHT);
+                    buttons |= (1 << Gamepad::BUTTON_UP) | (1 << Gamepad::BUTTON_RIGHT);
                     break;
                 case 2:
-                    gamepad->_buttons |= (1 << Gamepad::BUTTON_RIGHT);
+                    buttons |= (1 << Gamepad::BUTTON_RIGHT);
                     break;
                 case 3:
-                    gamepad->_buttons |= (1 << Gamepad::BUTTON_RIGHT) | (1 << Gamepad::BUTTON_DOWN);
+                    buttons |= (1 << Gamepad::BUTTON_RIGHT) | (1 << Gamepad::BUTTON_DOWN);
                     break;
                 case 4:
-                    gamepad->_buttons |= (1 << Gamepad::BUTTON_DOWN);
+                    buttons |= (1 << Gamepad::BUTTON_DOWN);
                     break;
                 case 5:
-                    gamepad->_buttons |= (1 << Gamepad::BUTTON_DOWN) | (1 << Gamepad::BUTTON_LEFT);
+                    buttons |= (1 << Gamepad::BUTTON_DOWN) | (1 << Gamepad::BUTTON_LEFT);
                     break;
                 case 6:
-                    gamepad->_buttons |= (1 << Gamepad::BUTTON_LEFT);
+                    buttons |= (1 << Gamepad::BUTTON_LEFT);
                     break;
                 case 7:
-                    gamepad->_buttons |= (1 << Gamepad::BUTTON_LEFT) | (1 << Gamepad::BUTTON_UP);
+                    buttons |= (1 << Gamepad::BUTTON_LEFT) | (1 << Gamepad::BUTTON_UP);
                     break;
             }
         }
         
+        gamepad->setButtons(buttons);
+        
         for (unsigned int i = 0; i < [gp numberOfSticks]; ++i)
         {
             float rawX = [[gp axisAtIndex: i*2] calibratedValue];
@@ -2109,13 +2111,12 @@ void Platform::pollGamepadState(Gamepad* gamepad)
             if (std::fabs(rawY) <= axisDeadZone)
                 rawY = 0;
             
-            gamepad->_joysticks[i].x = rawX;
-            gamepad->_joysticks[i].y = rawY;
+            gamepad->setJoystickValue(i, rawX, rawY);
         }
         
         for (unsigned int i = 0; i < [gp numberOfTriggerButtons]; ++i)
         {
-            gamepad->_triggers[i] = [[gp triggerButtonAtIndex: i] calibratedStateValue];
+            gamepad->setTriggerValue(i, [[gp triggerButtonAtIndex: i] calibratedStateValue]);
         }
     }
 }

+ 8 - 12
gameplay/src/PlatformWindows.cpp

@@ -44,9 +44,7 @@ static unsigned int __gamepadsConnected = 0;
 static const unsigned int XINPUT_BUTTON_COUNT = 14;
 static const unsigned int XINPUT_JOYSTICK_COUNT = 2;
 static const unsigned int XINPUT_TRIGGER_COUNT = 2;
-#endif
 
-#ifdef USE_XINPUT
 static XINPUT_STATE __xInputState;
 static bool __connectedXInput[4];
 
@@ -947,7 +945,6 @@ Platform* Platform::create(Game* game, void* attachToWindow)
                 Platform::gamepadEventConnectedInternal(i, XINPUT_BUTTON_COUNT, XINPUT_JOYSTICK_COUNT, XINPUT_TRIGGER_COUNT, 0, 0, "Microsoft", "XBox360 Controller");
                 __connectedXInput[i] = true;
             }
-
         }
     }
 #endif
@@ -1245,13 +1242,15 @@ void Platform::pollGamepadState(Gamepad* gamepad)
         };
 
         const unsigned int *mapping = xInputMapping;
-        for (gamepad->_buttons = 0; buttons; buttons >>= 1, mapping++)
+        unsigned int mappedButtons;
+        for (mappedButtons = 0; buttons; buttons >>= 1, mapping++)
         {
             if (buttons & 1)
             {
-                gamepad->_buttons |= (1 << *mapping);
+                mappedButtons |= (1 << *mapping);
             }
         }
+        gamepad->setButtons(mappedButtons);
 
         unsigned int i;
         for (i = 0; i < gamepad->_joystickCount; ++i)
@@ -1272,7 +1271,7 @@ void Platform::pollGamepadState(Gamepad* gamepad)
                 break;
             }
 
-            gamepad->_joysticks[i].set(x, y);
+            gamepad->setJoystickValue(i, x, y);
         }
 
         for (i = 0; i < gamepad->_triggerCount; ++i)
@@ -1292,20 +1291,17 @@ void Platform::pollGamepadState(Gamepad* gamepad)
 
             if (trigger < XINPUT_GAMEPAD_TRIGGER_THRESHOLD)
             {
-                gamepad->_triggers[i] = 0.0f;
+                gamepad->setTriggerValue(i, 0.0f);
             }
             else
             {
-                gamepad->_triggers[i] = (float)trigger / 255.0f;
+                gamepad->setTriggerValue(i, (float)trigger / 255.0f);
             }
         }
     }
 }
 #else
-void Platform::pollGamepadState(Gamepad* gamepad)
-{
-    // TODO: Support generic HID gamepads (including XBox controllers) without requiring XInput.
-}
+void Platform::pollGamepadState(Gamepad* gamepad) { }
 #endif
 
 void Platform::shutdownInternal()

+ 24 - 3
gameplay/src/RadioButton.cpp

@@ -81,7 +81,7 @@ const Vector2& RadioButton::getImageSize() const
 
 void RadioButton::addListener(Control::Listener* listener, int eventFlags)
 {
-    if ((eventFlags & Listener::TEXT_CHANGED) == Listener::TEXT_CHANGED)
+    if ((eventFlags & Control::Listener::TEXT_CHANGED) == Control::Listener::TEXT_CHANGED)
     {
         GP_ERROR("TEXT_CHANGED event is not applicable to RadioButton.");
     }
@@ -105,7 +105,7 @@ bool RadioButton::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int c
                     {
                         RadioButton::clearSelected(_groupId);
                         _selected = true;
-                        notifyListeners(Listener::VALUE_CHANGED);
+                        notifyListeners(Control::Listener::VALUE_CHANGED);
                     }
                 }
             }
@@ -116,6 +116,27 @@ bool RadioButton::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int c
     return Button::touchEvent(evt, x, y, contactIndex);
 }
 
+bool RadioButton::gamepadEvent(Gamepad::GamepadEvent evt, Gamepad* gamepad, unsigned int analogIndex)
+{
+    switch (evt)
+    {
+    case Gamepad::BUTTON_EVENT:
+        if (_state == Control::ACTIVE)
+        {
+            if (!gamepad->isButtonDown(Gamepad::BUTTON_A) &&
+                !gamepad->isButtonDown(Gamepad::BUTTON_X))
+            {
+                RadioButton::clearSelected(_groupId);
+                _selected = true;
+                notifyListeners(Control::Listener::VALUE_CHANGED);
+            }
+        }
+        break;
+    }
+
+    return Button::gamepadEvent(evt, gamepad, analogIndex);
+}
+
 void RadioButton::clearSelected(const std::string& groupId)
 {
     std::vector<RadioButton*>::const_iterator it;
@@ -127,7 +148,7 @@ void RadioButton::clearSelected(const std::string& groupId)
         {
             radioButton->_selected = false;
             radioButton->_dirty = true;
-            radioButton->notifyListeners(Listener::VALUE_CHANGED);
+            radioButton->notifyListeners(Control::Listener::VALUE_CHANGED);
         }
     }
 }

+ 7 - 0
gameplay/src/RadioButton.h

@@ -144,6 +144,13 @@ protected:
      */
     bool touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
 
+    /**
+     * Gamepad callback on gamepad events.
+     *
+     * @see Control::gamepadEvent
+     */
+    bool gamepadEvent(Gamepad::GamepadEvent evt, Gamepad* gamepad, unsigned int analogIndex);
+
     /**
      * Called when a control's properties change.  Updates this control's internal rendering
      * properties, such as its text viewport.

+ 1 - 1
gameplay/src/ScriptController.cpp

@@ -783,7 +783,7 @@ bool ScriptController::mouseEvent(Mouse::MouseEvent evt, int x, int y, int wheel
     return false;
 }
 
-void ScriptController::gamepadEvent(Gamepad::GamepadEvent evt, Gamepad* gamepad)
+void ScriptController::gamepadEvent(Gamepad::GamepadEvent evt, Gamepad* gamepad, unsigned int analogIndex)
 {
     std::vector<std::string>& list = _callbacks[GAMEPAD_EVENT];
     for (size_t i = 0, count = list.size(); i < count; ++i)

+ 3 - 2
gameplay/src/ScriptController.h

@@ -3,7 +3,8 @@
 
 #include "Base.h"
 #include "Game.h"
-#include "Gamepad.h"
+//#include "Gamepad.h"
+#include "Control.h"
 
 namespace gameplay
 {
@@ -872,7 +873,7 @@ private:
      * @param evt The gamepad event that occurred.
      * @param gamepad the gamepad the event occurred on
      */
-    void gamepadEvent(Gamepad::GamepadEvent evt, Gamepad* gamepad);
+    void gamepadEvent(Gamepad::GamepadEvent evt, Gamepad* gamepad, unsigned int analogIndex = 0);
 
     /**
      * Calls the specified Lua function using the given parameters.

+ 146 - 11
gameplay/src/Slider.cpp

@@ -1,17 +1,21 @@
 #include "Slider.h"
+#include "Game.h"
 
 namespace gameplay
 {
 
 // Fraction of slider to scroll when mouse scrollwheel is used.
-static const float SCROLL_FRACTION = 0.1f;
+static const float SCROLLWHEEL_FRACTION = 0.1f;
+// Fraction of slider to scroll for a delta of 1.0f when a gamepad is used.
+static const float GAMEPAD_FRACTION = 0.005f;
 // Distance that a slider must be moved before it starts consuming input events,
 // e.g. to prevent its parent container from scrolling at the same time.
 static const float SLIDER_THRESHOLD = 5.0f;
 
-Slider::Slider() : _min(0.0f), _max(0.0f), _step(0.0f), _value(0.0f), _minImage(NULL),
+Slider::Slider() : _min(0.0f), _max(0.0f), _step(0.0f), _value(0.0f), _delta(0.0f), _minImage(NULL),
     _maxImage(NULL), _trackImage(NULL), _markerImage(NULL), _valueTextVisible(false),
-    _valueTextAlignment(Font::ALIGN_BOTTOM_HCENTER), _valueTextPrecision(0), _valueText("")
+    _valueTextAlignment(Font::ALIGN_BOTTOM_HCENTER), _valueTextPrecision(0), _valueText(""),
+    _selectButtonDown(false), _directionButtonDown(false), _gamepadValue(0.0f)
 {
 }
 
@@ -125,7 +129,7 @@ unsigned int Slider::getValueTextPrecision() const
 
 void Slider::addListener(Control::Listener* listener, int eventFlags)
 {
-    if ((eventFlags & Listener::TEXT_CHANGED) == Listener::TEXT_CHANGED)
+    if ((eventFlags & Control::Listener::TEXT_CHANGED) == Control::Listener::TEXT_CHANGED)
     {
         GP_ERROR("TEXT_CHANGED event is not applicable to Slider.");
     }
@@ -168,7 +172,7 @@ bool Slider::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contac
             _value = _originalValue;
             if (_value != oldValue)
             {
-                notifyListeners(Listener::VALUE_CHANGED);
+                notifyListeners(Control::Listener::VALUE_CHANGED);
             }
 
             _dirty = true;
@@ -204,9 +208,7 @@ bool Slider::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contac
             float oldValue = _value;
             _value = (markerPosition * (_max - _min)) + _min;
             if (_step > 0.0f)
-            {
-                float stepDistance = _step / (_max - _min);
-            
+            {            
                 int numSteps = round(_value / _step);
                 _value = _step * numSteps;
             }
@@ -214,7 +216,7 @@ bool Slider::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contac
             // Call the callback if our value changed.
             if (_value != oldValue)
             {
-                notifyListeners(Listener::VALUE_CHANGED);
+                notifyListeners(Control::Listener::VALUE_CHANGED);
             }
             _dirty = true;
         }
@@ -255,7 +257,7 @@ bool Slider::mouseEvent(Mouse::MouseEvent evt, int x, int y, int wheelDelta)
             {
                 float total = _max - _min;
                 float oldValue = _value;
-                _value += (total * SCROLL_FRACTION) * wheelDelta;
+                _value += (total * SCROLLWHEEL_FRACTION) * wheelDelta;
             
                 if (_value > _max)
                     _value = _max;
@@ -264,7 +266,7 @@ bool Slider::mouseEvent(Mouse::MouseEvent evt, int x, int y, int wheelDelta)
 
                 if (_value != oldValue)
                 {
-                    notifyListeners(Listener::VALUE_CHANGED);
+                    notifyListeners(Control::Listener::VALUE_CHANGED);
                 }
 
                 _dirty = true;
@@ -280,6 +282,87 @@ bool Slider::mouseEvent(Mouse::MouseEvent evt, int x, int y, int wheelDelta)
     return false;
 }
 
+bool Slider::gamepadEvent(Gamepad::GamepadEvent evt, Gamepad* gamepad, unsigned int analogIndex)
+{
+    bool eventConsumed = false;
+
+    if (_state == ACTIVE)
+    {
+        switch (evt)
+        {
+            case Gamepad::BUTTON_EVENT:
+            {
+                if (gamepad->isButtonDown(Gamepad::BUTTON_LEFT))
+                {
+                    _delta = -1.0f;
+                    _directionButtonDown = true;
+                }
+                else if (gamepad->isButtonDown(Gamepad::BUTTON_RIGHT))
+                {
+                    _delta = 1.0f;
+                    _directionButtonDown = true;
+                }
+                else if (_delta != 0.0f && _directionButtonDown)
+                {
+                    _delta = 0.0f;
+                    _directionButtonDown = false;
+                }
+
+                if (_step > 0.0f && _delta != 0.0f)
+                {
+                    _value += _step * _delta;
+                    _gamepadValue = _value - (_step * _delta * 0.49f);
+                    _delta *= 0.2f;
+                }
+
+                // A slider consumes all button events until it is no longer active.
+                eventConsumed = true;
+                _dirty = true;
+                break;
+            }
+            case Gamepad::JOYSTICK_EVENT:
+            {
+                // The left analog stick can be used to change a slider's value.
+                if (analogIndex == 0)
+                {
+                    Vector2 joy;
+                    gamepad->getJoystickValues(analogIndex, &joy);
+                    _gamepadValue = _value;
+                    _delta = joy.x;
+                    _dirty = true;
+                    eventConsumed = true;
+                }
+                break;
+            }
+        }
+    }
+
+    if (evt == Gamepad::BUTTON_EVENT && _delta == 0.0f)
+    {
+        if (gamepad->isButtonDown(Gamepad::BUTTON_A) ||
+            gamepad->isButtonDown(Gamepad::BUTTON_X))
+        {
+            _selectButtonDown = true;
+            eventConsumed |= _consumeInputEvents;
+        }
+        else if (_selectButtonDown && 
+                 !gamepad->isButtonDown(Gamepad::BUTTON_A) &&
+                 !gamepad->isButtonDown(Gamepad::BUTTON_X))
+        {
+            _selectButtonDown = false;
+
+            if (_state == FOCUS)
+                setState(ACTIVE);
+            else if (_state == ACTIVE)
+                setState(FOCUS);
+
+            eventConsumed |= _consumeInputEvents;
+        }
+    }    
+
+    return eventConsumed;
+}
+
 void Slider::update(const Control* container, const Vector2& offset)
 {
     Label::update(container, offset);
@@ -292,6 +375,58 @@ void Slider::update(const Control* container, const Vector2& offset)
     char s[32];
     sprintf(s, "%.*f", _valueTextPrecision, _value);
     _valueText = s;
+
+    if (_delta != 0.0f)
+    {
+        float oldValue = _value;
+        float total = _max - _min;
+
+        if (_step > 0.0f)
+        {
+            _gamepadValue += (total * GAMEPAD_FRACTION) * _delta;
+            int numSteps = round(_gamepadValue / _step);
+            _value = _step * numSteps;
+        }
+        else
+        {
+            _value += (total * GAMEPAD_FRACTION) * _delta;
+        }
+            
+        if (_value > _max)
+            _value = _max;
+        else if (_value < _min)
+            _value = _min;
+
+        if (_value != oldValue)
+        {
+            notifyListeners(Control::Listener::VALUE_CHANGED);
+        }
+    }
+}
+
+void Slider::draw(SpriteBatch* spriteBatch, const Rectangle& clip, bool needsClear, bool cleared, float targetHeight)
+{
+    if (needsClear)
+    {
+        GL_ASSERT( glEnable(GL_SCISSOR_TEST) );
+        GL_ASSERT( glScissor(_clearBounds.x, targetHeight - _clearBounds.y - _clearBounds.height, _clearBounds.width, _clearBounds.height) );
+        Game::getInstance()->clear(Game::CLEAR_COLOR, Vector4::zero(), 1.0f, 0);
+        GL_ASSERT( glDisable(GL_SCISSOR_TEST) );
+    }
+
+    if (!_visible)
+        return;
+
+    spriteBatch->start();
+    drawBorder(spriteBatch, clip);
+    drawImages(spriteBatch, clip);
+    spriteBatch->finish();
+
+    drawText(clip);
+    if (_delta == 0.0f)
+    {
+        _dirty = false;
+    }
 }
 
 void Slider::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)

+ 25 - 0
gameplay/src/Slider.h

@@ -214,6 +214,19 @@ protected:
      */
     bool mouseEvent(Mouse::MouseEvent evt, int x, int y, int wheelDelta);
 
+    /**
+     * Gamepad callback on gamepad events.
+     *
+     * @see Control::gamepadEvent
+     */
+    bool gamepadEvent(Gamepad::GamepadEvent evt, Gamepad* gamepad, unsigned int analogIndex);
+
+    /**
+     * Slider overrides draw() so that it can avoid resetting the _dirty flag
+     * when a joystick is being used to change its value.
+     */
+    void draw(SpriteBatch* spriteBatch, const Rectangle& clip, bool needsClear, bool cleared, float targetHeight);
+
     /**
      * Draw the images associated with this control.
      *
@@ -258,6 +271,11 @@ protected:
      */
     float _value;
 
+    /**
+     * When a gamepad is in use, this stores how much to move the slider's value.
+     */
+    float _delta;
+
     /**
      * The X coordinate of the first touch event in a sequence.
      */
@@ -318,6 +336,13 @@ protected:
      */
     std::string _valueText;
 
+    // Used by gamepads to toggle Slider state between FOCUS and ACTIVE.
+    bool _selectButtonDown;
+
+    bool _directionButtonDown;
+
+    float _gamepadValue;
+
 private:
 
     /**

+ 5 - 3
gameplay/src/TextBox.cpp

@@ -39,7 +39,7 @@ int TextBox::getLastKeypress()
 
 void TextBox::addListener(Control::Listener* listener, int eventFlags)
 {
-    if ((eventFlags & Listener::VALUE_CHANGED) == Listener::VALUE_CHANGED)
+    if ((eventFlags & Control::Listener::VALUE_CHANGED) == Control::Listener::VALUE_CHANGED)
     {
         GP_ERROR("VALUE_CHANGED event is not applicable to TextBox.");
     }
@@ -142,7 +142,7 @@ bool TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
                         font->getLocationAtIndex(_text.c_str(), _textBounds, fontSize, &_caretLocation, textIndex,
                             textAlignment, true, rightToLeft);
                         _dirty = true;
-                        notifyListeners(Listener::TEXT_CHANGED);
+                        notifyListeners(Control::Listener::TEXT_CHANGED);
                         break;
                     }
                     case Keyboard::KEY_LEFT_ARROW:
@@ -255,6 +255,8 @@ bool TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
                     case Keyboard::KEY_ESCAPE:
                         break;
                     case Keyboard::KEY_TAB:
+                        // Allow tab to move the focus forward.
+                        return false;
                         break;
                     default:
                     {
@@ -303,7 +305,7 @@ bool TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
                     break;
                 }
 
-                notifyListeners(Listener::TEXT_CHANGED);
+                notifyListeners(Control::Listener::TEXT_CHANGED);
                 break;
             }
         }

+ 1 - 1
gameplay/src/VerticalLayout.cpp

@@ -42,7 +42,7 @@ void VerticalLayout::update(const Container* container, const Vector2& offset)
 
     float yPosition = 0;
 
-    std::vector<Control*> controls = container->getControls();
+    const std::vector<Control*>& controls = container->getControls();
 
     int i, end, iter;
     if (_bottomToTop)

+ 45 - 0
gameplay/src/lua/lua_Button.cpp

@@ -33,6 +33,7 @@ void luaRegister_Button()
         {"createAnimationFromBy", lua_Button_createAnimationFromBy},
         {"createAnimationFromTo", lua_Button_createAnimationFromTo},
         {"destroyAnimation", lua_Button_destroyAnimation},
+        {"getAbsoluteBounds", lua_Button_getAbsoluteBounds},
         {"getAlignment", lua_Button_getAlignment},
         {"getAnimation", lua_Button_getAnimation},
         {"getAnimationPropertyComponentCount", lua_Button_getAnimationPropertyComponentCount},
@@ -680,6 +681,50 @@ int lua_Button_destroyAnimation(lua_State* state)
     return 0;
 }
 
+int lua_Button_getAbsoluteBounds(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 1:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA))
+            {
+                Button* instance = getInstance(state);
+                void* returnPtr = (void*)&(instance->getAbsoluteBounds());
+                if (returnPtr)
+                {
+                    gameplay::ScriptUtil::LuaObject* object = (gameplay::ScriptUtil::LuaObject*)lua_newuserdata(state, sizeof(gameplay::ScriptUtil::LuaObject));
+                    object->instance = returnPtr;
+                    object->owns = false;
+                    luaL_getmetatable(state, "Rectangle");
+                    lua_setmetatable(state, -2);
+                }
+                else
+                {
+                    lua_pushnil(state);
+                }
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_Button_getAbsoluteBounds - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
 int lua_Button_getAlignment(lua_State* state)
 {
     // Get the number of parameters.

+ 1 - 0
gameplay/src/lua/lua_Button.h

@@ -13,6 +13,7 @@ int lua_Button_createAnimation(lua_State* state);
 int lua_Button_createAnimationFromBy(lua_State* state);
 int lua_Button_createAnimationFromTo(lua_State* state);
 int lua_Button_destroyAnimation(lua_State* state);
+int lua_Button_getAbsoluteBounds(lua_State* state);
 int lua_Button_getAlignment(lua_State* state);
 int lua_Button_getAnimation(lua_State* state);
 int lua_Button_getAnimationPropertyComponentCount(lua_State* state);

+ 45 - 0
gameplay/src/lua/lua_CheckBox.cpp

@@ -34,6 +34,7 @@ void luaRegister_CheckBox()
         {"createAnimationFromBy", lua_CheckBox_createAnimationFromBy},
         {"createAnimationFromTo", lua_CheckBox_createAnimationFromTo},
         {"destroyAnimation", lua_CheckBox_destroyAnimation},
+        {"getAbsoluteBounds", lua_CheckBox_getAbsoluteBounds},
         {"getAlignment", lua_CheckBox_getAlignment},
         {"getAnimation", lua_CheckBox_getAnimation},
         {"getAnimationPropertyComponentCount", lua_CheckBox_getAnimationPropertyComponentCount},
@@ -686,6 +687,50 @@ int lua_CheckBox_destroyAnimation(lua_State* state)
     return 0;
 }
 
+int lua_CheckBox_getAbsoluteBounds(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 1:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA))
+            {
+                CheckBox* instance = getInstance(state);
+                void* returnPtr = (void*)&(instance->getAbsoluteBounds());
+                if (returnPtr)
+                {
+                    gameplay::ScriptUtil::LuaObject* object = (gameplay::ScriptUtil::LuaObject*)lua_newuserdata(state, sizeof(gameplay::ScriptUtil::LuaObject));
+                    object->instance = returnPtr;
+                    object->owns = false;
+                    luaL_getmetatable(state, "Rectangle");
+                    lua_setmetatable(state, -2);
+                }
+                else
+                {
+                    lua_pushnil(state);
+                }
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_CheckBox_getAbsoluteBounds - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
 int lua_CheckBox_getAlignment(lua_State* state)
 {
     // Get the number of parameters.

+ 1 - 0
gameplay/src/lua/lua_CheckBox.h

@@ -13,6 +13,7 @@ int lua_CheckBox_createAnimation(lua_State* state);
 int lua_CheckBox_createAnimationFromBy(lua_State* state);
 int lua_CheckBox_createAnimationFromTo(lua_State* state);
 int lua_CheckBox_destroyAnimation(lua_State* state);
+int lua_CheckBox_getAbsoluteBounds(lua_State* state);
 int lua_CheckBox_getAlignment(lua_State* state);
 int lua_CheckBox_getAnimation(lua_State* state);
 int lua_CheckBox_getAnimationPropertyComponentCount(lua_State* state);

+ 45 - 0
gameplay/src/lua/lua_Container.cpp

@@ -46,6 +46,7 @@ void luaRegister_Container()
         {"createAnimationFromBy", lua_Container_createAnimationFromBy},
         {"createAnimationFromTo", lua_Container_createAnimationFromTo},
         {"destroyAnimation", lua_Container_destroyAnimation},
+        {"getAbsoluteBounds", lua_Container_getAbsoluteBounds},
         {"getAlignment", lua_Container_getAlignment},
         {"getAnimation", lua_Container_getAnimation},
         {"getAnimationPropertyComponentCount", lua_Container_getAnimationPropertyComponentCount},
@@ -747,6 +748,50 @@ int lua_Container_destroyAnimation(lua_State* state)
     return 0;
 }
 
+int lua_Container_getAbsoluteBounds(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 1:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA))
+            {
+                Container* instance = getInstance(state);
+                void* returnPtr = (void*)&(instance->getAbsoluteBounds());
+                if (returnPtr)
+                {
+                    gameplay::ScriptUtil::LuaObject* object = (gameplay::ScriptUtil::LuaObject*)lua_newuserdata(state, sizeof(gameplay::ScriptUtil::LuaObject));
+                    object->instance = returnPtr;
+                    object->owns = false;
+                    luaL_getmetatable(state, "Rectangle");
+                    lua_setmetatable(state, -2);
+                }
+                else
+                {
+                    lua_pushnil(state);
+                }
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_Container_getAbsoluteBounds - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
 int lua_Container_getAlignment(lua_State* state)
 {
     // Get the number of parameters.

+ 1 - 0
gameplay/src/lua/lua_Container.h

@@ -14,6 +14,7 @@ int lua_Container_createAnimation(lua_State* state);
 int lua_Container_createAnimationFromBy(lua_State* state);
 int lua_Container_createAnimationFromTo(lua_State* state);
 int lua_Container_destroyAnimation(lua_State* state);
+int lua_Container_getAbsoluteBounds(lua_State* state);
 int lua_Container_getAlignment(lua_State* state);
 int lua_Container_getAnimation(lua_State* state);
 int lua_Container_getAnimationPropertyComponentCount(lua_State* state);

+ 45 - 0
gameplay/src/lua/lua_Control.cpp

@@ -30,6 +30,7 @@ void luaRegister_Control()
         {"createAnimationFromBy", lua_Control_createAnimationFromBy},
         {"createAnimationFromTo", lua_Control_createAnimationFromTo},
         {"destroyAnimation", lua_Control_destroyAnimation},
+        {"getAbsoluteBounds", lua_Control_getAbsoluteBounds},
         {"getAlignment", lua_Control_getAlignment},
         {"getAnimation", lua_Control_getAnimation},
         {"getAnimationPropertyComponentCount", lua_Control_getAnimationPropertyComponentCount},
@@ -675,6 +676,50 @@ int lua_Control_destroyAnimation(lua_State* state)
     return 0;
 }
 
+int lua_Control_getAbsoluteBounds(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 1:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA))
+            {
+                Control* instance = getInstance(state);
+                void* returnPtr = (void*)&(instance->getAbsoluteBounds());
+                if (returnPtr)
+                {
+                    gameplay::ScriptUtil::LuaObject* object = (gameplay::ScriptUtil::LuaObject*)lua_newuserdata(state, sizeof(gameplay::ScriptUtil::LuaObject));
+                    object->instance = returnPtr;
+                    object->owns = false;
+                    luaL_getmetatable(state, "Rectangle");
+                    lua_setmetatable(state, -2);
+                }
+                else
+                {
+                    lua_pushnil(state);
+                }
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_Control_getAbsoluteBounds - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
 int lua_Control_getAlignment(lua_State* state)
 {
     // Get the number of parameters.

+ 1 - 0
gameplay/src/lua/lua_Control.h

@@ -13,6 +13,7 @@ int lua_Control_createAnimation(lua_State* state);
 int lua_Control_createAnimationFromBy(lua_State* state);
 int lua_Control_createAnimationFromTo(lua_State* state);
 int lua_Control_destroyAnimation(lua_State* state);
+int lua_Control_getAbsoluteBounds(lua_State* state);
 int lua_Control_getAlignment(lua_State* state);
 int lua_Control_getAnimation(lua_State* state);
 int lua_Control_getAnimationPropertyComponentCount(lua_State* state);

+ 45 - 0
gameplay/src/lua/lua_Form.cpp

@@ -50,6 +50,7 @@ void luaRegister_Form()
         {"createAnimationFromTo", lua_Form_createAnimationFromTo},
         {"destroyAnimation", lua_Form_destroyAnimation},
         {"draw", lua_Form_draw},
+        {"getAbsoluteBounds", lua_Form_getAbsoluteBounds},
         {"getAlignment", lua_Form_getAlignment},
         {"getAnimation", lua_Form_getAnimation},
         {"getAnimationPropertyComponentCount", lua_Form_getAnimationPropertyComponentCount},
@@ -787,6 +788,50 @@ int lua_Form_draw(lua_State* state)
     return 0;
 }
 
+int lua_Form_getAbsoluteBounds(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 1:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA))
+            {
+                Form* instance = getInstance(state);
+                void* returnPtr = (void*)&(instance->getAbsoluteBounds());
+                if (returnPtr)
+                {
+                    gameplay::ScriptUtil::LuaObject* object = (gameplay::ScriptUtil::LuaObject*)lua_newuserdata(state, sizeof(gameplay::ScriptUtil::LuaObject));
+                    object->instance = returnPtr;
+                    object->owns = false;
+                    luaL_getmetatable(state, "Rectangle");
+                    lua_setmetatable(state, -2);
+                }
+                else
+                {
+                    lua_pushnil(state);
+                }
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_Form_getAbsoluteBounds - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
 int lua_Form_getAlignment(lua_State* state)
 {
     // Get the number of parameters.

+ 1 - 0
gameplay/src/lua/lua_Form.h

@@ -15,6 +15,7 @@ int lua_Form_createAnimationFromBy(lua_State* state);
 int lua_Form_createAnimationFromTo(lua_State* state);
 int lua_Form_destroyAnimation(lua_State* state);
 int lua_Form_draw(lua_State* state);
+int lua_Form_getAbsoluteBounds(lua_State* state);
 int lua_Form_getAlignment(lua_State* state);
 int lua_Form_getAnimation(lua_State* state);
 int lua_Form_getAnimationPropertyComponentCount(lua_State* state);

+ 33 - 1
gameplay/src/lua/lua_Game.cpp

@@ -404,9 +404,41 @@ int lua_Game_gamepadEvent(lua_State* state)
             lua_error(state);
             break;
         }
+        case 4:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA) &&
+                (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL) &&
+                (lua_type(state, 3) == LUA_TUSERDATA || lua_type(state, 3) == LUA_TTABLE || lua_type(state, 3) == LUA_TNIL) &&
+                lua_type(state, 4) == LUA_TNUMBER)
+            {
+                // Get parameter 1 off the stack.
+                Gamepad::GamepadEvent param1 = (Gamepad::GamepadEvent)lua_enumFromString_GamepadGamepadEvent(luaL_checkstring(state, 2));
+
+                // Get parameter 2 off the stack.
+                bool param2Valid;
+                gameplay::ScriptUtil::LuaArray<Gamepad> param2 = gameplay::ScriptUtil::getObjectPointer<Gamepad>(3, "Gamepad", false, &param2Valid);
+                if (!param2Valid)
+                {
+                    lua_pushstring(state, "Failed to convert parameter 2 to type 'Gamepad'.");
+                    lua_error(state);
+                }
+
+                // Get parameter 3 off the stack.
+                unsigned int param3 = (unsigned int)luaL_checkunsigned(state, 4);
+
+                Game* instance = getInstance(state);
+                instance->gamepadEvent(param1, param2, param3);
+                
+                return 0;
+            }
+
+            lua_pushstring(state, "lua_Game_gamepadEvent - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
         default:
         {
-            lua_pushstring(state, "Invalid number of parameters (expected 3).");
+            lua_pushstring(state, "Invalid number of parameters (expected 3 or 4).");
             lua_error(state);
             break;
         }

+ 45 - 0
gameplay/src/lua/lua_ImageControl.cpp

@@ -31,6 +31,7 @@ void luaRegister_ImageControl()
         {"createAnimationFromBy", lua_ImageControl_createAnimationFromBy},
         {"createAnimationFromTo", lua_ImageControl_createAnimationFromTo},
         {"destroyAnimation", lua_ImageControl_destroyAnimation},
+        {"getAbsoluteBounds", lua_ImageControl_getAbsoluteBounds},
         {"getAlignment", lua_ImageControl_getAlignment},
         {"getAnimation", lua_ImageControl_getAnimation},
         {"getAnimationPropertyComponentCount", lua_ImageControl_getAnimationPropertyComponentCount},
@@ -682,6 +683,50 @@ int lua_ImageControl_destroyAnimation(lua_State* state)
     return 0;
 }
 
+int lua_ImageControl_getAbsoluteBounds(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 1:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA))
+            {
+                ImageControl* instance = getInstance(state);
+                void* returnPtr = (void*)&(instance->getAbsoluteBounds());
+                if (returnPtr)
+                {
+                    gameplay::ScriptUtil::LuaObject* object = (gameplay::ScriptUtil::LuaObject*)lua_newuserdata(state, sizeof(gameplay::ScriptUtil::LuaObject));
+                    object->instance = returnPtr;
+                    object->owns = false;
+                    luaL_getmetatable(state, "Rectangle");
+                    lua_setmetatable(state, -2);
+                }
+                else
+                {
+                    lua_pushnil(state);
+                }
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_ImageControl_getAbsoluteBounds - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
 int lua_ImageControl_getAlignment(lua_State* state)
 {
     // Get the number of parameters.

+ 1 - 0
gameplay/src/lua/lua_ImageControl.h

@@ -13,6 +13,7 @@ int lua_ImageControl_createAnimation(lua_State* state);
 int lua_ImageControl_createAnimationFromBy(lua_State* state);
 int lua_ImageControl_createAnimationFromTo(lua_State* state);
 int lua_ImageControl_destroyAnimation(lua_State* state);
+int lua_ImageControl_getAbsoluteBounds(lua_State* state);
 int lua_ImageControl_getAlignment(lua_State* state);
 int lua_ImageControl_getAnimation(lua_State* state);
 int lua_ImageControl_getAnimationPropertyComponentCount(lua_State* state);

+ 45 - 0
gameplay/src/lua/lua_Joystick.cpp

@@ -31,6 +31,7 @@ void luaRegister_Joystick()
         {"createAnimationFromBy", lua_Joystick_createAnimationFromBy},
         {"createAnimationFromTo", lua_Joystick_createAnimationFromTo},
         {"destroyAnimation", lua_Joystick_destroyAnimation},
+        {"getAbsoluteBounds", lua_Joystick_getAbsoluteBounds},
         {"getAlignment", lua_Joystick_getAlignment},
         {"getAnimation", lua_Joystick_getAnimation},
         {"getAnimationPropertyComponentCount", lua_Joystick_getAnimationPropertyComponentCount},
@@ -685,6 +686,50 @@ int lua_Joystick_destroyAnimation(lua_State* state)
     return 0;
 }
 
+int lua_Joystick_getAbsoluteBounds(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 1:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA))
+            {
+                Joystick* instance = getInstance(state);
+                void* returnPtr = (void*)&(instance->getAbsoluteBounds());
+                if (returnPtr)
+                {
+                    gameplay::ScriptUtil::LuaObject* object = (gameplay::ScriptUtil::LuaObject*)lua_newuserdata(state, sizeof(gameplay::ScriptUtil::LuaObject));
+                    object->instance = returnPtr;
+                    object->owns = false;
+                    luaL_getmetatable(state, "Rectangle");
+                    lua_setmetatable(state, -2);
+                }
+                else
+                {
+                    lua_pushnil(state);
+                }
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_Joystick_getAbsoluteBounds - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
 int lua_Joystick_getAlignment(lua_State* state)
 {
     // Get the number of parameters.

+ 1 - 0
gameplay/src/lua/lua_Joystick.h

@@ -13,6 +13,7 @@ int lua_Joystick_createAnimation(lua_State* state);
 int lua_Joystick_createAnimationFromBy(lua_State* state);
 int lua_Joystick_createAnimationFromTo(lua_State* state);
 int lua_Joystick_destroyAnimation(lua_State* state);
+int lua_Joystick_getAbsoluteBounds(lua_State* state);
 int lua_Joystick_getAlignment(lua_State* state);
 int lua_Joystick_getAnimation(lua_State* state);
 int lua_Joystick_getAnimationPropertyComponentCount(lua_State* state);

+ 45 - 0
gameplay/src/lua/lua_Label.cpp

@@ -31,6 +31,7 @@ void luaRegister_Label()
         {"createAnimationFromBy", lua_Label_createAnimationFromBy},
         {"createAnimationFromTo", lua_Label_createAnimationFromTo},
         {"destroyAnimation", lua_Label_destroyAnimation},
+        {"getAbsoluteBounds", lua_Label_getAbsoluteBounds},
         {"getAlignment", lua_Label_getAlignment},
         {"getAnimation", lua_Label_getAnimation},
         {"getAnimationPropertyComponentCount", lua_Label_getAnimationPropertyComponentCount},
@@ -679,6 +680,50 @@ int lua_Label_destroyAnimation(lua_State* state)
     return 0;
 }
 
+int lua_Label_getAbsoluteBounds(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 1:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA))
+            {
+                Label* instance = getInstance(state);
+                void* returnPtr = (void*)&(instance->getAbsoluteBounds());
+                if (returnPtr)
+                {
+                    gameplay::ScriptUtil::LuaObject* object = (gameplay::ScriptUtil::LuaObject*)lua_newuserdata(state, sizeof(gameplay::ScriptUtil::LuaObject));
+                    object->instance = returnPtr;
+                    object->owns = false;
+                    luaL_getmetatable(state, "Rectangle");
+                    lua_setmetatable(state, -2);
+                }
+                else
+                {
+                    lua_pushnil(state);
+                }
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_Label_getAbsoluteBounds - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
 int lua_Label_getAlignment(lua_State* state)
 {
     // Get the number of parameters.

+ 1 - 0
gameplay/src/lua/lua_Label.h

@@ -13,6 +13,7 @@ int lua_Label_createAnimation(lua_State* state);
 int lua_Label_createAnimationFromBy(lua_State* state);
 int lua_Label_createAnimationFromTo(lua_State* state);
 int lua_Label_destroyAnimation(lua_State* state);
+int lua_Label_getAbsoluteBounds(lua_State* state);
 int lua_Label_getAlignment(lua_State* state);
 int lua_Label_getAnimation(lua_State* state);
 int lua_Label_getAnimationPropertyComponentCount(lua_State* state);

+ 45 - 0
gameplay/src/lua/lua_RadioButton.cpp

@@ -34,6 +34,7 @@ void luaRegister_RadioButton()
         {"createAnimationFromBy", lua_RadioButton_createAnimationFromBy},
         {"createAnimationFromTo", lua_RadioButton_createAnimationFromTo},
         {"destroyAnimation", lua_RadioButton_destroyAnimation},
+        {"getAbsoluteBounds", lua_RadioButton_getAbsoluteBounds},
         {"getAlignment", lua_RadioButton_getAlignment},
         {"getAnimation", lua_RadioButton_getAnimation},
         {"getAnimationPropertyComponentCount", lua_RadioButton_getAnimationPropertyComponentCount},
@@ -688,6 +689,50 @@ int lua_RadioButton_destroyAnimation(lua_State* state)
     return 0;
 }
 
+int lua_RadioButton_getAbsoluteBounds(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 1:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA))
+            {
+                RadioButton* instance = getInstance(state);
+                void* returnPtr = (void*)&(instance->getAbsoluteBounds());
+                if (returnPtr)
+                {
+                    gameplay::ScriptUtil::LuaObject* object = (gameplay::ScriptUtil::LuaObject*)lua_newuserdata(state, sizeof(gameplay::ScriptUtil::LuaObject));
+                    object->instance = returnPtr;
+                    object->owns = false;
+                    luaL_getmetatable(state, "Rectangle");
+                    lua_setmetatable(state, -2);
+                }
+                else
+                {
+                    lua_pushnil(state);
+                }
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_RadioButton_getAbsoluteBounds - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
 int lua_RadioButton_getAlignment(lua_State* state)
 {
     // Get the number of parameters.

+ 1 - 0
gameplay/src/lua/lua_RadioButton.h

@@ -13,6 +13,7 @@ int lua_RadioButton_createAnimation(lua_State* state);
 int lua_RadioButton_createAnimationFromBy(lua_State* state);
 int lua_RadioButton_createAnimationFromTo(lua_State* state);
 int lua_RadioButton_destroyAnimation(lua_State* state);
+int lua_RadioButton_getAbsoluteBounds(lua_State* state);
 int lua_RadioButton_getAlignment(lua_State* state);
 int lua_RadioButton_getAnimation(lua_State* state);
 int lua_RadioButton_getAnimationPropertyComponentCount(lua_State* state);

+ 45 - 0
gameplay/src/lua/lua_Slider.cpp

@@ -32,6 +32,7 @@ void luaRegister_Slider()
         {"createAnimationFromBy", lua_Slider_createAnimationFromBy},
         {"createAnimationFromTo", lua_Slider_createAnimationFromTo},
         {"destroyAnimation", lua_Slider_destroyAnimation},
+        {"getAbsoluteBounds", lua_Slider_getAbsoluteBounds},
         {"getAlignment", lua_Slider_getAlignment},
         {"getAnimation", lua_Slider_getAnimation},
         {"getAnimationPropertyComponentCount", lua_Slider_getAnimationPropertyComponentCount},
@@ -694,6 +695,50 @@ int lua_Slider_destroyAnimation(lua_State* state)
     return 0;
 }
 
+int lua_Slider_getAbsoluteBounds(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 1:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA))
+            {
+                Slider* instance = getInstance(state);
+                void* returnPtr = (void*)&(instance->getAbsoluteBounds());
+                if (returnPtr)
+                {
+                    gameplay::ScriptUtil::LuaObject* object = (gameplay::ScriptUtil::LuaObject*)lua_newuserdata(state, sizeof(gameplay::ScriptUtil::LuaObject));
+                    object->instance = returnPtr;
+                    object->owns = false;
+                    luaL_getmetatable(state, "Rectangle");
+                    lua_setmetatable(state, -2);
+                }
+                else
+                {
+                    lua_pushnil(state);
+                }
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_Slider_getAbsoluteBounds - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
 int lua_Slider_getAlignment(lua_State* state)
 {
     // Get the number of parameters.

+ 1 - 0
gameplay/src/lua/lua_Slider.h

@@ -13,6 +13,7 @@ int lua_Slider_createAnimation(lua_State* state);
 int lua_Slider_createAnimationFromBy(lua_State* state);
 int lua_Slider_createAnimationFromTo(lua_State* state);
 int lua_Slider_destroyAnimation(lua_State* state);
+int lua_Slider_getAbsoluteBounds(lua_State* state);
 int lua_Slider_getAlignment(lua_State* state);
 int lua_Slider_getAnimation(lua_State* state);
 int lua_Slider_getAnimationPropertyComponentCount(lua_State* state);

+ 45 - 0
gameplay/src/lua/lua_TextBox.cpp

@@ -32,6 +32,7 @@ void luaRegister_TextBox()
         {"createAnimationFromBy", lua_TextBox_createAnimationFromBy},
         {"createAnimationFromTo", lua_TextBox_createAnimationFromTo},
         {"destroyAnimation", lua_TextBox_destroyAnimation},
+        {"getAbsoluteBounds", lua_TextBox_getAbsoluteBounds},
         {"getAlignment", lua_TextBox_getAlignment},
         {"getAnimation", lua_TextBox_getAnimation},
         {"getAnimationPropertyComponentCount", lua_TextBox_getAnimationPropertyComponentCount},
@@ -681,6 +682,50 @@ int lua_TextBox_destroyAnimation(lua_State* state)
     return 0;
 }
 
+int lua_TextBox_getAbsoluteBounds(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 1:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA))
+            {
+                TextBox* instance = getInstance(state);
+                void* returnPtr = (void*)&(instance->getAbsoluteBounds());
+                if (returnPtr)
+                {
+                    gameplay::ScriptUtil::LuaObject* object = (gameplay::ScriptUtil::LuaObject*)lua_newuserdata(state, sizeof(gameplay::ScriptUtil::LuaObject));
+                    object->instance = returnPtr;
+                    object->owns = false;
+                    luaL_getmetatable(state, "Rectangle");
+                    lua_setmetatable(state, -2);
+                }
+                else
+                {
+                    lua_pushnil(state);
+                }
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_TextBox_getAbsoluteBounds - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
 int lua_TextBox_getAlignment(lua_State* state)
 {
     // Get the number of parameters.

+ 1 - 0
gameplay/src/lua/lua_TextBox.h

@@ -13,6 +13,7 @@ int lua_TextBox_createAnimation(lua_State* state);
 int lua_TextBox_createAnimationFromBy(lua_State* state);
 int lua_TextBox_createAnimationFromTo(lua_State* state);
 int lua_TextBox_destroyAnimation(lua_State* state);
+int lua_TextBox_getAbsoluteBounds(lua_State* state);
 int lua_TextBox_getAlignment(lua_State* state);
 int lua_TextBox_getAnimation(lua_State* state);
 int lua_TextBox_getAnimationPropertyComponentCount(lua_State* state);