소스 검색

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

Adam Blake 12 년 전
부모
커밋
40a8856580

+ 1 - 1
gameplay/src/AbsoluteLayout.cpp

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

+ 8 - 0
gameplay/src/AnimationClip.cpp

@@ -556,6 +556,8 @@ bool AnimationClip::update(float elapsedTime)
 
 
 void AnimationClip::onBegin()
 void AnimationClip::onBegin()
 {
 {
+    addRef();
+
     // Initialize animation to play.
     // Initialize animation to play.
     setClipStateBit(CLIP_IS_STARTED_BIT);
     setClipStateBit(CLIP_IS_STARTED_BIT);
     if (_speed >= 0)
     if (_speed >= 0)
@@ -584,10 +586,14 @@ void AnimationClip::onBegin()
             listener++;
             listener++;
         }
         }
     }
     }
+
+    release();
 }
 }
 
 
 void AnimationClip::onEnd()
 void AnimationClip::onEnd()
 {
 {
+    addRef();
+
     _blendWeight = 1.0f;
     _blendWeight = 1.0f;
     resetClipStateBit(CLIP_ALL_BITS);
     resetClipStateBit(CLIP_ALL_BITS);
 
 
@@ -602,6 +608,8 @@ void AnimationClip::onEnd()
             listener++;
             listener++;
         }
         }
     }
     }
+
+    release();
 }
 }
 
 
 bool AnimationClip::isClipStateBitSet(unsigned char bit) const
 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);
         AnimationClip* clip = (*clipIter);
         GP_ASSERT(clip);
         GP_ASSERT(clip);
+        clip->addRef();
         if (clip->isClipStateBitSet(AnimationClip::CLIP_IS_RESTARTED_BIT))
         if (clip->isClipStateBitSet(AnimationClip::CLIP_IS_RESTARTED_BIT))
         {   // If the CLIP_IS_RESTARTED_BIT is set, we should end the clip and 
         {   // 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.
             // 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))
         else if (clip->update(elapsedTime))
         {
         {
-            SAFE_RELEASE(clip);
+            clip->release();
             clipIter = _runningClips.erase(clipIter);
             clipIter = _runningClips.erase(clipIter);
         }
         }
         else
         else
         {
         {
             clipIter++;
             clipIter++;
         }
         }
+        clip->release();
     }
     }
 
 
     Transform::resumeTransformChanged();
     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)
                 y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height)
             {
             {
                 _contactIndex = (int) contactIndex;
                 _contactIndex = (int) contactIndex;
-
                 setState(Control::ACTIVE);
                 setState(Control::ACTIVE);
-
-                notifyListeners(Listener::PRESS);
-
+                notifyListeners(Control::Listener::PRESS);
                 return _consumeInputEvents;
                 return _consumeInputEvents;
             }
             }
             else
             else
