Prechádzať zdrojové kódy

Fixes #476 (adding mouse event support to UI controls).
Using mouse events in Container to scroll in several different ways.
All input events can now be consumed by forms (touch, mouse, key).
Fix for FlowLayout's destructor.
Fixed memory leak when leaving a control's style unspecified.
Fixes an opacity animation in Container that was being unnecessarily re-created.

Adam Blake 13 rokov pred
rodič
commit
15aafb3c21

+ 1 - 1
gameplay/src/Button.h

@@ -71,7 +71,7 @@ protected:
 
 private:
 
-    /*
+    /**
      * Constructor.
      */
     Button(const Button& copy);

+ 244 - 113
gameplay/src/Container.cpp

@@ -26,11 +26,12 @@ Container::Container()
       _scrollBarLeftCap(NULL), _scrollBarHorizontal(NULL), _scrollBarRightCap(NULL),
       _scroll(SCROLL_NONE), _scrollBarBounds(Rectangle::empty()), _scrollPosition(Vector2::zero()),
       _scrollBarsAutoHide(false), _scrollBarOpacity(1.0f), _scrolling(false),
-       _scrollingFirstX(0), _scrollingFirstY(0),
-      _scrollingLastX(0), _scrollingLastY(0),
+       _scrollingFirstX(0), _scrollingFirstY(0), _scrollingLastX(0), _scrollingLastY(0),
       _scrollingStartTimeX(0), _scrollingStartTimeY(0), _scrollingLastTime(0),
       _scrollingVelocity(Vector2::zero()), _scrollingFriction(1.0f),
-      _scrollingRight(false), _scrollingDown(false), _scrollBarOpacityClip(NULL), _zIndexDefault(0)
+      _scrollingRight(false), _scrollingDown(false),
+      _scrollingMouseVertically(false), _scrollingMouseHorizontally(false),
+      _scrollBarOpacityClip(NULL), _zIndexDefault(0), _totalWidth(0), _totalHeight(0)
 {
 }
 
@@ -108,9 +109,7 @@ void Container::addControls(Theme* theme, Properties* properties)
         }
         else
         {
-            Theme::Style::Overlay* overlay = Theme::Style::Overlay::create();
-            controlStyle = new Theme::Style(theme, "", 1.0f / theme->_texture->getWidth(), 1.0f / theme->_texture->getHeight(),
-                Theme::Margin::empty(), Theme::Border::empty(), overlay, overlay, overlay, overlay);
+            controlStyle = theme->getEmptyStyle();
         }
 
         std::string controlName(controlSpace->getNamespace());
@@ -499,81 +498,15 @@ bool Container::isDirty()
 
 bool Container::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
 {
-    if (!isEnabled())
-    {
-        return false;
-    }
-
-    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())
-        {
-            continue;
-        }
-
-        const Rectangle& bounds = control->getBounds();
-        float boundsX = bounds.x;
-        float boundsY = bounds.y;
-        if (offset)
-        {
-            boundsX += offset->x;
-            boundsY += offset->y;
-        }
-
-        if (control->getState() != Control::NORMAL ||
-            (evt == Touch::TOUCH_PRESS &&
-                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.
-            eventConsumed |= control->touchEvent(evt, x - xPos - boundsX, y - yPos - boundsY, contactIndex);
-        }
-    }
-
-    if (!isEnabled())
-    {
-        return (_consumeTouchEvents | eventConsumed);
-    }
-
-    switch (evt)
-    {
-    case Touch::TOUCH_PRESS:
-        setState(Control::FOCUS);
-        break;
-    case Touch::TOUCH_RELEASE:
-        setState(Control::NORMAL);
-        break;
-    }
-
-    if (!eventConsumed && _scroll != SCROLL_NONE)
-    {
-        if (touchEventScroll(evt, x - xPos, y - yPos, contactIndex))
-        {
-            _dirty = true;
-        }
-    }
+    return pointerEvent(false, evt, x, y, (int)contactIndex);
+}
 
-    return (_consumeTouchEvents | eventConsumed);
+bool Container::mouseEvent(Mouse::MouseEvent evt, int x, int y, int wheelDelta)
+{
+    return pointerEvent(true, evt, x, y, wheelDelta);
 }
 
-void Container::keyEvent(Keyboard::KeyEvent evt, int key)
+bool Container::keyEvent(Keyboard::KeyEvent evt, int key)
 {
     std::vector<Control*>::const_iterator it;
     for (it = _controls.begin(); it < _controls.end(); it++)
@@ -587,9 +520,12 @@ void Container::keyEvent(Keyboard::KeyEvent evt, int key)
 
         if (control->isContainer() || control->getState() == Control::FOCUS)
         {
-            control->keyEvent(evt, key);
+            if (control->keyEvent(evt, key))
+                return _consumeInputEvents;
         }
     }
+
+    return false;
 }
 
 bool Container::isContainer()
@@ -641,8 +577,6 @@ void Container::updateScroll()
     const Theme::Padding& containerPadding = getPadding();
 
     // Calculate total width and height.
-    float totalWidth = 0;
-    float totalHeight = 0;
     std::vector<Control*> controls = getControls();
     unsigned int controlsCount = controls.size();
     for (unsigned int i = 0; i < controlsCount; i++)
@@ -653,15 +587,15 @@ void Container::updateScroll()
         const Theme::Margin& margin = control->getMargin();
 
         float newWidth = bounds.x + bounds.width;
-        if (newWidth > totalWidth)
+        if (newWidth > _totalWidth)
         {
-            totalWidth = newWidth;
+            _totalWidth = newWidth;
         }
 
         float newHeight = bounds.y + bounds.height;
-        if (newHeight > totalHeight)
+        if (newHeight > _totalHeight)
         {
-            totalHeight = newHeight;
+            _totalHeight = newHeight;
         }
     }
 
@@ -690,15 +624,15 @@ void Container::updateScroll()
     }
 
     // Stop scrolling when the far edge is reached.
-    if (-_scrollPosition.x > totalWidth - clipWidth)
+    if (-_scrollPosition.x > _totalWidth - clipWidth)
     {
-        _scrollPosition.x = -(totalWidth - clipWidth);
+        _scrollPosition.x = -(_totalWidth - clipWidth);
         _scrollingVelocity.x = 0;
     }
     
-    if (-_scrollPosition.y > totalHeight - clipHeight)
+    if (-_scrollPosition.y > _totalHeight - clipHeight)
     {
-        _scrollPosition.y = -(totalHeight - clipHeight);
+        _scrollPosition.y = -(_totalHeight - clipHeight);
         _scrollingVelocity.y = 0;
     }
 
@@ -715,15 +649,15 @@ void Container::updateScroll()
     }
 
     float scrollWidth = 0;
-    if (clipWidth < totalWidth)
-        scrollWidth = (clipWidth / totalWidth) * clipWidth;
+    if (clipWidth < _totalWidth)
+        scrollWidth = (clipWidth / _totalWidth) * clipWidth;
 
     float scrollHeight = 0;
-    if (clipHeight < totalHeight)
-        scrollHeight = (clipHeight / totalHeight) * clipHeight;
+    if (clipHeight < _totalHeight)
+        scrollHeight = (clipHeight / _totalHeight) * clipHeight;
 
