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

Merge branch 'bugfix' of https://github.com/ablake/GamePlay into bugfix

Adam Blake 12 лет назад
Родитель
Сommit
40a8856580

+ 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";

+ 2 - 0
gameplay/src/Button.h

@@ -85,6 +85,8 @@ protected:
      */
     bool touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
 
+    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);

+ 2 - 0
gameplay/src/CheckBox.h

@@ -131,6 +131,8 @@ protected:
      */
     bool touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
 
+    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.

+ 534 - 52
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,10 @@ 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), _focusChangeStartTime(0), _focusChangeRepeatDelay(FOCUS_CHANGE_REPEAT_DELAY), _focusChangeCount(0),
+      _totalWidth(0), _totalHeight(0),
       _contactIndices(0), _initializedWithScroll(false)
 {
 }
@@ -182,20 +191,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 +198,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 +215,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 +448,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 +627,504 @@ bool Container::keyEvent(Keyboard::KeyEvent evt, int key)
             continue;
         }
 
-        if (control->getState() == Control::FOCUS)
+        if (control->getState() == Control::FOCUS && 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;
+}
+
+bool Container::moveFocus(Direction direction, Control* outsideControl)
+{
+    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)
+            {
+                control->setState(Control::NORMAL);
+                start = control;
+                break;
+            }
+        }
+    }
+
+    int focusIndex = 0;
+    Control* next = NULL;
+    if (start)
+    {
+        const Rectangle& startBounds = start->getAbsoluteBounds();
+        Vector2 vStart, vNext;
+        float distance = FLT_MAX;
+
+        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;
+        }
+
+        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)
+                {
+                    // 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);
+                _focusPressed = 0;
+                return true;
+            }
+            
+            // 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 (control->keyEvent(evt, key))
+            if (((Container*)next)->moveFocus(direction, start))
+            {
+                _focusChangeStartTime = 0;
+                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);
+        }    
+
+        _focusChangeStartTime = Game::getAbsoluteTime();
+        if (outsideControl && outsideControl->_parent)
+        {
+            _focusPressed = direction;
+            _focusChangeCount = outsideControl->_parent->_focusChangeCount;
+            _focusChangeRepeatDelay = outsideControl->_parent->_focusChangeRepeatDelay;
+        }
+        addRef();
+        Game::getInstance()->schedule(_focusChangeRepeatDelay, this);
+
+        return true;
+    }
+
+    return false;
+}
+
+void Container::timeEvent(long timeDiff, void* cookie)
+{
+    double time = Game::getAbsoluteTime();
+    if (_focusPressed && abs(time - timeDiff - _focusChangeRepeatDelay - _focusChangeStartTime) < 50)
+    {
+        ++_focusChangeCount;
+        if (_focusChangeCount == 5)
+        {
+            _focusChangeRepeatDelay *= 0.5;
+        }
+
+        moveFocus((Direction)_focusPressed);
+    }
+    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();
+
+    // 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)
+        {
+            if (control->gamepadEvent(evt, gamepad, analogIndex))
             {
                 release();
                 return true;
             }
-            else if (evt == Keyboard::KEY_CHAR && key == Keyboard::KEY_TAB)
+        }
+    }
+
+    // First check if a selection button is down.
+    if (!_selectButtonDown)
+    {
+        if (gamepad->isButtonDown(Gamepad::BUTTON_A) ||
+            gamepad->isButtonDown(Gamepad::BUTTON_X))
+        {
+            _selectButtonDown = true;
+        }
+    }
+    else
+    {
+        if (!gamepad->isButtonDown(Gamepad::BUTTON_A) &&
+            !gamepad->isButtonDown(Gamepad::BUTTON_X))
+        {
+            _selectButtonDown = false;
+        }
+    }
+
+    bool eventConsumed = false;
+
+    // Don't allow focus changes or scrolling while a selection button is down.
+    if (!_selectButtonDown)
+    {
+        switch (evt)
+        {
+            case Gamepad::BUTTON_EVENT:
             {
-                // Shift focus to next control.
-                int focusIndex = control->getFocusIndex() + 1; // Index to search for.
-                if (focusIndex > _focusIndexMax)
+                // Shift focus forward or backward when the DPad is used.
+                if (!(_focusPressed & DOWN) &&
+                    gamepad->isButtonDown(Gamepad::BUTTON_DOWN))
                 {
-                    focusIndex = 0;
+                    _focusPressed |= DOWN;
+                    if (!moveFocus(DOWN))
+                    {
+                        startScrolling(0, -GAMEPAD_SCROLL_SPEED);
+                    }
+                    eventConsumed |= _consumeInputEvents;
+                }
+                else if ((_focusPressed & DOWN) &&
+                            !gamepad->isButtonDown(Gamepad::BUTTON_DOWN))
+                {
+                    _focusPressed &= ~DOWN;
+                    eventConsumed |= _consumeInputEvents;
+                }
+                    
+                if (!(_focusPressed & RIGHT) &&
+                    gamepad->isButtonDown(Gamepad::BUTTON_RIGHT))
+                {
+                    _focusPressed |= RIGHT;
+                    if (!moveFocus(RIGHT))
+                    {
+                        startScrolling(GAMEPAD_SCROLL_SPEED, 0);
+                    }
+                    eventConsumed |= _consumeInputEvents;
+                }
+                else if ((_focusPressed & RIGHT) &&
+                    !gamepad->isButtonDown(Gamepad::BUTTON_RIGHT))
+                {
+                    _focusPressed &= ~RIGHT;
+                    eventConsumed |= _consumeInputEvents;
                 }
-                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))
                 {
-                    Control* nextControl = *itt;
-                    if (nextControl->getFocusIndex() == focusIndex)
+                    _focusPressed |= UP;
+                    if (!moveFocus(UP))
                     {
-                        nextControl->setState(Control::FOCUS);
-                        release();
-                        return _consumeInputEvents;
+                        startScrolling(0, GAMEPAD_SCROLL_SPEED);
                     }
+                    eventConsumed |= _consumeInputEvents;
+                }
+                else if ((_focusPressed & UP) &&
+                    !gamepad->isButtonDown(Gamepad::BUTTON_UP))
+                {
+                    _focusPressed &= ~UP;
+                    eventConsumed |= _consumeInputEvents;
+                }
+
+                if (!(_focusPressed & LEFT) &&
+                    gamepad->isButtonDown(Gamepad::BUTTON_LEFT))
+                {
+                    _focusPressed |= LEFT;
+                    if (!moveFocus(LEFT))
+                    {
+                        startScrolling(-GAMEPAD_SCROLL_SPEED, 0);
+                    }
+                    eventConsumed |= _consumeInputEvents;
+                }
+                else if ((_focusPressed & LEFT) &&
+                    !gamepad->isButtonDown(Gamepad::BUTTON_LEFT))
+                {
+                    _focusPressed &= ~LEFT;
+                    eventConsumed |= _consumeInputEvents;
+                }
+                break;
+            }
+            case Gamepad::JOYSTICK_EVENT:
+            {
+                Vector2 joystick;
+                gamepad->getJoystickValues(analogIndex, &joystick);
+
+                switch (analogIndex)
+                {
+                case 0:
+                    // The left analog stick can be used in the same way as the DPad.
+                    if (!(_focusPressed & RIGHT) &&
+                        joystick.x > JOYSTICK_THRESHOLD)
+                    {
+                        _focusPressed |= RIGHT;
+                        if (!moveFocus(RIGHT))
+                        {
+                            startScrolling(GAMEPAD_SCROLL_SPEED * joystick.x, 0);
+                        }
+                    }
+                    else if (_focusPressed & RIGHT &&
+                        joystick.x < JOYSTICK_THRESHOLD)
+                    {
+                        _focusPressed &= ~RIGHT;
+                    }
+
+                    if (!(_focusPressed & DOWN) &&
+                        joystick.y < -JOYSTICK_THRESHOLD)
+                    {
+                        _focusPressed |= DOWN;
+                        if (!moveFocus(DOWN))
+                        {
+                            startScrolling(0, GAMEPAD_SCROLL_SPEED * joystick.y);
+                        }
+                    }
+                    else if (_focusPressed & DOWN &&
+                                joystick.y > -JOYSTICK_THRESHOLD)
+                    {
+                        _focusPressed &= ~DOWN;
+                    }
+
+                    if (!(_focusPressed & LEFT) &&
+                        joystick.x < -JOYSTICK_THRESHOLD)
+                    {
+                        _focusPressed |= LEFT;
+                        if (!moveFocus(LEFT))
+                        {
+                            startScrolling(GAMEPAD_SCROLL_SPEED * joystick.x, 0);
+                        }
+                    }
+                    else if (_focusPressed & LEFT &&
+                                joystick.x > -JOYSTICK_THRESHOLD)
+                    {
+                        _focusPressed &= ~LEFT;
+                    }
+                        
+                    if (!(_focusPressed & UP) &&
+                        joystick.y > JOYSTICK_THRESHOLD)
+                    {
+                        _focusPressed |= UP;
+                        if (!moveFocus(UP))
+                        {
+                            startScrolling(0, GAMEPAD_SCROLL_SPEED * joystick.y);
+                        }
+                    }
+                    else if (_focusPressed & UP &&
+                                joystick.y < JOYSTICK_THRESHOLD)
+                    {
+                        _focusPressed &= ~UP;
+                    }
+                    eventConsumed |= _consumeInputEvents;
+                    break;
+
+                case 1:
+                    // The right analog stick can be used to scroll.
+                    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;
                 }
             }
         }
     }
