Преглед на файлове

- Large overhaul to how focus and control state changes are managed within the UI system.
- Changed the default value of consumeInputEvents to true for all controls. This property now prevents controls from receiving input at all when set to false.
- Only one control can now be in focus at a time (a control's parent containers are no longer in focus when the child is in focus).
- Various other fixes and improvements to UI.

sgrenier преди 12 години
родител
ревизия
6adf21a905
променени са 60 файла, в които са добавени 1822 реда и са изтрити 2140 реда
  1. 5 101
      gameplay/src/Button.cpp
  2. 5 29
      gameplay/src/Button.h
  3. 16 40
      gameplay/src/CheckBox.cpp
  4. 5 21
      gameplay/src/CheckBox.h
  5. 73 622
      gameplay/src/Container.cpp
  6. 18 84
      gameplay/src/Container.h
  7. 145 168
      gameplay/src/Control.cpp
  8. 70 13
      gameplay/src/Control.h
  9. 7 0
      gameplay/src/Font.cpp
  10. 8 0
      gameplay/src/Font.h
  11. 447 87
      gameplay/src/Form.cpp
  12. 33 1
      gameplay/src/Form.h
  13. 0 1
      gameplay/src/Gamepad.cpp
  14. 0 2
      gameplay/src/ImageControl.cpp
  15. 26 20
      gameplay/src/Joystick.cpp
  16. 5 0
      gameplay/src/Joystick.h
  17. 3 2
      gameplay/src/Keyboard.h
  18. 9 11
      gameplay/src/Label.cpp
  19. 4 9
      gameplay/src/PlatformWindows.cpp
  20. 22 53
      gameplay/src/RadioButton.cpp
  21. 5 21
      gameplay/src/RadioButton.h
  22. 94 164
      gameplay/src/Slider.cpp
  23. 8 0
      gameplay/src/Slider.h
  24. 1 1
      gameplay/src/TerrainPatch.cpp
  25. 197 236
      gameplay/src/TextBox.cpp
  26. 26 11
      gameplay/src/TextBox.h
  27. 46 37
      gameplay/src/lua/lua_Button.cpp
  28. 1 1
      gameplay/src/lua/lua_Button.h
  29. 46 37
      gameplay/src/lua/lua_CheckBox.cpp
  30. 1 1
      gameplay/src/lua/lua_CheckBox.h
  31. 46 37
      gameplay/src/lua/lua_Container.cpp
  32. 1 1
      gameplay/src/lua/lua_Container.h
  33. 46 37
      gameplay/src/lua/lua_Control.cpp
  34. 1 1
      gameplay/src/lua/lua_Control.h
  35. 1 0
      gameplay/src/lua/lua_ControlListener.cpp
  36. 83 37
      gameplay/src/lua/lua_Form.cpp
  37. 2 1
      gameplay/src/lua/lua_Form.h
  38. 46 37
      gameplay/src/lua/lua_ImageControl.cpp
  39. 1 1
      gameplay/src/lua/lua_ImageControl.h
  40. 46 37
      gameplay/src/lua/lua_Joystick.cpp
  41. 1 1
      gameplay/src/lua/lua_Joystick.h
  42. 46 37
      gameplay/src/lua/lua_Label.cpp
  43. 1 1
      gameplay/src/lua/lua_Label.h
  44. 46 37
      gameplay/src/lua/lua_RadioButton.cpp
  45. 1 1
      gameplay/src/lua/lua_RadioButton.h
  46. 46 37
      gameplay/src/lua/lua_Slider.cpp
  47. 1 1
      gameplay/src/lua/lua_Slider.h
  48. 46 37
      gameplay/src/lua/lua_TextBox.cpp
  49. 1 1
      gameplay/src/lua/lua_TextBox.h
  50. 15 0
      samples/browser/res/common/default.theme
  51. 1 1
      samples/browser/res/common/forms/formSelect.form
  52. 4 3
      samples/browser/res/common/gamepad.form
  53. 0 3
      samples/browser/res/common/text.form
  54. 1 0
      samples/browser/sample-browser.vcxproj.user
  55. 8 8
      samples/browser/src/FormsSample.cpp
  56. 2 3
      samples/browser/src/SamplesGame.cpp
  57. 1 3
      samples/particles/res/editor.form
  58. 1 1
      samples/particles/src/ParticlesGame.cpp
  59. 1 1
      samples/racer/src/RacerGame.cpp
  60. 0 2
      samples/spaceship/src/SpaceshipGame.cpp

+ 5 - 101
gameplay/src/Button.cpp

@@ -39,107 +39,6 @@ Button* Button::create(Theme::Style* style, Properties* properties)
     return button;
 }
 
-bool Button::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
-{
-    switch (evt)
-    {
-    case Touch::TOUCH_PRESS:
-        if (_contactIndex == INVALID_CONTACT_INDEX)
-        {
-            if (x > _clipBounds.x && x <= _clipBounds.x + _clipBounds.width &&
-                y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height)
-            {
-                _contactIndex = (int) contactIndex;
-                setState(Control::ACTIVE);
-                notifyListeners(Control::Listener::PRESS);
-                return _consumeInputEvents;
-            }
-            else
-            {
-                setState(Control::NORMAL);
-            }
-        }
-        break;
-
-    case Touch::TOUCH_RELEASE:
-        if (_contactIndex == (int) contactIndex)
-        {
-            _contactIndex = INVALID_CONTACT_INDEX;
-            notifyListeners(Control::Listener::RELEASE);
-            if (!_parent->isScrolling() &&
-                x > _clipBounds.x && x <= _clipBounds.x + _clipBounds.width &&
-                y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height)
-            {
-                setState(Control::FOCUS);
-                notifyListeners(Control::Listener::CLICK);
-            }
-            else
-            {
-                setState(Control::NORMAL);
-            }
-            return _consumeInputEvents;
-        }
-        break;
-    case Touch::TOUCH_MOVE:
-        return Control::touchEvent(evt, x, y, contactIndex);
-    }
-
-    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;
-}
-
-bool Button::keyEvent(Keyboard::KeyEvent evt, int key)
-{
-    if (evt == Keyboard::KEY_PRESS && key == Keyboard::KEY_RETURN)
-    {
-        notifyListeners(Control::Listener::PRESS);
-        setState(Control::ACTIVE);
-        return _consumeInputEvents;
-    }
-    else if (_state == ACTIVE && evt == Keyboard::KEY_RELEASE && key == Keyboard::KEY_RETURN)
-    {
-        notifyListeners(Control::Listener::RELEASE);
-        notifyListeners(Control::Listener::CLICK);
-        setState(Control::FOCUS);
-        return _consumeInputEvents;
-    }
-
-    return false;
-}
-
 const char* Button::getType() const
 {
     return "button";
@@ -155,4 +54,9 @@ void Button::setDataBinding(unsigned int dataBinding)
     _dataBinding = dataBinding;
 }
 
+bool Button::canFocus() const
+{
+    return true;
+}
+
 }

+ 5 - 29
gameplay/src/Button.h

@@ -49,6 +49,11 @@ public:
      */
     static Button* create(const char* id, Theme::Style* style);
 
+    /**
+     * @see Control#canFocus()
+     */
+    bool canFocus() const;
+
 protected:
 
     /**
@@ -71,35 +76,6 @@ protected:
      */
     static Button* create(Theme::Style* style, Properties* properties);
 
-    /**
-     * Touch callback on touch events.  Controls return true if they consume the touch event.
-     *
-     * @param evt The touch event that occurred.
-     * @param x The x position of the touch in pixels. Left edge is zero.
-     * @param y The y position of the touch in pixels. Top edge is zero.
-     * @param contactIndex The order of occurrence for multiple touch contacts starting at zero.
-     *
-     * @return Whether the touch event was consumed by the control.
-     *
-     * @see Touch::TouchEvent
-     */
-    bool touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
-
-    /**
-     * Gamepad callback on gamepad events.
-     *
-     * @see Control::gamepadEvent
-     */
-    virtual bool gamepadEvent(Gamepad::GamepadEvent evt, Gamepad* gamepad, unsigned int analogIndex);
-
-    /**
-     * Keyboard callback on key events.
-     *
-     * @see Keyboard::KeyEvent
-     * @see Keyboard::Key
-     */
-    virtual bool keyEvent(Keyboard::KeyEvent evt, int key);
-
     /**
      * @see Control::getType
      */

+ 16 - 40
gameplay/src/CheckBox.cpp

@@ -75,69 +75,45 @@ void CheckBox::addListener(Control::Listener* listener, int eventFlags)
     Control::addListener(listener, eventFlags);
 }
 
-bool CheckBox::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
-{
-    switch (evt)
-    {
-    case Touch::TOUCH_RELEASE:
-        if (_contactIndex == (int) contactIndex && _state == Control::ACTIVE)
-        {
-            if (!_parent->isScrolling() &&
-                x > _clipBounds.x && x <= _clipBounds.x + _clipBounds.width &&
-                y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height)
-            {
-                setChecked( !_checked );
-            }
-        }
-        break;
-    }
-    return Button::touchEvent(evt, x, y, contactIndex);
-}
-
-bool CheckBox::gamepadEvent(Gamepad::GamepadEvent evt, Gamepad* gamepad, unsigned int analogIndex)
+bool CheckBox::keyEvent(Keyboard::KeyEvent evt, int key)
 {
-    switch (evt)
+    if (getState() == ACTIVE && evt == Keyboard::KEY_RELEASE && key == Keyboard::KEY_RETURN)
     {
-    case Gamepad::BUTTON_EVENT:
-        if (_state == Control::ACTIVE)
-        {
-            if (!gamepad->isButtonDown(Gamepad::BUTTON_A) &&
-                !gamepad->isButtonDown(Gamepad::BUTTON_X))
-            {
-                setChecked( !_checked );
-            }
-        }
-        break;
+        setChecked( !_checked );
     }
 
-    return Button::gamepadEvent(evt, gamepad, analogIndex);
+    return Button::keyEvent(evt, key);
 }
 
-bool CheckBox::keyEvent(Keyboard::KeyEvent evt, int key)
+void CheckBox::controlEvent(Control::Listener::EventType evt)
 {
-    if (_state == ACTIVE && evt == Keyboard::KEY_RELEASE && key == Keyboard::KEY_RETURN)
+    Button::controlEvent(evt);
+
+    switch (evt)
     {
+    case Control::Listener::CLICK:
         setChecked( !_checked );
+        break;
     }
-
-    return Button::keyEvent(evt, key);
 }
 
 void CheckBox::update(const Control* container, const Vector2& offset)
 {
     Label::update(container, offset);
 
+    Control::State state = getState();
+
     Vector2 size;
     if (_imageSize.isZero())
     {
         if (_checked)
         {
-            const Rectangle& selectedRegion = getImageRegion("checked", _state);
+            const Rectangle& selectedRegion = getImageRegion("checked", state);
             size.set(selectedRegion.width, selectedRegion.height);
         }
         else
         {
-            const Rectangle& unselectedRegion = getImageRegion("unchecked", _state);
+            const Rectangle& unselectedRegion = getImageRegion("unchecked", state);
             size.set(unselectedRegion.width, unselectedRegion.height);
         }
     }
@@ -162,11 +138,11 @@ void CheckBox::update(const Control* container, const Vector2& offset)
     
     if (_checked)
     {
-        _image = getImage("checked", _state);
+        _image = getImage("checked", state);
     }
     else
     {
-        _image = getImage("unchecked", _state);
+        _image = getImage("unchecked", state);
     }
 }
 

+ 5 - 21
gameplay/src/CheckBox.h

@@ -117,27 +117,6 @@ protected:
      */
     static CheckBox* create(Theme::Style* style, Properties* properties);
 
-    /**
-     * Touch callback on touch events.  Controls return true if they consume the touch event.
-     *
-     * @param evt The touch event that occurred.
-     * @param x The x position of the touch in pixels. Left edge is zero.
-     * @param y The y position of the touch in pixels. Top edge is zero.
-     * @param contactIndex The order of occurrence for multiple touch contacts starting at zero.
-     *
-     * @return Whether the touch event was consumed by the control.
-     *
-     * @see Touch::TouchEvent
-     */
-    bool touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
-
-    /**
-     * Gamepad callback on gamepad events.
-     *
-     * @see Control::gamepadEvent
-     */
-    bool gamepadEvent(Gamepad::GamepadEvent evt, Gamepad* gamepad, unsigned int analogIndex);
-
     /**
      * Keyboard callback on key events.
      *
@@ -146,6 +125,11 @@ protected:
      */
     bool keyEvent(Keyboard::KeyEvent evt, int key);
 