-    _scrollBarBounds.set(((-_scrollPosition.x) / totalWidth) * clipWidth,
-                         ((-_scrollPosition.y) / totalHeight) * clipHeight,
+    _scrollBarBounds.set(((-_scrollPosition.x) / _totalWidth) * clipWidth,
+                         ((-_scrollPosition.y) / _totalHeight) * clipHeight,
                          scrollWidth, scrollHeight);
 
     // If scroll velocity is 0 and scrollbars are not always visible, trigger fade-out animation.
@@ -731,8 +665,11 @@ void Container::updateScroll()
     {
         float to = 0;
         _scrollBarOpacity = 0.99f;
-        Animation* animation = createAnimationFromTo("scrollbar-fade-out", ANIMATE_OPACITY, &_scrollBarOpacity, &to, Curve::QUADRATIC_IN_OUT, 500L);
-        _scrollBarOpacityClip = animation->getClip();
+        if (!_scrollBarOpacityClip)
+        {
+            Animation* animation = createAnimationFromTo("scrollbar-fade-out", ANIMATE_OPACITY, &_scrollBarOpacity, &to, Curve::QUADRATIC_IN_OUT, 500L);
+            _scrollBarOpacityClip = animation->getClip();
+        }
         _scrollBarOpacityClip->play();
     }
 
@@ -757,17 +694,39 @@ bool Container::touchEventScroll(Touch::TouchEvent evt, int x, int y, unsigned i
             _scrollBarOpacityClip = NULL;
         }
         _scrollBarOpacity = 1.0f;
-        return true;
+        return _consumeInputEvents;
 
     case Touch::TOUCH_MOVE:
         if (_scrolling)
         {
+            long gameTime = Game::getAbsoluteTime();
+
             // Calculate the latest movement delta for the next update to use.
             int vx = x - _scrollingLastX;
             int vy = y - _scrollingLastY;
-            _scrollingVelocity.set(vx, vy);
-            _scrollPosition.x += vx;
-            _scrollPosition.y += vy;
+            if (_scrollingMouseVertically)
+            {
+                float yRatio = _totalHeight / _absoluteBounds.height;
+                vy *= yRatio;
+
+                _scrollingVelocity.set(0, -vy);
+                _scrollPosition.y -= vy;
+            }
+            else if (_scrollingMouseHorizontally)
+            {
+                float xRatio = _totalWidth / _absoluteBounds.width;
+                vx *= xRatio;
+
+                _scrollingVelocity.set(-vx, 0);
+                _scrollPosition.x -= vx;
+            }
+            else
+            {
+                _scrollingVelocity.set(vx, vy);
+                _scrollPosition.x += vx;
+                _scrollPosition.y += vy;
+            }
+
             _scrollingLastX = x;
             _scrollingLastY = y;
 
@@ -777,7 +736,7 @@ bool Container::touchEventScroll(Touch::TouchEvent evt, int x, int y, unsigned i
             {
                 _scrollingFirstX = x;
                 _scrollingRight = goingRight;
-                _scrollingStartTimeX = Game::getAbsoluteTime();
+                _scrollingStartTimeX = gameTime;
             }
 
             bool goingDown = (vy > 0);
@@ -785,36 +744,38 @@ bool Container::touchEventScroll(Touch::TouchEvent evt, int x, int y, unsigned i
             {
                 _scrollingFirstY = y;
                 _scrollingDown = goingDown;
-                _scrollingStartTimeY = Game::getAbsoluteTime();
+                _scrollingStartTimeY = gameTime;
             }
 
             if (!_scrollingStartTimeX)
-                _scrollingStartTimeX = Game::getAbsoluteTime();
+                _scrollingStartTimeX = gameTime;
 
             if (!_scrollingStartTimeY)
-                _scrollingStartTimeY = Game::getAbsoluteTime();
+                _scrollingStartTimeY = gameTime;
 
-            _scrollingLastTime = Game::getAbsoluteTime();
+            _scrollingLastTime = gameTime;
 
-            return true;
+            return _consumeInputEvents;
         }
         break;
 
     case Touch::TOUCH_RELEASE:
         _scrolling = false;
-        long timeSinceLastMove = Game::getAbsoluteTime() - _scrollingLastTime;
+        long gameTime = Game::getAbsoluteTime();
+        long timeSinceLastMove = gameTime - _scrollingLastTime;
         if (timeSinceLastMove > SCROLL_INERTIA_DELAY)
         {
             _scrollingVelocity.set(0, 0);
-            return true;
+            _scrollingMouseVertically = _scrollingMouseHorizontally = false;
+            return _consumeInputEvents;
         }
 
         int dx = _scrollingLastX - _scrollingFirstX;
         int dy = _scrollingLastY - _scrollingFirstY;
 
-        long timeTakenX = Game::getAbsoluteTime() - _scrollingStartTimeX;
+        long timeTakenX = gameTime - _scrollingStartTimeX;
         float elapsedSecsX = (float)timeTakenX * 0.001f;
-        long timeTakenY = Game::getAbsoluteTime() - _scrollingStartTimeY;
+        long timeTakenY = gameTime - _scrollingStartTimeY;
         float elapsedSecsY = (float)timeTakenY * 0.001f;
 
         float vx = dx;
@@ -824,14 +785,184 @@ bool Container::touchEventScroll(Touch::TouchEvent evt, int x, int y, unsigned i
         if (elapsedSecsY > 0)
             vy = (float)dy / elapsedSecsY;
 
-        _scrollingVelocity.set(vx, vy);
+        if (_scrollingMouseVertically)
+        {
+            float yRatio = _totalHeight / _absoluteBounds.height;
+            vy *= yRatio;
+            _scrollingVelocity.set(0, -vy);
+        }
+        else if (_scrollingMouseHorizontally)
+        {
+            float xRatio = _totalWidth / _absoluteBounds.width;
+            vx *= xRatio;
+            _scrollingVelocity.set(-vx, 0);
+        }
+        else
+        {
+            _scrollingVelocity.set(vx, vy);
+        }
 
-        return true;
+        _scrollingMouseVertically = _scrollingMouseHorizontally = false;
+        return _consumeInputEvents;
     }
 
     return false;
 }
 
+bool Container::mouseEventScroll(Mouse::MouseEvent evt, int x, int y, int wheelDelta)
+{
+    switch(evt)
+    {
+        case Mouse::MOUSE_PRESS_LEFT_BUTTON:
+        {
+            if (_scrollBarVertical)
+            {
+                float vWidth = _scrollBarVertical->getRegion().width;
+                Rectangle vBounds(_viewportBounds.x + _viewportBounds.width - vWidth,
+                                 _scrollBarBounds.y,
+                                 vWidth, _scrollBarBounds.height);
+
+                if (x + _viewportBounds.x >= vBounds.x &&
+                    x + _viewportBounds.x <= vBounds.x + vBounds.width)
+                {
+                    // Then we're within the horizontal bounds of the verticle scrollbar.
+                    // We want to either jump up or down, or drag the scrollbar itself.
+                    if (y < vBounds.y)
+                    {
+                        _scrollPosition.y += _totalHeight / 5.0f;
+                    }
+                    else if (y > vBounds.y + vBounds.height)
+                    {
+                        _scrollPosition.y -= _totalHeight / 5.0f;
+                    }
+                    else
+                    {
+                        _scrollingMouseVertically = true;
+                    }
+                }
+            }
+            
+            if (_scrollBarHorizontal)
+            {
+                float hHeight = _scrollBarHorizontal->getRegion().height;
+                Rectangle hBounds(_scrollBarBounds.x,
+                                  _viewportBounds.y + _viewportBounds.height - hHeight,
+                                  _scrollBarBounds.width, hHeight);
+            
+                if (y + _viewportBounds.y >= hBounds.y &&
+                         y + _viewportBounds.y <= hBounds.y + hBounds.height)
+                {
+                    // We're within the vertical bounds of the horizontal scrollbar.
+                    if (x < hBounds.x)
+                        _scrollPosition.x += _totalWidth / 5.0f;
+                    else if (x > hBounds.x + hBounds.width)
+                        _scrollPosition.x -= _totalWidth / 5.0f;
+                    else
+                        _scrollingMouseHorizontally = true;
+                }
+            }
+
+            return touchEventScroll(Touch::TOUCH_PRESS, x, y, 0);
+        }
+
+        case Mouse::MOUSE_MOVE:
+            return touchEventScroll(Touch::TOUCH_MOVE, x, y, 0);
+
+        case Mouse::MOUSE_RELEASE_LEFT_BUTTON:
+            return touchEventScroll(Touch::TOUCH_RELEASE, x, y, 0);
+
+        case Mouse::MOUSE_WHEEL:
+            _scrollPosition.y += (_totalHeight / 10.0f) * wheelDelta;
+            return _consumeInputEvents;
+    }
+
+    return false;
+}
+
+bool Container::pointerEvent(bool mouse, char evt, int x, int y, int data)
+{
+    if (!isEnabled())
+    {
+        return false;
+    }
+
+    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())
+        {
+            continue;
+        }
+
+        const Rectangle& bounds = control->getBounds();
+        float boundsX = bounds.x;
+        float boundsY = bounds.y;
+        if (offset)
+        {
+            boundsX += offset->x;
+            boundsY += offset->y;
+        }
+
+        if (control->getState() != 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_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())
+    {
+        return (_consumeInputEvents | eventConsumed);
+    }
+
+    switch (evt)
+    {
+    case Touch::TOUCH_PRESS:
+        setState(Control::FOCUS);
+        break;
+    case Touch::TOUCH_RELEASE:
+        setState(Control::NORMAL);
+        break;
+    }
+
+    if (!eventConsumed && _scroll != SCROLL_NONE)
+    {
+        if (mouse && mouseEventScroll((Mouse::MouseEvent)evt, x - xPos, y - yPos, data) ||
+            (!mouse && touchEventScroll((Touch::TouchEvent)evt, x - xPos, y - yPos, (unsigned int)data)))
+        {
+            _dirty = true;
+        }
+    }
+
+    return (_consumeInputEvents | eventConsumed);
+}
+
 Container::Scroll Container::getScroll(const char* scroll)
 {
     if (!scroll)

+ 37 - 2
gameplay/src/Container.h

@@ -45,6 +45,8 @@ namespace gameplay
  */
 class Container : public Control
 {
+    friend class DropDownList;
+
 public:
 
     /**
@@ -241,11 +243,27 @@ protected:
      * @param evt The key event that occured.
      * @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 void keyEvent(Keyboard::KeyEvent evt, int 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);
 
     /**
      * Gets a Layout::Type enum from a matching string.
@@ -307,6 +325,10 @@ protected:
      */
     bool touchEventScroll(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
 
+    bool mouseEventScroll(Mouse::MouseEvent evt, int x, int y, int wheelDelta);
+
+    bool pointerEvent(bool mouse, char evt, int x, int y, int data);
+
     /**
      * Get a Scroll enum from a matching string.
      *
@@ -329,7 +351,7 @@ protected:
      */
     Theme::ThemeImage* _scrollBarTopCap;
     /**
-     * Scrollbar verticle image.
+     * Scrollbar vertical image.
      */
     Theme::ThemeImage* _scrollBarVertical;
     /**
@@ -417,6 +439,16 @@ protected:
      */ 
     bool _scrollingDown;
 
+    /**
+     * Locked to scrolling vertically by grabbing the scrollbar with the mouse.
+     */
+    bool _scrollingMouseVertically;
+
+    /**
+     * Locked to scrolling horizontally by grabbing the scrollbar with the mouse.
+     */
+    bool _scrollingMouseHorizontally;
+
 private:
 
     /**
@@ -426,6 +458,9 @@ private:
 
     AnimationClip* _scrollBarOpacityClip;
     int _zIndexDefault;
+
+    float _totalWidth;
+    float _totalHeight;
 };
 
 

+ 36 - 9
gameplay/src/Control.cpp

@@ -7,7 +7,7 @@ namespace gameplay
 
 Control::Control()
     : _id(""), _state(Control::NORMAL), _bounds(Rectangle::empty()), _clipBounds(Rectangle::empty()), _viewportClipBounds(Rectangle::empty()),
-    _dirty(true), _consumeTouchEvents(true), _listeners(NULL), _styleOverridden(false), _skin(NULL), _clearBounds(Rectangle::empty())
+    _dirty(true), _consumeInputEvents(true), _listeners(NULL), _styleOverridden(false), _skin(NULL), _clearBounds(Rectangle::empty())
 {
 }
 
@@ -607,14 +607,14 @@ Theme::Style::OverlayType Control::getOverlayType() const
     }
 }
 
-void Control::setConsumeTouchEvents(bool consume)
+void Control::setConsumeInputEvents(bool consume)
 {
-    _consumeTouchEvents = consume;
+    _consumeInputEvents = consume;
 }
     
-bool Control::getConsumeTouchEvents()
+bool Control::getConsumeInputEvents()
 {
-    return _consumeTouchEvents;
+    return _consumeInputEvents;
 }
 
 int Control::getZIndex() const
@@ -692,7 +692,7 @@ bool Control::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int conta
     {
     case Touch::TOUCH_PRESS:
         notifyListeners(Listener::PRESS);
-        break;
+        return _consumeInputEvents;
             
     case Touch::TOUCH_RELEASE:
         // Always trigger Listener::RELEASE
@@ -704,14 +704,41 @@ bool Control::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int conta
         {
             notifyListeners(Listener::CLICK);
         }
-        break;
+        return _consumeInputEvents;
     }
 
-    return _consumeTouchEvents;
+    return false;
 }
 
-void Control::keyEvent(Keyboard::KeyEvent evt, int key)
+bool Control::keyEvent(Keyboard::KeyEvent evt, int key)
 {
+    return false;
+}
+
+bool Control::mouseEvent(Mouse::MouseEvent evt, int x, int y, int wheelDelta)
+{
+    if (!isEnabled())
+    {
+        return false;
+    }
+
+    // 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:
+        return touchEvent(Touch::TOUCH_MOVE, x, y, 0);
+
+    default:
+        break;
+    }
+
+    return false;
 }
 
 void Control::notifyListeners(Listener::EventType eventType)

+ 35 - 8
gameplay/src/Control.h

@@ -9,6 +9,7 @@
 #include "ThemeStyle.h"
 #include "Touch.h"
 #include "Keyboard.h"
+#include "Mouse.h"
 
 namespace gameplay
 {
@@ -124,7 +125,17 @@ public:
             /**
              * Event triggered when the contents of a text box are modified.
              */
-            TEXT_CHANGED    = 0x10
+            TEXT_CHANGED    = 0x10,
+
+            /**
+             * Event triggered when a control is clicked with the middle mouse button.
+             */
+            MIDDLE_CLICK    = 0x20,
+
+            /**
+             * Event triggered when a control is clicked with the right mouse button.
+             */
+            RIGHT_CLICK     = 0x40,
         };
 
         /**
@@ -630,19 +641,19 @@ public:
     bool isEnabled();
 
     /**
-     * Set whether this control consumes touch events,
+     * Set whether this control consumes input events,
      * preventing them from being passed to the game.
      *
-     * @param consume Whether this control consumes touch events.
+     * @param consume Whether this control consumes input events.
      */
-    void setConsumeTouchEvents(bool consume);
+    void setConsumeInputEvents(bool consume);
 
     /**
      * Get whether this control consumes touch events.
      *
      * @return Whether this control consumes touch events.
      */
-    bool getConsumeTouchEvents();
+    bool getConsumeInputEvents();
 
     /**
      * Set the style this control will use when rendering.
@@ -738,11 +749,27 @@ protected:
      * @param evt The key event that occured.
      * @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 void keyEvent(Keyboard::KeyEvent evt, int 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);
 
     /**
      * Called when a control's properties change.  Updates this control's internal rendering
@@ -878,9 +905,9 @@ protected:
     bool _dirty;
     
     /**
-     * Flag for whether the Control consume's touch events.
+     * Flag for whether the Control consumes input events.
      */
-    bool _consumeTouchEvents;
+    bool _consumeInputEvents;
     
     /**
      * The Control's Alignmnet

+ 1 - 0
gameplay/src/FlowLayout.cpp

@@ -18,6 +18,7 @@ FlowLayout::FlowLayout(const FlowLayout& copy)
 
 FlowLayout::~FlowLayout()
 {
+    __instance = NULL;
 }
 
 FlowLayout* FlowLayout::create()

+ 120 - 60
gameplay/src/Form.cpp

@@ -543,63 +543,20 @@ bool Form::touchEventInternal(Touch::TouchEvent evt, int x, int y, unsigned int
 
         if (form->isEnabled())
         {
-            Node* node = form->_node;
-            if (node)
+            if (form->_node)
             {
-                Scene* scene = node->getScene();
-                GP_ASSERT(scene);
-                Camera* camera = scene->getActiveCamera();
-
-                if (camera)
+                Vector3 point;
+                if (form->projectPoint(x, y, &point))
                 {
-                    // Get info about the form's position.
-                    Matrix m = node->getMatrix();
-                    Vector3 min(0, 0, 0);
-                    m.transformPoint(&min);
-
-                    // Unproject point into world space.
-                    Ray ray;
-                    camera->pickRay(Game::getInstance()->getViewport(), x, y, &ray);
-
-                    // Find the quad's plane.
-                    // We know its normal is the quad's forward vector.
-                    Vector3 normal = node->getForwardVectorWorld();
-
-                    // To get the plane's distance from the origin,
-                    // we'll find the distance from the plane defined
-                    // by the quad's forward vector and one of its points
-                    // to the plane defined by the same vector and the origin.
-                    const float& a = normal.x; const float& b = normal.y; const float& c = normal.z;
-                    const float d = -(a*min.x) - (b*min.y) - (c*min.z);
-                    const float distance = abs(d) /  sqrt(a*a + b*b + c*c);
-                    Plane plane(normal, -distance);
-
-                    // Check for collision with plane.
-                    float collisionDistance = ray.intersects(plane);
-                    if (collisionDistance != Ray::INTERSECTS_NONE)
+                    const Rectangle& bounds = form->getBounds();
+                    if (form->getState() == Control::FOCUS ||
+                        (evt == Touch::TOUCH_PRESS &&
+                         point.x >= bounds.x &&
+                         point.x <= bounds.x + bounds.width &&
+                         point.y >= bounds.y &&
+                         point.y <= bounds.y + bounds.height))
                     {
-                        // Multiply the ray's direction vector by collision distance
-                        // and add that to the ray's origin.
-                        Vector3 point = ray.getOrigin() + collisionDistance*ray.getDirection();
-
-                        // Project this point into the plane.
-                        m.invert();
-                        m.transformPoint(&point);
-
-                        // Pass the touch event on.
-                        const Rectangle& bounds = form->getBounds();
-                        if (form->getState() == Control::FOCUS ||
-                            (evt == Touch::TOUCH_PRESS &&
-                                point.x >= bounds.x &&
-                                point.x <= bounds.x + bounds.width &&
-                                point.y >= bounds.y &&
-                                point.y <= bounds.y + bounds.height))
-                        {
-                            if (form->touchEvent(evt, point.x - bounds.x, bounds.height - point.y - bounds.y, contactIndex))
-                            {
-                                return true;
-                            }
-                        }
+                        return form->touchEvent(evt, point.x - bounds.x, bounds.height - point.y - bounds.y, contactIndex);
                     }
                 }
             }
@@ -615,10 +572,7 @@ bool Form::touchEventInternal(Touch::TouchEvent evt, int x, int y, unsigned int
                         y <= bounds.y + bounds.height))
                 {
                     // Pass on the event's position relative to the form.
-                    if (form->touchEvent(evt, x - bounds.x, y - bounds.y, contactIndex))
-                    {
-                        return true;
-                    }
+                    return form->touchEvent(evt, x - bounds.x, y - bounds.y, contactIndex);
                 }
             }
         }
@@ -627,7 +581,7 @@ bool Form::touchEventInternal(Touch::TouchEvent evt, int x, int y, unsigned int
     return false;
 }
 
-void Form::keyEventInternal(Keyboard::KeyEvent evt, int key)
+bool Form::keyEventInternal(Keyboard::KeyEvent evt, int key)
 {
     std::vector<Form*>::const_iterator it;
     for (it = __forms.begin(); it < __forms.end(); it++)
@@ -636,9 +590,115 @@ void Form::keyEventInternal(Keyboard::KeyEvent evt, int key)
         GP_ASSERT(form);
         if (form->isEnabled())
         {
-            form->keyEvent(evt, key);
+            if (form->keyEvent(evt, key))
+                return true;
         }
     }
+
+    return false;
+}
+
+bool Form::mouseEventInternal(Mouse::MouseEvent evt, int x, int y, int wheelDelta)
+{
+    std::vector<Form*>::const_iterator it;
+    for (it = __forms.begin(); it < __forms.end(); it++)
+    {
+        Form* form = *it;
+        GP_ASSERT(form);
+
+        if (form->isEnabled())
+        {
+            if (form->_node)
+            {
+                Vector3 point;
+                if (form->projectPoint(x, y, &point))
+                {
+                    const Rectangle& bounds = form->getBounds();
+                    if (form->getState() == Control::FOCUS ||
+                        ((evt == Mouse::MOUSE_PRESS_LEFT_BUTTON ||
+                          evt == Mouse::MOUSE_PRESS_MIDDLE_BUTTON ||
+                          evt == Mouse::MOUSE_PRESS_RIGHT_BUTTON ||
+                          evt == Mouse::MOUSE_WHEEL) &&
+                         point.x >= bounds.x &&
+                         point.x <= bounds.x + bounds.width &&
+                         point.y >= bounds.y &&
+                         point.y <= bounds.y + bounds.height))
+                    {
+                        return form->mouseEvent(evt, point.x - bounds.x, bounds.height - point.y - bounds.y, wheelDelta);
+                    }
+                }
+            }
+            else
+            {
+                // Simply compare with the form's bounds.
+                const Rectangle& bounds = form->getBounds();
+                if (form->getState() == Control::FOCUS ||
+                    ((evt == Mouse::MOUSE_PRESS_LEFT_BUTTON ||
+                      evt == Mouse::MOUSE_PRESS_MIDDLE_BUTTON ||
+                      evt == Mouse::MOUSE_PRESS_RIGHT_BUTTON ||
+                      evt == Mouse::MOUSE_WHEEL) &&
+                        x >= bounds.x &&
+                        x <= bounds.x + bounds.width &&
+                        y >= bounds.y &&
+                        y <= bounds.y + bounds.height))
+                {
+                    // Pass on the event's position relative to the form.
+                    return form->mouseEvent(evt, x - bounds.x, y - bounds.y, wheelDelta);
+                }
+            }
+        }
+    }
+
+    return false;
+}
+
+bool Form::projectPoint(int x, int y, Vector3* point)
+{
+    Scene* scene = _node->getScene();
+    GP_ASSERT(scene);
+    Camera* camera = scene->getActiveCamera();
+
+    if (camera)
+    {
+        // Get info about the form's position.
+        Matrix m = _node->getMatrix();
+        Vector3 min(0, 0, 0);
+        m.transformPoint(&min);
+
+        // Unproject point into world space.
+        Ray ray;
+        camera->pickRay(Game::getInstance()->getViewport(), x, y, &ray);
+
+        // Find the quad's plane.
+        // We know its normal is the quad's forward vector.
+        Vector3 normal = _node->getForwardVectorWorld();
+
+        // To get the plane's distance from the origin,
+        // we'll find the distance from the plane defined
+        // by the quad's forward vector and one of its points
+        // to the plane defined by the same vector and the origin.
+        const float& a = normal.x; const float& b = normal.y; const float& c = normal.z;
+        const float d = -(a*min.x) - (b*min.y) - (c*min.z);
+        const float distance = abs(d) /  sqrt(a*a + b*b + c*c);
+        Plane plane(normal, -distance);
+
+        // Check for collision with plane.
+        float collisionDistance = ray.intersects(plane);
+        if (collisionDistance != Ray::INTERSECTS_NONE)
+        {
+            // Multiply the ray's direction vector by collision distance
+            // and add that to the ray's origin.
+            point->set(ray.getOrigin() + collisionDistance*ray.getDirection());
+
+            // Project this point into the plane.
+            m.invert();
+            m.transformPoint(point);
+
+            return true;
+        }
+    }
+
+    return false;
 }
 
 int Form::nextHighestPowerOfTwo(int x)

+ 31 - 1
gameplay/src/Form.h

@@ -8,6 +8,7 @@
 #include "FrameBuffer.h"
 #include "Touch.h"
 #include "Keyboard.h"
+#include "Mouse.h"
 
 namespace gameplay
 {
@@ -175,9 +176,38 @@ private:
 
     /**
      * Propagate key events to enabled forms.
+     *
+     * @return Whether the key event was consumed by a form.
+     */
+    static bool keyEventInternal(Keyboard::KeyEvent evt, int key);
+
+    /**
+     * Propagate mouse events to enabled forms.
+     *
+     * @return True if the mouse event is consumed or false if it is not consumed.
+     *
+     * @see Mouse::MouseEvent
      */
-    static void keyEventInternal(Keyboard::KeyEvent evt, int key);
+    static bool mouseEventInternal(Mouse::MouseEvent evt, int x, int y, int wheelDelta);
 
+    /**
+     * Unproject a point (from a mouse or touch event) into the scene and then project it onto the form.
+     *
+     * @param x The x coordinate of the mouse/touch point.
+     * @param y The y coordinate of the mouse/touch point.
+     * @param point A destination vector to populate with the projected point, in the form's plane.
+     *
+     * @return True if the projected point lies within the form's plane, false otherwise.
+     */
+    bool projectPoint(int x, int y, Vector3* point);
+
+    /**
+     * Get the next highest power of two of an integer.  Used when creating framebuffers.
+     *
+     * @param x The number to start with.
+     *
+     * @return The next highest power of two after x, or x if it is already a power of two.
+     */
     static int nextHighestPowerOfTwo(int x);
 
     Theme* _theme;              // The Theme applied to this Form.

+ 6 - 2
gameplay/src/Joystick.cpp

@@ -22,7 +22,7 @@ Joystick* Joystick::create(Theme::Style* style, Properties* properties)
 {
     Joystick* joystick = new Joystick();
     joystick->initialize(style, properties);
-    joystick->_consumeTouchEvents = false;
+    joystick->_consumeInputEvents = false;
 
     return joystick;
 }
@@ -59,7 +59,7 @@ void Joystick::addListener(Control::Listener* listener, int eventFlags)
         GP_ERROR("TEXT_CHANGED event is not applicable to this control.");
     }
 
-    _consumeTouchEvents = true;
+    _consumeInputEvents = true;
 
     Control::addListener(listener, eventFlags);
 }
@@ -94,6 +94,7 @@ bool Joystick::touchEvent(Touch::TouchEvent touchEvent, int x, int y, unsigned i
                 if (_value != value)
                 {
                     _value.set(value);
+                    _dirty = true;
                     notifyListeners(Control::Listener::VALUE_CHANGED);
                 }
 
@@ -114,6 +115,7 @@ bool Joystick::touchEvent(Touch::TouchEvent touchEvent, int x, int y, unsigned i
                     if (_value != value)
                     {
                         _value.set(value);
+                        _dirty = true;
                         notifyListeners(Control::Listener::VALUE_CHANGED);
                     }
                 }
@@ -126,6 +128,7 @@ bool Joystick::touchEvent(Touch::TouchEvent touchEvent, int x, int y, unsigned i
                     if (_value != value)
                     {
                         _value.set(value);
+                        _dirty = true;
                         notifyListeners(Control::Listener::VALUE_CHANGED);
                     }
                 }
@@ -146,6 +149,7 @@ bool Joystick::touchEvent(Touch::TouchEvent touchEvent, int x, int y, unsigned i
                 if (_value != value)
                 {
                     _value.set(value);
+                    _dirty = true;
                     notifyListeners(Control::Listener::VALUE_CHANGED);
                 }
 

+ 2 - 2
gameplay/src/Label.cpp

@@ -20,7 +20,7 @@ Label* Label::create(Theme::Style* style, Properties* properties)
 {
     Label* label = new Label();
     label->initialize(style, properties);
-    label->_consumeTouchEvents = false;
+    label->_consumeInputEvents = false;
 
     return label;
 }
@@ -49,7 +49,7 @@ void Label::addListener(Control::Listener* listener, int eventFlags)
         GP_ERROR("VALUE_CHANGED event is not applicable to this control.");
     }
 
-    _consumeTouchEvents = true;
+    _consumeInputEvents = true;
 
     Control::addListener(listener, eventFlags);
 }

+ 16 - 0
gameplay/src/Platform.h

@@ -3,6 +3,7 @@
 
 #include "Touch.h"
 #include "Keyboard.h"
+#include "Mouse.h"
 
 namespace gameplay
 {
@@ -143,6 +144,21 @@ public:
      */
     static void keyEventInternal(Keyboard::KeyEvent evt, int key);
 
+    /**
+     * Mouse callback on mouse events. If the game does not consume the mouse move event or left mouse click event
+     * then it is interpreted as a touch event instead.
+     *
+     * @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
+     */
+    static bool mouseEventInternal(Mouse::MouseEvent evt, int x, int y, int wheelDelta);
+
     /**
      * Sleeps synchronously for the given amount of time (in milliseconds).
      *

+ 19 - 3
gameplay/src/PlatformAndroid.cpp

@@ -628,11 +628,11 @@ static int32_t engine_handle_input(struct android_app* app, AInputEvent* event)
             case AKEY_EVENT_ACTION_DOWN:
                 Game::getInstance()->keyEvent(Keyboard::KEY_PRESS, getKey(keycode, metastate));
                 if (int character = getUnicode(keycode, metastate))
-                    Game::getInstance()->keyEvent(Keyboard::KEY_CHAR, character);
+                    gameplay::Platform::keyEventInternal(Keyboard::KEY_CHAR, character);
                 break;
                     
             case AKEY_EVENT_ACTION_UP:
-                Game::getInstance()->keyEvent(Keyboard::KEY_RELEASE, getKey(keycode, metastate));
+                gameplay::Platform::keyEventInternal(Keyboard::KEY_RELEASE, getKey(keycode, metastate));
                 break;
         }
     }
@@ -941,8 +941,24 @@ void Platform::displayKeyboard(bool display)
 void Platform::touchEventInternal(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
 {
     if (!Form::touchEventInternal(evt, x, y, contactIndex))
-    {
         Game::getInstance()->touchEvent(evt, x, y, contactIndex);
+}
+
+void Platform::keyEventInternal(Keyboard::KeyEvent evt, int key)
+{
+    if (!Form::keyEventInternal(evt, key))
+        Game::getInstance()->keyEvent(evt, key);
+}
+
+bool Platform::mouseEventInternal(Mouse::MouseEvent evt, int x, int y, int wheelDelta)
+{
+    if (Form::mouseEventInternal(evt, x, y, wheelDelta))
+    {
+        return true;
+    }
+    else
+    {
+        return Game::getInstance()->mouseEvent(evt, x, y, wheelDelta);
     }
 }
 

+ 25 - 16
gameplay/src/PlatformMacOSX.mm

@@ -213,7 +213,7 @@ static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTime
 
 - (void) mouse: (Mouse::MouseEvent) mouseEvent orTouchEvent: (Touch::TouchEvent) touchEvent x: (float) x y: (float) y s: (int) s 
 {
-    if (!Game::getInstance()->mouseEvent(mouseEvent, x, y, s))
+    if (!gameplay::Platform::mouseEventInternal(mouseEvent, x, y, s))
     {
         gameplay::Platform::touchEventInternal(touchEvent, x, y, 0);
     }
@@ -236,7 +236,7 @@ static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTime
 - (void)mouseMoved:(NSEvent *) event 
 {
     NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
-    Game::getInstance()->mouseEvent(Mouse::MOUSE_MOVE, point.x, __height - point.y, 0);
+    gameplay::Platform::mouseEventInternal(Mouse::MOUSE_MOVE, point.x, __height - point.y, 0);
 }
 
 - (void) mouseDragged: (NSEvent*) event
@@ -254,14 +254,14 @@ static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTime
      NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
     __lx = point.x;
     __ly = __height - point.y;    
-    _game->mouseEvent(Mouse::MOUSE_PRESS_RIGHT_BUTTON, point.x, __height - point.y, 0);
+    gameplay::Platform::mouseEventInternal(Mouse::MOUSE_PRESS_RIGHT_BUTTON, point.x, __height - point.y, 0);
 }
 
 - (void) rightMouseUp: (NSEvent*) event
 {
    __rightMouseDown = false;
     NSPoint point = [event locationInWindow];
-    _game->mouseEvent(Mouse::MOUSE_RELEASE_RIGHT_BUTTON, point.x, __height - point.y, 0);
+    gameplay::Platform::mouseEventInternal(Mouse::MOUSE_RELEASE_RIGHT_BUTTON, point.x, __height - point.y, 0);
 }
 
 - (void) rightMouseDragged: (NSEvent*) event
@@ -284,27 +284,27 @@ static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTime
     
     // In right-mouse case, whether __rightMouseDown is true or false
     // this should not matter, mouse move is still occuring
-    _game->mouseEvent(Mouse::MOUSE_MOVE, point.x, __height - point.y, 0);
+    gameplay::Platform::mouseEventInternal(Mouse::MOUSE_MOVE, point.x, __height - point.y, 0);
 }
 
 - (void)otherMouseDown: (NSEvent *) event 
 {
     __otherMouseDown = true;
     NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
-    _game->mouseEvent(Mouse::MOUSE_PRESS_MIDDLE_BUTTON, point.x, __height - point.y, 0);
+    gameplay::Platform::mouseEventInternal(Mouse::MOUSE_PRESS_MIDDLE_BUTTON, point.x, __height - point.y, 0);
 }
 
 - (void)otherMouseUp: (NSEvent *) event 
 {
     __otherMouseDown = false;
     NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
-    _game->mouseEvent(Mouse::MOUSE_RELEASE_MIDDLE_BUTTON, point.x, __height - point.y, 0);
+    gameplay::Platform::mouseEventInternal(Mouse::MOUSE_RELEASE_MIDDLE_BUTTON, point.x, __height - point.y, 0);
 }
 
 - (void)otherMouseDragged: (NSEvent *) event 
 {
     NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
-    _game->mouseEvent(Mouse::MOUSE_MOVE, point.x, __height - point.y, 0);
+    gameplay::Platform::mouseEventInternal(Mouse::MOUSE_MOVE, point.x, __height - point.y, 0);
 }
 
 - (void) mouseEntered: (NSEvent*)event
@@ -315,7 +315,7 @@ static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTime
 - (void)scrollWheel: (NSEvent *) event 
 {
     NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
-    Game::getInstance()->mouseEvent(Mouse::MOUSE_WHEEL, point.x, __height - point.y, (int)([event deltaY] * 10.0f));
+    gameplay::Platform::mouseEventInternal(Mouse::MOUSE_WHEEL, point.x, __height - point.y, (int)([event deltaY] * 10.0f));
 }
 
 - (void) mouseExited: (NSEvent*)event
@@ -770,17 +770,26 @@ void Platform::displayKeyboard(bool display)
 void Platform::touchEventInternal(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
 {
     if (!Form::touchEventInternal(evt, x, y, contactIndex))
-    {
         Game::getInstance()->touchEvent(evt, x, y, contactIndex);
-    }
 }
     
-void Platform::keyEventInternal(Keyboard::KeyEvent evt, int key)
-{
-    Game::getInstance()->keyEvent(evt, key);
-    Form::keyEventInternal(evt, key);
+void Platform::keyEventInternal(Keyboard::KeyEvent evt, int key)
+{
+    if (!Form::keyEventInternal(evt, key))
+        Game::getInstance()->keyEvent(evt, key);
+}
+
+bool Platform::mouseEventInternal(Mouse::MouseEvent evt, int x, int y, int wheelDelta)
+{
+    if (Form::mouseEventInternal(evt, x, y, wheelDelta))
+    {
+        return true;
+    }
+    else
+    {
+        return Game::getInstance()->mouseEvent(evt, x, y, wheelDelta);
+    }
 }
-    
 
 void Platform::sleep(long ms)
 {

+ 21 - 11
gameplay/src/PlatformQNX.cpp

@@ -768,7 +768,7 @@ long timespec2millis(struct timespec *a)
  */
 void mouseOrTouchEvent(Mouse::MouseEvent mouseEvent, Touch::TouchEvent touchEvent, int x, int y)
 {
-    if (!Game::getInstance()->mouseEvent(mouseEvent, x, y, 0))
+    if (!gameplay::Platform::mouseEventInternal(mouseEvent, x, y, 0))
     {
         Platform::touchEventInternal(touchEvent, x, y, 0);
     }
@@ -887,14 +887,14 @@ int Platform::enterMessagePump()
                             {
                                 move = false;
                                 mouse_pressed |= SCREEN_RIGHT_MOUSE_BUTTON;
-                                Game::getInstance()->mouseEvent(Mouse::MOUSE_PRESS_RIGHT_BUTTON, position[0], position[1], 0);
+                                gameplay::Platform::mouseEventInternal(Mouse::MOUSE_PRESS_RIGHT_BUTTON, position[0], position[1], 0);
                             }
                         }
                         else if (mouse_pressed & SCREEN_RIGHT_MOUSE_BUTTON)
                         {
                             move = false;
                             mouse_pressed &= ~SCREEN_RIGHT_MOUSE_BUTTON;
-                            Game::getInstance()->mouseEvent(Mouse::MOUSE_RELEASE_RIGHT_BUTTON, position[0], position[1], 0);
+                            gameplay::Platform::mouseEventInternal(Mouse::MOUSE_RELEASE_RIGHT_BUTTON, position[0], position[1], 0);
                         }
 
                         // Handle middle mouse.
@@ -904,14 +904,14 @@ int Platform::enterMessagePump()
                             {
                                 move = false;
                                 mouse_pressed |= SCREEN_MIDDLE_MOUSE_BUTTON;
-                                Game::getInstance()->mouseEvent(Mouse::MOUSE_PRESS_MIDDLE_BUTTON, position[0], position[1], 0);
+                                gameplay::Platform::mouseEventInternal(Mouse::MOUSE_PRESS_MIDDLE_BUTTON, position[0], position[1], 0);
                             }
                         }
                         else if (mouse_pressed & SCREEN_MIDDLE_MOUSE_BUTTON)
                         {
                             move = false;
                             mouse_pressed &= ~SCREEN_MIDDLE_MOUSE_BUTTON;
-                            Game::getInstance()->mouseEvent(Mouse::MOUSE_RELEASE_MIDDLE_BUTTON, position[0], position[1], 0);
+                            gameplay::Platform::mouseEventInternal(Mouse::MOUSE_RELEASE_MIDDLE_BUTTON, position[0], position[1], 0);
                         }
 
                         // Fire a move event if none of the buttons changed.
@@ -921,13 +921,13 @@ int Platform::enterMessagePump()
                         }
                         else if (move)
                         {
-                            Game::getInstance()->mouseEvent(Mouse::MOUSE_MOVE, position[0], position[1], 0);
+                            gameplay::Platform::mouseEventInternal(Mouse::MOUSE_MOVE, position[0], position[1], 0);
                         }
 
                         // Handle mouse wheel events.
                         if (wheel)
                         {
-                            Game::getInstance()->mouseEvent(Mouse::MOUSE_WHEEL, position[0], position[1], -wheel);
+                            gameplay::Platform::mouseEventInternal(Mouse::MOUSE_WHEEL, position[0], position[1], -wheel);
                         }
                         break;
                     }