+    else
+    {
+        eventConsumed |= _consumeInputEvents;
+    }
+
+    if (!_focusPressed && _scrolling)
+    {
+        stopScrolling();
+    }
 
     release();
-    return false;
+    return eventConsumed;
 }
 
 bool Container::isContainer() const
@@ -700,11 +1168,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 +1208,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 +1216,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 +1283,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)

+ 39 - 3
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,8 @@ public:
      */
     virtual void setAnimationPropertyValue(int propertyId, AnimationValue* value, float blendWeight = 1.0f);
 
+    void timeEvent(long timeDiff, void* cookie);
+
 protected:
 
     /**
@@ -295,6 +298,8 @@ protected:
      */
     virtual bool mouseEvent(Mouse::MouseEvent evt, int x, int y, int wheelDelta);
 
+    virtual bool gamepadEvent(Gamepad::GamepadEvent evt, Gamepad* gamepad, unsigned int analogIndex);
+
     /**
      * Gets a Layout::Type enum from a matching string.
      *
@@ -334,6 +339,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 +525,40 @@ 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);
+
+    // 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.
+    double _focusChangeStartTime;
+    double _focusChangeRepeatDelay;
+    unsigned int _focusChangeCount;
 
     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);

+ 33 - 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,8 @@ public:
      */
     const Rectangle& getBounds() const;
 