@@ -68,14 +65,13 @@ bool Button::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contac
         if (_contactIndex == (int) contactIndex)
         if (_contactIndex == (int) contactIndex)
         {
         {
             _contactIndex = INVALID_CONTACT_INDEX;
             _contactIndex = INVALID_CONTACT_INDEX;
-            notifyListeners(Listener::RELEASE);
+            notifyListeners(Control::Listener::RELEASE);
             if (!_parent->isScrolling() &&
             if (!_parent->isScrolling() &&
                 x > _clipBounds.x && x <= _clipBounds.x + _clipBounds.width &&
                 x > _clipBounds.x && x <= _clipBounds.x + _clipBounds.width &&
                 y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height)
                 y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height)
             {
             {
                 setState(Control::FOCUS);
                 setState(Control::FOCUS);
-
-                notifyListeners(Listener::CLICK);
+                notifyListeners(Control::Listener::CLICK);
             }
             }
             else
             else
             {
             {
@@ -93,6 +89,40 @@ bool Button::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contac
     return false;
     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
 const char* Button::getType() const
 {
 {
     return "button";
     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);
     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
      * @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);
     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)
 void CheckBox::update(const Control* container, const Vector2& offset)
 {
 {
     Label::update(container, 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 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
      * Called when a control's properties change.  Updates this control's internal rendering
      * properties, such as its text viewport.
      * properties, such as its text viewport.

+ 534 - 52
gameplay/src/Container.cpp

@@ -10,8 +10,8 @@
 #include "RadioButton.h"
 #include "RadioButton.h"
 #include "Slider.h"
 #include "Slider.h"
 #include "TextBox.h"
 #include "TextBox.h"
-#include "ImageControl.h"
 #include "Joystick.h"
 #include "Joystick.h"
+#include "ImageControl.h"
 #include "Game.h"
 #include "Game.h"
 
 
 namespace gameplay
 namespace gameplay
@@ -23,6 +23,12 @@ static const long SCROLL_INERTIA_DELAY = 100L;
 static const float SCROLL_FRICTION_FACTOR = 5.0f;
 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.
 // 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;
 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.
  * Sort function for use with _controls.sort(), based on Z-Order.
@@ -43,7 +49,10 @@ Container::Container()
       _scrollingVelocity(Vector2::zero()), _scrollingFriction(1.0f),
       _scrollingVelocity(Vector2::zero()), _scrollingFriction(1.0f),
       _scrollingRight(false), _scrollingDown(false),
       _scrollingRight(false), _scrollingDown(false),
       _scrollingMouseVertically(false), _scrollingMouseHorizontally(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)
       _contactIndices(0), _initializedWithScroll(false)
 {
 {
 }
 }
@@ -182,20 +191,6 @@ void Container::addControls(Theme* theme, Properties* properties)
         {
         {
             addControl(control);
             addControl(control);
             control->release();
             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.
         // Get the next control.
@@ -203,7 +198,7 @@ void Container::addControls(Theme* theme, Properties* properties)
     }
     }
 
 
     // Sort controls by Z-Order.
     // Sort controls by Z-Order.
-    std::sort(_controls.begin(), _controls.end(), &sortControlsByZOrder);
+    sortControls();
 }
 }
 
 
 Layout* Container::getLayout()
 Layout* Container::getLayout()
@@ -220,11 +215,26 @@ unsigned int Container::addControl(Control* control)
         control->_parent->removeControl(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)
     if (control->_parent != this)
     {
     {
         _controls.push_back(control);
         _controls.push_back(control);
         control->addRef();
         control->addRef();
         control->_parent = this;
         control->_parent = this;
+        sortControls();
         return (unsigned int)(_controls.size() - 1);
         return (unsigned int)(_controls.size() - 1);
     }
     }
     else
     else
@@ -438,12 +448,6 @@ void Container::update(const Control* container, const Vector2& offset)
         _viewportClipBounds.width -= _scrollBarVertical->getRegion().width;
         _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);
     GP_ASSERT(_layout);
     if (_scroll != SCROLL_NONE)
     if (_scroll != SCROLL_NONE)
     {
     {
@@ -623,40 +627,504 @@ bool Container::keyEvent(Keyboard::KeyEvent evt, int key)
             continue;
             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();
                 release();
                 return true;
                 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();
     release();
-    return false;
+    return eventConsumed;
 }
 }
 
 
 bool Container::isContainer() const
 bool Container::isContainer() const
@@ -700,11 +1168,14 @@ void Container::updateScroll()
         _layout->update(this, _scrollPosition);
         _layout->update(this, _scrollPosition);
     }
     }
 
 
-    // Update Time.
-    static double lastFrameTime = Game::getGameTime();
+    // Update time.
+    if (!_lastFrameTime)
+    {
+        _lastFrameTime = Game::getGameTime();
+    }
     double frameTime = 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::Border& containerBorder = getBorder(_state);
     const Theme::Padding& containerPadding = getPadding();
     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;
     float clipHeight = _bounds.height - containerBorder.top - containerBorder.bottom - containerPadding.top - containerPadding.bottom - hHeight;
 
 
     // Apply and dampen inertia.
     // Apply and dampen inertia.
-    if (!_scrolling && !_scrollingVelocity.isZero())
+    if (!_scrollingVelocity.isZero())
     {
     {
         // Calculate the time passed since last update.
         // Calculate the time passed since last update.
         float elapsedSecs = (float)elapsedTime * 0.001f;
         float elapsedSecs = (float)elapsedTime * 0.001f;
@@ -745,14 +1216,17 @@ void Container::updateScroll()
         _scrollPosition.x += _scrollingVelocity.x * elapsedSecs;
         _scrollPosition.x += _scrollingVelocity.x * elapsedSecs;
         _scrollPosition.y += _scrollingVelocity.y * 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.
     // Stop scrolling when the far edge is reached.
@@ -809,6 +1283,14 @@ void Container::updateScroll()
     _layout->update(this, _scrollPosition);
     _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)
 bool Container::touchEventScroll(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
 {
 {
     switch(evt)
     switch(evt)

+ 39 - 3
gameplay/src/Container.h

@@ -3,6 +3,7 @@
 
 
 #include "Control.h"
 #include "Control.h"
 #include "Layout.h"
 #include "Layout.h"
+#include "TimeListener.h"
 
 
 namespace gameplay
 namespace gameplay
 {
 {
@@ -44,7 +45,7 @@ namespace gameplay
     }
     }
  @endverbatim
  @endverbatim
  */
  */
-class Container : public Control
+class Container : public Control, TimeListener
 {
 {
 
 
 public:
 public:
@@ -212,6 +213,8 @@ public:
      */
      */
     virtual void setAnimationPropertyValue(int propertyId, AnimationValue* value, float blendWeight = 1.0f);
     virtual void setAnimationPropertyValue(int propertyId, AnimationValue* value, float blendWeight = 1.0f);
 
 
+    void timeEvent(long timeDiff, void* cookie);
+
 protected:
 protected:
 
 
     /**
     /**
@@ -295,6 +298,8 @@ protected:
      */
      */
     virtual bool mouseEvent(Mouse::MouseEvent evt, int x, int y, int wheelDelta);
     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.
      * Gets a Layout::Type enum from a matching string.
      *
      *
@@ -334,6 +339,13 @@ protected:
      */
      */
     void updateScroll();
     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.
      * Applies touch events to scroll state.
      *
      *
@@ -513,16 +525,40 @@ private:
      */
      */
     Container(const Container& copy);
     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;
     AnimationClip* _scrollBarOpacityClip;
     int _zIndexDefault;
     int _zIndexDefault;
     int _focusIndexDefault;
     int _focusIndexDefault;
     int _focusIndexMax;
     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 _totalWidth;
     float _totalHeight;
     float _totalHeight;
-
     int _contactIndices;
     int _contactIndices;
-
     bool _initializedWithScroll;
     bool _initializedWithScroll;
 };
 };
 
 

+ 70 - 30
gameplay/src/Control.cpp

@@ -8,7 +8,7 @@ namespace gameplay
 Control::Control()
 Control::Control()
     : _id(""), _state(Control::NORMAL), _bounds(Rectangle::empty()), _clipBounds(Rectangle::empty()), _viewportClipBounds(Rectangle::empty()),
     : _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),
     _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]");
     addScriptEvent("controlEvent", "<Control>[Control::Listener::EventType]");
 }
 }
@@ -17,9 +17,9 @@ Control::~Control()
 {
 {
     if (_listeners)
     if (_listeners)
     {
     {
-        for (std::map<Listener::EventType, std::list<Listener*>*>::const_iterator itr = _listeners->begin(); itr != _listeners->end(); ++itr)
+        for (std::map<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(list);
         }
         }
         SAFE_DELETE(_listeners);
         SAFE_DELETE(_listeners);
@@ -187,6 +187,11 @@ const Rectangle& Control::getBounds() const
     return _bounds;
     return _bounds;
 }
 }
 
 
+const Rectangle& Control::getAbsoluteBounds() const
+{
+    return _absoluteBounds;
+}
+
 float Control::getX() const
 float Control::getX() const
 {
 {
     return _bounds.x;
     return _bounds.x;
@@ -691,29 +696,29 @@ void Control::addListener(Control::Listener* listener, int eventFlags)
 {
 {
     GP_ASSERT(listener);
     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)
     if (_listeners == NULL || listener == NULL)
         return;
         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);
         itr->second->remove(listener);
 
 
         if(itr->second->empty())
         if(itr->second->empty())
         {
         {
-            std::list<Listener*>* list = itr->second;
+            std::list<Control::Listener*>* list = itr->second;
             _listeners->erase(itr++);
             _listeners->erase(itr++);
             SAFE_DELETE(list);
             SAFE_DELETE(list);
         }
         }
@@ -740,23 +745,23 @@ void Control::removeListener(Control::Listener* listener)
         SAFE_DELETE(_listeners);
         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);
     GP_ASSERT(listener);
 
 
     if (!_listeners)
     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())
     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);
         itr = _listeners->find(eventType);
     }
     }
 
 