+    /**
+     * @see Control#controlEvent
+     */
+    void controlEvent(Control::Listener::EventType evt);
+
     /**
      * Called when a control's properties change.  Updates this control's internal rendering
      * properties, such as its text viewport.

+ 73 - 622
gameplay/src/Container.cpp

@@ -12,6 +12,7 @@
 #include "TextBox.h"
 #include "Joystick.h"
 #include "ImageControl.h"
+#include "Form.h"
 #include "Game.h"
 
 namespace gameplay
@@ -56,10 +57,7 @@ Container::Container()
       _scrollingRight(false), _scrollingDown(false),
       _scrollingMouseVertically(false), _scrollingMouseHorizontally(false),
       _scrollBarOpacityClip(NULL), _zIndexDefault(0), _focusIndexDefault(0), _focusIndexMax(0),
-      _focusPressed(0), _selectButtonDown(false),
-      _lastFrameTime(0), _focusChangeRepeat(false),
-      _focusChangeStartTime(0), _focusChangeRepeatDelay(FOCUS_CHANGE_REPEAT_DELAY), _focusChangeCount(0),
-      _totalWidth(0), _totalHeight(0),
+      _selectButtonDown(false), _lastFrameTime(0), _totalWidth(0), _totalHeight(0),
       _initializedWithScroll(false), _scrollWheelRequiresFocus(false), _allowRelayout(true)
 {
 	clearContacts();
@@ -321,21 +319,22 @@ void Container::removeControl(unsigned int index)
     Control* control = *it;
     _controls.erase(it);
     control->_parent = NULL;
+
+    Form::verifyRemovedControlState(control);
+
     SAFE_RELEASE(control);
 }
 
 void Container::removeControl(const char* id)
 {
     GP_ASSERT(id);
-    std::vector<Control*>::iterator it;
-    for (it = _controls.begin(); it < _controls.end(); it++)
+
+    for (size_t i = 0, size = _controls.size(); i < size; ++i)
     {
-        Control* c = *it;
+        Control* c = _controls[i];
         if (strcmp(id, c->getId()) == 0)
         {
-            c->_parent = NULL;
-            SAFE_RELEASE(c);
-            _controls.erase(it);
+            removeControl((unsigned int)i);
             return;
         }
     }
@@ -344,14 +343,13 @@ void Container::removeControl(const char* id)
 void Container::removeControl(Control* control)
 {
     GP_ASSERT(control);
-    std::vector<Control*>::iterator it;
-    for (it = _controls.begin(); it < _controls.end(); it++)
+
+    for (size_t i = 0, size = _controls.size(); i < size; ++i)
     {
-        if (*it == control)
+        Control* c = _controls[i];
+        if (c == control)
         {
-            control->_parent = NULL;
-            SAFE_RELEASE(control);
-            _controls.erase(it);
+            removeControl((unsigned int)i);
             return;
         }
     }
@@ -359,8 +357,8 @@ void Container::removeControl(Control* control)
 
 Control* Container::getControl(unsigned int index) const
 {
-    std::vector<Control*>::const_iterator it = _controls.begin() + index;
-    return *it;
+    GP_ASSERT(index < _controls.size());
+    return _controls[index];
 }
 
 Control* Container::getControl(const char* id) const
@@ -387,11 +385,21 @@ Control* Container::getControl(const char* id) const
     return NULL;
 }
 
+unsigned int Container::getControlCount() const
+{
+    return (unsigned int)_controls.size();
+}
+
 const std::vector<Control*>& Container::getControls() const
 {
     return _controls;
 }
 
+bool Container::isForm() const
+{
+    return false;
+}
+
 void Container::setScroll(Scroll scroll)
 {
     if (scroll != _scroll)
@@ -483,12 +491,14 @@ void Container::update(const Control* container, const Vector2& offset)
     // Update this container's viewport.
     Control::update(container, offset);
 
+    Control::State state = getState();
+
     // 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);
+        _scrollBarLeftCap = getImage("scrollBarLeftCap", state);
+        _scrollBarHorizontal = getImage("horizontalScrollBar", state);
+        _scrollBarRightCap = getImage("scrollBarRightCap", state);
 
         GP_ASSERT(_scrollBarLeftCap && _scrollBarHorizontal && _scrollBarRightCap);
 
@@ -498,9 +508,9 @@ void Container::update(const Control* container, const Vector2& offset)
 
     if ((_scroll & SCROLL_VERTICAL) == SCROLL_VERTICAL)
     {
-        _scrollBarTopCap = getImage("scrollBarTopCap", _state);
-        _scrollBarVertical = getImage("verticalScrollBar", _state);
-        _scrollBarBottomCap = getImage("scrollBarBottomCap", _state);
+        _scrollBarTopCap = getImage("scrollBarTopCap", state);
+        _scrollBarVertical = getImage("verticalScrollBar", state);
+        _scrollBarBottomCap = getImage("scrollBarBottomCap", state);
 
         GP_ASSERT(_scrollBarTopCap && _scrollBarVertical && _scrollBarBottomCap);
 
@@ -546,7 +556,7 @@ void Container::update(const Control* container, const Vector2& offset)
                         width = w;
                 }
             }
-            width += getBorder(_state).left + getBorder(_state).right + getPadding().left + getPadding().right;
+            width += getBorder(state).left + getBorder(state).right + getPadding().left + getPadding().right;
             if (width != oldSize.x)
             {
                 setWidth(width);
@@ -575,7 +585,7 @@ void Container::update(const Control* container, const Vector2& offset)
                         height = h;
                 }
             }
-            height += getBorder(_state).top + getBorder(_state).bottom + getPadding().top + getPadding().bottom;
+            height += getBorder(state).top + getBorder(state).bottom + getPadding().top + getPadding().bottom;
             if (height != oldSize.y)
             {
                 setHeight(height);
@@ -738,152 +748,13 @@ bool Container::isDirty()
     return false;
 }
 
-bool Container::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
-{
-    return pointerEvent(false, evt, x, y, (int)contactIndex);
-}
-
-bool Container::mouseEvent(Mouse::MouseEvent evt, int x, int y, int wheelDelta)
-{
-    return pointerEvent(true, evt, x, y, wheelDelta);
-}
-
-bool Container::keyEvent(Keyboard::KeyEvent evt, int key)
-{
-    // This event may run untrusted code by notifying listeners of events.
-    // If the user calls exit() or otherwise releases this container, we
-    // need to keep it alive until the method returns.
-    addRef();
-
-    bool eventConsumed = false;
-
-    std::vector<Control*>::const_iterator it;
-    for (it = _controls.begin(); it < _controls.end(); it++)
-    {
-        Control* control = *it;
-        GP_ASSERT(control);
-        if (!control->isEnabled() || !control->isVisible())
-        {
-            continue;
-        }
-
-        if ((control->hasFocus() || control->getState() == ACTIVE) && control->keyEvent(evt, key))
-        {
-            eventConsumed |= true;
-            break;
-        }
-    }
-
-    switch (evt)
-    {
-        case Keyboard::KEY_PRESS:
-        {
-            if (!eventConsumed)
-            {
-                switch (key)
-                {
-                case Keyboard::KEY_TAB:
-                    _focusPressed |= NEXT;
-                    if (moveFocus(NEXT))
-                        eventConsumed |= true;
-                    break;
-                case Keyboard::KEY_UP_ARROW:
-                    _focusPressed |= UP;
-                    if (moveFocus(UP))
-                        eventConsumed |= true;
-                    break;
-                case Keyboard::KEY_DOWN_ARROW:
-                    _focusPressed |= DOWN;
-                    if (moveFocus(DOWN))
-                        eventConsumed |= true;
-                    break;
-                case Keyboard::KEY_LEFT_ARROW:
-                    _focusPressed |= LEFT;
-                    if (moveFocus(LEFT))
-                        eventConsumed |= true;
-                    break;
-                case Keyboard::KEY_RIGHT_ARROW:
-                    _focusPressed |= RIGHT;
-                    if (moveFocus(RIGHT))
-                        eventConsumed |= true;
-                    break;
-                }
-            }
-            break;
-        }
-        case Keyboard::KEY_RELEASE:
-        {
-            switch (key)
-            {
-            case Keyboard::KEY_TAB:
-                _focusPressed &= ~NEXT;
-                eventConsumed |= true;
-                break;
-            case Keyboard::KEY_UP_ARROW:
-                _focusPressed &= ~UP;
-                eventConsumed |= true;
-                break;
-            case Keyboard::KEY_DOWN_ARROW:
-                _focusPressed &= ~DOWN;
-                eventConsumed |= true;
-                break;
-            case Keyboard::KEY_LEFT_ARROW:
-                _focusPressed &= ~LEFT;
-                eventConsumed |= true;
-                break;
-            case Keyboard::KEY_RIGHT_ARROW:
-                _focusPressed &= ~RIGHT;
-                eventConsumed |= true;
-                break;
-            }
-            break;
-        }
-    }
-
-    release();
-    return eventConsumed;
-}
-
-void Container::guaranteeFocus(Control* inFocus)
-{
-    std::vector<Control*>::const_iterator it;
-    for (it = _controls.begin(); it < _controls.end(); it++)
-    {
-        Control* control = *it;
-        GP_ASSERT(control);
-        if (control == inFocus)
-            continue;
-
-        if (control->isContainer())
-        {
-            ((Container*)control)->guaranteeFocus(inFocus);
-        }
-        else if (control->hasFocus())
-        {
-            control->setState(NORMAL);
-            return;
-        }
-    }
-}
-
 bool Container::moveFocus(Direction direction, Control* outsideControl)
 {
-    _direction = direction;
-
     Control* start = outsideControl;
     if (!start)
     {
-        std::vector<Control*>::const_iterator it;
-        for (it = _controls.begin(); it < _controls.end(); it++)
-        {
-            Control* control = *it;
-            GP_ASSERT(control);
-            if (control->hasFocus())
-            {
-                start = control;
-                break;
-            }
-        }
+        if (Form::_focusControl && Form::_focusControl->_parent == this)
+            start = Form::_focusControl;
     }
 
     int focusIndex = 0;
@@ -893,9 +764,8 @@ bool Container::moveFocus(Direction direction, Control* outsideControl)
         const Rectangle& startBounds = start->getAbsoluteBounds();
         Vector2 vStart, vNext;
         float distance = FLT_MAX;
-        start->setState(Control::NORMAL);
 
-        switch(direction)
+        switch (direction)
         {
         case UP:
             vStart.set(startBounds.x + startBounds.width * 0.5f,
@@ -969,16 +839,11 @@ bool Container::moveFocus(Direction direction, Control* outsideControl)
         {
             // Check for controls in the given direction in our parent container.
             if (direction != NEXT && !outsideControl && _parent && _parent->moveFocus(direction, start))
-            {
-                setState(NORMAL);
-                _focusChangeRepeat = false;
-                _focusPressed = 0;
                 return true;
-            }
             
             // No control is in the given direction.  Move to the next control in the focus order.
             int focusDelta;
-            switch(direction)
+            switch (direction)
             {
             case UP:
             case LEFT:
@@ -1004,12 +869,7 @@ bool Container::moveFocus(Direction direction, Control* outsideControl)
             if (focusIndex > _focusIndexMax)
             {
                 if (direction == NEXT && !outsideControl && _parent && _parent->moveFocus(direction, start))
-                {
-                    setState(NORMAL);
-                    _focusChangeRepeat = false;
-                    _focusPressed = 0;
                     return true;
-                }
 
                 focusIndex = 0;
             }
@@ -1038,20 +898,16 @@ bool Container::moveFocus(Direction direction, Control* outsideControl)
     // 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 this control is a container, try to move focus to the first control within it
         if (next->isContainer())
         {
-            if ((direction == NEXT && ((Container*)next)->moveFocus(direction)) ||
-                ((Container*)next)->moveFocus(direction, start))
-            {
-                _focusChangeRepeat = false;
-                _focusPressed = 0;
+            if ((direction == NEXT && ((Container*)next)->moveFocus(direction)) || ((Container*)next)->moveFocus(direction, start))
                 return true;
-            }
         }
 
+        if (next->canFocus())
+            Form::setFocusControl(next);
+
         // 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)
@@ -1076,47 +932,12 @@ bool Container::moveFocus(Direction direction, Control* outsideControl)
             _scrollPosition.y = -(bounds.y + bounds.height - _viewportBounds.height);
         }
 
-        if (outsideControl && outsideControl->_parent)
-        {
-            _focusPressed = outsideControl->_parent->_focusPressed;
-            _focusChangeCount = outsideControl->_parent->_focusChangeCount;
-            _focusChangeRepeatDelay = outsideControl->_parent->_focusChangeRepeatDelay;
-            outsideControl->_parent->guaranteeFocus(next);
-        }
-
-        _focusChangeStartTime = Game::getAbsoluteTime();
-        _focusChangeRepeat = true;
-        addRef();
-        Game::getInstance()->schedule(_focusChangeRepeatDelay, this);
-
         return true;
     }
 
     return false;
 }
 
-void Container::timeEvent(long timeDiff, void* cookie)
-{
-    double time = Game::getAbsoluteTime();
-    if (_focusChangeRepeat && hasFocus() && _focusPressed &&
-        abs(time - timeDiff - _focusChangeRepeatDelay - _focusChangeStartTime) < 50)
-    {
-        ++_focusChangeCount;
-        if (_focusChangeCount == 5)
-        {
-            _focusChangeRepeatDelay *= 0.5;
-        }
-        moveFocus(_direction);
-    }
-    else
-    {
-        _focusChangeCount = 0;
-        _focusChangeRepeatDelay = FOCUS_CHANGE_REPEAT_DELAY;
-    }
-
-    release();
-}
-
 void Container::startScrolling(float x, float y, bool resetTime)
 {
     _scrollingVelocity.set(-x, y);
@@ -1143,222 +964,6 @@ void Container::stopScrolling()
     _dirty = true;
 }
 
-bool Container::gamepadEvent(Gamepad::GamepadEvent evt, Gamepad* gamepad, unsigned int analogIndex)
-{
-    addRef();
-
-    bool eventConsumed = false;
-
-    // Pass the event to any control that is active or in focus.
-    std::vector<Control*>::const_iterator it;
-    for (it = _controls.begin(); it < _controls.end(); it++)
-    {
-        Control* control = *it;
-        GP_ASSERT(control);
-        if (control->hasFocus() || control->getState() == Control::ACTIVE)
-        {
-            eventConsumed |= control->gamepadEvent(evt, gamepad, analogIndex);
-            break;
-        }
-    }
-
-    // First check if a selection button is down.
-    if (!_selectButtonDown)
-    {
-        if (gamepad->isButtonDown(Gamepad::BUTTON_A) ||
-            gamepad->isButtonDown(Gamepad::BUTTON_X))
-        {
-            _selectButtonDown = true;
-            _focusChangeRepeat = false;
-            eventConsumed |= _consumeInputEvents;
-        }
-    }
-    else
-    {
-        if (!gamepad->isButtonDown(Gamepad::BUTTON_A) &&
-            !gamepad->isButtonDown(Gamepad::BUTTON_X))
-        {
-            _selectButtonDown = false;
-        }
-    }
-
-    Vector2 joystick;
-    gamepad->getJoystickValues(analogIndex, &joystick);
-
-    // Don't allow focus changes or scrolling while a selection button is down.
-    if (!_selectButtonDown && !eventConsumed)
-    {
-        switch (evt)
-        {
-            case Gamepad::BUTTON_EVENT:
-            {
-                // Shift focus forward or backward when the DPad is used.
-                if (!(_focusPressed & DOWN) &&
-                    gamepad->isButtonDown(Gamepad::BUTTON_DOWN))
-                {
-                    _focusPressed |= DOWN;
-                    eventConsumed = true;
-                    if (moveFocus(DOWN))
-                        break;
-                    else
-                        startScrolling(0, -GAMEPAD_SCROLL_SPEED);
-                }
-                    
-                if (!(_focusPressed & RIGHT) &&
-                    gamepad->isButtonDown(Gamepad::BUTTON_RIGHT))
-                {
-                    _focusPressed |= RIGHT;
-                    eventConsumed = true;
-                    if (moveFocus(RIGHT))
-                        break;
-                    else
-                        startScrolling(GAMEPAD_SCROLL_SPEED, 0);
-                }
-
-                if (!(_focusPressed & UP) &&
-                    gamepad->isButtonDown(Gamepad::BUTTON_UP))
-                {
-                    _focusPressed |= UP;
-                    eventConsumed = true;
-                    if (moveFocus(UP))
-                        break;
-                    else
-                        startScrolling(0, GAMEPAD_SCROLL_SPEED);
-                }
-
-                if (!(_focusPressed & LEFT) &&
-                    gamepad->isButtonDown(Gamepad::BUTTON_LEFT))
-                {
-                    _focusPressed |= LEFT;
-                    eventConsumed = true;
-                    if (moveFocus(LEFT))
-                        break;
-                    else
-                        startScrolling(-GAMEPAD_SCROLL_SPEED, 0);
-                }
-                break;
-            }
-            case Gamepad::JOYSTICK_EVENT:
-            {
-                switch (analogIndex)
-                {
-                case 0:
-                    // The left analog stick can be used in the same way as the DPad.
-                    eventConsumed = true;
-                    if (!(_focusPressed & RIGHT) &&
-                        joystick.x > JOYSTICK_THRESHOLD)
-                    {
-                        _focusPressed |= RIGHT;
-                        if (moveFocus(RIGHT))
-                            break;
-                        else
-                            startScrolling(GAMEPAD_SCROLL_SPEED * joystick.x, 0);
-                    }
-
-                    if (!(_focusPressed & DOWN) &&
-                        joystick.y < -JOYSTICK_THRESHOLD)
-                    {
-                        _focusPressed |= DOWN;
-                        if (moveFocus(DOWN))
-                            break;
-                        else
-                            startScrolling(0, GAMEPAD_SCROLL_SPEED * joystick.y);
-                    }
-
-                    if (!(_focusPressed & LEFT) &&
-                        joystick.x < -JOYSTICK_THRESHOLD)
-                    {
-                        _focusPressed |= LEFT;
-                        if (moveFocus(LEFT))
-                            break;
-                        else
-                            startScrolling(GAMEPAD_SCROLL_SPEED * joystick.x, 0);
-                    }
-                        
-                    if (!(_focusPressed & UP) &&
-                        joystick.y > JOYSTICK_THRESHOLD)
-                    {
-                        _focusPressed |= UP;
-                        if (moveFocus(UP))
-                            break;
-                        else
-                            startScrolling(0, GAMEPAD_SCROLL_SPEED * joystick.y);
-                    }
-                    break;
-
-                case 1:
-                    // The right analog stick can be used to scroll.
-                    if (_scroll != SCROLL_NONE)
-                    {
-                        if (_scrolling)
-                        {
-                            if (joystick.isZero())
-                            {
-                                stopScrolling();
-                            }
-                            else
-                            {
-                                startScrolling(GAMEPAD_SCROLL_SPEED * joystick.x, GAMEPAD_SCROLL_SPEED * joystick.y, false);
-                            }
-                        }
-                        else
-                        {
-                            startScrolling(GAMEPAD_SCROLL_SPEED * joystick.x, GAMEPAD_SCROLL_SPEED * joystick.y);
-                        }
-                        release();
-                        return _consumeInputEvents;
-                    }
-                    break;
-                }
-            }
-        }
-    }
-
-    if ((evt == Gamepad::BUTTON_EVENT || evt == Gamepad::JOYSTICK_EVENT) &&
-        analogIndex == 0)
-    {
-        if ((_focusPressed & DOWN) &&
-            !gamepad->isButtonDown(Gamepad::BUTTON_DOWN) &&
-            joystick.y > -JOYSTICK_THRESHOLD)
-        {
-            _focusPressed &= ~DOWN;
-            eventConsumed = true;
-        }
-
-        if ((_focusPressed & RIGHT) &&
-            !gamepad->isButtonDown(Gamepad::BUTTON_RIGHT) &&
-            joystick.x < JOYSTICK_THRESHOLD)
-        {
-            _focusPressed &= ~RIGHT;
-            eventConsumed = true;
-        }
-    
-        if ((_focusPressed & UP) &&
-            !gamepad->isButtonDown(Gamepad::BUTTON_UP) &&
-            joystick.y < JOYSTICK_THRESHOLD)
-        {
-            _focusPressed &= ~UP;
-            eventConsumed = true;
-        }
-
-        if ((_focusPressed & LEFT) &&
-            !gamepad->isButtonDown(Gamepad::BUTTON_LEFT) &&
-            joystick.x > -JOYSTICK_THRESHOLD)
-        {
-            _focusPressed &= ~LEFT;
-            eventConsumed = true;
-        }
-    }
-
-    if (!_focusPressed && _scrolling)
-    {
-        stopScrolling();
-    }
-
-    release();
-    return eventConsumed;
-}
-
 bool Container::isContainer() const
 {
     return true;
@@ -1400,6 +1005,8 @@ void Container::updateScroll()
         _layout->update(this, _scrollPosition);
     }
 
+    Control::State state = getState();
+
     // Update time.
     if (!_lastFrameTime)
     {
@@ -1409,7 +1016,7 @@ void Container::updateScroll()
     float elapsedTime = (float)(frameTime - _lastFrameTime);
     _lastFrameTime = frameTime;
 
-    const Theme::Border& containerBorder = getBorder(_state);
+    const Theme::Border& containerBorder = getBorder(state);
     const Theme::Padding& containerPadding = getPadding();
 
     // Calculate total width and height.
@@ -1435,8 +1042,8 @@ void Container::updateScroll()
         }
     }
 
-    float vWidth = getImageRegion("verticalScrollBar", _state).width;
-    float hHeight = getImageRegion("horizontalScrollBar", _state).height;
+    float vWidth = getImageRegion("verticalScrollBar", state).width;
+    float hHeight = getImageRegion("horizontalScrollBar", state).height;
     float clipWidth = _absoluteBounds.width - containerBorder.left - containerBorder.right - containerPadding.left - containerPadding.right - vWidth;
     float clipHeight = _absoluteBounds.height - containerBorder.top - containerBorder.bottom - containerPadding.top - containerPadding.bottom - hHeight;
 
@@ -1526,7 +1133,7 @@ void Container::sortControls()
 
 bool Container::touchEventScroll(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
 {
-    switch(evt)
+    switch (evt)
     {
     case Touch::TOUCH_PRESS:
         if (_contactIndex == INVALID_CONTACT_INDEX)
@@ -1548,6 +1155,7 @@ bool Container::touchEventScroll(Touch::TouchEvent evt, int x, int y, unsigned i
             return _consumeInputEvents;
         }
         break;
+
     case Touch::TOUCH_MOVE:
         if (_scrolling && _contactIndex == (int) contactIndex)
         {
@@ -1670,7 +1278,7 @@ bool Container::touchEventScroll(Touch::TouchEvent evt, int x, int y, unsigned i
 
 bool Container::mouseEventScroll(Mouse::MouseEvent evt, int x, int y, int wheelDelta)
 {
-    switch(evt)
+    switch (evt)
     {
         case Mouse::MOUSE_PRESS_LEFT_BUTTON:
         {
@@ -1681,8 +1289,8 @@ bool Container::mouseEventScroll(Mouse::MouseEvent evt, int x, int y, int wheelD
                                  _scrollBarBounds.y,
                                  vWidth, _scrollBarBounds.height);
 
-                if (x + _viewportBounds.x >= vBounds.x &&
-                    x + _viewportBounds.x <= vBounds.x + vBounds.width)
+                if (x >= vBounds.x &&
+                    x <= vBounds.x + vBounds.width)
                 {
                     // Then we're within the horizontal bounds of the vertical scrollbar.
                     // We want to either jump up or down, or drag the scrollbar itself.
@@ -1700,16 +1308,16 @@ bool Container::mouseEventScroll(Mouse::MouseEvent evt, int x, int y, int wheelD
                     }
                 }
             }
-            
+
             if (_scrollBarHorizontal)
             {
                 float hHeight = _scrollBarHorizontal->getRegion().height;
                 Rectangle hBounds(_scrollBarBounds.x,
                                   _viewportBounds.y + _viewportBounds.height,
                                   _scrollBarBounds.width, hHeight);
-            
-                if (y + _viewportBounds.y >= hBounds.y &&
-                         y + _viewportBounds.y <= hBounds.y + hBounds.height)
+
+                if (y >= hBounds.y &&
+                    y <= hBounds.y + hBounds.height)
                 {
                     // We're within the vertical bounds of the horizontal scrollbar.
                     if (x < hBounds.x)
@@ -1731,27 +1339,24 @@ bool Container::mouseEventScroll(Mouse::MouseEvent evt, int x, int y, int wheelD
             return touchEventScroll(Touch::TOUCH_RELEASE, x, y, 0);
 
         case Mouse::MOUSE_WHEEL:
-            if ((_state == HOVER && (!_scrollWheelRequiresFocus || _previousState == FOCUS)) ||
-                _state == FOCUS && _scrollWheelRequiresFocus)
+        {
+            if (_scrollingVelocity.isZero())
             {
-                if (_scrollingVelocity.isZero())
-                {
-                    _lastFrameTime = Game::getGameTime();
-                }
-                _scrolling = _scrollingMouseVertically = _scrollingMouseHorizontally = false;
+                _lastFrameTime = Game::getGameTime();
+            }
+            _scrolling = _scrollingMouseVertically = _scrollingMouseHorizontally = false;
 
-                _scrollingVelocity.y += _scrollWheelSpeed * wheelDelta;
+            _scrollingVelocity.y += _scrollWheelSpeed * wheelDelta;
 
-                if (_scrollBarOpacityClip && _scrollBarOpacityClip->isPlaying())
-                {
-                    _scrollBarOpacityClip->stop();
-                    _scrollBarOpacityClip = NULL;
-                }
-                _scrollBarOpacity = 1.0f;
-                _dirty = true;
-                return _consumeInputEvents;
+            if (_scrollBarOpacityClip && _scrollBarOpacityClip->isPlaying())
+            {
+                _scrollBarOpacityClip->stop();
+                _scrollBarOpacityClip = NULL;
             }
-            break;
+            _scrollBarOpacity = 1.0f;
+            _dirty = true;
+            return _consumeInputEvents;
+        }
     }
 
     return false;
@@ -1767,160 +1372,6 @@ bool Container::inContact()
 	return false;
 }
 
-bool Container::pointerEvent(bool mouse, char evt, int x, int y, int data)
-{
-    if (!isEnabled() || !isVisible())
-    {
-        return false;
-    }
-
-    // This event may run untrusted code by notifying listeners of events.
-    // If the user calls exit() or otherwise releases this container, we
-    // need to keep it alive until the method returns.
-    addRef();
-
-    bool eventConsumed = false;
-    const Theme::Border& border = getBorder(_state);
-    const Theme::Padding& padding = getPadding();
-    float xPos = border.left + padding.left;
-    float yPos = border.top + padding.top;
-
-    Vector2* offset = NULL;
-    if (_scroll != SCROLL_NONE)
-    {
-        offset = &_scrollPosition;
-    }
-
-    std::vector<Control*>::const_iterator it;
-    for (it = _controls.begin(); it < _controls.end(); it++)
-    {
-        Control* control = *it;
-        GP_ASSERT(control);
-        if (!control->isEnabled() || !control->isVisible())
-        {
-            continue;
-        }
-
-        const Rectangle& bounds = control->getBounds();
-        float boundsX = bounds.x;
-        float boundsY = bounds.y;
-        if (offset)
-        {
-            boundsX += offset->x;
-            boundsY += offset->y;
-        }
-
-        Control::State currentState = control->getState();
-        if ((currentState != Control::NORMAL) ||
-            ((evt == Touch::TOUCH_PRESS ||
-              evt == Mouse::MOUSE_PRESS_LEFT_BUTTON ||
-              evt == Mouse::MOUSE_PRESS_MIDDLE_BUTTON ||
-              evt == Mouse::MOUSE_PRESS_RIGHT_BUTTON ||
-              evt == Mouse::MOUSE_MOVE ||
-              evt == Mouse::MOUSE_WHEEL) &&
-                x >= xPos + boundsX &&
-                x <= xPos + boundsX + bounds.width &&
-                y >= yPos + boundsY &&
-                y <= yPos + boundsY + bounds.height))
-        {
-            // Pass on the event's clip relative to the control.
-            if (mouse)
-                eventConsumed |= control->mouseEvent((Mouse::MouseEvent)evt, x - xPos - boundsX, y - yPos - boundsY, data);
-            else
-                eventConsumed |= control->touchEvent((Touch::TouchEvent)evt, x - xPos - boundsX, y - yPos - boundsY, (unsigned int)data);
-        }
-    }
-
-    if (!isEnabled() || !isVisible())
-    {
-        _contactIndex = INVALID_CONTACT_INDEX;
-        clearContacts();
-        _scrolling = false;
-        _scrollingMouseVertically = _scrollingMouseHorizontally = false;
-
-        release();
-        return (_consumeInputEvents | eventConsumed);
-    }
-
-    bool withinClipBounds = (x > _clipBounds.x && x <= _clipBounds.x + _clipBounds.width &&
-                             y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height);
-    switch (evt)
-    {
-    case Touch::TOUCH_PRESS:
-        if (withinClipBounds)
-        {
-            setState(Control::ACTIVE);
-        }
-        else if (!inContact())
-        {
-            setState(Control::NORMAL);
-            _contactIndex = INVALID_CONTACT_INDEX;
-            release();
-            return false;
-        }
-        if (!mouse)
-        	_contactIndices[data] = true;
-        break;
-    case Mouse::MOUSE_MOVE:
-        if (_state != ACTIVE)
-        {
-            if (_state != HOVER && withinClipBounds)
-            {
-                _previousState = _state;
-                setState(HOVER);
-                notifyListeners(Control::Listener::ENTER);
-            }
-            else if (_state == HOVER && !withinClipBounds)
-            {
-                setState(_previousState);
-                notifyListeners(Control::Listener::LEAVE);
-            }
-            else if (_state != HOVER)
-            {
-                release();
-                return false;
-            }
-        }
-        break;
-    case Mouse::MOUSE_WHEEL:
-        if (!withinClipBounds && !_scrollWheelRequiresFocus)
-        {
-            release();
-            return false;
-        }
-        break;
-    case Touch::TOUCH_RELEASE:
-    	if (!mouse)
-    		_contactIndices[data] = false;
-
-    	if (!inContact())
-        {
-			if (_state == ACTIVE && withinClipBounds)
-			{
-				setState(FOCUS);
-			}
-			else
-			{
-				setState(NORMAL);
-			}
-        }
-        break;
-    }
-
-    if (!eventConsumed && _scroll != SCROLL_NONE &&
-        (evt != Touch::TOUCH_PRESS || withinClipBounds))
-    {
-        if ((mouse && mouseEventScroll((Mouse::MouseEvent)evt, x - xPos, y - yPos, data)) ||
-            (!mouse && touchEventScroll((Touch::TouchEvent)evt, x - xPos, y - yPos, (unsigned int)data)))
-        {
-            eventConsumed = true;
-        }
-    }
-
-    release();
-    return (_consumeInputEvents | eventConsumed);
-}
-
 Container::Scroll Container::getScroll(const char* scroll)
 {
     if (!scroll)

+ 18 - 84
gameplay/src/Container.h

@@ -48,8 +48,10 @@ namespace gameplay
     }
  @endverbatim
  */