+    const Rectangle& getAbsoluteBounds() const;
+
     /**
      * Get the x coordinate of this control's bounds.
      *
@@ -746,6 +749,7 @@ public:
      * @param listener The listener to add.
      * @param eventFlags The events to listen for.
      */
+    //virtual void addListener(Control::Listener* listener, int eventFlags);
     virtual void addListener(Control::Listener* listener, int eventFlags);
 
     /**
@@ -841,6 +845,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 +863,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 +936,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 +1025,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 +1086,8 @@ 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, Listener::EventType eventType);
+    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)

+ 4 - 4
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);
 
@@ -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.
      */

+ 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}

+ 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);
         }
     }
 }

+ 2 - 0
gameplay/src/RadioButton.h

@@ -144,6 +144,8 @@ protected:
      */
     bool touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
 
+    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

@@ -780,7 +780,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 |= _consumeInputEvents;
+                _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 |= _consumeInputEvents;
+                }
+                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)

+ 20 - 0
gameplay/src/Slider.h

@@ -214,6 +214,14 @@ protected:
      */
     bool mouseEvent(Mouse::MouseEvent evt, int x, int y, int wheelDelta);
 
+    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 +266,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 +331,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)

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

@@ -130,6 +130,7 @@ void luaRegister_Container()
         {"setVisible", lua_Container_setVisible},
         {"setWidth", lua_Container_setWidth},
         {"setZIndex", lua_Container_setZIndex},