-    std::list<Listener*>* listenerList = itr->second;
+    std::list<Control::Listener*>* listenerList = itr->second;
     listenerList->push_back(listener);
     listenerList->push_back(listener);
 }
 }
 
 
@@ -773,7 +778,7 @@ bool Control::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int conta
         {
         {
             _contactIndex = (int) contactIndex;
             _contactIndex = (int) contactIndex;
 
 
-            notifyListeners(Listener::PRESS);
+            notifyListeners(Control::Listener::PRESS);
 
 
             return _consumeInputEvents;
             return _consumeInputEvents;
         }
         }
@@ -789,15 +794,15 @@ bool Control::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int conta
         {
         {
             _contactIndex = INVALID_CONTACT_INDEX;
             _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 &&
             if (x > _clipBounds.x && x <= _clipBounds.x + _clipBounds.width &&
                 y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height)
                 y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height)
             {
             {
                 // Leave this control in the FOCUS state.
                 // Leave this control in the FOCUS state.
-                notifyListeners(Listener::CLICK);
+                notifyListeners(Control::Listener::CLICK);
             }
             }
 
 
             return _consumeInputEvents;
             return _consumeInputEvents;
@@ -834,7 +839,42 @@ bool Control::mouseEvent(Mouse::MouseEvent evt, int x, int y, int wheelDelta)
     return false;
     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.
     // This method runs untrusted code by notifying listeners of events.
     // If the user calls exit() or otherwise releases this control, we
     // If the user calls exit() or otherwise releases this control, we
@@ -843,11 +883,11 @@ void Control::notifyListeners(Listener::EventType eventType)
 
 
     if (_listeners)
     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())
         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);
                 GP_ASSERT(*listenerItr);
                 (*listenerItr)->controlEvent(this, eventType);
                 (*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);
             spriteBatch->draw(rightX, _absoluteBounds.y, border.right, border.top, topRight.u1, topRight.v1, topRight.u2, topRight.v2, skinColor, clip);
         if (border.left)
         if (border.left)
             spriteBatch->draw(_absoluteBounds.x, midY, border.left, midHeight, left.u1, left.v1, left.u2, left.v2, skinColor, clip);
             spriteBatch->draw(_absoluteBounds.x, midY, border.left, midHeight, left.u1, left.v1, left.u2, left.v2, skinColor, clip);