-class Container : public Control, TimeListener
+class Container : public Control
 {
+    friend class Form;
+    friend class Control;
 
 public:
 
@@ -143,6 +145,13 @@ public:
      */
     Control* getControl(const char* id) const;
 
+    /**
+     * Returns the number of child controls for this container.
+     *
+     * @return The number of child controls.
+     */
+    unsigned int getControlCount() const;
+
     /**
      * Get the vector of controls within this container.
      *
@@ -151,6 +160,13 @@ public:
      */
     const std::vector<Control*>& getControls() const;
 
+    /**
+     * Determines if this container is a top level form.
+     *
+     * @return True if the container is a top level form, false otherwise.
+     */
+    virtual bool isForm() const;
+
     /**
      * Sets the allowed scroll directions for this container.
      *
@@ -262,13 +278,6 @@ public:
      */
     virtual void setAnimationPropertyValue(int propertyId, AnimationValue* value, float blendWeight = 1.0f);
 
-    /**
-     * @see TimeListener::timeEvent
-     *
-     * @script{ignore}
-     */
-    void timeEvent(long timeDiff, void* cookie);
-
 protected:
 
     /**
@@ -310,62 +319,13 @@ protected:
      */
     virtual void update(const Control* container, const Vector2& offset);
 
-    /**
-     * Touch callback on touch events.  Controls return true if they consume the touch event.
-     *
-     * @param evt The touch event that occurred.
-     * @param x The x position of the touch in pixels. Left edge is zero.
-     * @param y The y position of the touch in pixels. Top edge is zero.
-     * @param contactIndex The order of occurrence for multiple touch contacts starting at zero.
-     *
-     * @return Whether the touch event was consumed by a control within this container.
-     *
-     * @see Touch::TouchEvent
-     */
-    virtual bool touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
-    
-    /**
-     * Keyboard callback on key events.  Passes key events on to the currently focused control.
-     *
-     * @param evt The key event that occurred.
-     * @param key If evt is KEY_PRESS or KEY_RELEASE then key is the key code from Keyboard::Key.
-     *            If evt is KEY_CHAR then key is the unicode value of the character.
-     *
-     * @return Whether the key event was consumed by this control.
-     * 
-     * @see Keyboard::KeyEvent
-     * @see Keyboard::Key
-     */
-    virtual bool keyEvent(Keyboard::KeyEvent evt, int key);
-
-    /**
-     * Mouse callback on mouse events.
-     *
-     * @param evt The mouse event that occurred.
-     * @param x The x position of the mouse in pixels. Left edge is zero.
-     * @param y The y position of the mouse in pixels. Top edge is zero.
-     * @param wheelDelta The number of mouse wheel ticks. Positive is up (forward), negative is down (backward).
-     *
-     * @return True if the mouse event is consumed or false if it is not consumed.
-     *
-     * @see Mouse::mouseEvent
-     */
-    virtual bool mouseEvent(Mouse::MouseEvent evt, int x, int y, int wheelDelta);
-
-    /**
-     * Gamepad callback on gamepad events.
-     *
-     * @see Control::gamepadEvent
-     */
-    virtual bool gamepadEvent(Gamepad::GamepadEvent evt, Gamepad* gamepad, unsigned int analogIndex);
-
     /**
      * Gets a Layout::Type enum from a matching string.
      *
      * @param layoutString The layout string to parse
      */
     static Layout::Type getLayoutType(const char* layoutString);
-    
+
     /**
      * Returns whether this container or any of its controls have been modified and require an update.
      * 
@@ -433,22 +393,6 @@ protected:
      */
     bool mouseEventScroll(Mouse::MouseEvent evt, int x, int y, int wheelDelta);
 
-    /**
-     * Mouse pointer event callback.
-     *
-     * @param mouse Whether to treat the event as a mouse event or a touch event.
-     * @param evt The pointer event (either a Mouse::MouseEvent or a Touch::TouchEvent).
-     * @param x The x position of the pointer event in pixels. Left edge is zero.
-     * @param y The y position of the pointer event in pixels. Top edge is zero.
-     * @param data The event's data (depends on whether it is a mouse event or a touch event).
-     *
-     * @return Whether the pointer event was consumed by this container.
-     * 
-     * @see Mouse::MouseEvent
-     * @see Touch::TouchEvent
-     */
-    bool pointerEvent(bool mouse, char evt, int x, int y, int data);
-
     /**
      * Get a Scroll enum from a matching string.
      *
@@ -601,8 +545,6 @@ private:
     // in which case scrolling can be initiated.
     bool moveFocus(Direction direction, Control* outsideControl = NULL);
 
-    void guaranteeFocus(Control* inFocus);
-
     // Starts scrolling at the given horizontal and vertical speeds.
     void startScrolling(float x, float y, bool resetTime = true);
 
@@ -615,17 +557,9 @@ private:
     int _zIndexDefault;
     int _focusIndexDefault;
     int _focusIndexMax;
-    unsigned int _focusPressed;
     bool _selectButtonDown;
     double _lastFrameTime;
 
-    // Timing information for repeating focus changes.
-    bool _focusChangeRepeat;
-    double _focusChangeStartTime;
-    double _focusChangeRepeatDelay;
-    unsigned int _focusChangeCount;
-    Direction _direction;
-
     float _totalWidth;
     float _totalHeight;
     bool _contactIndices[MAX_CONTACT_INDICES];

+ 145 - 168
gameplay/src/Control.cpp

@@ -1,6 +1,7 @@
 #include "Base.h"
 #include "Game.h"
 #include "Control.h"
+#include "Form.h"
 
 #define BOUNDS_X_PERCENTAGE_BIT 1
 #define BOUNDS_Y_PERCENTAGE_BIT 2
@@ -44,15 +45,17 @@ static bool parseCoordPair(const char* s, float* v1, float* v2, bool* v1Percenta
 }
 
 Control::Control()
-    : _id(""), _state(Control::NORMAL), _boundsBits(0), _dirty(true), _consumeInputEvents(false),
-    _alignment(ALIGN_TOP_LEFT), _isAlignmentSet(false), _autoWidth(AUTO_SIZE_NONE), _autoHeight(AUTO_SIZE_NONE), _listeners(NULL), _visible(true),
-    _zIndex(-1), _contactIndex(INVALID_CONTACT_INDEX), _focusIndex(-1), _parent(NULL), _styleOverridden(false), _skin(NULL), _previousState(NORMAL)
+    : _id(""), _enabled(true), _boundsBits(0), _dirty(true), _consumeInputEvents(true), _alignment(ALIGN_TOP_LEFT), _isAlignmentSet(false),
+    _autoWidth(AUTO_SIZE_NONE), _autoHeight(AUTO_SIZE_NONE), _listeners(NULL), _visible(true), _zIndex(-1),
+    _contactIndex(INVALID_CONTACT_INDEX), _focusIndex(-1), _parent(NULL), _styleOverridden(false), _skin(NULL)
 {
     addScriptEvent("controlEvent", "<Control>[Control::Listener::EventType]");
 }
 
 Control::~Control()
 {
+    Form::verifyRemovedControlState(this);
+
     if (_listeners)
     {
         for (std::map<Control::Listener::EventType, std::list<Control::Listener*>*>::const_iterator itr = _listeners->begin(); itr != _listeners->end(); ++itr)
@@ -94,7 +97,7 @@ void Control::initialize(Theme::Style* style, Properties* properties)
     _autoWidth = parseAutoSize(properties->getString("autoWidth"));
     _autoHeight = parseAutoSize(properties->getString("autoHeight"));
 
-    _consumeInputEvents = properties->getBool("consumeInputEvents", false);
+    _consumeInputEvents = properties->getBool("consumeInputEvents", true);
 
     _visible = properties->getBool("visible", true);
 
@@ -408,15 +411,13 @@ void Control::setAutoHeight(AutoSize mode)
 
 void Control::setVisible(bool visible)
 {
-    if (visible && !_visible)
+    if (_visible != visible)
     {
-        _visible = true;
-        _dirty = true;
-    }
-    else if (!visible && _visible)
-    {
-        _visible = false;
+        _visible = visible;
         _dirty = true;
+
+        if (!_visible)
+            Form::controlDisabled(this);
     }
 }
 
@@ -425,9 +426,25 @@ bool Control::isVisible() const
     return _visible;
 }
 
+bool Control::isVisibleInHierarchy() const
+{
+    if (!_visible)
+        return false;
+
+    if (_parent)
+        return _parent->isVisibleInHierarchy();
+
+    return true;
+}
+
 bool Control::hasFocus() const
 {
-    return (_state == FOCUS || (_state == HOVER && _previousState == FOCUS));
+    return (Form::_focusControl == this);
+}
+
+bool Control::canFocus() const
+{
+    return false;
 }
 
 void Control::setOpacity(float opacity, unsigned char states)
@@ -453,21 +470,30 @@ float Control::getOpacity(State state) const
 
 void Control::setEnabled(bool enabled)
 {
-	if (enabled && _state == Control::DISABLED)
-	{
-		_state = Control::NORMAL;
+    if (_enabled != enabled)
+    {
+        _enabled = enabled;
         _dirty = true;
-	}
-	else if (!enabled && _state != Control::DISABLED)
-	{
-		_state = Control::DISABLED;
-		_dirty = true;
-	}
+
+        if (!_enabled)
+            Form::controlDisabled(this);
+    }
 }
 
 bool Control::isEnabled() const
 {
-    return _state != DISABLED;
+    return _enabled;
+}
+
+bool Control::isEnabledInHierarchy() const
+{
+    if (!_enabled)
+        return false;
+
+    if (_parent)
+        return _parent->isEnabledInHierarchy();
+
+    return true;
 }
 
 void Control::setBorder(float top, float bottom, float left, float right, unsigned char states)
@@ -790,22 +816,29 @@ Theme::Style* Control::getStyle() const
     return _style;
 }
 
-void Control::setState(State state)
+Control::State Control::getState() const
 {
-    if (getOverlay(_state) != getOverlay(state))
-        _dirty = true;
+    if (!_enabled)
+        return DISABLED;
 
-    _state = state;
-}
+    if (Form::_activeControl == this)
+    {
+        if (Form::_activeControlState == ACTIVE)
+            return ACTIVE;
+        if (Form::_focusControl == this)
+            return FOCUS;
+        return Form::_activeControlState;
+    }
+    
+    if (Form::_focusControl == this)
+        return FOCUS;
 
-Control::State Control::getState() const
-{
-    return _state;
+    return State::NORMAL;
 }
 
 Theme::Style::OverlayType Control::getOverlayType() const
 {
-    switch (_state)
+    switch (getState())
     {
     case Control::NORMAL:
         return Theme::Style::OVERLAY_NORMAL;
@@ -843,6 +876,11 @@ void Control::setZIndex(int zIndex)
     {
         _zIndex = zIndex;
         _dirty = true;
+
+        if (_parent && _parent->isContainer())
+        {
+            static_cast<Container*>(_parent)->sortControls();
+        }
     }
 }
 
@@ -931,133 +969,24 @@ void Control::addSpecificListener(Control::Listener* listener, Control::Listener
 
 bool Control::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
 {
-    switch (evt)
-    {
-    case Touch::TOUCH_PRESS:
-        // Controls that don't have an ACTIVE state go to the FOCUS state when pressed.
-        // (Other controls, such as buttons and sliders, become ACTIVE when pressed and go to the FOCUS state on release.)
-        // Labels are never any state other than NORMAL.
-        if (_contactIndex == INVALID_CONTACT_INDEX && x > _clipBounds.x && x <= _clipBounds.x + _clipBounds.width &&
-            y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height)
-        {
-            _contactIndex = (int) contactIndex;
-
-            notifyListeners(Control::Listener::PRESS);
-
-            return _consumeInputEvents;
-        }
-        else
-        {
-            // If this control was in focus, it's not any more.
-            _state = NORMAL;
-        }
-        break;
-            
-    case Touch::TOUCH_MOVE:
-        break;
-
-    case Touch::TOUCH_RELEASE:
-        if (_contactIndex == (int)contactIndex)
-        {
-            _contactIndex = INVALID_CONTACT_INDEX;
-
-            // Always trigger Control::Listener::RELEASE
-            notifyListeners(Control::Listener::RELEASE);
-
-            // Only trigger Control::Listener::CLICK if both PRESS and RELEASE took place within the control's bounds.
-            if (x > _clipBounds.x && x <= _clipBounds.x + _clipBounds.width &&
-                y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height)
-            {
-                // Leave this control in the FOCUS state.
-                notifyListeners(Control::Listener::CLICK);
-            }
-
-            return _consumeInputEvents;
-        }
-        break;
-    }
-
-    return false;
+    return _consumeInputEvents;
 }
 
 bool Control::keyEvent(Keyboard::KeyEvent evt, int key)
 {
-    return false;
+    return _consumeInputEvents;
 }
 
 bool Control::mouseEvent(Mouse::MouseEvent evt, int x, int y, int wheelDelta)
 {
-    // By default, mouse events are either interpreted as touch events or ignored.
-    switch (evt)
-    {
-    case Mouse::MOUSE_PRESS_LEFT_BUTTON:
-        return touchEvent(Touch::TOUCH_PRESS, x, y, 0);
-
-    case Mouse::MOUSE_RELEASE_LEFT_BUTTON:
-        return touchEvent(Touch::TOUCH_RELEASE, x, y, 0);
-
-    case Mouse::MOUSE_MOVE:
-        if (_state != ACTIVE)
-        {
-            if (_state != HOVER &&
-                x > _clipBounds.x && x <= _clipBounds.x + _clipBounds.width &&
-                y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height)
-            {
-                _previousState = _state;
-                setState(HOVER);
-                notifyListeners(Control::Listener::ENTER);
-                return _consumeInputEvents;
-            }
-            else if (_state == HOVER && !(x > _clipBounds.x && x <= _clipBounds.x + _clipBounds.width &&
-                        y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height))
-            {
-                setState(_previousState);
-                notifyListeners(Control::Listener::LEAVE);
-                return _consumeInputEvents;
-            }
-        }
-        return touchEvent(Touch::TOUCH_MOVE, x, y, 0);
-
-    default:
-        break;
-    }
-
+    // Return false instead of _consumeInputEvents to allow handling to be 
+    // routed to touchEvent before consuming.
     return false;
 }
 
 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;
+    return _consumeInputEvents;
 }
 
 void Control::notifyListeners(Control::Listener::EventType eventType)
@@ -1067,6 +996,8 @@ void Control::notifyListeners(Control::Listener::EventType eventType)
     // need to keep it alive until the method returns.
     addRef();
 
+    controlEvent(eventType);
+
     if (_listeners)
     {
         std::map<Control::Listener::EventType, std::list<Control::Listener*>*>::const_iterator itr = _listeners->find(eventType);
@@ -1086,6 +1017,10 @@ void Control::notifyListeners(Control::Listener::EventType eventType)
     release();
 }
 
+void Control::controlEvent(Control::Listener::EventType evt)
+{
+}
+
 void Control::update(const Control* container, const Vector2& offset)
 {
     Game* game = Game::getInstance();
@@ -1173,7 +1108,7 @@ void Control::update(const Control* container, const Vector2& offset)
 
     // Calculate the absolute viewport bounds (content area, which does not include border and padding)
     // Absolute bounds minus border and padding.
-    const Theme::Border& border = getBorder(_state);
+    const Theme::Border& border = getBorder(getState());
     const Theme::Padding& padding = getPadding();
     x += border.left + padding.left;
     y += border.top + padding.top;
@@ -1226,10 +1161,10 @@ void Control::update(const Control* container, const Vector2& offset)
     }
 
     // Cache themed attributes for performance.
-    _skin = getSkin(_state);
+    _skin = getSkin(getState());
 
     // Current opacity should be multiplied by that of the parent container.
-    _opacity = getOpacity(_state);
+    _opacity = getOpacity(getState());
     if (container)
         _opacity *= container->_opacity;
 }
@@ -1251,7 +1186,7 @@ void Control::drawBorder(SpriteBatch* spriteBatch, const Rectangle& clip)
     const Theme::UVs& bottomRight = _skin->getUVs(Theme::Skin::BOTTOM_RIGHT);
 
     // Calculate screen-space positions.
-    const Theme::Border& border = getBorder(_state);
+    const Theme::Border& border = getBorder(getState());
     const Theme::Padding& padding = getPadding();
     Vector4 skinColor = _skin->getColor();
     skinColor.w *= _opacity;
@@ -1386,6 +1321,42 @@ const char* Control::getType() const
     return "control";
 }
 
+Control* Control::getParent() const
+{
+    return _parent;
+}
+
+bool Control::isChild(Control* control) const
+{
+    if (!control)
+        return false;
+
+    Control* parent = _parent;
+    while (parent)
+    {
+        if (parent == control)
+            return true;
+        parent = parent->_parent;
+    }
+
+    return false;
+}
+
+Form* Control::getForm() const
+{
+    if (_parent)
+        return _parent->getForm();
+
+    if (isContainer())
+    {
+        Container* container = static_cast<Container*>(const_cast<Control*>(this));
+        if (container->isForm())
+            return static_cast<Form*>(container);
+    }
+
+    return NULL;
+}
+
 // Implementation of AnimationHandler
 unsigned int Control::getAnimationPropertyComponentCount(int propertyId) const
 {
@@ -1511,33 +1482,39 @@ Theme::Style::Overlay* Control::getOverlay(State state) const
 {
     GP_ASSERT(_style);
 
-    switch(state)
+    Theme::Style::Overlay* overlay = NULL;
+
+    switch (state)
     {
     case Control::NORMAL:
         return _style->getOverlay(Theme::Style::OVERLAY_NORMAL);
+
     case Control::FOCUS:
-        return _style->getOverlay(Theme::Style::OVERLAY_FOCUS);
+        overlay = _style->getOverlay(Theme::Style::OVERLAY_FOCUS);
+        break;
+
     case Control::ACTIVE:
-    {
-        Theme::Style::Overlay* activeOverlay = _style->getOverlay(Theme::Style::OVERLAY_ACTIVE);
-        if (activeOverlay)
-            return activeOverlay;
-        else
-            return getOverlay(_previousState);
-    }
+        overlay = _style->getOverlay(Theme::Style::OVERLAY_ACTIVE);
+        if (!overlay && hasFocus())
+            overlay = _style->getOverlay(Theme::Style::OVERLAY_FOCUS);
+        break;
+
     case Control::DISABLED:
-        return _style->getOverlay(Theme::Style::OVERLAY_DISABLED);
+        overlay = _style->getOverlay(Theme::Style::OVERLAY_DISABLED);
+        break;
+
     case Control::HOVER:
-    {
-        Theme::Style::Overlay* hoverOverlay = _style->getOverlay(Theme::Style::OVERLAY_HOVER);
-        if (hoverOverlay)
-            return hoverOverlay;
-        else
-            return getOverlay(_previousState);
-    }
-    default:
-        return NULL;
+        overlay = _style->getOverlay(Theme::Style::OVERLAY_HOVER);
+        if (!overlay && hasFocus())
+            overlay = _style->getOverlay(Theme::Style::OVERLAY_FOCUS);
+        break;
     }
+
+    // Fall back to normal overlay if more specific state overlay not found
+    if (!overlay)
+        overlay = _style->getOverlay(Theme::Style::OVERLAY_NORMAL);
+
+    return overlay;
 }
 
 void Control::overrideStyle()

+ 70 - 13
gameplay/src/Control.h

@@ -17,6 +17,7 @@ namespace gameplay
 {
 
 class Container;
+class Form;
 
 /**
  * Base class for UI controls.
@@ -43,7 +44,7 @@ public:
         NORMAL = 0x01,
 
         /**
-         * State of a control currently in focus.
+         * State of a control when it is currently in focus.
          */
         FOCUS = 0x02,
 
@@ -173,8 +174,18 @@ public:
              * Event triggered when a mouse cursor leaves a control.
              */
             LEAVE           = 0x100,
+
+            /**
+             * Event triggered when a control gains focus.
+             */
+            FOCUS_GAINED    = 0x200,
+
+            /**
+             * Event triggered when a control loses focus.
+             */
+            FOCUS_LOST      = 0x400
         };
-    
+
         /*
          * Destructor.
          */
@@ -193,7 +204,7 @@ public:
      * @script{ignore}
      * A constant used for setting themed attributes on all control states simultaneously.
      */
-    static const unsigned char STATE_ALL = NORMAL | FOCUS | ACTIVE | DISABLED | HOVER;
+    static const unsigned char STATE_ALL = NORMAL | ACTIVE | FOCUS | DISABLED | HOVER;
 
     /**
      * Position animation property. Data = x, y
@@ -760,6 +771,14 @@ public:
      */
     bool isVisible() const;
 
+    /**
+     * Determines if this control is visible in its hierarchy.
+     *
+     * A control is visible in its hierarchy if it is visible and all of its parents
+     * are also visible.
+     */
+    bool isVisibleInHierarchy() const;
+
     /**
      * Set the opacity of this control.
      *
@@ -792,11 +811,12 @@ public:
     bool isEnabled() const;
 
     /**
-     * Change this control's state.
+     * Determines if this control is enabled in its hierarchy.
      *
-     * @param state The state to switch this control to.
+     * A control is enabled in its hierarchy if it is enabled and all of its parents
+     * are also enabled.
      */
-    void setState(State state);
+    bool isEnabledInHierarchy() const;
 
     /**
      * Get this control's current state.
@@ -876,6 +896,29 @@ public:
      */
     virtual const char* getType() const;
 
+    /**
+     * Returns this control's parent, or NULL if this control does not have a parent.
+     *
+     * @return This control's parent.
+     */
+    Control* getParent() const;
+
+    /**
+     * Determines if this control is a child (at any level of hierarchy) of the 
+     * specified control.
+     *
+     * @param control The control to check.
+     * @return True if this control is a direct or indirect child of the specified control.
+     */
+    bool isChild(Control* control) const;
+
+    /**
+     * Returns this control's top level form, or NULL if this control does not belong to a form.
+     *
+     * @return this control's form.
+     */
+    Form* getForm() const;
+
     /**
      * Adds a listener to be notified of specific events affecting
      * this control.  Event types can be OR'ed together.
@@ -1074,6 +1117,14 @@ protected:
      */
     void notifyListeners(Control::Listener::EventType eventType);
 
+    /**
+     * Called when a control event is fired for this control, before external
+     * listeners are notified of the event.
+     *
+     * @param evt The event type.
+     */
+    virtual void controlEvent(Control::Listener::EventType evt);
+
     /**
      * Gets the Alignment by string.
      *
@@ -1083,22 +1134,28 @@ protected:
     static Alignment getAlignment(const char* alignment);
 
     /**
-     * Gets whether this control is in focus.
-     * Note that a control's state can be HOVER while the control is in focus.
-     * When the cursor leaves the control, it will return to the FOCUS state.
-     * This method will still return true in this case.
+     * Determines if this control is currently in focus.
+     *
+     * @return True if the control is currently in focus.
      */
     bool hasFocus() const;
 
+    /**
+     * Determines if this control accepts focus.
+     *
+     * @return True if this control accepts focus, false if it does not.
+     */
+    virtual bool canFocus() const;
+
     /** 
      * The Control's ID.
      */ 
     std::string _id;
 
     /**
-     * Determines overlay used during draw().
+     * Whether the control is enabled.
      */
-    State _state;
+    bool _enabled;
 
     /**
      * Bits indicating whether bounds values are absolute values or percentages.
@@ -1243,7 +1300,7 @@ private:
     
     bool _styleOverridden;
     Theme::Skin* _skin;
-    State _previousState;
+
 };
 
 }

+ 7 - 0
gameplay/src/Font.cpp

@@ -161,6 +161,13 @@ Font::Format Font::getFormat()
     return _format;
 }
 
+bool Font::isCharacterSupported(int character) const
+{
+    // TODO: Update this once we support unicode fonts
+    int glyphIndex = character - 32; // HACK for ASCII
+    return (glyphIndex >= 0 && glyphIndex < (int)_glyphCount);
+}
+
 void Font::start()
 {
     GP_ASSERT(_batch);

+ 8 - 0
gameplay/src/Font.h

@@ -129,6 +129,14 @@ public:
      */
     Format getFormat();
 
+    /**
+     * Determines if this font supports the specified character code.
+     *
+     * @param character The character code to check.
+     * @return True if this Font supports (can draw) the specified character, false otherwise.
+     */
+    bool isCharacterSupported(int character) const;
+
     /**
      * Starts text drawing for this font.
      */

+ 447 - 87
gameplay/src/Form.cpp

@@ -19,9 +19,12 @@ namespace gameplay
 
 static Effect* __formEffect = NULL;
 static std::vector<Form*> __forms;
+Control* Form::_focusControl = NULL;
+Control* Form::_activeControl = NULL;
+Control::State Form::_activeControlState = Control::NORMAL;
 
 Form::Form() : _theme(NULL), _frameBuffer(NULL), _spriteBatch(NULL), _node(NULL),
-    _nodeQuad(NULL), _nodeMaterial(NULL) , _u2(0), _v1(0), _isGamepad(false)
+    _nodeQuad(NULL), _nodeMaterial(NULL) , _u2(0), _v1(0)
 {
 }
 
@@ -168,7 +171,7 @@ Form* Form::create(const char* url)
     }
     form->initialize(style, formProperties);
 
-    form->_consumeInputEvents = formProperties->getBool("consumeInputEvents", false);
+    form->_consumeInputEvents = formProperties->getBool("consumeInputEvents", true);
 
     form->_scroll = getScroll(formProperties->getString("scroll"));
     form->_scrollBarsAutoHide = formProperties->getBool("scrollBarsAutoHide");
@@ -181,7 +184,7 @@ Form* Form::create(const char* url)
     form->addControls(theme, formProperties);
 
     SAFE_DELETE(properties);
-    
+
     form->updateFrameBuffer();
 
     __forms.push_back(form);