@@ -1137,15 +1137,25 @@ void Platform::displayKeyboard(bool display)
 void Platform::touchEventInternal(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
 {
     if (!Form::touchEventInternal(evt, x, y, contactIndex))
-    {
         Game::getInstance()->touchEvent(evt, x, y, contactIndex);
-    }
 }
 
 void Platform::keyEventInternal(Keyboard::KeyEvent evt, int key)
 {
-    Game::getInstance()->keyEvent(evt, key);
-    Form::keyEventInternal(evt, key);
+    if (!Form::keyEventInternal(evt, key))
+        Game::getInstance()->keyEvent(evt, key);
+}
+
+bool Platform::mouseEventInternal(Mouse::MouseEvent evt, int x, int y, int wheelDelta)
+{
+    if (Form::mouseEventInternal(evt, x, y, wheelDelta))
+    {
+        return true;
+    }
+    else
+    {
+        return Game::getInstance()->mouseEvent(evt, x, y, wheelDelta);
+    }
 }
 
 void Platform::sleep(long ms)

+ 26 - 12
gameplay/src/PlatformWin32.cpp

@@ -295,7 +295,7 @@ LRESULT CALLBACK __WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
         int y = GET_Y_LPARAM(lParam);
 
         UpdateCapture(wParam);
-        if (!gameplay::Game::getInstance()->mouseEvent(gameplay::Mouse::MOUSE_PRESS_LEFT_BUTTON, x, y, 0))
+        if (!gameplay::Platform::mouseEventInternal(gameplay::Mouse::MOUSE_PRESS_LEFT_BUTTON, x, y, 0))
         {
             gameplay::Platform::touchEventInternal(gameplay::Touch::TOUCH_PRESS, x, y, 0);
         }
@@ -306,7 +306,7 @@ LRESULT CALLBACK __WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
         int x = GET_X_LPARAM(lParam);
         int y = GET_Y_LPARAM(lParam);
 
-        if (!gameplay::Game::getInstance()->mouseEvent(gameplay::Mouse::MOUSE_RELEASE_LEFT_BUTTON, x, y, 0))
+        if (!gameplay::Platform::mouseEventInternal(gameplay::Mouse::MOUSE_RELEASE_LEFT_BUTTON, x, y, 0))
         {
             gameplay::Platform::touchEventInternal(gameplay::Touch::TOUCH_RELEASE, x, y, 0);
         }
@@ -317,21 +317,21 @@ LRESULT CALLBACK __WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
         UpdateCapture(wParam);
         lx = GET_X_LPARAM(lParam);
         ly = GET_Y_LPARAM(lParam);
-        gameplay::Game::getInstance()->mouseEvent(gameplay::Mouse::MOUSE_PRESS_RIGHT_BUTTON, lx, ly, 0);
+        gameplay::Platform::mouseEventInternal(gameplay::Mouse::MOUSE_RELEASE_RIGHT_BUTTON, lx, ly, 0);
         break;
 
     case WM_RBUTTONUP:
-        gameplay::Game::getInstance()->mouseEvent(gameplay::Mouse::MOUSE_RELEASE_RIGHT_BUTTON,  GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), 0);
+        gameplay::Platform::mouseEventInternal(gameplay::Mouse::MOUSE_RELEASE_RIGHT_BUTTON, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), 0);
         UpdateCapture(wParam);
         break;
 
     case WM_MBUTTONDOWN:
         UpdateCapture(wParam);