-        
+
         // Always draw the background.
         // Always draw the background.
         spriteBatch->draw(_absoluteBounds.x + border.left, _absoluteBounds.y + border.top, _bounds.width - border.left - border.right, _bounds.height - border.top - border.bottom,
         spriteBatch->draw(_absoluteBounds.x + border.left, _absoluteBounds.y + border.top, _bounds.width - border.left - border.right, _bounds.height - border.top - border.bottom,
             center.u1, center.v1, center.u2, center.v2, skinColor, clip);
             center.u1, center.v1, center.u2, center.v2, skinColor, clip);

+ 33 - 17
gameplay/src/Control.h

@@ -11,6 +11,7 @@
 #include "Keyboard.h"
 #include "Keyboard.h"
 #include "Mouse.h"
 #include "Mouse.h"
 #include "ScriptTarget.h"
 #include "ScriptTarget.h"
+#include "Gamepad.h"
 
 
 namespace gameplay
 namespace gameplay
 {
 {
@@ -85,12 +86,6 @@ public:
         ALIGN_BOTTOM_RIGHT = ALIGN_BOTTOM | ALIGN_RIGHT
         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()
      * Implement Control::Listener and call Control::addListener()
      * in order to listen for events on controls.
      * in order to listen for events on controls.
@@ -156,6 +151,12 @@ public:
         virtual void controlEvent(Control* control, EventType evt) = 0;
         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
      * Position animation property. Data = x, y
      */
      */
@@ -244,6 +245,8 @@ public:
      */
      */
     const Rectangle& getBounds() const;
     const Rectangle& getBounds() const;
 
 
+    const Rectangle& getAbsoluteBounds() const;
+
     /**
     /**
      * Get the x coordinate of this control's bounds.
      * Get the x coordinate of this control's bounds.
      *
      *
@@ -746,6 +749,7 @@ public:
      * @param listener The listener to add.
      * @param listener The listener to add.
      * @param eventFlags The events to listen for.
      * @param eventFlags The events to listen for.
      */
      */
+    //virtual void addListener(Control::Listener* listener, int eventFlags);
     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);
     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
      * Called when a control's properties change.  Updates this control's internal rendering
      * properties, such as its text viewport.
      * properties, such as its text viewport.
@@ -850,6 +863,14 @@ protected:
      */
      */
     virtual void update(const Control* container, const Vector2& offset);
     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.
      * Draw the images associated with this control.
      *
      *
@@ -915,7 +936,8 @@ protected:
      *
      *
      * @param eventType The event to trigger.
      * @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.
      * Gets the Alignment by string.
@@ -1003,7 +1025,8 @@ protected:
     /**
     /**
      * Listeners map of EventType's to a list of Listeners.
      * 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.
      * The Control's Theme::Style.
@@ -1063,15 +1086,8 @@ private:
 
 
     Theme::Skin* getSkin(State state);
     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;
     bool _styleOverridden;
     Theme::Skin* _skin;
     Theme::Skin* _skin;

+ 119 - 119
gameplay/src/Form.cpp

@@ -389,138 +389,138 @@ void Form::setNode(Node* node)
 }
 }
 
 
 void Form::update(float elapsedTime)
 void Form::update(float elapsedTime)
-{
-    updateBounds();
-}
-
-void Form::updateBounds()
 {
 {
     if (isDirty())
     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
         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;
         x = 0;
+    }
+
+    if (y < 0)
+    {
+        height += y;
+        y = -y;
+    }
+    else
+    {
         y = 0;
         y = 0;
-        _absoluteBounds.set(x, y, _bounds.width, _bounds.height);
+    }
 
 
-        // Calculate the absolute viewport bounds. Absolute bounds minus border and padding.
-        const Theme::Border& border = getBorder(_state);
-        const Theme::Padding& padding = getPadding();
+    _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.
     // Check for a collision with each Form in __forms.
     // Pass the event on.
     // Pass the event on.
-    bool eventConsumed = false;
     size_t size = __forms.size();
     size_t size = __forms.size();
     for (size_t i = 0; i < size; ++i)
     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 &&
                          point.y <= bounds.y + bounds.height))
                          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))
                         y <= bounds.y + bounds.height))
                 {
                 {
                     // Pass on the event's position relative to the form.
                     // 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)
 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 Form::mouseEventInternal(Mouse::MouseEvent evt, int x, int y, int wheelDelta)
 {
 {
-    bool eventConsumed = false;
-
     for (size_t i = 0; i < __forms.size(); ++i)
     for (size_t i = 0; i < __forms.size(); ++i)
     {
     {
         Form* form = __forms[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 &&
                          point.y <= bounds.y + bounds.height))
                          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))
                         y <= bounds.y + bounds.height))
                 {
                 {
                     // Pass on the event's position relative to the form.
                     // 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 Form::gamepadEventInternal(Gamepad::GamepadEvent evt, Gamepad* gamepad, unsigned int analogIndex)
 {
 {
-    bool eventConsumed = false;
-
     for (size_t i = 0; i < __forms.size(); ++i)
     for (size_t i = 0; i < __forms.size(); ++i)
     {
     {
         Form* form = __forms[i];
         Form* form = __forms[i];
         GP_ASSERT(form);
         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)
 bool Form::projectPoint(int x, int y, Vector3* point)

+ 4 - 4
gameplay/src/Game.cpp

@@ -317,12 +317,12 @@ void Game::frame()
         // Update gamepads.
         // Update gamepads.
         Gamepad::updateInternal(elapsedTime);
         Gamepad::updateInternal(elapsedTime);
 
 
-        // Update forms.
-        Form::updateInternal(elapsedTime);
-
         // Application Update.
         // Application Update.
         update(elapsedTime);
         update(elapsedTime);
 
 
+        // Update forms.
+        Form::updateInternal(elapsedTime);
+
         // Run script update.
         // Run script update.
         _scriptController->update(elapsedTime);
         _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);
     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 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.
      * 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)
     if (buttons != _buttons)
     {
     {
         _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)
     if (_joysticks[index].x != x || _joysticks[index].y != y)
     {
     {
         _joysticks[index].set(x, 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)
     if (_triggers[index] != value)
     {
     {
         _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.
      * Constructor.
      */
      */
     Image();
     Image();
-        
+
     /**
     /**
      * Destructor.
      * 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)
 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.");
         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;
                 float dy = 0.0f;
 
 
                 _contactIndex = (int) contactIndex;
                 _contactIndex = (int) contactIndex;
-                notifyListeners(Listener::PRESS);
+                notifyListeners(Control::Listener::PRESS);
 
 
                 // Get the displacement of the touch from the centre.
                 // Get the displacement of the touch from the centre.
                 if (!_relative)
                 if (!_relative)
@@ -168,7 +168,7 @@ bool Joystick::touchEvent(Touch::TouchEvent touchEvent, int x, int y, unsigned i
                 {
                 {
                     _value.set(value);
                     _value.set(value);
                     _dirty = true;
                     _dirty = true;
-                    notifyListeners(Listener::VALUE_CHANGED);
+                    notifyListeners(Control::Listener::VALUE_CHANGED);
                 }
                 }
 
 
                 _state = ACTIVE;
                 _state = ACTIVE;
@@ -203,7 +203,7 @@ bool Joystick::touchEvent(Touch::TouchEvent touchEvent, int x, int y, unsigned i
                 {
                 {
                     _value.set(value);
                     _value.set(value);
                     _dirty = true;
                     _dirty = true;
-                    notifyListeners(Listener::VALUE_CHANGED);
+                    notifyListeners(Control::Listener::VALUE_CHANGED);
                 }
                 }
 
 
                 return _consumeInputEvents;
                 return _consumeInputEvents;
@@ -216,7 +216,7 @@ bool Joystick::touchEvent(Touch::TouchEvent touchEvent, int x, int y, unsigned i
             {
             {
                 _contactIndex = INVALID_CONTACT_INDEX;
                 _contactIndex = INVALID_CONTACT_INDEX;
 
 
-                notifyListeners(Listener::RELEASE);
+                notifyListeners(Control::Listener::RELEASE);
 
 
                 // Reset displacement and direction vectors.
                 // Reset displacement and direction vectors.
                 _displacement.set(0.0f, 0.0f);
                 _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);
                     _value.set(value);
                     _dirty = true;
                     _dirty = true;
-                    notifyListeners(Listener::VALUE_CHANGED);
+                    notifyListeners(Control::Listener::VALUE_CHANGED);
                 }
                 }
 
 
                 _state = NORMAL;
                 _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)
 void Joystick::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
 {
 {
     GP_ASSERT(spriteBatch);
     GP_ASSERT(spriteBatch);
-    spriteBatch->start();
 
 
     // If the joystick is not absolute, then only draw if it is active.
     // If the joystick is not absolute, then only draw if it is active.
     if (!_relative || (_relative && _state == 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->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
 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->_id = id;
     label->setStyle(style);
     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;
     return label;
 }
 }
 
 
@@ -28,9 +34,8 @@ Label* Label::create(Theme::Style* style, Properties* properties)
 {
 {
     Label* label = new Label();
     Label* label = new Label();
     label->initialize(style, properties);
     label->initialize(style, properties);
+
     label->_consumeInputEvents = false;
     label->_consumeInputEvents = false;
-    
-    // Ensure that labels cannot receive focus.
     label->_focusIndex = -2;
     label->_focusIndex = -2;
 
 
     return label;
     return label;
@@ -41,7 +46,6 @@ void Label::initialize(Theme::Style* style, Properties* properties)
     GP_ASSERT(properties);
     GP_ASSERT(properties);
 
 
     Control::initialize(style, properties);
     Control::initialize(style, properties);
-
     const char* text = properties->getString("text");
     const char* text = properties->getString("text");
     if (text)
     if (text)
     {
     {
@@ -51,11 +55,11 @@ void Label::initialize(Theme::Style* style, Properties* properties)
 
 
 void Label::addListener(Control::Listener* listener, int eventFlags)
 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.");
         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.");
         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->drawText(_text.c_str(), _textBounds, _textColor, getFontSize(_state), getTextAlignment(_state), true, getTextRightToLeft(_state), &_viewportClipBounds);
         _font->finish();
         _font->finish();
     }
     }
-
-    _dirty = false;
 }
 }
 
 
 const char* Label::getType() const
 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,
 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)
                                              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);
     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.
      * Internal method used only from static code in various platform implementation.
      *
      *
      * @script{ignore}
      * @script{ignore}
@@ -308,7 +315,8 @@ public:
     static void gamepadEventConnectedInternal(GamepadHandle handle, unsigned int buttonCount, unsigned int joystickCount, unsigned int triggerCount,
     static void gamepadEventConnectedInternal(GamepadHandle handle, unsigned int buttonCount, unsigned int joystickCount, unsigned int triggerCount,
                                               unsigned int vendorId, unsigned int productId, 
                                               unsigned int vendorId, unsigned int productId, 
                                               const char* vendorString, const char* productString);
                                               const char* vendorString, const char* productString);
-   /**
+
+    /**
      * Internal method used only from static code in various platform implementation.
      * Internal method used only from static code in various platform implementation.
      *
      *
      * @script{ignore}
      * @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_BUTTON_COUNT = 14;
 static const unsigned int XINPUT_JOYSTICK_COUNT = 2;
 static const unsigned int XINPUT_JOYSTICK_COUNT = 2;
 static const unsigned int XINPUT_TRIGGER_COUNT = 2;
 static const unsigned int XINPUT_TRIGGER_COUNT = 2;
-#endif
 
 
-#ifdef USE_XINPUT
 static XINPUT_STATE __xInputState;
 static XINPUT_STATE __xInputState;
 static bool __connectedXInput[4];
 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");
                 Platform::gamepadEventConnectedInternal(i, XINPUT_BUTTON_COUNT, XINPUT_JOYSTICK_COUNT, XINPUT_TRIGGER_COUNT, 0, 0, "Microsoft", "XBox360 Controller");
                 __connectedXInput[i] = true;
                 __connectedXInput[i] = true;
             }
             }
-
         }
         }
     }
     }
 #endif
 #endif
@@ -1245,13 +1242,15 @@ void Platform::pollGamepadState(Gamepad* gamepad)
         };
         };
 
 
         const unsigned int *mapping = xInputMapping;
         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)
             if (buttons & 1)
             {
             {
-                gamepad->_buttons |= (1 << *mapping);
+                mappedButtons |= (1 << *mapping);
             }
             }
         }
         }
+        gamepad->setButtons(mappedButtons);
 
 
         unsigned int i;
         unsigned int i;
         for (i = 0; i < gamepad->_joystickCount; ++i)
         for (i = 0; i < gamepad->_joystickCount; ++i)
@@ -1272,7 +1271,7 @@ void Platform::pollGamepadState(Gamepad* gamepad)
                 break;
                 break;
             }
             }
 
 
-            gamepad->_joysticks[i].set(x, y);
+            gamepad->setJoystickValue(i, x, y);
         }
         }
 
 
         for (i = 0; i < gamepad->_triggerCount; ++i)
         for (i = 0; i < gamepad->_triggerCount; ++i)
@@ -1292,20 +1291,17 @@ void Platform::pollGamepadState(Gamepad* gamepad)
 
 
             if (trigger < XINPUT_GAMEPAD_TRIGGER_THRESHOLD)
             if (trigger < XINPUT_GAMEPAD_TRIGGER_THRESHOLD)
             {
             {
-                gamepad->_triggers[i] = 0.0f;
+                gamepad->setTriggerValue(i, 0.0f);
             }
             }
             else
             else
             {
             {
-                gamepad->_triggers[i] = (float)trigger / 255.0f;
+                gamepad->setTriggerValue(i, (float)trigger / 255.0f);
             }
             }
         }
         }
     }
     }
 }
 }
 #else
 #else
-void Platform::pollGamepadState(Gamepad* gamepad)
-{
-    // TODO: Support generic HID gamepads (including XBox controllers) without requiring XInput.
-}
+void Platform::pollGamepadState(Gamepad* gamepad) { }
 #endif
 #endif
 
 
 void Platform::shutdownInternal()
 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)
 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.");
         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);
                         RadioButton::clearSelected(_groupId);
                         _selected = true;
                         _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);
     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)
 void RadioButton::clearSelected(const std::string& groupId)
 {
 {
     std::vector<RadioButton*>::const_iterator it;
     std::vector<RadioButton*>::const_iterator it;
@@ -127,7 +148,7 @@ void RadioButton::clearSelected(const std::string& groupId)
         {
         {
             radioButton->_selected = false;
             radioButton->_selected = false;
             radioButton->_dirty = true;
             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 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
      * Called when a control's properties change.  Updates this control's internal rendering
      * properties, such as its text viewport.
      * 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;
     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];
     std::vector<std::string>& list = _callbacks[GAMEPAD_EVENT];
     for (size_t i = 0, count = list.size(); i < count; ++i)
     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 "Base.h"
 #include "Game.h"
 #include "Game.h"
-#include "Gamepad.h"
+//#include "Gamepad.h"
+#include "Control.h"
 
 
 namespace gameplay
 namespace gameplay
 {
 {
@@ -872,7 +873,7 @@ private:
      * @param evt The gamepad event that occurred.
      * @param evt The gamepad event that occurred.
      * @param gamepad the gamepad the event occurred on
      * @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.
      * Calls the specified Lua function using the given parameters.

+ 146 - 11
gameplay/src/Slider.cpp

@@ -1,17 +1,21 @@
 #include "Slider.h"
 #include "Slider.h"
+#include "Game.h"
 
 
 namespace gameplay
 namespace gameplay
 {
 {
 
 
 // Fraction of slider to scroll when mouse scrollwheel is used.
 // 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,
 // 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.
 // e.g. to prevent its parent container from scrolling at the same time.
 static const float SLIDER_THRESHOLD = 5.0f;
 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),
     _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)
 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.");
         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;
             _value = _originalValue;
             if (_value != oldValue)
             if (_value != oldValue)
             {
             {
-                notifyListeners(Listener::VALUE_CHANGED);
+                notifyListeners(Control::Listener::VALUE_CHANGED);
             }
             }
 
 
             _dirty = true;
             _dirty = true;
@@ -204,9 +208,7 @@ bool Slider::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contac
             float oldValue = _value;
             float oldValue = _value;
             _value = (markerPosition * (_max - _min)) + _min;
             _value = (markerPosition * (_max - _min)) + _min;
             if (_step > 0.0f)
             if (_step > 0.0f)
-            {
-                float stepDistance = _step / (_max - _min);
-            
+            {            
                 int numSteps = round(_value / _step);
                 int numSteps = round(_value / _step);
                 _value = _step * numSteps;
                 _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.
             // Call the callback if our value changed.
             if (_value != oldValue)
             if (_value != oldValue)
             {
             {
-                notifyListeners(Listener::VALUE_CHANGED);
+                notifyListeners(Control::Listener::VALUE_CHANGED);
             }
             }
             _dirty = true;
             _dirty = true;
         }
         }
@@ -255,7 +257,7 @@ bool Slider::mouseEvent(Mouse::MouseEvent evt, int x, int y, int wheelDelta)
             {
             {
                 float total = _max - _min;
                 float total = _max - _min;
                 float oldValue = _value;
                 float oldValue = _value;
-                _value += (total * SCROLL_FRACTION) * wheelDelta;
+                _value += (total * SCROLLWHEEL_FRACTION) * wheelDelta;
             
             
                 if (_value > _max)
                 if (_value > _max)
                     _value = _max;
                     _value = _max;
@@ -264,7 +266,7 @@ bool Slider::mouseEvent(Mouse::MouseEvent evt, int x, int y, int wheelDelta)
 
 
                 if (_value != oldValue)
                 if (_value != oldValue)
                 {
                 {
-                    notifyListeners(Listener::VALUE_CHANGED);
+                    notifyListeners(Control::Listener::VALUE_CHANGED);
                 }
                 }
 
 
                 _dirty = true;
                 _dirty = true;
@@ -280,6 +282,87 @@ bool Slider::mouseEvent(Mouse::MouseEvent evt, int x, int y, int wheelDelta)
     return false;
     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)
 void Slider::update(const Control* container, const Vector2& offset)
 {
 {
     Label::update(container, offset);
     Label::update(container, offset);
@@ -292,6 +375,58 @@ void Slider::update(const Control* container, const Vector2& offset)
     char s[32];
     char s[32];
     sprintf(s, "%.*f", _valueTextPrecision, _value);
     sprintf(s, "%.*f", _valueTextPrecision, _value);
     _valueText = s;
     _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)
 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 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.
      * Draw the images associated with this control.
      *
      *
@@ -258,6 +266,11 @@ protected:
      */
      */
     float _value;
     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.
      * The X coordinate of the first touch event in a sequence.
      */
      */
@@ -318,6 +331,13 @@ protected:
      */
      */
     std::string _valueText;
     std::string _valueText;
 
 
+    // Used by gamepads to toggle Slider state between FOCUS and ACTIVE.
+    bool _selectButtonDown;
+
+    bool _directionButtonDown;
+
+    float _gamepadValue;
+
 private:
 private:
 
 
     /**
     /**

+ 5 - 3
gameplay/src/TextBox.cpp

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

+ 1 - 1
gameplay/src/VerticalLayout.cpp

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

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

@@ -130,6 +130,7 @@ void luaRegister_Container()
         {"setVisible", lua_Container_setVisible},
         {"setVisible", lua_Container_setVisible},
         {"setWidth", lua_Container_setWidth},
         {"setWidth", lua_Container_setWidth},
         {"setZIndex", lua_Container_setZIndex},
         {"setZIndex", lua_Container_setZIndex},
+        {"timeEvent", lua_Container_timeEvent},
         {NULL, NULL}
         {NULL, NULL}
     };
     };
     const luaL_Reg lua_statics[] = 
     const luaL_Reg lua_statics[] = 
@@ -5000,4 +5001,44 @@ int lua_Container_static_create(lua_State* state)
     return 0;
     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_HEIGHT(lua_State* state);
 int lua_Container_static_ANIMATE_SIZE_WIDTH(lua_State* state);
 int lua_Container_static_ANIMATE_SIZE_WIDTH(lua_State* state);
 int lua_Container_static_create(lua_State* state);
 int lua_Container_static_create(lua_State* state);
+int lua_Container_timeEvent(lua_State* state);
 
 
 void luaRegister_Container();
 void luaRegister_Container();
 
 

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

@@ -136,6 +136,7 @@ void luaRegister_Form()
         {"setVisible", lua_Form_setVisible},
         {"setVisible", lua_Form_setVisible},
         {"setWidth", lua_Form_setWidth},
         {"setWidth", lua_Form_setWidth},
         {"setZIndex", lua_Form_setZIndex},
         {"setZIndex", lua_Form_setZIndex},
+        {"timeEvent", lua_Form_timeEvent},
         {"update", lua_Form_update},
         {"update", lua_Form_update},
         {NULL, NULL}
         {NULL, NULL}
     };
     };
@@ -5203,6 +5204,46 @@ int lua_Form_static_getForm(lua_State* state)
     return 0;
     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)
 int lua_Form_update(lua_State* state)
 {
 {
     // Get the number of parameters.
     // 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_ANIMATE_SIZE_WIDTH(lua_State* state);
 int lua_Form_static_create(lua_State* state);
 int lua_Form_static_create(lua_State* state);
 int lua_Form_static_getForm(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);
 int lua_Form_update(lua_State* state);
 
 
 void luaRegister_Form();
 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);
             lua_error(state);
             break;
             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:
         default:
         {
         {
-            lua_pushstring(state, "Invalid number of parameters (expected 3).");
+            lua_pushstring(state, "Invalid number of parameters (expected 3 or 4).");
             lua_error(state);
             lua_error(state);
             break;
             break;
         }
         }