+        {"timeEvent", lua_Container_timeEvent},
         {NULL, NULL}
     };
     const luaL_Reg lua_statics[] = 
@@ -5000,4 +5001,44 @@ int lua_Container_static_create(lua_State* state)
     return 0;
 }
 
+int lua_Container_timeEvent(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 3:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA) &&
+                lua_type(state, 2) == LUA_TNUMBER &&
+                lua_type(state, 3) == LUA_TNONE)
+            {
+                // Get parameter 1 off the stack.
+                long param1 = (long)luaL_checklong(state, 2);
+
+                // Get parameter 2 off the stack.
+                void* param2 = (void*)luaL_checkint(state, 3);
+
+                Container* instance = getInstance(state);
+                instance->timeEvent(param1, param2);
+                
+                return 0;
+            }
+
+            lua_pushstring(state, "lua_Container_timeEvent - 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_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
 }

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

@@ -107,6 +107,7 @@ int lua_Container_static_ANIMATE_SIZE(lua_State* state);
 int lua_Container_static_ANIMATE_SIZE_HEIGHT(lua_State* state);
 int lua_Container_static_ANIMATE_SIZE_WIDTH(lua_State* state);
 int lua_Container_static_create(lua_State* state);
+int lua_Container_timeEvent(lua_State* state);
 
 void luaRegister_Container();
 

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

@@ -136,6 +136,7 @@ void luaRegister_Form()
         {"setVisible", lua_Form_setVisible},
         {"setWidth", lua_Form_setWidth},
         {"setZIndex", lua_Form_setZIndex},
+        {"timeEvent", lua_Form_timeEvent},
         {"update", lua_Form_update},
         {NULL, NULL}
     };
@@ -5203,6 +5204,46 @@ int lua_Form_static_getForm(lua_State* state)
     return 0;
 }
 
+int lua_Form_timeEvent(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 3:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA) &&
+                lua_type(state, 2) == LUA_TNUMBER &&
+                lua_type(state, 3) == LUA_TNONE)
+            {
+                // Get parameter 1 off the stack.
+                long param1 = (long)luaL_checklong(state, 2);
+
+                // Get parameter 2 off the stack.
+                void* param2 = (void*)luaL_checkint(state, 3);
+
+                Form* instance = getInstance(state);
+                instance->timeEvent(param1, param2);
+                
+                return 0;
+            }
+
+            lua_pushstring(state, "lua_Form_timeEvent - 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_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
 int lua_Form_update(lua_State* state)
 {
     // Get the number of parameters.

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

@@ -111,6 +111,7 @@ int lua_Form_static_ANIMATE_SIZE_HEIGHT(lua_State* state);
 int lua_Form_static_ANIMATE_SIZE_WIDTH(lua_State* state);
 int lua_Form_static_create(lua_State* state);
 int lua_Form_static_getForm(lua_State* state);
+int lua_Form_timeEvent(lua_State* state);
 int lua_Form_update(lua_State* state);
 
 void luaRegister_Form();

+ 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;
         }