@@ -191,10 +194,9 @@ Form* Form::create(const char* url)
 
 Form* Form::getForm(const char* id)
 {
-    std::vector<Form*>::const_iterator it;
-    for (it = __forms.begin(); it < __forms.end(); ++it)
+    for (size_t i = 0, size = __forms.size(); i < size; ++i)
     {
-        Form* f = *it;
+        Form* f = __forms[i];
         GP_ASSERT(f);
         if (strcmp(id, f->getId()) == 0)
         {
@@ -204,6 +206,11 @@ Form* Form::getForm(const char* id)
     return NULL;
 }
 
+bool Form::isForm() const
+{
+    return true;
+}
+
 Theme* Form::getTheme() const
 {
     return _theme;
@@ -350,13 +357,15 @@ void Form::setNode(Node* node)
 
 void Form::update(float elapsedTime)
 {
-    if (true)//isDirty())
+    if (isDirty())
     {
         update(NULL, Vector2::zero());
 
+        Control::State state = getState();
+
         // Cache themed attributes for performance.
-        _skin = getSkin(_state);
-        _opacity = getOpacity(_state);
+        _skin = getSkin(state);
+        _opacity = getOpacity(state);
 
         GP_ASSERT(_layout);
         if (_scroll != SCROLL_NONE)
@@ -399,7 +408,7 @@ unsigned int Form::draw()
     // to render the contents of the framebuffer directly to the display.
 
     // Check whether this form has changed since the last call to draw() and if so, render into the framebuffer.
-    if (true)//isDirty())
+    if (isDirty())
     {
         FrameBuffer* previousFrameBuffer = _frameBuffer->bind();
 
@@ -443,100 +452,327 @@ const char* Form::getType() const
     return "form";
 }
 
+Control* Form::getActiveControl()
+{
+    return _activeControl;
+}
+
 void Form::updateInternal(float elapsedTime)
 {
-    size_t size = __forms.size();
-    for (size_t i = 0; i < size; ++i)
+    for (size_t i = 0, size = __forms.size(); i < size; ++i)
     {
         Form* form = __forms[i];
-        GP_ASSERT(form);
 
-        if (form->isEnabled() && form->isVisible())
+        if (form && form->isEnabled() && form->isVisible())
         {
             form->update(elapsedTime);
         }
     }
 }
 
-static bool shouldPropagateTouchEvent(Control::State state, Touch::TouchEvent evt, const Rectangle& bounds, int x, int y)
+bool Form::screenToForm(Control* ctrl, int* x, int* y)
 {
-    return (state != Control::NORMAL ||
-            (evt == Touch::TOUCH_PRESS &&
-             x >= bounds.x &&
-             x <= bounds.x + bounds.width &&
-             y >= bounds.y &&
-             y <= bounds.y + bounds.height));
+    Form* form = ctrl->getForm();
+    if (form)
+    {
+        if (form->_node)
+        {
+            // Form is attached to a scene node, so project the screen space point into the
+            // form's coordinate space (which may be transformed by the node).
+            Vector3 point;
+            if (form->projectPoint(*x, *y, &point))
+            {
+                *x = (int)point.x;
+                *y = form->_bounds.height - (int)point.y;
+            }
+            else
+            {
+                return false;
+            }
+        }
+
+        *x -= form->_bounds.x;
+        *y -= form->_bounds.y;
+
+        return true;
+    }
+
+    return false;
 }
 
-bool Form::touchEventInternal(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+Control* Form::findInputControl(int* x, int* y, bool focus)
 {
-    // Check for a collision with each Form in __forms.
-    // Pass the event on.
-    size_t size = __forms.size();
-    for (size_t i = 0; i < size; ++i)
+    for (int i = (int)__forms.size() - 1; i >= 0; --i)
     {
         Form* form = __forms[i];
-        GP_ASSERT(form);
+        if (!form || !form->isEnabled() || !form->isVisible())
+            continue;
+
+        // Convert to local form coordinates
+        int formX = *x;
+        int formY = *y;
+        if (!screenToForm(form, &formX, &formY))
+            continue;
+
+        // Search for an input control within this form
+        Control* ctrl = findInputControl(form, formX, formY, focus);
+        if (ctrl)
+        {
+            *x = formX;
+            *y = formY;
+            return ctrl;
+        }
+
+        // If the form consumes input events and the point intersects the form,
+        // don't traverse other forms below it.
+        if (form->_consumeInputEvents && form->_absoluteClipBounds.contains(formX, formY))
+            return NULL;
+    }
+
+    return NULL;
+}
+
+Control* Form::findInputControl(Control* control, int x, int y, bool focus)
+{
+    Control* result = NULL;
+
+    // Does the passed in control's bounds intersect the specified coordinates - and 
+    // does the control support the specified input state?
+    if (control->_consumeInputEvents && control->_visible && control->_enabled && (!focus || control->canFocus()))
+    {
+        if (control->_absoluteClipBounds.contains(x, y))
+            result = control;
+    }
+
+    // If the control has children, search for an input control inside it that also
+    // supports the above conditions.
+    if (control->isContainer())
+    {
+        Container* container = static_cast<Container*>(control);
+        for (int i = (int)container->getControlCount() - 1; i >= 0; --i)
+        {
+            Control* ctrl = findInputControl(container->getControl((unsigned int)i), x, y, focus);
+            if (ctrl)
+                result = ctrl;
+        }
+    }
+
+    return result;
+}
+
+Control* Form::handlePointerPressRelease(int* x, int* y, bool pressed)
+{
+    Control* ctrl = NULL;
+
+    int newX = *x;
+    int newY = *y;
+
+    if (pressed)
+    {
+        // Update active state changes
+        if ((ctrl = findInputControl(&newX, &newY, false)) != NULL)
+        {
+            if (_activeControl != ctrl || _activeControlState != Control::ACTIVE)
+            {
+                if (_activeControl)
+                    _activeControl->_dirty = true;
+
+                _activeControl = ctrl;
+                _activeControlState = Control::ACTIVE;
+                _activeControl->_dirty = true;
+            }
+
+            ctrl->notifyListeners(Control::Listener::PRESS);
+        }
+
+        // Update focus state?
+        if (!(ctrl && ctrl->canFocus()))
+        {
+            newX = *x;
+            newY = *y;
+            ctrl = findInputControl(&newX, &newY, true);
+        }
+
+        // Update focus
+        if (_focusControl != ctrl)
+        {
+            setFocusControl(ctrl);
+        }
+    }
+    else // !pressed
+    {
+        Control* active = _activeControlState == Control::ACTIVE ? _activeControl : NULL;
+
+        if (active)
+        {
+            active->addRef(); // protect against event-hanlder evil
 
-        if (form->isEnabled() && form->isVisible())
+            // Release happened for the active control (that was pressed)
+            ctrl = active;
+
+            // Transform point to form-space
+            screenToForm(ctrl, &newX, &newY);
+
+            // No longer any active control
+            _activeControl->_dirty = true;
+            _activeControl = NULL;
+            _activeControlState = Control::NORMAL;
+        }
+        else
         {
-            if (form->_node)
+            // Update active and hover control state on release
+            Control* inputControl = findInputControl(&newX, &newY, false);
+            if (inputControl)
             {
-                Vector3 point;
-                if (form->projectPoint(x, y, &point))
+                ctrl = inputControl;
+
+                if (_activeControl != ctrl || _activeControlState != Control::HOVER)
                 {
-                    const Rectangle& bounds = form->getBounds();
-                    if (shouldPropagateTouchEvent(form->getState(), evt, bounds, point.x, point.y))
-                    {
-                        if (form->touchEvent(evt, point.x - bounds.x, bounds.height - point.y - bounds.y, contactIndex))
-                            return true;
-                    }
+                    if (_activeControl)
+                        _activeControl->_dirty = true;
+
+                    _activeControl = ctrl;
+                    _activeControlState = Control::HOVER;
+                    _activeControl->_dirty = true;
                 }
             }
             else
             {
-                // Simply compare with the form's bounds.
-                const Rectangle& bounds = form->getBounds();
-                if (shouldPropagateTouchEvent(form->getState(), evt, bounds, x, y))
+                // No longer any active control
+                if (_activeControl)
+                    _activeControl->_dirty = true;
+
+                _activeControl = NULL;
+                _activeControlState = Control::NORMAL;
+            }
+        }
+
+        if (active)
+        {
+            // Fire release event for the previously active control
+            active->notifyListeners(Control::Listener::RELEASE);
+
+            // If the release event was received on the same control that was
+            // originally pressed, fire a click event
+            if (active->_absoluteClipBounds.contains(newX, newY))
+            {
+                Control* parent = active->getParent();
+                if (!parent || (parent->isContainer() && !static_cast<Container*>(parent)->isScrolling()))
                 {
-                    // Pass on the event's position relative to the form.
-                    if (form->touchEvent(evt, x - bounds.x, y - bounds.y, contactIndex))
-                        return true;
+                    active->notifyListeners(Control::Listener::CLICK);
                 }
             }
+
+            active->release();
         }
     }
-    return false;
+
+    *x = newX;
+    *y = newY;
+
+    return ctrl;
 }
 
-bool Form::keyEventInternal(Keyboard::KeyEvent evt, int key)
+Control* Form::handlePointerMove(int* x, int* y)
 {
-    size_t size = __forms.size();
-    for (size_t i = 0; i < size; ++i)
+    Control* ctrl = NULL;
+
+    // Handle hover control changes on move, only if there is no currently active control
+    // (i.e. when the mouse or a finger is not down).
+    if (_activeControl && (_activeControlState == Control::ACTIVE))
     {
-        Form* form = __forms[i];
-        GP_ASSERT(form);
-        if (form->isEnabled() && form->isVisible() && form->hasFocus() && !form->_isGamepad)
+        ctrl = _activeControl;
+        screenToForm(ctrl, x, y);
+    }
+    else
+    {
+        ctrl = findInputControl(x, y, false);
+        if (ctrl)
         {
-            if (form->keyEvent(evt, key))
-                return true;
+            // Update hover control
+            if (_activeControl != ctrl || _activeControlState != Control::HOVER)
+            {
+                if (_activeControl)
+                    _activeControl->_dirty = true;
+
+                _activeControl = ctrl;
+                _activeControlState = Control::HOVER;
+                _activeControl->_dirty = true;
+            }
+        }
+        else
+        {
+            // No active/hover control
+            if (_activeControl)
+                _activeControl->_dirty = true;
+
+            _activeControl = NULL;
+            _activeControlState = Control::NORMAL;
         }
     }
-    return false;
+
+    return ctrl;
+}
+
+void Form::verifyRemovedControlState(Control* control)
+{
+    if (_focusControl == control)
+        _focusControl = NULL;
+
+    if (_activeControl == control)
+    {
+        _activeControl = NULL;
+        _activeControlState = Control::NORMAL;
+    }
 }
 
-static bool shouldPropagateMouseEvent(Control::State state, Mouse::MouseEvent evt, const Rectangle& bounds, int x, int y)
+bool Form::touchEventInternal(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
 {
-    return (state != Control::NORMAL ||
-            ((evt == Mouse::MOUSE_PRESS_LEFT_BUTTON ||
-              evt == Mouse::MOUSE_PRESS_MIDDLE_BUTTON ||
-              evt == Mouse::MOUSE_PRESS_RIGHT_BUTTON ||
-              evt == Mouse::MOUSE_MOVE ||
-              evt == Mouse::MOUSE_WHEEL) &&
-                x >= bounds.x &&
-                x <= bounds.x + bounds.width &&
-                y >= bounds.y &&
-                y <= bounds.y + bounds.height));
+    Control* ctrl = NULL;
+    int formX = x;
+    int formY = y;
+
+    // Handle focus, active and hover state changes
+    if (contactIndex == 0)
+    {
+        switch (evt)
+        {
+        case Touch::TOUCH_PRESS:
+        case Touch::TOUCH_RELEASE:
+            ctrl = handlePointerPressRelease(&formX, &formY, evt == Touch::TOUCH_PRESS);
+            break;
+
+        case Touch::TOUCH_MOVE:
+            ctrl = handlePointerMove(&formX, &formY);
+            break;
+        }
+    }
+
+    // Dispatch input events to all controls that intersect this point
+    if (ctrl == NULL)
+    {
+        formX = x;
+        formY = y;
+        ctrl = findInputControl(&formX, &formY, false);
+    }
+
+    if (ctrl)
+    {
+        // Dispatch the event from the bottom upwards, until a control intersecting the point consumes the event
+        while (ctrl)
+        {
+            if (ctrl->touchEvent(evt, formX - ctrl->_absoluteBounds.x, formY - ctrl->_absoluteBounds.y, contactIndex))
+                return true;
+
+            // Consume all input events anyways?
+            if (ctrl->getConsumeInputEvents())
+                return true;
+
+            ctrl = ctrl->getParent();
+        }
+    }
+
+    return false;
 }
 
 bool Form::mouseEventInternal(Mouse::MouseEvent evt, int x, int y, int wheelDelta)
@@ -545,50 +781,135 @@ bool Form::mouseEventInternal(Mouse::MouseEvent evt, int x, int y, int wheelDelt
     if (Game::getInstance()->isMouseCaptured())
         return false;
 
-    for (size_t i = 0; i < __forms.size(); ++i)
+    Control* ctrl = NULL;
+    int formX = x;
+    int formY = y;
+
+    // Handle focus, active and hover state changes
+    switch (evt)
     {
-        Form* form = __forms[i];
-        GP_ASSERT(form);
+    case Mouse::MOUSE_PRESS_LEFT_BUTTON:
+    case Mouse::MOUSE_RELEASE_LEFT_BUTTON:
+        ctrl = handlePointerPressRelease(&formX, &formY, evt == Mouse::MOUSE_PRESS_LEFT_BUTTON);
+        break;
+
+    case Mouse::MOUSE_MOVE:
+        ctrl = handlePointerMove(&formX, &formY);
+        break;
+    }
 
-        if (form->isEnabled() && form->isVisible())
+    // Dispatch input events to all controls that intersect this point
+    if (ctrl == NULL)
+    {
+        formX = x;
+        formY = y;
+        ctrl = findInputControl(&formX, &formY, false);
+    }
+
+    if (ctrl)
+    {
+        // Handle container scrolling
+        Control* tmp = ctrl;
+        while (tmp)
         {
-            if (form->_node)
+            if (tmp->isContainer())
             {
-                Vector3 point;
-                if (form->projectPoint(x, y, &point))
+                Container* container = static_cast<Container*>(tmp);
+                if (container->_scroll != SCROLL_NONE)
                 {
-                    const Rectangle& bounds = form->getBounds();
-                    if (shouldPropagateMouseEvent(form->getState(), evt, bounds, point.x, point.y))
-                    {
-                        if (form->mouseEvent(evt, point.x - bounds.x, bounds.height - point.y - bounds.y, wheelDelta))
-                            return true;
-                    }
+                    if (container->mouseEventScroll(evt, formX - tmp->_absoluteBounds.x, formY - tmp->_absoluteBounds.y, wheelDelta))
+                        return true;
+                    break; // scrollable parent container found
                 }
             }
-            else
+            tmp = tmp->_parent;
+        }
+
+        // Dispatch the event from the bottom upwards, until a control intersecting the point consumes the event
+        while (ctrl)
+        {
+            int localX = formX - ctrl->_absoluteBounds.x;
+            int localY = formY - ctrl->_absoluteBounds.y;
+            if (ctrl->mouseEvent(evt, localX, localY, wheelDelta))
+                return true;
+
+            // Forward to touch event hanlder if unhandled by mouse handler
+            switch (evt)
             {
-                // Simply compare with the form's bounds.
-                const Rectangle& bounds = form->getBounds();
-                if (shouldPropagateMouseEvent(form->getState(), evt, bounds, x, y))
+            case Mouse::MOUSE_PRESS_LEFT_BUTTON:
+                if (ctrl->touchEvent(Touch::TOUCH_PRESS, localX, localY, 0))
+                    return true;
+                break;
+            case Mouse::MOUSE_RELEASE_LEFT_BUTTON:
+                if (ctrl->touchEvent(Touch::TOUCH_RELEASE, localX, localY, 0))
+                    return true;
+                break;
+            case Mouse::MOUSE_MOVE:
+                if (ctrl->touchEvent(Touch::TOUCH_MOVE, localX, localY, 0))
+                    return true;
+                break;
+            }
+
+            // Consume all input events anyways?
+            if (ctrl->getConsumeInputEvents())
+                return true;
+
+            ctrl = ctrl->getParent();
+        }
+    }
+
+    return false;
+}
+
+bool Form::keyEventInternal(Keyboard::KeyEvent evt, int key)
+{
+    // Ignore the escape key
+    if (key == Keyboard::KEY_ESCAPE)
+        return false;
+
+    // Handle focus changing
+    if (_focusControl)
+    {
+        switch (evt)
+        {
+        case Keyboard::KeyEvent::KEY_PRESS:
+            switch (key)
+            {
+            case Keyboard::KEY_TAB:
+                if (_focusControl->_parent && _focusControl->_parent->isContainer())
                 {
-                    // Pass on the event's position relative to the form.
-                    if (form->mouseEvent(evt, x - bounds.x, y - bounds.y, wheelDelta))
+                    if (static_cast<Container*>(_focusControl->_parent)->moveFocus(Container::NEXT))
                         return true;
                 }
+                break;
             }
+            break;
+        }
+    }
+
+    // Dispatch key events
+    Control* ctrl = _focusControl;
+    while (ctrl)
+    {
+        if (ctrl->isEnabled() && ctrl->isVisible())
+        {
+            if (ctrl->keyEvent(evt, key))
+                return true;
         }
+
+        ctrl = ctrl->getParent();
     }
+
     return false;
 }
 
 void Form::gamepadEventInternal(Gamepad::GamepadEvent evt, Gamepad* gamepad, unsigned int analogIndex)
 {
-    for (size_t i = 0; i < __forms.size(); ++i)
+    for (int i = (int)__forms.size() - 1; i >= 0; --i)
     {
         Form* form = __forms[i];
-        GP_ASSERT(form);
 
-        if (form->isEnabled() && form->isVisible() && form->hasFocus())
+        if (form && form->isEnabled() && form->isVisible() && form->hasFocus())
         {
             if (form->gamepadEvent(evt, gamepad, analogIndex))
                 return;
@@ -598,7 +919,7 @@ void Form::gamepadEventInternal(Gamepad::GamepadEvent evt, Gamepad* gamepad, uns
 
 void Form::resizeEventInternal(unsigned int width, unsigned int height)
 {
-    for (size_t i = 0; i < __forms.size(); ++i)
+    for (size_t i = 0, size = __forms.size(); i < size; ++i)
     {
         Form* form = __forms[i];
         if (form)
@@ -619,6 +940,9 @@ void Form::resizeEventInternal(unsigned int width, unsigned int height)
 
 bool Form::projectPoint(int x, int y, Vector3* point)
 {
+    if (!_node)
+        return false;
+
     Scene* scene = _node->getScene();
     Camera* camera;
 
@@ -676,4 +1000,40 @@ unsigned int Form::nextPowerOfTwo(unsigned int v)
     }
 }
 
+void Form::controlDisabled(Control* control)
+{
+    if (Form::_focusControl && (Form::_focusControl == control || Form::_focusControl->isChild(control)))
+    {
+        setFocusControl(NULL);
+    }
+
+    if (Form::_activeControl)
+    {
+        if (Form::_activeControl == control || Form::_activeControl->isChild(control))
+        {
+            Form::_activeControl = NULL;
+            Form::_activeControlState = Control::NORMAL;
+        }
+    }
+}
+
+void Form::setFocusControl(Control* control)
+{
+    Control* oldFocus = _focusControl;
+
+    _focusControl = control;
+
+    if (oldFocus)
+    {
+        oldFocus->_dirty = true;
+        oldFocus->notifyListeners(Control::Listener::FOCUS_LOST);
+    }
+
+    if (_focusControl)
+    {
+        _focusControl->_dirty = true;
+        _focusControl->notifyListeners(Control::Listener::FOCUS_GAINED);
+    }
+}
+
 }

+ 33 - 1
gameplay/src/Form.h

@@ -51,6 +51,8 @@ class Form : public Container
     friend class Platform;
     friend class Game;
     friend class Gamepad;
+    friend class Control;
+    friend class Container;
 
 public:
 
@@ -85,6 +87,11 @@ public:
      */
     static Form* getForm(const char* id);
     
+    /**
+     * @see Container#isForm()
+     */
+    bool isForm() const;
+
     /**
      * Gets the theme for the form.
      *
@@ -119,6 +126,13 @@ public:
      */
     const char* getType() const;
 
+    /**
+     * Returns the single currently active control within the UI system.
+     *
+     * @return The currently active control, or NULL if no controls are currently active.
+     */
+    static Control* getActiveControl();
+
 protected:
 
     /**
@@ -218,6 +232,22 @@ private:
      */
     void updateFrameBuffer();
 
+    static Control* findInputControl(int* x, int* y, bool focus);
+
+    static Control* findInputControl(Control* control, int x, int y, bool focus);
+
+    static Control* handlePointerPressRelease(int* x, int* y, bool pressed);
+
+    static Control* handlePointerMove(int* x, int* y);
+
+    static bool screenToForm(Control* ctrl, int* x, int* y);
+
+    static void verifyRemovedControlState(Control* control);
+
+    static void controlDisabled(Control* control);
+
+    static void setFocusControl(Control* control);
+
     Theme* _theme;                      // The Theme applied to this Form.
     FrameBuffer* _frameBuffer;          // FBO the Form is rendered into for texturing the quad. 
     SpriteBatch* _spriteBatch;
@@ -227,7 +257,9 @@ private:
     float _u2;
     float _v1;
     Matrix _projectionMatrix;           // Orthographic projection matrix to be set on SpriteBatch objects when rendering into the FBO.
-    bool _isGamepad;
+    static Control* _focusControl;
+    static Control* _activeControl;
+    static Control::State _activeControlState;
 };
 
 }

+ 0 - 1
gameplay/src/Gamepad.cpp

@@ -19,7 +19,6 @@ Gamepad::Gamepad(const char* formPath)
     _form = Form::create(formPath);
     GP_ASSERT(_form);
     _form->setConsumeInputEvents(false);
-    _form->_isGamepad = true;
     _vendorString = "None";
     _productString = "Virtual";
 

+ 0 - 2
gameplay/src/ImageControl.cpp

@@ -24,7 +24,6 @@ ImageControl* ImageControl::create(const char* id, Theme::Style* style)
         imageControl->_id = id;
     imageControl->setStyle(style);
 
-    imageControl->_consumeInputEvents = false;
     imageControl->_focusIndex = -2;
 
     return imageControl;
@@ -35,7 +34,6 @@ ImageControl* ImageControl::create(Theme::Style* style, Properties* properties)
     ImageControl* imageControl = new ImageControl();
     imageControl->initialize(style, properties);
 
-    imageControl->_consumeInputEvents = false;
     imageControl->_focusIndex = -2;
 
     return imageControl;

+ 26 - 20
gameplay/src/Joystick.cpp

@@ -42,6 +42,8 @@ void Joystick::initialize(Theme::Style* style, Properties* properties)
 
     Control::initialize(style, properties);
 
+    Control::State state = getState();
+
     if (!properties->exists("radius"))
     {
         GP_ERROR("Failed to load joystick; required attribute 'radius' is missing.");
@@ -59,7 +61,7 @@ void Joystick::initialize(Theme::Style* style, Properties* properties)
         setRelative(false);
     }
 
-    Theme::ThemeImage* inner = getImage("inner", _state);
+    Theme::ThemeImage* inner = getImage("inner", state);
     if (inner)
     {
         _innerSize = new Vector2();
@@ -75,7 +77,7 @@ void Joystick::initialize(Theme::Style* style, Properties* properties)
         }
     }
 
-    Theme::ThemeImage* outer = getImage("outer", _state);
+    Theme::ThemeImage* outer = getImage("outer", state);
     if (outer)
     {
         _outerSize = new Vector2();
@@ -118,9 +120,9 @@ void Joystick::addListener(Control::Listener* listener, int eventFlags)
     Control::addListener(listener, eventFlags);
 }
 
-bool Joystick::touchEvent(Touch::TouchEvent touchEvent, int x, int y, unsigned int contactIndex)
+bool Joystick::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
 {
-    switch (touchEvent)
+    switch (evt)
     {
         case Touch::TOUCH_PRESS:
         {
@@ -129,8 +131,7 @@ bool Joystick::touchEvent(Touch::TouchEvent touchEvent, int x, int y, unsigned i
                 float dx = 0.0f;
                 float dy = 0.0f;
 
-                _contactIndex = (int) contactIndex;
-                notifyListeners(Control::Listener::PRESS);
+                _contactIndex = (int)contactIndex;
 
                 // Get the displacement of the touch from the centre.
                 if (!_relative)
@@ -148,7 +149,7 @@ bool Joystick::touchEvent(Touch::TouchEvent touchEvent, int x, int y, unsigned i
 
                 // If the displacement is greater than the radius, then cap the displacement to the
                 // radius.
-                
+
                 Vector2 value;
                 if ((fabs(_displacement.x) > _radius) || (fabs(_displacement.y) > _radius))
                 {
@@ -171,20 +172,20 @@ bool Joystick::touchEvent(Touch::TouchEvent touchEvent, int x, int y, unsigned i
                     notifyListeners(Control::Listener::VALUE_CHANGED);
                 }
 
-                setState(ACTIVE);
-                return _consumeInputEvents;
+                return true;
             }
             break;
         }
+
         case Touch::TOUCH_MOVE:
         {
             if (_contactIndex == (int) contactIndex)
             {
                 float dx = x - ((_relative) ? _screenRegion.x - _bounds.x : 0.0f) - _screenRegion.width * 0.5f;
                 float dy = -(y - ((_relative) ? _screenRegion.y - _bounds.y : 0.0f) - _screenRegion.height * 0.5f);
-            
+
                 _displacement.set(dx, dy);
-            
+
                 Vector2 value;
                 if ((fabs(_displacement.x) > _radius) || (fabs(_displacement.y) > _radius))
                 {
@@ -206,18 +207,17 @@ bool Joystick::touchEvent(Touch::TouchEvent touchEvent, int x, int y, unsigned i
                     notifyListeners(Control::Listener::VALUE_CHANGED);
                 }
 
-                return _consumeInputEvents;
+                return true;
             }
             break;
         }
+
         case Touch::TOUCH_RELEASE:
         {
             if (_contactIndex == (int) contactIndex)
             {
                 _contactIndex = INVALID_CONTACT_INDEX;
 
-                notifyListeners(Control::Listener::RELEASE);
-
                 // Reset displacement and direction vectors.
                 _displacement.set(0.0f, 0.0f);
                 Vector2 value(_displacement);
@@ -228,22 +228,23 @@ bool Joystick::touchEvent(Touch::TouchEvent touchEvent, int x, int y, unsigned i
                     notifyListeners(Control::Listener::VALUE_CHANGED);
                 }
 
-                setState(NORMAL);
-                return _consumeInputEvents;
+                return true;
             }
             break;
         }
     }
 
-    return false;
+    return Control::touchEvent(evt, x, y, contactIndex);
 }
 
 void Joystick::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
 {
     GP_ASSERT(spriteBatch);
 
+    Control::State state = getState();
+
     // If the joystick is not absolute, then only draw if it is active.
-    if (!_relative || (_relative && _state == ACTIVE))
+    if (!_relative || (_relative && state == ACTIVE))
     {
         if (!_relative)
         {
@@ -252,7 +253,7 @@ void Joystick::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
         }
 
         // Draw the outer image.
-        Theme::ThemeImage* outer = getImage("outer", _state);
+        Theme::ThemeImage* outer = getImage("outer", state);
         if (outer)
         {
             const Theme::UVs& uvs = outer->getUVs();
@@ -264,7 +265,7 @@ void Joystick::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
         }
 
         // Draw the inner image.
-        Theme::ThemeImage* inner = getImage("inner", _state);
+        Theme::ThemeImage* inner = getImage("inner", state);
         if (inner)
         {
             Vector2 position(_screenRegion.x, _screenRegion.y);
@@ -289,4 +290,9 @@ const char* Joystick::getType() const
     return "joystick";
 }
 
+bool Joystick::canFocus() const
+{
+    return true;
+}
+
 }

+ 5 - 0
gameplay/src/Joystick.h

@@ -120,6 +120,11 @@ public:
      */
     inline const unsigned int getIndex() const;
 
+    /**
+     * @see Control#canFocus()
+     */
+    bool canFocus() const;
+
 protected:
     
     /**

+ 3 - 2
gameplay/src/Keyboard.h

@@ -14,14 +14,15 @@ class Keyboard
 
 public:
 
-   /**
+    /**
      * The keyboard event.
      */
     enum KeyEvent
     {
         KEY_PRESS,
         KEY_RELEASE,
-        KEY_CHAR
+        KEY_CHAR,
+        KEY_REPEAT
     };
 
     /**

+ 9 - 11
gameplay/src/Label.cpp

@@ -21,9 +21,6 @@ Label* Label::create(const char* id, Theme::Style* style)
         label->_id = id;
     label->setStyle(style);
 
-    // Labels don't consume input events by default like other controls.
-    label->_consumeInputEvents = false;
-
     // Ensure that labels cannot receive focus.
     label->_focusIndex = -2;
 
@@ -35,7 +32,6 @@ Label* Label::create(Theme::Style* style, Properties* properties)
     Label* label = new Label();
     label->initialize(style, properties);
 
-    label->_consumeInputEvents = false;
     label->_focusIndex = -2;
 
     return label;
@@ -89,19 +85,20 @@ void Label::update(const Control* container, const Vector2& offset)
 
     _textBounds.set((int)_viewportBounds.x, (int)_viewportBounds.y, _viewportBounds.width, _viewportBounds.height);
 
-    _font = getFont(_state);
-    _textColor = getTextColor(_state);
+    Control::State state = getState();
+    _font = getFont(state);
+    _textColor = getTextColor(state);
     _textColor.w *= _opacity;
 
-    Font* font = getFont(_state);
+    Font* font = getFont(state);
     if ((_autoWidth == Control::AUTO_SIZE_FIT || _autoHeight == Control::AUTO_SIZE_FIT) && font)
     {
         unsigned int w, h;
-        font->measureText(_text.c_str(), getFontSize(_state), &w, &h);
+        font->measureText(_text.c_str(), getFontSize(state), &w, &h);
         if (_autoWidth == Control::AUTO_SIZE_FIT)
-            setWidth(w + getBorder(_state).left + getBorder(_state).right + getPadding().left + getPadding().right);
+            setWidth(w + getBorder(state).left + getBorder(state).right + getPadding().left + getPadding().right);
         if (_autoHeight == Control::AUTO_SIZE_FIT)
-            setHeight(h + getBorder(_state).top + getBorder(_state).bottom + getPadding().top + getPadding().bottom);
+            setHeight(h + getBorder(state).top + getBorder(state).bottom + getPadding().top + getPadding().bottom);
     }
 }
 
@@ -113,8 +110,9 @@ void Label::drawText(const Rectangle& clip)
     // Draw the text.
     if (_font)
     {
+        Control::State state = getState();
         _font->start();
-        _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();
     }
 }

+ 4 - 9
gameplay/src/PlatformWindows.cpp

@@ -90,6 +90,7 @@ static gameplay::Keyboard::Key getKey(WPARAM win32KeyCode, bool shiftDown)
     case VK_ESCAPE:
         return gameplay::Keyboard::KEY_ESCAPE;
     case VK_BACK:
+    case VK_F16: // generated by CTRL + BACKSPACE
         return gameplay::Keyboard::KEY_BACKSPACE;
     case VK_TAB:
         return shiftDown ? gameplay::Keyboard::KEY_BACK_TAB : gameplay::Keyboard::KEY_TAB;
@@ -444,9 +445,7 @@ LRESULT CALLBACK __WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
         if (wParam == VK_CAPITAL)
             capsOn = !capsOn;
 
-        // Suppress key repeats.
-        if ((lParam & 0x40000000) == 0)
-            gameplay::Platform::keyEventInternal(gameplay::Keyboard::KEY_PRESS, getKey(wParam, shiftDown ^ capsOn));
+        gameplay::Platform::keyEventInternal(gameplay::Keyboard::KEY_PRESS, getKey(wParam, shiftDown ^ capsOn));
         break;
         
     case WM_KEYUP:
@@ -457,15 +456,11 @@ LRESULT CALLBACK __WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
         break;
 
     case WM_CHAR:
-        // Suppress key repeats.
-        if ((lParam & 0x40000000) == 0)
-            gameplay::Platform::keyEventInternal(gameplay::Keyboard::KEY_CHAR, wParam);
+        gameplay::Platform::keyEventInternal(gameplay::Keyboard::KEY_CHAR, wParam);
         break;
 
     case WM_UNICHAR:
-        // Suppress key repeats.
-        if ((lParam & 0x40000000) == 0)
-            gameplay::Platform::keyEventInternal(gameplay::Keyboard::KEY_CHAR, wParam);
+        gameplay::Platform::keyEventInternal(gameplay::Keyboard::KEY_CHAR, wParam);
         break;
 
     case WM_SETFOCUS:

+ 22 - 53
gameplay/src/RadioButton.cpp

@@ -98,54 +98,6 @@ void RadioButton::addListener(Control::Listener* listener, int eventFlags)
     Control::addListener(listener, eventFlags);
 }
 
-bool RadioButton::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
-{
-    switch (evt)
-    {
-    case Touch::TOUCH_RELEASE:
-        {
-            if (_contactIndex == (int) _contactIndex && _state == Control::ACTIVE)
-            {
-                if (!_parent->isScrolling() &&
-                    x > _clipBounds.x && x <= _clipBounds.x + _clipBounds.width &&
-                    y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height)
-                {
-                    if (!_selected)
-                    {
-                        RadioButton::clearSelected(_groupId);
-                        _selected = true;
-                        notifyListeners(Control::Listener::VALUE_CHANGED);
-                    }
-                }
-            }
-        }
-        break;
-    }
-
-    return Button::touchEvent(evt, x, y, contactIndex);
-}
-
-bool RadioButton::gamepadEvent(Gamepad::GamepadEvent evt, Gamepad* gamepad, unsigned int analogIndex)
-{
-    switch (evt)
-    {
-    case Gamepad::BUTTON_EVENT:
-        if (_state == Control::ACTIVE)
-        {
-            if (!gamepad->isButtonDown(Gamepad::BUTTON_A) &&
-                !gamepad->isButtonDown(Gamepad::BUTTON_X))
-            {
-                RadioButton::clearSelected(_groupId);
-                _selected = true;
-                notifyListeners(Control::Listener::VALUE_CHANGED);
-            }
-        }
-        break;
-    }
-
-    return Button::gamepadEvent(evt, gamepad, analogIndex);
-}
-
 void RadioButton::clearSelected(const std::string& groupId)
 {
     std::vector<RadioButton*>::const_iterator it;
@@ -164,7 +116,7 @@ void RadioButton::clearSelected(const std::string& groupId)
 
 bool RadioButton::keyEvent(Keyboard::KeyEvent evt, int key)
 {
-    if (_state == ACTIVE && evt == Keyboard::KEY_RELEASE && key == Keyboard::KEY_RETURN && !_selected)
+    if (getState() == ACTIVE && evt == Keyboard::KEY_RELEASE && key == Keyboard::KEY_RETURN && !_selected)
     {
         RadioButton::clearSelected(_groupId);
         _selected = true;
@@ -174,6 +126,23 @@ bool RadioButton::keyEvent(Keyboard::KeyEvent evt, int key)
     return Button::keyEvent(evt, key);
 }
 
+void RadioButton::controlEvent(Control::Listener::EventType evt)
+{
+    Button::controlEvent(evt);
+
+    switch (evt)
+    {
+    case Control::Listener::CLICK:
+        if (!_selected)
+        {
+            RadioButton::clearSelected(_groupId);
+            _selected = true;
+            notifyListeners(Control::Listener::VALUE_CHANGED);
+        }
+        break;
+    }
+}
+
 void RadioButton::update(const Control* container, const Vector2& offset)
 {
     Label::update(container, offset);
@@ -183,12 +152,12 @@ void RadioButton::update(const Control* container, const Vector2& offset)
     {
         if (_selected)
         {
-            const Rectangle& selectedRegion = getImageRegion("selected", _state);
+            const Rectangle& selectedRegion = getImageRegion("selected", getState());
             size.set(selectedRegion.width, selectedRegion.height);
         }
         else
         {
-            const Rectangle& unselectedRegion = getImageRegion("unselected", _state);
+            const Rectangle& unselectedRegion = getImageRegion("unselected", getState());
             size.set(unselectedRegion.width, unselectedRegion.height);
         }
     }
@@ -213,11 +182,11 @@ void RadioButton::update(const Control* container, const Vector2& offset)
     
     if (_selected)
     {
-        _image = getImage("selected", _state);
+        _image = getImage("selected", getState());
     }
     else
     {
-        _image = getImage("unselected", _state);
+        _image = getImage("unselected", getState());
     }
 }
 

+ 5 - 21
gameplay/src/RadioButton.h

@@ -130,27 +130,6 @@ protected:
      */
     static RadioButton* create(Theme::Style* style, Properties* properties);
 
-    /**
-     * Touch callback on touch events.  Controls return true if they consume the touch event.
-     *
-     * @param evt The touch event that occurred.
-     * @param x The x position of the touch in pixels. Left edge is zero.
-     * @param y The y position of the touch in pixels. Top edge is zero.
-     * @param contactIndex The order of occurrence for multiple touch contacts starting at zero.
-     *
-     * @return Whether the touch event was consumed by the control.
-     *
-     * @see Touch::TouchEvent
-     */
-    bool touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
-
-    /**
-     * Gamepad callback on gamepad events.
-     *
-     * @see Control::gamepadEvent
-     */
-    bool gamepadEvent(Gamepad::GamepadEvent evt, Gamepad* gamepad, unsigned int analogIndex);
-
     /**
      * Keyboard callback on key events.
      *
@@ -159,6 +138,11 @@ protected:
      */
     bool keyEvent(Keyboard::KeyEvent evt, int key);
 
+    /**
+     * @see Control#controlEvent
+     */
+    void controlEvent(Control::Listener::EventType evt);
+
     /**
      * Called when a control's properties change.  Updates this control's internal rendering
      * properties, such as its text viewport.

+ 94 - 164
gameplay/src/Slider.cpp

@@ -6,8 +6,8 @@ namespace gameplay
 
 // Fraction of slider to scroll when mouse scrollwheel is used.
 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;
+// Fraction of slider to scroll for a delta of 1.0f when a gamepad or keyboard is used.
+static const float MOVE_FRACTION = 0.005f;
 // Distance that a slider must be moved before it starts consuming input events,
 // e.g. to prevent its parent container from scrolling at the same time.
 static const float SLIDER_THRESHOLD = 5.0f;
@@ -162,132 +162,83 @@ void Slider::addListener(Control::Listener* listener, int eventFlags)
     Control::addListener(listener, eventFlags);
 }
 
-bool Slider::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+bool Slider::canFocus() const
 {
-    switch (evt)
+    return true;
+}
+
+void Slider::updateValue(int x, int y)
+{
+    State state = getState();
+
+    // If the point lies within this slider, update the value of the slider accordingly
+    if (x > _clipBounds.x && x <= _clipBounds.x + _clipBounds.width &&
+        y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height)
     {
-    case Touch::TOUCH_PRESS:
-        if (_contactIndex != INVALID_CONTACT_INDEX)
-            return false;
-        else if (x > _clipBounds.x && x <= _clipBounds.x + _clipBounds.width &&
-            y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height)
+        // Horizontal case.
+        const Theme::Border& border = getBorder(state);
+        const Theme::Padding& padding = getPadding();
+        const Rectangle& minCapRegion = _minImage->getRegion();
+        const Rectangle& maxCapRegion = _maxImage->getRegion();
+
+        float markerPosition = ((float)x - maxCapRegion.width - border.left - padding.left) /
+            (_bounds.width - border.left - border.right - padding.left - padding.right - minCapRegion.width - maxCapRegion.width);
+            
+        if (markerPosition > 1.0f)
         {
-            _state = Control::ACTIVE;
-            _originalX = x;
-            _originalValue = _value;
-            _originalConsumeInputEvents = _consumeInputEvents;
-            _moveCancelled = false;
-            // Fall through to calculate new value.
+            markerPosition = 1.0f;
         }
-        else
+        else if (markerPosition < 0.0f)
         {
-            _state = NORMAL;
-            _dirty = true;
-            break;
+            markerPosition = 0.0f;
         }
-    case Touch::TOUCH_MOVE:
-    
-        if (evt != Touch::TOUCH_PRESS && _contactIndex != (int)contactIndex)
-            return false;
 
-        if (_moveCancelled)
-        {
-            break;
-        }
-        else if (abs(x - _originalX) > SLIDER_THRESHOLD)
-        {
-            // Start consuming input events once we've passed the slider's threshold.
-            _consumeInputEvents = true;
+        float oldValue = _value;
+        _value = (markerPosition * (_max - _min)) + _min;
+        if (_step > 0.0f)
+        {            
+            int numSteps = round(_value / _step);
+            _value = _step * numSteps;
         }
-        else if (_parent->isScrolling())
-        {
-            // Cancel the change in slider value if we pass the parent container's scrolling threshold.
-            float oldValue = _value;
-            _value = _originalValue;
-            if (_value != oldValue)
-            {
-                notifyListeners(Control::Listener::VALUE_CHANGED);
-            }
 
-            _dirty = true;
-            _moveCancelled = true;
-            _state = NORMAL;
-            _contactIndex = INVALID_CONTACT_INDEX;
-            _consumeInputEvents = _originalConsumeInputEvents;
-            break;
+        // Call the callback if our value changed.
+        if (_value != oldValue)
+        {
+            notifyListeners(Control::Listener::VALUE_CHANGED);
         }
+        _dirty = true;
+    }
+}
 
-        if (_state == ACTIVE &&
-            x > _clipBounds.x && x <= _clipBounds.x + _clipBounds.width &&
-            y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height)
-        {
-            // Horizontal case.
-            const Theme::Border& border = getBorder(_state);
-            const Theme::Padding& padding = getPadding();
-            const Rectangle& minCapRegion = _minImage->getRegion();
-            const Rectangle& maxCapRegion = _maxImage->getRegion();
-
-            float markerPosition = ((float)x - maxCapRegion.width - border.left - padding.left) /
-                (_bounds.width - border.left - border.right - padding.left - padding.right - minCapRegion.width - maxCapRegion.width);
-            
-            if (markerPosition > 1.0f)
-            {
-                markerPosition = 1.0f;
-            }
-            else if (markerPosition < 0.0f)
-            {
-                markerPosition = 0.0f;
-            }
+bool Slider::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+{
+    State state = getState();
 
-            float oldValue = _value;
-            _value = (markerPosition * (_max - _min)) + _min;
-            if (_step > 0.0f)
-            {            
-                int numSteps = round(_value / _step);
-                _value = _step * numSteps;
-            }
+    switch (evt)
+    {
+    case Touch::TOUCH_PRESS:
+        updateValue(x, y);
+        return true;
 
-            // Call the callback if our value changed.
-            if (_value != oldValue)
-            {
-                notifyListeners(Control::Listener::VALUE_CHANGED);
-            }
-            _dirty = true;
+    case Touch::TOUCH_MOVE:
+        if (state == ACTIVE)
+        {
+            updateValue(x, y);
+            return true;
         }
         break;
-    case Touch::TOUCH_RELEASE:
-        _consumeInputEvents = _originalConsumeInputEvents;
-
-        if (_contactIndex != (int) contactIndex)
-            return false;
-
-        _dirty = true;
-        _state = FOCUS;
-        break;
     }
-    
-    if (evt == Touch::TOUCH_MOVE)
-        return _consumeInputEvents;
-    else
-        return Control::touchEvent(evt, x, y, contactIndex);
+
+    return Control::touchEvent(evt, x, y, contactIndex);
 }
 
 bool Slider::mouseEvent(Mouse::MouseEvent evt, int x, int y, int wheelDelta)
 {
     switch (evt)
     {
-        case Mouse::MOUSE_PRESS_LEFT_BUTTON:
-            return touchEvent(Touch::TOUCH_PRESS, x, y, 0);
-
-        case Mouse::MOUSE_MOVE:
-            return Control::mouseEvent(evt, x, y, 0);
-
-        case Mouse::MOUSE_RELEASE_LEFT_BUTTON:
-            return touchEvent(Touch::TOUCH_RELEASE, x, y, 0);
-
         case Mouse::MOUSE_WHEEL:
         {
-            if ((hasFocus() && _state == HOVER) || _state == ACTIVE)
+            if (hasFocus())
             {
                 float total = _max - _min;
                 float oldValue = _value;
@@ -319,12 +270,13 @@ bool Slider::mouseEvent(Mouse::MouseEvent evt, int x, int y, int wheelDelta)
             break;
     }
 
+    // Return false to fall through to touch handling
     return false;
 }
 
 bool Slider::gamepadEvent(Gamepad::GamepadEvent evt, Gamepad* gamepad, unsigned int analogIndex)
 {
-    bool eventConsumed = false;
+    /*bool eventConsumed = false;
 
     if (_state == ACTIVE)
     {
@@ -400,68 +352,43 @@ bool Slider::gamepadEvent(Gamepad::GamepadEvent evt, Gamepad* gamepad, unsigned
         }
     }    
 
-    return eventConsumed;
+    return eventConsumed;*/
+    
+    return Control::gamepadEvent(evt, gamepad, analogIndex);
 }
 
 bool Slider::keyEvent(Keyboard::KeyEvent evt, int key)
 {
-    if (_state == ACTIVE)
+    switch (evt)
     {
-        switch (evt)
+    case Keyboard::KEY_PRESS:
+        switch (key)
         {
-        case Keyboard::KEY_PRESS:
-            switch (key)
+        case Keyboard::KEY_LEFT_ARROW:
+            if (_step > 0.0f)
             {
-            case Keyboard::KEY_LEFT_ARROW:
-                _delta = -1.0f;
-                _directionButtonDown = true;
-                _dirty = true;
-                _gamepadValue = _value;
-                return true;
-
-            case Keyboard::KEY_RIGHT_ARROW:
-                _delta = 1.0f;
-                _directionButtonDown = true;
-                _dirty = true;
-                _gamepadValue = _value;
-                return true;
+                _value = std::max(_value - _step, _min);
             }
-            break;
-
-        case Keyboard::KEY_RELEASE:
-            switch (key)
+            else
             {
-            case Keyboard::KEY_LEFT_ARROW:
-                if (_delta == -1.0f)
-                {
-                    _directionButtonDown = false;
-                    _dirty = true;
-                    _delta = 0.0f;
-                    return true;
-                }
-                break;
+                _value = std::max(_value - (_max - _min) * MOVE_FRACTION, _min);
+            }
+            _dirty = true;
+            return true;
 
-            case Keyboard::KEY_RIGHT_ARROW:
-                if (_delta == 1.0f)
-                {
-                    _directionButtonDown = false;
-                    _dirty = true;
-                    _delta = 0.0f;
-                    return true;
-                }
-                break;
+        case Keyboard::KEY_RIGHT_ARROW:
+            if (_step > 0.0f)
+            {
+                _value = std::min(_value + _step, _max);
+            }
+            else
+            {
+                _value = std::min(_value + (_max - _min) * MOVE_FRACTION, _max);
             }
+            _dirty = true;
+            return true;
         }
-    }
-
-    if (evt == Keyboard::KEY_PRESS && key == Keyboard::KEY_RETURN)
-    {
-        if (hasFocus())
-            setState(ACTIVE);
-        else if (_state == ACTIVE)
-            setState(FOCUS);
-
-        return _consumeInputEvents;
+        break;
     }
 
     return Control::keyEvent(evt, key);
@@ -471,10 +398,12 @@ void Slider::update(const Control* container, const Vector2& offset)
 {
     Label::update(container, offset);
 
-    _minImage = getImage("minCap", _state);
-    _maxImage = getImage("maxCap", _state);
-    _markerImage = getImage("marker", _state);
-    _trackImage = getImage("track", _state);
+    Control::State state = getState();
+
+    _minImage = getImage("minCap", state);
+    _maxImage = getImage("maxCap", state);
+    _markerImage = getImage("marker", state);
+    _trackImage = getImage("track", state);
 
     char s[32];
     sprintf(s, "%.*f", _valueTextPrecision, _value);
@@ -487,13 +416,13 @@ void Slider::update(const Control* container, const Vector2& offset)
 
         if (_step > 0.0f)
         {
-            _gamepadValue += (total * GAMEPAD_FRACTION) * _delta;
+            _gamepadValue += (total * MOVE_FRACTION) * _delta;
             int numSteps = round(_gamepadValue / _step);
             _value = _step * numSteps;
         }
         else
         {
-            _value += (total * GAMEPAD_FRACTION) * _delta;
+            _value += (total * MOVE_FRACTION) * _delta;
         }
 
         if (_value > _max)
@@ -515,7 +444,7 @@ void Slider::update(const Control* container, const Vector2& offset)
         height = std::max(height, _trackImage->getRegion().height);
         height += _bounds.height;
         if (_valueTextVisible && _font)
-            height += getFontSize(_state);
+            height += getFontSize(state);
         setHeight(height);
     }
 }
@@ -589,7 +518,7 @@ void Slider::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
     pos.y = midY - minCapRegion.height * 0.5f;
     pos.x -= minCapRegion.width * 0.5f;
     spriteBatch->draw(pos.x, pos.y, minCapRegion.width, minCapRegion.height, minCap.u1, minCap.v1, minCap.u2, minCap.v2, minCapColor, _viewportClipBounds);
-        
+
     pos.x = _viewportBounds.x + _viewportBounds.width - maxCapRegion.width * 0.5f;
     spriteBatch->draw(pos.x, pos.y, maxCapRegion.width, maxCapRegion.height, maxCap.u1, maxCap.v1, maxCap.u2, maxCap.v2, maxCapColor, _viewportClipBounds);
 
@@ -607,8 +536,9 @@ void Slider::drawText(const Rectangle& clip)
 
     if (_valueTextVisible && _font)
     {
+        Control::State state = getState();
         _font->start();
-        _font->drawText(_valueText.c_str(), _textBounds, _textColor, getFontSize(_state), _valueTextAlignment, true, getTextRightToLeft(_state), &_viewportClipBounds);
+        _font->drawText(_valueText.c_str(), _textBounds, _textColor, getFontSize(state), _valueTextAlignment, true, getTextRightToLeft(state), &_viewportClipBounds);
         _font->finish();
     }
 }

+ 8 - 0
gameplay/src/Slider.h

@@ -164,6 +164,11 @@ public:
      */
     void addListener(Control::Listener* listener, int eventFlags);
 
+    /**
+     * @see Control#canFocus()
+     */
+    bool canFocus() const;
+
 protected:
 
     /**
@@ -357,6 +362,9 @@ private:
      * Constructor.
      */
     Slider(const Slider& copy);
+
+    void updateValue(int x, int y);
+
 };
 
 }

+ 1 - 1
gameplay/src/TerrainPatch.cpp

@@ -492,7 +492,7 @@ bool TerrainPatch::updateMaterial()
         if (_layers.size() > 0)
             material->getParameter("u_samplers")->setValue((const Texture::Sampler**)&_samplers[0], (unsigned int)_samplers.size());
 
-        if (_terrain->isFlagSet(Terrain::DEBUG_PATCHES))
+        if (_terrain->isFlagSet(Terrain::DEBUG_PATCHES) && !_levels[i]->model->getMaterial())
         {
             material->getParameter("u_row")->setValue((float)_row);
             material->getParameter("u_column")->setValue((float)_column);

+ 197 - 236
gameplay/src/TextBox.cpp

@@ -4,11 +4,12 @@
 namespace gameplay
 {
 
-static bool space(char c) {
+static bool space(char c)
+{
     return isspace(c);
 }
 
-TextBox::TextBox() : _lastKeypress(0), _fontSize(0), _caretImage(NULL), _passwordChar('*'), _inputMode(TEXT), _ctrlPressed(false)
+TextBox::TextBox() : _caretLocation(0), _lastKeypress(0), _fontSize(0), _caretImage(NULL), _passwordChar('*'), _inputMode(TEXT), _ctrlPressed(false)
 {
 }
 
@@ -49,70 +50,89 @@ int TextBox::getLastKeypress()
     return _lastKeypress;
 }
 
-void TextBox::addListener(Control::Listener* listener, int eventFlags)
+unsigned int TextBox::getCaretLocation() const
 {
-    if ((eventFlags & Control::Listener::VALUE_CHANGED) == Control::Listener::VALUE_CHANGED)
-    {
-        GP_ERROR("VALUE_CHANGED event is not applicable to TextBox.");
-    }
+    return _caretLocation;
+}
 
-    Control::addListener(listener, eventFlags);
+void TextBox::setCaretLocation(unsigned int index)
+{
+    _caretLocation = index;
+    if (_caretLocation > _text.length())
+        _caretLocation = (unsigned int)_text.length();
 }
 
 bool TextBox::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
-{   
+{
+    State state = getState();
+
     switch (evt)
     {
     case Touch::TOUCH_PRESS: 
-        if (x > _clipBounds.x && x <= _clipBounds.x + _clipBounds.width &&
-                 y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height)
+        if (state == ACTIVE)
         {
-            _contactIndex = (int) contactIndex;
-
-            if (_state == NORMAL)
-                Game::getInstance()->displayKeyboard(true);
-
             setCaretLocation(x, y);
-
-            _state = ACTIVE;
             _dirty = true;
         }
-        else
-        {
-            _contactIndex = INVALID_CONTACT_INDEX;
-            _state = NORMAL;
-            Game::getInstance()->displayKeyboard(false);
-            _dirty = true;
-            return false;
-        }
         break;
     case Touch::TOUCH_MOVE:
-        if (_state == ACTIVE &&
-            x > _clipBounds.x && x <= _clipBounds.x + _clipBounds.width &&
-            y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height)
+        if (state == ACTIVE)
         {
             setCaretLocation(x, y);
             _dirty = true;
         }
         break;
-    case Touch::TOUCH_RELEASE:
-        if (x > _clipBounds.x && x <= _clipBounds.x + _clipBounds.width &&
-            y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height)
+    }
+
+    return Label::touchEvent(evt, x, y, contactIndex);
+}
+
+static bool isWhitespace(char c)
+{
+    switch (c)
+    {
+    case ' ':
+    case '\t':
+    case '\r':
+    case '\n':
+        return true;
+
+    default:
+        return false;
+    }
+}
+
+static unsigned int findNextWord(std::string& text, unsigned int from, bool backwards)
+{
+    int pos = (int)from;
+    if (backwards)
+    {
+        if (pos > 0)
         {
-            setCaretLocation(x, y);
-            _state = FOCUS;
+            // Moving backwards: skip all consecutive whitespace characters
+            while (pos > 0 && isWhitespace(text.at(pos-1)))
+                --pos;
+            // Now search back to the first whitespace character
+            while (pos > 0 && !isWhitespace(text.at(pos-1)))
+                --pos;
         }
-        else
+    }
+    else
+    {
+        const int len = (const int)text.length();
+        if (pos < len)
         {
-            _state = NORMAL;
-            Game::getInstance()->displayKeyboard(false);
+            // Moving forward: skip all consecutive non-whitespace characters
+            ++pos;
+            while (pos < len && !isWhitespace(text.at(pos)))
+                ++pos;
+            // Now search for the first non-whitespace character
+            while (pos < len && isWhitespace(text.at(pos)))
+                ++pos;
         }
-        _contactIndex = INVALID_CONTACT_INDEX;
-        _dirty = true;
-        break;
     }
 
-    return _consumeInputEvents;
+    return (unsigned int)pos;
 }
 
 bool TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
@@ -130,139 +150,100 @@ bool TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
                 }
                 case Keyboard::KEY_HOME:
                 {
-                    Font* font = getFont(_state);
-                    GP_ASSERT(font);
-                    unsigned int fontSize = getFontSize(_state);
-                    Font::Justify textAlignment = getTextAlignment(_state);
-                    bool rightToLeft = getTextRightToLeft(_state);
-                    font->getLocationAtIndex(getDisplayedText().c_str(), _textBounds, fontSize, &_caretLocation, 0,
-                        textAlignment, true, rightToLeft);
+                    _caretLocation = 0;
                     _dirty = true;
                     break;
                 }
                 case Keyboard::KEY_END:
                 {
-                    Font* font = getFont(_state);
-                    GP_ASSERT(font);
-                    unsigned int fontSize = getFontSize(_state);
-                    Font::Justify textAlignment = getTextAlignment(_state);
-                    bool rightToLeft = getTextRightToLeft(_state);
-                    const std::string displayedText = getDisplayedText();
-                    font->getLocationAtIndex(displayedText.c_str(), _textBounds, fontSize, &_caretLocation, displayedText.size(),
-                        textAlignment, true, rightToLeft);
+                    _caretLocation = _text.length();
                     _dirty = true;
                     break;
                 }
                 case Keyboard::KEY_DELETE:
                 {
-                    Font* font = getFont(_state);
-                    GP_ASSERT(font);
-                    unsigned int fontSize = getFontSize(_state);
-                    Font::Justify textAlignment = getTextAlignment(_state);
-                    bool rightToLeft = getTextRightToLeft(_state);
-
-                    int textIndex = font->getIndexAtLocation(getDisplayedText().c_str(), _textBounds, fontSize, _caretLocation, &_caretLocation,
-                        textAlignment, true, rightToLeft);
-                        
-                    _text.erase(textIndex, 1);
-                    font->getLocationAtIndex(getDisplayedText().c_str(), _textBounds, fontSize, &_caretLocation, textIndex,
-                        textAlignment, true, rightToLeft);
-                    _dirty = true;
-                    notifyListeners(Control::Listener::TEXT_CHANGED);
+                    if (_caretLocation < _text.length())
+                    {
+                        int newCaretLocation;
+                        if (_ctrlPressed)
+                        {
+                            newCaretLocation = findNextWord(getDisplayedText(), _caretLocation, false);
+                        }
+                        else
+                        {
+                            newCaretLocation = _caretLocation + 1;
+                        }
+                        _text.erase(_caretLocation, newCaretLocation - _caretLocation);
+                        _dirty = true;
+                        notifyListeners(Control::Listener::TEXT_CHANGED);
+                    }
                     break;
                 }
                 case Keyboard::KEY_TAB:
                 {
                     // Allow tab to move the focus forward.
                     return false;
-                    break;
                 }
                 case Keyboard::KEY_LEFT_ARROW:
                 {
-                    const std::string displayedText = getDisplayedText();
-                    Font* font = getFont(_state);
-                    GP_ASSERT(font);
-                    unsigned int fontSize = getFontSize(_state);
-                    Font::Justify textAlignment = getTextAlignment(_state);
-                    bool rightToLeft = getTextRightToLeft(_state);
-
-                    int textIndex = font->getIndexAtLocation(displayedText.c_str(), _textBounds, fontSize, _caretLocation, &_caretLocation,
-                        textAlignment, true, rightToLeft);
-                    if (_ctrlPressed)
-                    {
-                        std::string::const_reverse_iterator it = std::find_if(displayedText.rend() - (textIndex - 1), displayedText.rend(), space);
-                        textIndex = std::distance(displayedText.begin(), it.base());
-                    }
-                    else
+                    if (_caretLocation > 0)
                     {
-                        --textIndex;
+                        if (_ctrlPressed)
+                        {
+                            _caretLocation = findNextWord(getDisplayedText(), _caretLocation, true);
+                        }
+                        else
+                        {
+                            --_caretLocation;
+                        }
                     }
-                    font->getLocationAtIndex(displayedText.c_str(), _textBounds, fontSize, &_caretLocation, textIndex,
-                        textAlignment, true, rightToLeft);
                     _dirty = true;
                     break;
                 }
                 case Keyboard::KEY_RIGHT_ARROW:
                 {
-                    const std::string displayedText = getDisplayedText();
-                    Font* font = getFont(_state);
-                    GP_ASSERT(font);
-                    unsigned int fontSize = getFontSize(_state);
-                    Font::Justify textAlignment = getTextAlignment(_state);
-                    bool rightToLeft = getTextRightToLeft(_state);
-
-                    int textIndex = font->getIndexAtLocation(displayedText.c_str(), _textBounds, fontSize, _caretLocation, &_caretLocation,
-                        textAlignment, true, rightToLeft);
-                    if (_ctrlPressed)
+                    if (_caretLocation < _text.length())
                     {
-                        std::string::const_iterator it = std::find_if(displayedText.begin() + (textIndex + 1), displayedText.end(), space);
-                        textIndex = std::distance(displayedText.begin(), it);
-                    }
-                    else
-                    {
-                        ++textIndex;
+                        if (_ctrlPressed)
+                        {
+                            _caretLocation = findNextWord(getDisplayedText(), _caretLocation, false);
+                        }
+                        else
+                        {
+                            ++_caretLocation;
+                        }
                     }
-                    font->getLocationAtIndex(displayedText.c_str(), _textBounds, fontSize, &_caretLocation, textIndex,
-                        textAlignment, true, rightToLeft);
                     _dirty = true;
                     break;
                 }
                 case Keyboard::KEY_UP_ARROW:
                 {
-                    Font* font = getFont(_state);
-                    GP_ASSERT(font);
-                    unsigned int fontSize = getFontSize(_state);
-                    Font::Justify textAlignment = getTextAlignment(_state);
-                    bool rightToLeft = getTextRightToLeft(_state);
-                    _prevCaretLocation.set(_caretLocation);
-                    _caretLocation.y -= fontSize;
-                    int textIndex = font->getIndexAtLocation(getDisplayedText().c_str(), _textBounds, fontSize, _caretLocation, &_caretLocation,
-                        textAlignment, true, rightToLeft);
-                    if (textIndex == -1)
-                    {
-                        _caretLocation.set(_prevCaretLocation);
-                    }
-
-                    _dirty = true;
+                    // TODO: Support multiline
                     break;
                 }
                 case Keyboard::KEY_DOWN_ARROW:
                 {
-                    Font* font = getFont(_state);
-                    GP_ASSERT(font);
-                    unsigned int fontSize = getFontSize(_state);
-                    Font::Justify textAlignment = getTextAlignment(_state);
-                    bool rightToLeft = getTextRightToLeft(_state);
-                    _prevCaretLocation.set(_caretLocation);
-                    _caretLocation.y += fontSize;
-                    int textIndex = font->getIndexAtLocation(getDisplayedText().c_str(), _textBounds, fontSize, _caretLocation, &_caretLocation,
-                        textAlignment, true, rightToLeft);
-                    if (textIndex == -1)
+                    // TODO: Support multiline
+                    break;
+                }
+                case Keyboard::KEY_BACKSPACE:
+                {
+                    if (_caretLocation > 0)
                     {
-                        _caretLocation.set(_prevCaretLocation);
+                        int newCaretLocation;
+                        if (_ctrlPressed)
+                        {
+                            newCaretLocation = findNextWord(getDisplayedText(), _caretLocation, true);
+                        }
+                        else
+                        {
+                            newCaretLocation = _caretLocation - 1;
+                        }
+                        _text.erase(newCaretLocation, _caretLocation - newCaretLocation);
+                        _caretLocation = newCaretLocation;
+                        _dirty = true;
+                        notifyListeners(Control::Listener::TEXT_CHANGED);
                     }
-
-                    _dirty = true;
                     break;
                 }
             }
@@ -271,93 +252,37 @@ bool TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
 
         case Keyboard::KEY_CHAR:
         {
-            Font* font = getFont(_state);
-            GP_ASSERT(font);
-            unsigned int fontSize = getFontSize(_state);
-            Font::Justify textAlignment = getTextAlignment(_state);
-            bool rightToLeft = getTextRightToLeft(_state);
-
-            int textIndex = font->getIndexAtLocation(getDisplayedText().c_str(), _textBounds, fontSize, _caretLocation, &_caretLocation,
-                textAlignment, true, rightToLeft);
-            if (textIndex == -1)
-            {
-                textIndex = 0;
-                font->getLocationAtIndex(getDisplayedText().c_str(), _textBounds, fontSize, &_caretLocation, 0,
-                    textAlignment, true, rightToLeft);
-            }
-
             switch (key)
             {
-                case Keyboard::KEY_BACKSPACE:
-                {
-                    if (textIndex > 0)
-                    {
-                        --textIndex;
-                        _text.erase(textIndex, 1);
-                        font->getLocationAtIndex(getDisplayedText().c_str(), _textBounds, fontSize, &_caretLocation, textIndex,
-                            textAlignment, true, rightToLeft);
-
-                        _dirty = true;
-                    }
-                    break;
-                }
                 case Keyboard::KEY_RETURN:
-                    // TODO: Handle line-break insertion correctly.
+                    // TODO: Support multi-line
                     break;
                 case Keyboard::KEY_ESCAPE:
                     break;
+                case Keyboard::KEY_BACKSPACE:
+                    break;
                 case Keyboard::KEY_TAB:
                     // Allow tab to move the focus forward.
                     return false;
-                    break;
                 default:
                 {
-                    // Insert character into string.
-                    _text.insert(textIndex, 1, (char)key);
-
-                    // Get new location of caret.
-                    font->getLocationAtIndex(getDisplayedText().c_str(), _textBounds, fontSize, &_caretLocation, textIndex + 1,
-                        textAlignment, true, rightToLeft);
-
-                    if (key == ' ')
+                    // Insert character into string, only if our font supports this character
+                    if (_font && _font->isCharacterSupported(key))
                     {
-                        // If a space was entered, check that caret is still within bounds.
-                        if (_caretLocation.x >= _textBounds.x + _textBounds.width ||
-                            _caretLocation.y >= _textBounds.y + _textBounds.height)
+                        if (_caretLocation <= _text.length())
                         {
-                            // If not, undo the character insertion.
-                            _text.erase(textIndex, 1);
-                            font->getLocationAtIndex(getDisplayedText().c_str(), _textBounds, fontSize, &_caretLocation, textIndex,
-                                textAlignment, true, rightToLeft);
-
-                            // No need to check again.
-                            break;
+                            _text.insert(_caretLocation, 1, (char)key);
+                            ++_caretLocation;
                         }
-                    }
-
-                    // Always check that the text still fits within the clip region.
-                    Rectangle textBounds;
-                    font->measureText(getDisplayedText().c_str(), _textBounds, fontSize, &textBounds, textAlignment, true, true);
-                    if (textBounds.x < _textBounds.x || textBounds.y < _textBounds.y ||
-                        textBounds.width >= _textBounds.width || textBounds.height >= _textBounds.height)
-                    {
-                        // If not, undo the character insertion.
-                        _text.erase(textIndex, 1);
-                        font->getLocationAtIndex(getDisplayedText().c_str(), _textBounds, fontSize, &_caretLocation, textIndex,
-                            textAlignment, true, rightToLeft);
 
-                        // TextBox is not dirty.
-                        break;
+                        _dirty = true;
+                        notifyListeners(Control::Listener::TEXT_CHANGED);
                     }
-                
-                    _dirty = true;
                     break;
                 }
             
                 break;
             }
-
-            notifyListeners(Control::Listener::TEXT_CHANGED);
             break;
         }
         case Keyboard::KEY_RELEASE:
@@ -373,20 +298,39 @@ bool TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
 
     _lastKeypress = key;
 
-    return _consumeInputEvents;
+    return Label::keyEvent(evt, key);
+}
+
+void TextBox::controlEvent(Control::Listener::EventType evt)
+{
+    Label::controlEvent(evt);
+
+    switch (evt)
+    {
+    case Control::Listener::FOCUS_GAINED:
+        Game::getInstance()->displayKeyboard(true);
+        break;
+
+    case Control::Listener::FOCUS_LOST:
+        Game::getInstance()->displayKeyboard(false);
+        break;
+    }
 }
 
 void TextBox::update(const Control* container, const Vector2& offset)
 {
     Label::update(container, offset);
 
-    _fontSize = getFontSize(_state);
-    _caretImage = getImage("textCaret", _state);
+    Control::State state = getState();
+    _fontSize = getFontSize(state);
+    _caretImage = getImage("textCaret", state);
 }
 
 void TextBox::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
 {
-    if (_caretImage && (_state == ACTIVE || hasFocus()))
+    Control::State state = getState();
+
+    if (_caretImage && (state == ACTIVE || hasFocus()))
     {
         // Draw the cursor at its current location.
         const Rectangle& region = _caretImage->getRegion();
@@ -398,7 +342,13 @@ void TextBox::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
             color.w *= _opacity;
 
             float caretWidth = region.width * _fontSize / region.height;
-            spriteBatch->draw(_caretLocation.x - caretWidth * 0.5f, _caretLocation.y, caretWidth, _fontSize, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color, _viewportClipBounds);
+
+            Font* font = getFont(state);
+            unsigned int fontSize = getFontSize(state);
+            Vector2 point;
+            font->getLocationAtIndex(getDisplayedText().c_str(), _textBounds, fontSize, &point, _caretLocation, 
+                 getTextAlignment(state), true, getTextRightToLeft(state));
+            spriteBatch->draw(point.x - caretWidth * 0.5f, point.y, caretWidth, fontSize, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color, _viewportClipBounds);
         }
     }
 
@@ -407,18 +357,18 @@ void TextBox::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
 
 void TextBox::setCaretLocation(int x, int y)
 {
+    Control::State state = getState();
+
+    Vector2 point(x + _absoluteBounds.x, y + _absoluteBounds.y);
+
     // Get index into string and cursor location from the latest touch location.
-    _prevCaretLocation.set(_caretLocation);
-    _caretLocation.set(x + _absoluteBounds.x,
-                       y + _absoluteBounds.y);
-
-    Font* font = getFont(_state);
-    unsigned int fontSize = getFontSize(_state);
-    Font::Justify textAlignment = getTextAlignment(_state);
-    bool rightToLeft = getTextRightToLeft(_state);
+    Font* font = getFont(state);
+    unsigned int fontSize = getFontSize(state);
+    Font::Justify textAlignment = getTextAlignment(state);
+    bool rightToLeft = getTextRightToLeft(state);
     const std::string displayedText = getDisplayedText();
 
-    int index = font->getIndexAtLocation(displayedText.c_str(), _textBounds, fontSize, _caretLocation, &_caretLocation,
+    int index = font->getIndexAtLocation(displayedText.c_str(), _textBounds, fontSize, point, &point,
             textAlignment, true, rightToLeft);
 
     if (index == -1)
@@ -427,44 +377,49 @@ void TextBox::setCaretLocation(int x, int y)
         Rectangle textBounds;
         font->measureText(displayedText.c_str(), _textBounds, fontSize, &textBounds, textAlignment, true, true);
 
-        if (_caretLocation.x > textBounds.x + textBounds.width &&
-            _caretLocation.y > textBounds.y + textBounds.height)
+        if (point.x > textBounds.x + textBounds.width &&
+            point.y > textBounds.y + textBounds.height)
         {
-            font->getLocationAtIndex(displayedText.c_str(), _textBounds, fontSize, &_caretLocation, (unsigned int)_text.length(),
+            font->getLocationAtIndex(displayedText.c_str(), _textBounds, fontSize, &point, (unsigned int)_text.length(),
                 textAlignment, true, rightToLeft);
             return;
         }
 
-        if (_caretLocation.x < textBounds.x)
+        if (point.x < textBounds.x)
         {
-            _caretLocation.x = textBounds.x;
+            point.x = textBounds.x;
         }
-        else if (_caretLocation.x > textBounds.x + textBounds.width)
+        else if (point.x > textBounds.x + textBounds.width)
         {
-            _caretLocation.x = textBounds.x + textBounds.width;
+            point.x = textBounds.x + textBounds.width;
         }
 
-        if (_caretLocation.y < textBounds.y)
+        if (point.y < textBounds.y)
         {
-            _caretLocation.y = textBounds.y;
+            point.y = textBounds.y;
         }
-        else if (_caretLocation.y > textBounds.y + textBounds.height)
+        else if (point.y > textBounds.y + textBounds.height)
         {
-            Font* font = getFont(_state);
+            Font* font = getFont(state);
             GP_ASSERT(font);
-            unsigned int fontSize = getFontSize(_state);
-            _caretLocation.y = textBounds.y + textBounds.height - fontSize;
+            unsigned int fontSize = getFontSize(state);
+            point.y = textBounds.y + textBounds.height - fontSize;
         }
 
-        index = font->getIndexAtLocation(displayedText.c_str(), _textBounds, fontSize, _caretLocation, &_caretLocation,
+        index = font->getIndexAtLocation(displayedText.c_str(), _textBounds, fontSize, point, &point,
             textAlignment, true, rightToLeft);
-
-        if (index == -1)
-        {
-            // We failed to find a valid location; just put the caret back to where it was.
-            _caretLocation.set(_prevCaretLocation);
-        }
     }
+
+    if (index != -1)
+        _caretLocation = index;
+}
+
+void TextBox::getCaretLocation(Vector2* p)
+{
+    GP_ASSERT(p);
+
+    State state = getState();
+    getFont(state)->getLocationAtIndex(getDisplayedText().c_str(), _textBounds, getFontSize(state), p, _caretLocation, getTextAlignment(state), true, getTextRightToLeft(state));
 }
 
 const char* TextBox::getType() const
@@ -492,6 +447,11 @@ TextBox::InputMode TextBox::getInputMode() const
     return _inputMode;
 }
 
+bool TextBox::canFocus() const
+{
+    return true;
+}
+
 void TextBox::drawText(const Rectangle& clip)
 {
     if (_text.size() <= 0)
@@ -500,9 +460,10 @@ void TextBox::drawText(const Rectangle& clip)
     // Draw the text.
     if (_font)
     {
+        Control::State state = getState();
         const std::string displayedText = getDisplayedText();
         _font->start();
-        _font->drawText(displayedText.c_str(), _textBounds, _textColor, getFontSize(_state), getTextAlignment(_state), true, getTextRightToLeft(_state), &_viewportClipBounds);
+        _font->drawText(displayedText.c_str(), _textBounds, _textColor, getFontSize(state), getTextAlignment(state), true, getTextRightToLeft(state), &_viewportClipBounds);
         _font->finish();
     }
 }

+ 26 - 11
gameplay/src/TextBox.h

@@ -43,7 +43,8 @@ public:
     /**
      * Input modes. Default is Text.
      */
-    enum InputMode {
+    enum InputMode
+    {
         /**
          * Text: Text is displayed directly.
          */
@@ -72,16 +73,18 @@ public:
     virtual void initialize(Theme::Style* style, Properties* properties);
 
     /**
-     * Add a listener to be notified of specific events affecting
-     * this control.  Event types can be OR'ed together.
-     * E.g. To listen to touch-press and touch-release events,
-     * pass <code>Control::Listener::TOUCH | Control::Listener::RELEASE</code>
-     * as the second parameter.
+     * Returns the current location of the caret with the text of this TextBox.
+     *
+     * @return The current caret location.
+     */
+    unsigned int getCaretLocation() const;
+
+    /**
+     * Sets the location of the caret within this text box.
      *
-     * @param listener The listener to add.
-     * @param eventFlags The events to listen for.
+     * @param index The new location of the caret within the text of this TextBox.
      */
-    virtual void addListener(Control::Listener* listener, int eventFlags);
+    void setCaretLocation(unsigned int index);
 
     /**
      * Get the last key pressed within this text box.
@@ -123,6 +126,11 @@ public:
      */
     InputMode getInputMode() const;
 
+    /**
+     * @see Control#canFocus()
+     */
+    bool canFocus() const;
+
 protected:
 
     /**
@@ -171,6 +179,11 @@ protected:
      */
     bool keyEvent(Keyboard::KeyEvent evt, int key);
 
+    /**
+     * @see Control#controlEvent
+     */
+    void controlEvent(Control::Listener::EventType evt);
+
     /**
      * Called when a control's properties change.  Updates this control's internal rendering
      * properties, such as its text viewport.
@@ -213,9 +226,9 @@ protected:
     std::string getDisplayedText() const;
 
     /**
-     * The current position of the TextBox's caret.
+     * The current location of the TextBox's caret.
      */
-    Vector2 _caretLocation;
+    unsigned int _caretLocation;
 
     /**
      * The previous position of the TextBox's caret.
@@ -260,6 +273,8 @@ private:
     TextBox(const TextBox& copy);
 
     void setCaretLocation(int x, int y);
+
+    void getCaretLocation(Vector2* p);
 };
 
 }

+ 46 - 37
gameplay/src/lua/lua_Button.cpp

@@ -6,6 +6,7 @@
 #include "Base.h"
 #include "Button.h"
 #include "Control.h"
+#include "Form.h"
 #include "Game.h"
 #include "Gamepad.h"
 #include "Label.h"
@@ -60,6 +61,7 @@ void luaRegister_Button()
         {"getMargin", lua_Button_getMargin},
         {"getOpacity", lua_Button_getOpacity},
         {"getPadding", lua_Button_getPadding},
+        {"getParent", lua_Button_getParent},
         {"getRefCount", lua_Button_getRefCount},
         {"getSkinColor", lua_Button_getSkinColor},
         {"getSkinRegion", lua_Button_getSkinRegion},
@@ -106,7 +108,6 @@ void luaRegister_Button()
         {"setSize", lua_Button_setSize},
         {"setSkinColor", lua_Button_setSkinColor},
         {"setSkinRegion", lua_Button_setSkinRegion},
-        {"setState", lua_Button_setState},
         {"setStyle", lua_Button_setStyle},
         {"setText", lua_Button_setText},
         {"setTextAlignment", lua_Button_setTextAlignment},
@@ -1916,6 +1917,50 @@ int lua_Button_getPadding(lua_State* state)
     return 0;
 }
 
+int lua_Button_getParent(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 1:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA))
+            {
+                Button* instance = getInstance(state);
+                void* returnPtr = (void*)instance->getParent();
+                if (returnPtr)
+                {
+                    gameplay::ScriptUtil::LuaObject* object = (gameplay::ScriptUtil::LuaObject*)lua_newuserdata(state, sizeof(gameplay::ScriptUtil::LuaObject));
+                    object->instance = returnPtr;
+                    object->owns = false;
+                    luaL_getmetatable(state, "Control");
+                    lua_setmetatable(state, -2);
+                }
+                else
+                {
+                    lua_pushnil(state);
+                }
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_Button_getParent - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
 int lua_Button_getRefCount(lua_State* state)
 {
     // Get the number of parameters.
@@ -4160,42 +4205,6 @@ int lua_Button_setSkinRegion(lua_State* state)
     return 0;
 }
 
-int lua_Button_setState(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 2:
-        {
-            if ((lua_type(state, 1) == LUA_TUSERDATA) &&
-                (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
-            {
-                // Get parameter 1 off the stack.
-                Control::State param1 = (Control::State)lua_enumFromString_ControlState(luaL_checkstring(state, 2));
-
-                Button* instance = getInstance(state);
-                instance->setState(param1);
-                
-                return 0;
-            }
-
-            lua_pushstring(state, "lua_Button_setState - Failed to match the given parameters to a valid function signature.");
-            lua_error(state);
-            break;
-        }
-        default:
-        {
-            lua_pushstring(state, "Invalid number of parameters (expected 2).");
-            lua_error(state);
-            break;
-        }
-    }
-    return 0;
-}
-
 int lua_Button_setStyle(lua_State* state)
 {
     // Get the number of parameters.

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

@@ -39,6 +39,7 @@ int lua_Button_getImageUVs(lua_State* state);
 int lua_Button_getMargin(lua_State* state);
 int lua_Button_getOpacity(lua_State* state);
 int lua_Button_getPadding(lua_State* state);
+int lua_Button_getParent(lua_State* state);
 int lua_Button_getRefCount(lua_State* state);
 int lua_Button_getSkinColor(lua_State* state);
 int lua_Button_getSkinRegion(lua_State* state);
@@ -85,7 +86,6 @@ int lua_Button_setPosition(lua_State* state);
 int lua_Button_setSize(lua_State* state);
 int lua_Button_setSkinColor(lua_State* state);
 int lua_Button_setSkinRegion(lua_State* state);
-int lua_Button_setState(lua_State* state);
 int lua_Button_setStyle(lua_State* state);
 int lua_Button_setText(lua_State* state);
 int lua_Button_setTextAlignment(lua_State* state);

+ 46 - 37
gameplay/src/lua/lua_CheckBox.cpp

@@ -7,6 +7,7 @@
 #include "Button.h"
 #include "CheckBox.h"
 #include "Control.h"
+#include "Form.h"
 #include "Game.h"
 #include "Gamepad.h"
 #include "Label.h"
@@ -62,6 +63,7 @@ void luaRegister_CheckBox()
         {"getMargin", lua_CheckBox_getMargin},
         {"getOpacity", lua_CheckBox_getOpacity},
         {"getPadding", lua_CheckBox_getPadding},
+        {"getParent", lua_CheckBox_getParent},
         {"getRefCount", lua_CheckBox_getRefCount},
         {"getSkinColor", lua_CheckBox_getSkinColor},
         {"getSkinRegion", lua_CheckBox_getSkinRegion},
@@ -112,7 +114,6 @@ void luaRegister_CheckBox()
         {"setSize", lua_CheckBox_setSize},
         {"setSkinColor", lua_CheckBox_setSkinColor},
         {"setSkinRegion", lua_CheckBox_setSkinRegion},
-        {"setState", lua_CheckBox_setState},
         {"setStyle", lua_CheckBox_setStyle},
         {"setText", lua_CheckBox_setText},
         {"setTextAlignment", lua_CheckBox_setTextAlignment},
@@ -1966,6 +1967,50 @@ int lua_CheckBox_getPadding(lua_State* state)
     return 0;
 }
 
+int lua_CheckBox_getParent(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 1:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA))
+            {
+                CheckBox* instance = getInstance(state);
+                void* returnPtr = (void*)instance->getParent();
+                if (returnPtr)
+                {
+                    gameplay::ScriptUtil::LuaObject* object = (gameplay::ScriptUtil::LuaObject*)lua_newuserdata(state, sizeof(gameplay::ScriptUtil::LuaObject));
+                    object->instance = returnPtr;
+                    object->owns = false;
+                    luaL_getmetatable(state, "Control");
+                    lua_setmetatable(state, -2);
+                }
+                else
+                {
+                    lua_pushnil(state);
+                }
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_CheckBox_getParent - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
 int lua_CheckBox_getRefCount(lua_State* state)
 {
     // Get the number of parameters.
@@ -4356,42 +4401,6 @@ int lua_CheckBox_setSkinRegion(lua_State* state)
     return 0;
 }
 
-int lua_CheckBox_setState(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 2:
-        {
-            if ((lua_type(state, 1) == LUA_TUSERDATA) &&
-                (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
-            {
-                // Get parameter 1 off the stack.
-                Control::State param1 = (Control::State)lua_enumFromString_ControlState(luaL_checkstring(state, 2));
-
-                CheckBox* instance = getInstance(state);
-                instance->setState(param1);
-                
-                return 0;
-            }
-
-            lua_pushstring(state, "lua_CheckBox_setState - Failed to match the given parameters to a valid function signature.");
-            lua_error(state);
-            break;
-        }
-        default:
-        {
-            lua_pushstring(state, "Invalid number of parameters (expected 2).");
-            lua_error(state);
-            break;
-        }
-    }
-    return 0;
-}
-
 int lua_CheckBox_setStyle(lua_State* state)
 {
     // Get the number of parameters.

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

@@ -40,6 +40,7 @@ int lua_CheckBox_getImageUVs(lua_State* state);
 int lua_CheckBox_getMargin(lua_State* state);
 int lua_CheckBox_getOpacity(lua_State* state);
 int lua_CheckBox_getPadding(lua_State* state);
+int lua_CheckBox_getParent(lua_State* state);
 int lua_CheckBox_getRefCount(lua_State* state);
 int lua_CheckBox_getSkinColor(lua_State* state);
 int lua_CheckBox_getSkinRegion(lua_State* state);
@@ -90,7 +91,6 @@ int lua_CheckBox_setPosition(lua_State* state);
 int lua_CheckBox_setSize(lua_State* state);
 int lua_CheckBox_setSkinColor(lua_State* state);
 int lua_CheckBox_setSkinRegion(lua_State* state);
-int lua_CheckBox_setState(lua_State* state);
 int lua_CheckBox_setStyle(lua_State* state);
 int lua_CheckBox_setText(lua_State* state);
 int lua_CheckBox_setTextAlignment(lua_State* state);

+ 46 - 37
gameplay/src/lua/lua_Container.cpp

@@ -10,6 +10,7 @@
 #include "Container.h"
 #include "Control.h"
 #include "FlowLayout.h"
+#include "Form.h"
 #include "Game.h"
 #include "ImageControl.h"
 #include "Joystick.h"
@@ -75,6 +76,7 @@ void luaRegister_Container()
         {"getMargin", lua_Container_getMargin},
         {"getOpacity", lua_Container_getOpacity},
         {"getPadding", lua_Container_getPadding},
+        {"getParent", lua_Container_getParent},
         {"getRefCount", lua_Container_getRefCount},
         {"getScroll", lua_Container_getScroll},
         {"getScrollPosition", lua_Container_getScrollPosition},
@@ -136,7 +138,6 @@ void luaRegister_Container()
         {"setSize", lua_Container_setSize},
         {"setSkinColor", lua_Container_setSkinColor},
         {"setSkinRegion", lua_Container_setSkinRegion},
-        {"setState", lua_Container_setState},
         {"setStyle", lua_Container_setStyle},
         {"setTextAlignment", lua_Container_setTextAlignment},
         {"setTextColor", lua_Container_setTextColor},
@@ -2113,6 +2114,50 @@ int lua_Container_getPadding(lua_State* state)
     return 0;
 }
 
+int lua_Container_getParent(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 1:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA))
+            {
+                Container* instance = getInstance(state);
+                void* returnPtr = (void*)instance->getParent();
+                if (returnPtr)
+                {
+                    gameplay::ScriptUtil::LuaObject* object = (gameplay::ScriptUtil::LuaObject*)lua_newuserdata(state, sizeof(gameplay::ScriptUtil::LuaObject));
+                    object->instance = returnPtr;
+                    object->owns = false;
+                    luaL_getmetatable(state, "Control");
+                    lua_setmetatable(state, -2);
+                }
+                else
+                {
+                    lua_pushnil(state);
+                }
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_Container_getParent - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
 int lua_Container_getRefCount(lua_State* state)
 {
     // Get the number of parameters.
@@ -4951,42 +4996,6 @@ int lua_Container_setSkinRegion(lua_State* state)
     return 0;
 }
 
-int lua_Container_setState(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 2:
-        {
-            if ((lua_type(state, 1) == LUA_TUSERDATA) &&
-                (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
-            {
-                // Get parameter 1 off the stack.
-                Control::State param1 = (Control::State)lua_enumFromString_ControlState(luaL_checkstring(state, 2));
-
-                Container* instance = getInstance(state);
-                instance->setState(param1);
-                
-                return 0;
-            }
-
-            lua_pushstring(state, "lua_Container_setState - Failed to match the given parameters to a valid function signature.");
-            lua_error(state);
-            break;
-        }
-        default:
-        {
-            lua_pushstring(state, "Invalid number of parameters (expected 2).");
-            lua_error(state);
-            break;
-        }
-    }
-    return 0;
-}
-
 int lua_Container_setStyle(lua_State* state)
 {
     // Get the number of parameters.

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

@@ -42,6 +42,7 @@ int lua_Container_getLayout(lua_State* state);
 int lua_Container_getMargin(lua_State* state);
 int lua_Container_getOpacity(lua_State* state);
 int lua_Container_getPadding(lua_State* state);
+int lua_Container_getParent(lua_State* state);
 int lua_Container_getRefCount(lua_State* state);
 int lua_Container_getScroll(lua_State* state);
 int lua_Container_getScrollPosition(lua_State* state);
@@ -103,7 +104,6 @@ int lua_Container_setScrollingFriction(lua_State* state);
 int lua_Container_setSize(lua_State* state);
 int lua_Container_setSkinColor(lua_State* state);
 int lua_Container_setSkinRegion(lua_State* state);
-int lua_Container_setState(lua_State* state);
 int lua_Container_setStyle(lua_State* state);
 int lua_Container_setTextAlignment(lua_State* state);
 int lua_Container_setTextColor(lua_State* state);

+ 46 - 37
gameplay/src/lua/lua_Control.cpp

@@ -5,6 +5,7 @@
 #include "AnimationTarget.h"
 #include "Base.h"
 #include "Control.h"
+#include "Form.h"
 #include "Game.h"
 #include "Node.h"
 #include "Ref.h"
@@ -57,6 +58,7 @@ void luaRegister_Control()
         {"getMargin", lua_Control_getMargin},
         {"getOpacity", lua_Control_getOpacity},
         {"getPadding", lua_Control_getPadding},
+        {"getParent", lua_Control_getParent},
         {"getRefCount", lua_Control_getRefCount},
         {"getSkinColor", lua_Control_getSkinColor},
         {"getSkinRegion", lua_Control_getSkinRegion},
@@ -103,7 +105,6 @@ void luaRegister_Control()
         {"setSize", lua_Control_setSize},
         {"setSkinColor", lua_Control_setSkinColor},
         {"setSkinRegion", lua_Control_setSkinRegion},
-        {"setState", lua_Control_setState},
         {"setStyle", lua_Control_setStyle},
         {"setTextAlignment", lua_Control_setTextAlignment},
         {"setTextColor", lua_Control_setTextColor},
@@ -1911,6 +1912,50 @@ int lua_Control_getPadding(lua_State* state)
     return 0;
 }
 
+int lua_Control_getParent(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 1:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA))
+            {
+                Control* instance = getInstance(state);
+                void* returnPtr = (void*)instance->getParent();
+                if (returnPtr)
+                {
+                    gameplay::ScriptUtil::LuaObject* object = (gameplay::ScriptUtil::LuaObject*)lua_newuserdata(state, sizeof(gameplay::ScriptUtil::LuaObject));
+                    object->instance = returnPtr;
+                    object->owns = false;
+                    luaL_getmetatable(state, "Control");
+                    lua_setmetatable(state, -2);
+                }
+                else
+                {
+                    lua_pushnil(state);
+                }
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_Control_getParent - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
 int lua_Control_getRefCount(lua_State* state)
 {
     // Get the number of parameters.
@@ -4155,42 +4200,6 @@ int lua_Control_setSkinRegion(lua_State* state)
     return 0;
 }
 
-int lua_Control_setState(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 2:
-        {
-            if ((lua_type(state, 1) == LUA_TUSERDATA) &&
-                (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
-            {
-                // Get parameter 1 off the stack.
-                Control::State param1 = (Control::State)lua_enumFromString_ControlState(luaL_checkstring(state, 2));
-
-                Control* instance = getInstance(state);
-                instance->setState(param1);
-                
-                return 0;
-            }
-
-            lua_pushstring(state, "lua_Control_setState - Failed to match the given parameters to a valid function signature.");
-            lua_error(state);
-            break;
-        }
-        default:
-        {
-            lua_pushstring(state, "Invalid number of parameters (expected 2).");
-            lua_error(state);
-            break;
-        }
-    }
-    return 0;
-}
-
 int lua_Control_setStyle(lua_State* state)
 {
     // Get the number of parameters.

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

@@ -39,6 +39,7 @@ int lua_Control_getImageUVs(lua_State* state);
 int lua_Control_getMargin(lua_State* state);
 int lua_Control_getOpacity(lua_State* state);
 int lua_Control_getPadding(lua_State* state);
+int lua_Control_getParent(lua_State* state);
 int lua_Control_getRefCount(lua_State* state);
 int lua_Control_getSkinColor(lua_State* state);
 int lua_Control_getSkinRegion(lua_State* state);
@@ -85,7 +86,6 @@ int lua_Control_setPosition(lua_State* state);
 int lua_Control_setSize(lua_State* state);
 int lua_Control_setSkinColor(lua_State* state);
 int lua_Control_setSkinRegion(lua_State* state);
-int lua_Control_setState(lua_State* state);
 int lua_Control_setStyle(lua_State* state);
 int lua_Control_setTextAlignment(lua_State* state);
 int lua_Control_setTextColor(lua_State* state);

+ 1 - 0
gameplay/src/lua/lua_ControlListener.cpp

@@ -5,6 +5,7 @@
 #include "AnimationTarget.h"
 #include "Base.h"
 #include "Control.h"
+#include "Form.h"
 #include "Game.h"
 #include "Node.h"
 #include "Ref.h"

+ 83 - 37
gameplay/src/lua/lua_Form.cpp

@@ -79,6 +79,7 @@ void luaRegister_Form()
         {"getMargin", lua_Form_getMargin},
         {"getOpacity", lua_Form_getOpacity},
         {"getPadding", lua_Form_getPadding},
+        {"getParent", lua_Form_getParent},
         {"getRefCount", lua_Form_getRefCount},
         {"getScroll", lua_Form_getScroll},
         {"getScrollPosition", lua_Form_getScrollPosition},
@@ -142,7 +143,6 @@ void luaRegister_Form()
         {"setSize", lua_Form_setSize},
         {"setSkinColor", lua_Form_setSkinColor},
         {"setSkinRegion", lua_Form_setSkinRegion},
-        {"setState", lua_Form_setState},
         {"setStyle", lua_Form_setStyle},
         {"setTextAlignment", lua_Form_setTextAlignment},
         {"setTextColor", lua_Form_setTextColor},
@@ -166,6 +166,7 @@ void luaRegister_Form()
         {"ANIMATE_SIZE_HEIGHT", lua_Form_static_ANIMATE_SIZE_HEIGHT},
         {"ANIMATE_SIZE_WIDTH", lua_Form_static_ANIMATE_SIZE_WIDTH},
         {"create", lua_Form_static_create},
+        {"getActiveControl", lua_Form_static_getActiveControl},
         {"getForm", lua_Form_static_getForm},
         {NULL, NULL}
     };
@@ -2156,6 +2157,50 @@ int lua_Form_getPadding(lua_State* state)
     return 0;
 }
 
+int lua_Form_getParent(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 1:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA))
+            {
+                Form* instance = getInstance(state);
+                void* returnPtr = (void*)instance->getParent();
+                if (returnPtr)
+                {
+                    gameplay::ScriptUtil::LuaObject* object = (gameplay::ScriptUtil::LuaObject*)lua_newuserdata(state, sizeof(gameplay::ScriptUtil::LuaObject));
+                    object->instance = returnPtr;
+                    object->owns = false;
+                    luaL_getmetatable(state, "Control");
+                    lua_setmetatable(state, -2);
+                }
+                else
+                {
+                    lua_pushnil(state);
+                }
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_Form_getParent - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
 int lua_Form_getRefCount(lua_State* state)
 {
     // Get the number of parameters.
@@ -5080,42 +5125,6 @@ int lua_Form_setSkinRegion(lua_State* state)
     return 0;
 }
 
-int lua_Form_setState(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 2:
-        {
-            if ((lua_type(state, 1) == LUA_TUSERDATA) &&
-                (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
-            {
-                // Get parameter 1 off the stack.
-                Control::State param1 = (Control::State)lua_enumFromString_ControlState(luaL_checkstring(state, 2));
-
-                Form* instance = getInstance(state);
-                instance->setState(param1);
-                
-                return 0;
-            }
-
-            lua_pushstring(state, "lua_Form_setState - Failed to match the given parameters to a valid function signature.");
-            lua_error(state);
-            break;
-        }
-        default:
-        {
-            lua_pushstring(state, "Invalid number of parameters (expected 2).");
-            lua_error(state);
-            break;
-        }
-    }
-    return 0;
-}
-
 int lua_Form_setStyle(lua_State* state)
 {
     // Get the number of parameters.
@@ -5855,6 +5864,43 @@ int lua_Form_static_create(lua_State* state)
     return 0;
 }
 
+int lua_Form_static_getActiveControl(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 0:
+        {
+            void* returnPtr = (void*)Form::getActiveControl();
+            if (returnPtr)
+            {
+                gameplay::ScriptUtil::LuaObject* object = (gameplay::ScriptUtil::LuaObject*)lua_newuserdata(state, sizeof(gameplay::ScriptUtil::LuaObject));
+                object->instance = returnPtr;
+                object->owns = false;
+                luaL_getmetatable(state, "Control");
+                lua_setmetatable(state, -2);
+            }
+            else
+            {
+                lua_pushnil(state);
+            }
+
+            return 1;
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 0).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
 int lua_Form_static_getForm(lua_State* state)
 {
     // Get the number of parameters.

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

@@ -43,6 +43,7 @@ int lua_Form_getLayout(lua_State* state);
 int lua_Form_getMargin(lua_State* state);
 int lua_Form_getOpacity(lua_State* state);
 int lua_Form_getPadding(lua_State* state);
+int lua_Form_getParent(lua_State* state);
 int lua_Form_getRefCount(lua_State* state);
 int lua_Form_getScroll(lua_State* state);
 int lua_Form_getScrollPosition(lua_State* state);
@@ -106,7 +107,6 @@ int lua_Form_setScrollingFriction(lua_State* state);
 int lua_Form_setSize(lua_State* state);
 int lua_Form_setSkinColor(lua_State* state);
 int lua_Form_setSkinRegion(lua_State* state);
-int lua_Form_setState(lua_State* state);
 int lua_Form_setStyle(lua_State* state);
 int lua_Form_setTextAlignment(lua_State* state);
 int lua_Form_setTextColor(lua_State* state);
@@ -125,6 +125,7 @@ int lua_Form_static_ANIMATE_SIZE(lua_State* state);
 int lua_Form_static_ANIMATE_SIZE_HEIGHT(lua_State* state);
 int lua_Form_static_ANIMATE_SIZE_WIDTH(lua_State* state);
 int lua_Form_static_create(lua_State* state);
+int lua_Form_static_getActiveControl(lua_State* state);
 int lua_Form_static_getForm(lua_State* state);
 int lua_Form_update(lua_State* state);
 

+ 46 - 37
gameplay/src/lua/lua_ImageControl.cpp

@@ -6,6 +6,7 @@
 #include "Base.h"
 #include "Button.h"
 #include "Control.h"
+#include "Form.h"
 #include "Game.h"
 #include "Gamepad.h"
 #include "ImageControl.h"
@@ -61,6 +62,7 @@ void luaRegister_ImageControl()
         {"getMargin", lua_ImageControl_getMargin},
         {"getOpacity", lua_ImageControl_getOpacity},
         {"getPadding", lua_ImageControl_getPadding},
+        {"getParent", lua_ImageControl_getParent},
         {"getRefCount", lua_ImageControl_getRefCount},
         {"getRegionDst", lua_ImageControl_getRegionDst},
         {"getRegionSrc", lua_ImageControl_getRegionSrc},
@@ -113,7 +115,6 @@ void luaRegister_ImageControl()
         {"setSize", lua_ImageControl_setSize},
         {"setSkinColor", lua_ImageControl_setSkinColor},
         {"setSkinRegion", lua_ImageControl_setSkinRegion},
-        {"setState", lua_ImageControl_setState},
         {"setStyle", lua_ImageControl_setStyle},
         {"setText", lua_ImageControl_setText},
         {"setTextAlignment", lua_ImageControl_setTextAlignment},
@@ -1923,6 +1924,50 @@ int lua_ImageControl_getPadding(lua_State* state)
     return 0;
 }
 
+int lua_ImageControl_getParent(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 1:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA))
+            {
+                ImageControl* instance = getInstance(state);
+                void* returnPtr = (void*)instance->getParent();
+                if (returnPtr)
+                {
+                    gameplay::ScriptUtil::LuaObject* object = (gameplay::ScriptUtil::LuaObject*)lua_newuserdata(state, sizeof(gameplay::ScriptUtil::LuaObject));
+                    object->instance = returnPtr;
+                    object->owns = false;
+                    luaL_getmetatable(state, "Control");
+                    lua_setmetatable(state, -2);
+                }
+                else
+                {
+                    lua_pushnil(state);
+                }
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_ImageControl_getParent - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
 int lua_ImageControl_getRefCount(lua_State* state)
 {
     // Get the number of parameters.
@@ -4476,42 +4521,6 @@ int lua_ImageControl_setSkinRegion(lua_State* state)
     return 0;
 }
 
-int lua_ImageControl_setState(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 2:
-        {
-            if ((lua_type(state, 1) == LUA_TUSERDATA) &&
-                (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
-            {
-                // Get parameter 1 off the stack.
-                Control::State param1 = (Control::State)lua_enumFromString_ControlState(luaL_checkstring(state, 2));
-
-                ImageControl* instance = getInstance(state);
-                instance->setState(param1);
-                
-                return 0;
-            }
-
-            lua_pushstring(state, "lua_ImageControl_setState - Failed to match the given parameters to a valid function signature.");
-            lua_error(state);
-            break;
-        }
-        default:
-        {
-            lua_pushstring(state, "Invalid number of parameters (expected 2).");
-            lua_error(state);
-            break;
-        }
-    }
-    return 0;
-}
-
 int lua_ImageControl_setStyle(lua_State* state)
 {
     // Get the number of parameters.

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

@@ -39,6 +39,7 @@ int lua_ImageControl_getImageUVs(lua_State* state);
 int lua_ImageControl_getMargin(lua_State* state);
 int lua_ImageControl_getOpacity(lua_State* state);
 int lua_ImageControl_getPadding(lua_State* state);
+int lua_ImageControl_getParent(lua_State* state);
 int lua_ImageControl_getRefCount(lua_State* state);
 int lua_ImageControl_getRegionDst(lua_State* state);
 int lua_ImageControl_getRegionSrc(lua_State* state);
@@ -91,7 +92,6 @@ int lua_ImageControl_setRegionSrc(lua_State* state);
 int lua_ImageControl_setSize(lua_State* state);
 int lua_ImageControl_setSkinColor(lua_State* state);
 int lua_ImageControl_setSkinRegion(lua_State* state);
-int lua_ImageControl_setState(lua_State* state);
 int lua_ImageControl_setStyle(lua_State* state);
 int lua_ImageControl_setText(lua_State* state);
 int lua_ImageControl_setTextAlignment(lua_State* state);

+ 46 - 37
gameplay/src/lua/lua_Joystick.cpp

@@ -5,6 +5,7 @@
 #include "AnimationTarget.h"
 #include "Base.h"
 #include "Control.h"
+#include "Form.h"
 #include "Game.h"
 #include "Joystick.h"
 #include "Node.h"
@@ -61,6 +62,7 @@ void luaRegister_Joystick()
         {"getOpacity", lua_Joystick_getOpacity},
         {"getOuterRegionSize", lua_Joystick_getOuterRegionSize},
         {"getPadding", lua_Joystick_getPadding},
+        {"getParent", lua_Joystick_getParent},
         {"getRefCount", lua_Joystick_getRefCount},
         {"getSkinColor", lua_Joystick_getSkinColor},
         {"getSkinRegion", lua_Joystick_getSkinRegion},
@@ -112,7 +114,6 @@ void luaRegister_Joystick()
         {"setSize", lua_Joystick_setSize},
         {"setSkinColor", lua_Joystick_setSkinColor},
         {"setSkinRegion", lua_Joystick_setSkinRegion},
-        {"setState", lua_Joystick_setState},
         {"setStyle", lua_Joystick_setStyle},
         {"setTextAlignment", lua_Joystick_setTextAlignment},
         {"setTextColor", lua_Joystick_setTextColor},
@@ -2044,6 +2045,50 @@ int lua_Joystick_getPadding(lua_State* state)
     return 0;
 }
 
+int lua_Joystick_getParent(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 1:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA))
+            {
+                Joystick* instance = getInstance(state);
+                void* returnPtr = (void*)instance->getParent();
+                if (returnPtr)
+                {
+                    gameplay::ScriptUtil::LuaObject* object = (gameplay::ScriptUtil::LuaObject*)lua_newuserdata(state, sizeof(gameplay::ScriptUtil::LuaObject));
+                    object->instance = returnPtr;
+                    object->owns = false;
+                    luaL_getmetatable(state, "Control");
+                    lua_setmetatable(state, -2);
+                }
+                else
+                {
+                    lua_pushnil(state);
+                }
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_Joystick_getParent - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
 int lua_Joystick_getRefCount(lua_State* state)
 {
     // Get the number of parameters.
@@ -4487,42 +4532,6 @@ int lua_Joystick_setSkinRegion(lua_State* state)
     return 0;
 }
 
-int lua_Joystick_setState(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 2:
-        {
-            if ((lua_type(state, 1) == LUA_TUSERDATA) &&
-                (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
-            {
-                // Get parameter 1 off the stack.
-                Control::State param1 = (Control::State)lua_enumFromString_ControlState(luaL_checkstring(state, 2));
-
-                Joystick* instance = getInstance(state);
-                instance->setState(param1);
-                
-                return 0;
-            }
-
-            lua_pushstring(state, "lua_Joystick_setState - Failed to match the given parameters to a valid function signature.");
-            lua_error(state);
-            break;
-        }
-        default:
-        {
-            lua_pushstring(state, "Invalid number of parameters (expected 2).");
-            lua_error(state);
-            break;
-        }
-    }
-    return 0;
-}
-
 int lua_Joystick_setStyle(lua_State* state)
 {
     // Get the number of parameters.

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

@@ -42,6 +42,7 @@ int lua_Joystick_getMargin(lua_State* state);
 int lua_Joystick_getOpacity(lua_State* state);
 int lua_Joystick_getOuterRegionSize(lua_State* state);
 int lua_Joystick_getPadding(lua_State* state);
+int lua_Joystick_getParent(lua_State* state);
 int lua_Joystick_getRefCount(lua_State* state);
 int lua_Joystick_getSkinColor(lua_State* state);
 int lua_Joystick_getSkinRegion(lua_State* state);
@@ -93,7 +94,6 @@ int lua_Joystick_setRelative(lua_State* state);
 int lua_Joystick_setSize(lua_State* state);
 int lua_Joystick_setSkinColor(lua_State* state);
 int lua_Joystick_setSkinRegion(lua_State* state);
-int lua_Joystick_setState(lua_State* state);
 int lua_Joystick_setStyle(lua_State* state);
 int lua_Joystick_setTextAlignment(lua_State* state);
 int lua_Joystick_setTextColor(lua_State* state);

+ 46 - 37
gameplay/src/lua/lua_Label.cpp

@@ -5,6 +5,7 @@
 #include "AnimationTarget.h"
 #include "Base.h"
 #include "Control.h"
+#include "Form.h"
 #include "Game.h"
 #include "Label.h"
 #include "Node.h"
@@ -58,6 +59,7 @@ void luaRegister_Label()
         {"getMargin", lua_Label_getMargin},
         {"getOpacity", lua_Label_getOpacity},
         {"getPadding", lua_Label_getPadding},
+        {"getParent", lua_Label_getParent},
         {"getRefCount", lua_Label_getRefCount},
         {"getSkinColor", lua_Label_getSkinColor},
         {"getSkinRegion", lua_Label_getSkinRegion},
@@ -105,7 +107,6 @@ void luaRegister_Label()
         {"setSize", lua_Label_setSize},
         {"setSkinColor", lua_Label_setSkinColor},
         {"setSkinRegion", lua_Label_setSkinRegion},
-        {"setState", lua_Label_setState},
         {"setStyle", lua_Label_setStyle},
         {"setText", lua_Label_setText},
         {"setTextAlignment", lua_Label_setTextAlignment},
@@ -1915,6 +1916,50 @@ int lua_Label_getPadding(lua_State* state)
     return 0;
 }
 
+int lua_Label_getParent(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 1:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA))
+            {
+                Label* instance = getInstance(state);
+                void* returnPtr = (void*)instance->getParent();
+                if (returnPtr)
+                {
+                    gameplay::ScriptUtil::LuaObject* object = (gameplay::ScriptUtil::LuaObject*)lua_newuserdata(state, sizeof(gameplay::ScriptUtil::LuaObject));
+                    object->instance = returnPtr;
+                    object->owns = false;
+                    luaL_getmetatable(state, "Control");
+                    lua_setmetatable(state, -2);
+                }
+                else
+                {
+                    lua_pushnil(state);
+                }
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_Label_getParent - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
 int lua_Label_getRefCount(lua_State* state)
 {
     // Get the number of parameters.
@@ -4194,42 +4239,6 @@ int lua_Label_setSkinRegion(lua_State* state)
     return 0;
 }
 
-int lua_Label_setState(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 2:
-        {
-            if ((lua_type(state, 1) == LUA_TUSERDATA) &&
-                (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
-            {
-                // Get parameter 1 off the stack.
-                Control::State param1 = (Control::State)lua_enumFromString_ControlState(luaL_checkstring(state, 2));
-
-                Label* instance = getInstance(state);
-                instance->setState(param1);
-                
-                return 0;
-            }
-
-            lua_pushstring(state, "lua_Label_setState - Failed to match the given parameters to a valid function signature.");
-            lua_error(state);
-            break;
-        }
-        default:
-        {
-            lua_pushstring(state, "Invalid number of parameters (expected 2).");
-            lua_error(state);
-            break;
-        }
-    }
-    return 0;
-}
-
 int lua_Label_setStyle(lua_State* state)
 {
     // Get the number of parameters.

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

@@ -39,6 +39,7 @@ int lua_Label_getImageUVs(lua_State* state);
 int lua_Label_getMargin(lua_State* state);
 int lua_Label_getOpacity(lua_State* state);
 int lua_Label_getPadding(lua_State* state);
+int lua_Label_getParent(lua_State* state);
 int lua_Label_getRefCount(lua_State* state);
 int lua_Label_getSkinColor(lua_State* state);
 int lua_Label_getSkinRegion(lua_State* state);
@@ -86,7 +87,6 @@ int lua_Label_setPosition(lua_State* state);
 int lua_Label_setSize(lua_State* state);
 int lua_Label_setSkinColor(lua_State* state);
 int lua_Label_setSkinRegion(lua_State* state);
-int lua_Label_setState(lua_State* state);
 int lua_Label_setStyle(lua_State* state);
 int lua_Label_setText(lua_State* state);
 int lua_Label_setTextAlignment(lua_State* state);

+ 46 - 37
gameplay/src/lua/lua_RadioButton.cpp

@@ -6,6 +6,7 @@
 #include "Base.h"
 #include "Button.h"
 #include "Control.h"
+#include "Form.h"
 #include "Game.h"
 #include "Gamepad.h"
 #include "Label.h"
@@ -63,6 +64,7 @@ void luaRegister_RadioButton()
         {"getMargin", lua_RadioButton_getMargin},
         {"getOpacity", lua_RadioButton_getOpacity},
         {"getPadding", lua_RadioButton_getPadding},
+        {"getParent", lua_RadioButton_getParent},
         {"getRefCount", lua_RadioButton_getRefCount},
         {"getSkinColor", lua_RadioButton_getSkinColor},
         {"getSkinRegion", lua_RadioButton_getSkinRegion},
@@ -114,7 +116,6 @@ void luaRegister_RadioButton()
         {"setSize", lua_RadioButton_setSize},
         {"setSkinColor", lua_RadioButton_setSkinColor},
         {"setSkinRegion", lua_RadioButton_setSkinRegion},
-        {"setState", lua_RadioButton_setState},
         {"setStyle", lua_RadioButton_setStyle},
         {"setText", lua_RadioButton_setText},
         {"setTextAlignment", lua_RadioButton_setTextAlignment},
@@ -2003,6 +2004,50 @@ int lua_RadioButton_getPadding(lua_State* state)
     return 0;
 }
 
+int lua_RadioButton_getParent(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 1:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA))
+            {
+                RadioButton* instance = getInstance(state);
+                void* returnPtr = (void*)instance->getParent();
+                if (returnPtr)
+                {
+                    gameplay::ScriptUtil::LuaObject* object = (gameplay::ScriptUtil::LuaObject*)lua_newuserdata(state, sizeof(gameplay::ScriptUtil::LuaObject));
+                    object->instance = returnPtr;
+                    object->owns = false;
+                    luaL_getmetatable(state, "Control");
+                    lua_setmetatable(state, -2);
+                }
+                else
+                {
+                    lua_pushnil(state);
+                }
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_RadioButton_getParent - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
 int lua_RadioButton_getRefCount(lua_State* state)
 {
     // Get the number of parameters.
@@ -4429,42 +4474,6 @@ int lua_RadioButton_setSkinRegion(lua_State* state)
     return 0;
 }
 
-int lua_RadioButton_setState(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 2:
-        {
-            if ((lua_type(state, 1) == LUA_TUSERDATA) &&
-                (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
-            {
-                // Get parameter 1 off the stack.
-                Control::State param1 = (Control::State)lua_enumFromString_ControlState(luaL_checkstring(state, 2));
-
-                RadioButton* instance = getInstance(state);
-                instance->setState(param1);
-                
-                return 0;
-            }
-
-            lua_pushstring(state, "lua_RadioButton_setState - Failed to match the given parameters to a valid function signature.");
-            lua_error(state);
-            break;
-        }
-        default:
-        {
-            lua_pushstring(state, "Invalid number of parameters (expected 2).");
-            lua_error(state);
-            break;
-        }
-    }
-    return 0;
-}
-
 int lua_RadioButton_setStyle(lua_State* state)
 {
     // Get the number of parameters.

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

@@ -41,6 +41,7 @@ int lua_RadioButton_getImageUVs(lua_State* state);
 int lua_RadioButton_getMargin(lua_State* state);
 int lua_RadioButton_getOpacity(lua_State* state);
 int lua_RadioButton_getPadding(lua_State* state);
+int lua_RadioButton_getParent(lua_State* state);
 int lua_RadioButton_getRefCount(lua_State* state);
 int lua_RadioButton_getSkinColor(lua_State* state);
 int lua_RadioButton_getSkinRegion(lua_State* state);
@@ -92,7 +93,6 @@ int lua_RadioButton_setSelected(lua_State* state);
 int lua_RadioButton_setSize(lua_State* state);
 int lua_RadioButton_setSkinColor(lua_State* state);
 int lua_RadioButton_setSkinRegion(lua_State* state);
-int lua_RadioButton_setState(lua_State* state);
 int lua_RadioButton_setStyle(lua_State* state);
 int lua_RadioButton_setText(lua_State* state);
 int lua_RadioButton_setTextAlignment(lua_State* state);

+ 46 - 37
gameplay/src/lua/lua_Slider.cpp

@@ -5,6 +5,7 @@
 #include "AnimationTarget.h"
 #include "Base.h"
 #include "Control.h"
+#include "Form.h"
 #include "Game.h"
 #include "Label.h"
 #include "Node.h"
@@ -61,6 +62,7 @@ void luaRegister_Slider()
         {"getMin", lua_Slider_getMin},
         {"getOpacity", lua_Slider_getOpacity},
         {"getPadding", lua_Slider_getPadding},
+        {"getParent", lua_Slider_getParent},
         {"getRefCount", lua_Slider_getRefCount},
         {"getSkinColor", lua_Slider_getSkinColor},
         {"getSkinRegion", lua_Slider_getSkinRegion},
@@ -115,7 +117,6 @@ void luaRegister_Slider()
         {"setSize", lua_Slider_setSize},
         {"setSkinColor", lua_Slider_setSkinColor},
         {"setSkinRegion", lua_Slider_setSkinRegion},
-        {"setState", lua_Slider_setState},
         {"setStep", lua_Slider_setStep},
         {"setStyle", lua_Slider_setStyle},
         {"setText", lua_Slider_setText},
@@ -2000,6 +2001,50 @@ int lua_Slider_getPadding(lua_State* state)
     return 0;
 }
 
+int lua_Slider_getParent(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 1:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA))
+            {
+                Slider* instance = getInstance(state);
+                void* returnPtr = (void*)instance->getParent();
+                if (returnPtr)
+                {
+                    gameplay::ScriptUtil::LuaObject* object = (gameplay::ScriptUtil::LuaObject*)lua_newuserdata(state, sizeof(gameplay::ScriptUtil::LuaObject));
+                    object->instance = returnPtr;
+                    object->owns = false;
+                    luaL_getmetatable(state, "Control");
+                    lua_setmetatable(state, -2);
+                }
+                else
+                {
+                    lua_pushnil(state);
+                }
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_Slider_getParent - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
 int lua_Slider_getRefCount(lua_State* state)
 {
     // Get the number of parameters.
@@ -4526,42 +4571,6 @@ int lua_Slider_setSkinRegion(lua_State* state)
     return 0;
 }
 
-int lua_Slider_setState(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 2:
-        {
-            if ((lua_type(state, 1) == LUA_TUSERDATA) &&
-                (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
-            {
-                // Get parameter 1 off the stack.
-                Control::State param1 = (Control::State)lua_enumFromString_ControlState(luaL_checkstring(state, 2));
-
-                Slider* instance = getInstance(state);
-                instance->setState(param1);
-                
-                return 0;
-            }
-
-            lua_pushstring(state, "lua_Slider_setState - Failed to match the given parameters to a valid function signature.");
-            lua_error(state);
-            break;
-        }
-        default:
-        {
-            lua_pushstring(state, "Invalid number of parameters (expected 2).");
-            lua_error(state);
-            break;
-        }
-    }
-    return 0;
-}
-
 int lua_Slider_setStep(lua_State* state)
 {
     // Get the number of parameters.

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

@@ -41,6 +41,7 @@ int lua_Slider_getMax(lua_State* state);
 int lua_Slider_getMin(lua_State* state);
 int lua_Slider_getOpacity(lua_State* state);
 int lua_Slider_getPadding(lua_State* state);
+int lua_Slider_getParent(lua_State* state);
 int lua_Slider_getRefCount(lua_State* state);
 int lua_Slider_getSkinColor(lua_State* state);
 int lua_Slider_getSkinRegion(lua_State* state);
@@ -95,7 +96,6 @@ int lua_Slider_setPosition(lua_State* state);
 int lua_Slider_setSize(lua_State* state);
 int lua_Slider_setSkinColor(lua_State* state);
 int lua_Slider_setSkinRegion(lua_State* state);
-int lua_Slider_setState(lua_State* state);
 int lua_Slider_setStep(lua_State* state);
 int lua_Slider_setStyle(lua_State* state);
 int lua_Slider_setText(lua_State* state);

+ 46 - 37
gameplay/src/lua/lua_TextBox.cpp

@@ -5,6 +5,7 @@
 #include "AnimationTarget.h"
 #include "Base.h"
 #include "Control.h"
+#include "Form.h"
 #include "Game.h"
 #include "Label.h"
 #include "Node.h"
@@ -62,6 +63,7 @@ void luaRegister_TextBox()
         {"getMargin", lua_TextBox_getMargin},
         {"getOpacity", lua_TextBox_getOpacity},
         {"getPadding", lua_TextBox_getPadding},
+        {"getParent", lua_TextBox_getParent},
         {"getPasswordChar", lua_TextBox_getPasswordChar},
         {"getRefCount", lua_TextBox_getRefCount},
         {"getSkinColor", lua_TextBox_getSkinColor},
@@ -113,7 +115,6 @@ void luaRegister_TextBox()
         {"setSize", lua_TextBox_setSize},
         {"setSkinColor", lua_TextBox_setSkinColor},
         {"setSkinRegion", lua_TextBox_setSkinRegion},
-        {"setState", lua_TextBox_setState},
         {"setStyle", lua_TextBox_setStyle},
         {"setText", lua_TextBox_setText},
         {"setTextAlignment", lua_TextBox_setTextAlignment},
@@ -1993,6 +1994,50 @@ int lua_TextBox_getPadding(lua_State* state)
     return 0;
 }
 
+int lua_TextBox_getParent(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 1:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA))
+            {
+                TextBox* instance = getInstance(state);
+                void* returnPtr = (void*)instance->getParent();
+                if (returnPtr)
+                {
+                    gameplay::ScriptUtil::LuaObject* object = (gameplay::ScriptUtil::LuaObject*)lua_newuserdata(state, sizeof(gameplay::ScriptUtil::LuaObject));
+                    object->instance = returnPtr;
+                    object->owns = false;
+                    luaL_getmetatable(state, "Control");
+                    lua_setmetatable(state, -2);
+                }
+                else
+                {
+                    lua_pushnil(state);
+                }
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_TextBox_getParent - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
 int lua_TextBox_getPasswordChar(lua_State* state)
 {
     // Get the number of parameters.
@@ -4431,42 +4476,6 @@ int lua_TextBox_setSkinRegion(lua_State* state)
     return 0;
 }
 
-int lua_TextBox_setState(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 2:
-        {
-            if ((lua_type(state, 1) == LUA_TUSERDATA) &&
-                (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
-            {
-                // Get parameter 1 off the stack.
-                Control::State param1 = (Control::State)lua_enumFromString_ControlState(luaL_checkstring(state, 2));
-
-                TextBox* instance = getInstance(state);
-                instance->setState(param1);
-                
-                return 0;
-            }
-
-            lua_pushstring(state, "lua_TextBox_setState - Failed to match the given parameters to a valid function signature.");
-            lua_error(state);
-            break;
-        }
-        default:
-        {
-            lua_pushstring(state, "Invalid number of parameters (expected 2).");
-            lua_error(state);
-            break;
-        }
-    }
-    return 0;
-}
-
 int lua_TextBox_setStyle(lua_State* state)
 {
     // Get the number of parameters.

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

@@ -41,6 +41,7 @@ int lua_TextBox_getLastKeypress(lua_State* state);
 int lua_TextBox_getMargin(lua_State* state);
 int lua_TextBox_getOpacity(lua_State* state);
 int lua_TextBox_getPadding(lua_State* state);
+int lua_TextBox_getParent(lua_State* state);
 int lua_TextBox_getPasswordChar(lua_State* state);
 int lua_TextBox_getRefCount(lua_State* state);
 int lua_TextBox_getSkinColor(lua_State* state);
@@ -92,7 +93,6 @@ int lua_TextBox_setPosition(lua_State* state);
 int lua_TextBox_setSize(lua_State* state);
 int lua_TextBox_setSkinColor(lua_State* state);
 int lua_TextBox_setSkinRegion(lua_State* state);
-int lua_TextBox_setState(lua_State* state);
 int lua_TextBox_setStyle(lua_State* state);
 int lua_TextBox_setText(lua_State* state);
 int lua_TextBox_setTextAlignment(lua_State* state);

+ 15 - 0
samples/browser/res/common/default.theme

@@ -339,6 +339,11 @@ theme mainMenu
             textAlignment = ALIGN_VCENTER_LEFT
         }
 
+        stateFocus
+		{
+			textAlignment = ALIGN_VCENTER_LEFT
+		}
+
         stateActive
         {
             font = res/common/arial.gpb
@@ -358,6 +363,16 @@ theme mainMenu
         {
             textAlignment = ALIGN_TOP_LEFT
         }
+
+        stateFocus
+        {
+            textAlignment = ALIGN_TOP_LEFT
+        }
+
+        stateHover
+        {
+            textAlignment = ALIGN_TOP_LEFT
+        }
     }
 
     style title

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

@@ -6,7 +6,7 @@ form formSelect
     alignment = ALIGN_TOP_LEFT
     width = 200
     autoHeight = true
-	consumeInputEvents = true
+	consumeInputEvents = false
 
 	radioButton form0
 	{

+ 4 - 3
samples/browser/res/common/gamepad.form

@@ -3,12 +3,13 @@ form VIRTUAL GAMEPAD
     theme = res/common/gamepad.theme
     autoWidth = true
     autoHeight = true
+    consumeInputEvents = false
 
     container left
     {
         alignment = ALIGN_BOTTOM_LEFT
         size = 300, 300
-        consumeEvents = false
+        consumeInputEvents = false
         
         joystick
         {
@@ -23,13 +24,13 @@ form VIRTUAL GAMEPAD
     {
         alignment = ALIGN_BOTTOM_RIGHT
         size = 256, 256
-		consumeEvents = false
+		consumeInputEvents = false
 		
         container inner
         {
             size = 230, 230
             alignment = ALIGN_VCENTER_HCENTER
-            consumeEvents = false
+            consumeInputEvents = false
             
             button A
             {

+ 0 - 3
samples/browser/res/common/text.form

@@ -25,7 +25,6 @@ form textTest
         style = buttonStyle
         autoWidth = true
         height = 50
-        consumeInputEvents = false
         text = Font (arial)
     }
 
@@ -71,7 +70,6 @@ form textTest
             style = buttonStyle
             width = 50
             height = 50
-            consumeInputEvents = false
             text = -
         }
 
@@ -102,7 +100,6 @@ form textTest
             style = buttonStyle
             size = 60, 60
             text = 7
-            consumeInputEvents = false
         }
 
         button topCenterButton : topLeftButton

+ 1 - 0
samples/browser/sample-browser.vcxproj.user

@@ -3,6 +3,7 @@
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
     <LocalDebuggerEnvironment>PATH=%PATH%;../../bin/windows/x86;</LocalDebuggerEnvironment>
     <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
+    <LocalDebuggerDebuggerType>NativeOnly</LocalDebuggerDebuggerType>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
     <LocalDebuggerEnvironment>PATH=%PATH%;../../bin/windows/x86;</LocalDebuggerEnvironment>

+ 8 - 8
samples/browser/src/FormsSample.cpp

@@ -79,7 +79,7 @@ void FormsSample::initialize()
     
     RadioButton* form0Button = static_cast<RadioButton*>(_formSelect->getControl("form0"));
     form0Button->addListener(this, Control::Listener::CLICK);
-    form0Button->setState(Control::FOCUS);
+    //form0Button->setState(Control::FOCUS);
 
     RadioButton* form1Button = static_cast<RadioButton*>(_formSelect->getControl("form1"));
     form1Button->addListener(this, Control::Listener::CLICK);
@@ -107,7 +107,7 @@ void FormsSample::initialize()
     createSampleForm(_forms[0]->getTheme());
 
     Button* button = static_cast<Button*>(_forms[0]->getControl("testButton"));
-    button->setState(Control::FOCUS);
+    //button->setState(Control::FOCUS);
 
     // Create a scene with a camera node.
     Camera* camera = Camera::createPerspective(45.0f, (float)getWidth() / (float)getHeight(), 0.25f, 100.0f);
@@ -135,8 +135,8 @@ void FormsSample::formChanged()
         _activeForm->setEnabled(false);
     _activeForm = _forms[_formIndex];
     _activeForm->setEnabled(true);
-    _activeForm->setState(Control::FOCUS);
-    _formSelect->setState(Control::NORMAL);
+    //_activeForm->setState(Control::FOCUS);
+    //_formSelect->setState(Control::NORMAL);
 
     // Add the form to a node to allow it to be placed in 3D space.
     const Rectangle& bounds = _activeForm->getBounds();
@@ -210,16 +210,16 @@ void FormsSample::update(float elapsedTime)
         _keyFlags |= KEY_SELECT_MASK;
         if (_formSelect->getState() == Control::FOCUS)
         {
-            _formSelect->setState(Control::NORMAL);
+            //_formSelect->setState(Control::NORMAL);
         }
         else if (_activeForm->getState() == Control::FOCUS)
         {
-            _activeForm->setState(Control::NORMAL);
-            _formSelect->setState(Control::FOCUS);
+            //_activeForm->setState(Control::NORMAL);
+            //_formSelect->setState(Control::FOCUS);
         }
         else
         {
-            _formSelect->setState(Control::FOCUS);
+            //_formSelect->setState(Control::FOCUS);
         }
     }
     else if ((_keyFlags & KEY_SELECT_MASK) && !_gamepad->isButtonDown(Gamepad::BUTTON_MENU1))

+ 2 - 3
samples/browser/src/SamplesGame.cpp

@@ -62,13 +62,12 @@ void SamplesGame::initialize()
             sampleButton->setText(sampleRecord.title.c_str());
             sampleButton->setAutoWidth(true);
             sampleButton->setHeight(60);      // Tall enough to touch easily on a BB10 device.
-            sampleButton->setConsumeInputEvents(false);   // This lets the user scroll the container if they swipe starting from a button.
             sampleButton->addListener(this, Control::Listener::CLICK);
             _sampleSelectForm->addControl(sampleButton);
             sampleButton->release();
         }
     }
-    _sampleSelectForm->setState(Control::FOCUS);
+    //_sampleSelectForm->setState(Control::FOCUS);
 
     // Disable virtual gamepads.
     unsigned int gamepadCount = getGamepadCount();
@@ -257,7 +256,7 @@ void SamplesGame::exitActiveSample()
         SAFE_DELETE(_activeSample);
 
         _sampleSelectForm->setEnabled(true);
-        _sampleSelectForm->setState(Control::FOCUS);
+        //_sampleSelectForm->setState(Control::FOCUS);
     }
 
     // Reset some game options

+ 1 - 3
samples/particles/res/editor.form

@@ -315,8 +315,7 @@ form particleEditor
             textAlignment = ALIGN_TOP_HCENTER
             valueTextVisible = true
             valueTextAlignment = ALIGN_BOTTOM_HCENTER
-            valueTextPrecision = 2            
-            consumeInputEvents = false
+            valueTextPrecision = 2
         }
 
         slider energyMax : energyMin
@@ -342,7 +341,6 @@ form particleEditor
             valueTextVisible = true
             valueTextAlignment = ALIGN_BOTTOM_HCENTER
             valueTextPrecision = 2
-            consumeInputEvents = false
         }
 
         slider startGreen : startRed

+ 1 - 1
samples/particles/src/ParticlesGame.cpp

@@ -136,7 +136,7 @@ void ParticlesGame::initialize()
     // Load the form for editing ParticleEmitters.
     _form = Form::create("res/editor.form");
     _form->setConsumeInputEvents(false);
-    _form->setState(Control::FOCUS);
+    //_form->setState(Control::FOCUS);
 
     // Store pointers to UI controls we care about.
     _startRed = (Slider*)_form->getControl("startRed");

+ 1 - 1
samples/racer/src/RacerGame.cpp

@@ -574,7 +574,7 @@ void RacerGame::menuEvent()
         static_cast<Button*>(_overlay->getControl("menuButton"))->setText("Resume");
         pause();
         _menu->setEnabled(true);
-        _menu->setState(Control::FOCUS);
+        //_menu->setState(Control::FOCUS);
     }
     else
     {

+ 0 - 2
samples/spaceship/src/SpaceshipGame.cpp

@@ -919,7 +919,6 @@ void SpaceshipGame::buildFriendsChooser()
 		button->setPosition(0, 110*(i+1));
 		button->setWidth(400);
 		button->setHeight(100);
-		button->setConsumeInputEvents(false);   // This lets the user scroll the container if they swipe starting from a button.
 		button->addListener(this, Control::Listener::CLICK);
 		_friendsContainer->addControl(button);
 		button->release();
@@ -957,7 +956,6 @@ void SpaceshipGame::buildChallengeChooser()
 		button->setPosition(0, 110*i);
 		button->setWidth(400);
 		button->setHeight(100);
-		button->setConsumeInputEvents(false);   // This lets the user scroll the container if they swipe starting from a button.
 		button->addListener(this, Control::Listener::CLICK);
 		_challengeContainer->addControl(button);
 		button->release();