-        gameplay::Game::getInstance()->mouseEvent(gameplay::Mouse::MOUSE_PRESS_MIDDLE_BUTTON,  GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), 0);
+        gameplay::Platform::mouseEventInternal(gameplay::Mouse::MOUSE_PRESS_MIDDLE_BUTTON, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), 0);
         break;
 
     case WM_MBUTTONUP:
-        gameplay::Game::getInstance()->mouseEvent(gameplay::Mouse::MOUSE_RELEASE_MIDDLE_BUTTON,  GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), 0);
+        gameplay::Platform::mouseEventInternal(gameplay::Mouse::MOUSE_RELEASE_MIDDLE_BUTTON, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), 0);
         UpdateCapture(wParam);
         break;
 
@@ -341,7 +341,7 @@ LRESULT CALLBACK __WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
         int y = GET_Y_LPARAM(lParam);
 
         // Allow Game::mouseEvent a chance to handle (and possibly consume) the event.
-        if (!gameplay::Game::getInstance()->mouseEvent(gameplay::Mouse::MOUSE_MOVE, x, y, 0))
+        if (!gameplay::Platform::mouseEventInternal(gameplay::Mouse::MOUSE_MOVE, x, y, 0))
         {
             if ((wParam & MK_LBUTTON) == MK_LBUTTON)
             {
@@ -368,7 +368,11 @@ LRESULT CALLBACK __WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
     }
 
     case WM_MOUSEWHEEL:
-        gameplay::Game::getInstance()->mouseEvent(gameplay::Mouse::MOUSE_WHEEL, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), GET_WHEEL_DELTA_WPARAM(wParam) / 120);
+        tagPOINT point;
+        point.x = GET_X_LPARAM(lParam);
+        point.y = GET_Y_LPARAM(lParam);
+        ScreenToClient(__hwnd, &point);
+        gameplay::Platform::mouseEventInternal(gameplay::Mouse::MOUSE_WHEEL, point.x, point.y, GET_WHEEL_DELTA_WPARAM(wParam) / 120);
         break;
 
     case WM_KEYDOWN:
@@ -745,15 +749,25 @@ void Platform::displayKeyboard(bool display)
 void Platform::touchEventInternal(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
 {
     if (!Form::touchEventInternal(evt, x, y, contactIndex))
-    {
         Game::getInstance()->touchEvent(evt, x, y, contactIndex);
-    }
 }
 
 void Platform::keyEventInternal(Keyboard::KeyEvent evt, int key)
 {
-    Game::getInstance()->keyEvent(evt, key);
-    Form::keyEventInternal(evt, key);
+    if (!Form::keyEventInternal(evt, key))
+        Game::getInstance()->keyEvent(evt, key);
+}
+
+bool Platform::mouseEventInternal(Mouse::MouseEvent evt, int x, int y, int wheelDelta)
+{
+    if (Form::mouseEventInternal(evt, x, y, wheelDelta))
+    {
+        return true;
+    }
+    else
+    {
+        return Game::getInstance()->mouseEvent(evt, x, y, wheelDelta);
+    }
 }
 
 void Platform::sleep(long ms)

+ 17 - 8
gameplay/src/PlatformiOS.mm

@@ -928,17 +928,26 @@ void Platform::displayKeyboard(bool display)
 void Platform::touchEventInternal(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
 {
     if (!Form::touchEventInternal(evt, x, y, contactIndex))
-    {
         Game::getInstance()->touchEvent(evt, x, y, contactIndex);
-    }
-}
-
-void Platform::keyEventInternal(Keyboard::KeyEvent evt, int key)
-{
-    Game::getInstance()->keyEvent(evt, key);
-    Form::keyEventInternal(evt, key);
 }
     
+void Platform::keyEventInternal(Keyboard::KeyEvent evt, int key)
+{
+    if (!Form::keyEventInternal(evt, key))
+        Game::getInstance()->keyEvent(evt, key);
+}
+
+bool Platform::mouseEventInternal(Mouse::MouseEvent evt, int x, int y, int wheelDelta)
+{
+    if (Form::mouseEventInternal(evt, x, y, wheelDelta))
+    {
+        return true;
+    }
+    else
+    {
+        return Game::getInstance()->mouseEvent(evt, x, y, wheelDelta);
+    }
+}    
 
 void Platform::sleep(long ms)
 {

+ 1 - 0
gameplay/src/Slider.cpp

@@ -134,6 +134,7 @@ bool Slider::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contac
         if (evt == Touch::TOUCH_RELEASE)
         {
             _state = NORMAL;
+            _dirty = true;
         }
         break;
     }

+ 5 - 8
gameplay/src/SpriteBatch.cpp

@@ -159,8 +159,9 @@ SpriteBatch* SpriteBatch::create(Texture* texture, Effect* effect, unsigned int
     batch->_textureHeightRatio = 1.0f / (float)texture->getHeight();
 
     // Bind an ortho projection to the material by default (user can override with setProjectionMatrix)
-    material->getParameter("u_projectionMatrix")->bindValue(batch, &SpriteBatch::getOrthoMatrix);
-
+    Game* game = Game::getInstance();
+    Matrix::createOrthographicOffCenter(0, game->getWidth(), game->getHeight(), 0, 0, 1, &batch->_projectionMatrix);
+    material->getParameter("u_projectionMatrix")->bindValue(batch, &SpriteBatch::getProjectionMatrix);
     return batch;
 }
 
@@ -431,15 +432,11 @@ Material* SpriteBatch::getMaterial() const
 
 void SpriteBatch::setProjectionMatrix(const Matrix& matrix)
 {
-    // Bind the specified matrix to a parameter named 'u_projectionMatrix' (assumed to exist).
-    _batch->getMaterial()->getParameter("u_projectionMatrix")->setValue(matrix);
+    _projectionMatrix = matrix;
 }
 
-const Matrix& SpriteBatch::getOrthoMatrix() const
+const Matrix& SpriteBatch::getProjectionMatrix() const
 {
-    // Update matrix with ortho projection and return it.
-    Game* game = Game::getInstance();
-    Matrix::createOrthographicOffCenter(0, game->getWidth(), game->getHeight(), 0, 0, 1, &_projectionMatrix);
     return _projectionMatrix;
 }
 

+ 2 - 2
gameplay/src/SpriteBatch.h

@@ -273,6 +273,8 @@ public:
      */
     void setProjectionMatrix(const Matrix& matrix);
 
+    const Matrix& getProjectionMatrix() const;
+
 private:
 
     /**
@@ -303,8 +305,6 @@ private:
      */
     SpriteBatch(const SpriteBatch& copy);
 
-    const Matrix& getOrthoMatrix() const;
-
     /**
      * Adds a single sprite to a SpriteVertex array.
      * 

+ 19 - 8
gameplay/src/TextBox.cpp

@@ -54,21 +54,21 @@ bool TextBox::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int conta
             _state = ACTIVE;
             Game::getInstance()->displayKeyboard(true);
             _dirty = true;
-            return _consumeTouchEvents;
+            return _consumeInputEvents;
         }
         else if (x > _clipBounds.x && x <= _clipBounds.x + _clipBounds.width &&
                  y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height)
         {
             setCaretLocation(x, y);
             _dirty = true;
-            return _consumeTouchEvents;
+            return _consumeInputEvents;
         }
         else
         {
             _state = NORMAL;
             Game::getInstance()->displayKeyboard(false);
             _dirty = true;
-            return _consumeTouchEvents;
+            return _consumeInputEvents;
         }
         break;
     case Touch::TOUCH_MOVE:
@@ -78,7 +78,7 @@ bool TextBox::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int conta
         {
             setCaretLocation(x, y);
             _dirty = true;
-            return _consumeTouchEvents;
+            return _consumeInputEvents;
         }
         break;
     case Touch::TOUCH_RELEASE:
@@ -88,23 +88,25 @@ bool TextBox::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int conta
             setCaretLocation(x, y);
             _state = FOCUS;
             _dirty = true;
-            return _consumeTouchEvents;
+            return _consumeInputEvents;
         }
         else
         {
             _state = NORMAL;
             Game::getInstance()->displayKeyboard(false);
             _dirty = true;
-            return _consumeTouchEvents;
+            return _consumeInputEvents;
         }
         break;
     }
 
-    return _consumeTouchEvents;
+    return _consumeInputEvents;
 }
 
-void TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
+bool TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
 {
+    bool consume = false;
+
     if (_state == FOCUS)
     {
         switch (evt)
@@ -142,6 +144,7 @@ void TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
                         font->getLocationAtIndex(_text.c_str(), _textBounds, fontSize, &_caretLocation, textIndex,
                             textAlignment, true, rightToLeft);
                         _dirty = true;
+                        consume = true;
                         notifyListeners(Listener::TEXT_CHANGED);
                         break;
                     }
@@ -158,6 +161,7 @@ void TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
                         font->getLocationAtIndex(_text.c_str(), _textBounds, fontSize, &_caretLocation, textIndex - 1,
                             textAlignment, true, rightToLeft);
                         _dirty = true;
+                        consume = true;
                         break;
                     }
                     case Keyboard::KEY_RIGHT_ARROW:
@@ -173,6 +177,7 @@ void TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
                         font->getLocationAtIndex(_text.c_str(), _textBounds, fontSize, &_caretLocation, textIndex + 1,
                             textAlignment, true, rightToLeft);
                         _dirty = true;
+                        consume = true;
                         break;
                     }
                     case Keyboard::KEY_UP_ARROW:
@@ -187,6 +192,7 @@ void TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
                         font->getIndexAtLocation(_text.c_str(), _textBounds, fontSize, _caretLocation, &_caretLocation,
                             textAlignment, true, rightToLeft);
                         _dirty = true;
+                        consume = true;
                         break;
                     }
                     case Keyboard::KEY_DOWN_ARROW:
@@ -201,6 +207,7 @@ void TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
                         font->getIndexAtLocation(_text.c_str(), _textBounds, fontSize, _caretLocation, &_caretLocation,
                             textAlignment, true, rightToLeft);
                         _dirty = true;
+                        consume = true;
                         break;
                     }
                 }
@@ -230,6 +237,7 @@ void TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
                                 textAlignment, true, rightToLeft);
 
                             _dirty = true;
+                            consume = true;
                         }
                         break;
                     }
@@ -240,6 +248,7 @@ void TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
                     {
                         // Insert character into string.
                         _text.insert(textIndex, 1, (char)key);
+                        consume = true;
 
                         // Get new location of caret.
                         font->getLocationAtIndex(_text.c_str(), _textBounds, fontSize, &_caretLocation, textIndex + 1,
@@ -289,6 +298,8 @@ void TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
     }
 
     _lastKeypress = key;
+
+    return (consume & _consumeInputEvents);
 }
 
 void TextBox::update(const Control* container, const Vector2& offset)

+ 1 - 1
gameplay/src/TextBox.h

@@ -104,7 +104,7 @@ protected:
      * @see Keyboard::KeyEvent
      * @see Keyboard::Key
      */
-    void keyEvent(Keyboard::KeyEvent evt, int key);
+    bool keyEvent(Keyboard::KeyEvent evt, int key);
 
     /**
      * Called when a control's properties change.  Updates this control's internal rendering

+ 19 - 0
gameplay/src/Theme.cpp

@@ -451,6 +451,25 @@ Theme::Style* Theme::getStyle(const char* name) const
     return NULL;
 }
 
+Theme::Style* Theme::getEmptyStyle()
+{
+    Theme::Style* emptyStyle = getStyle("EMPTY_STYLE");
+
+    if (!emptyStyle)
+    {
+        Theme::Style::Overlay* overlay = Theme::Style::Overlay::create();
+        overlay->addRef();
+        overlay->addRef();
+        overlay->addRef();
+        emptyStyle = new Theme::Style((Theme*)this, "EMPTY_STYLE", 1.0f / _texture->getWidth(), 1.0f / _texture->getHeight(),
+            Theme::Margin::empty(), Theme::Border::empty(), overlay, overlay, overlay, overlay);
+
+        _styles.push_back(emptyStyle);
+    }
+
+    return emptyStyle;
+}
+
 void Theme::setProjectionMatrix(const Matrix& matrix)
 {
     GP_ASSERT(_spriteBatch);

+ 3 - 0
gameplay/src/Theme.h

@@ -419,6 +419,9 @@ private:
 
     Theme::Style* getStyle(const char* id) const;
 
+    // Used when a control does not specify a style.
+    Theme::Style* getEmptyStyle();
+
     void setProjectionMatrix(const Matrix& matrix);
 
     SpriteBatch* getSpriteBatch() const;