Przeglądaj źródła

Merge pull request #497 from blackberry-gaming/next-kcunney

Adds Gamepad support to Gameplay.
Sean Paul Taylor 13 lat temu
rodzic
commit
2554f61722

+ 2 - 0
gameplay/gameplay.vcxproj

@@ -43,6 +43,7 @@
     <ClCompile Include="src\FrameBuffer.cpp" />
     <ClCompile Include="src\Frustum.cpp" />
     <ClCompile Include="src\Game.cpp" />
+    <ClCompile Include="src\Gamepad.cpp" />
     <ClCompile Include="src\gameplay-main-android.cpp" />
     <ClCompile Include="src\gameplay-main-qnx.cpp" />
     <ClCompile Include="src\gameplay-main-win32.cpp" />
@@ -135,6 +136,7 @@
     <ClInclude Include="src\FrameBuffer.h" />
     <ClInclude Include="src\Frustum.h" />
     <ClInclude Include="src\Game.h" />
+    <ClInclude Include="src\Gamepad.h" />
     <ClInclude Include="src\gameplay.h" />
     <ClInclude Include="src\Image.h" />
     <ClInclude Include="src\Joint.h" />

+ 6 - 0
gameplay/gameplay.vcxproj.filters

@@ -282,6 +282,9 @@
     <ClCompile Include="src\Joystick.cpp">
       <Filter>src</Filter>
     </ClCompile>
+    <ClCompile Include="src\Gamepad.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="src\Animation.h">
@@ -560,6 +563,9 @@
     <ClInclude Include="src\MathUtil.h">
       <Filter>src</Filter>
     </ClInclude>
+    <ClInclude Include="src\Gamepad.h">
+      <Filter>src</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <None Include="src\gameplay-main-macosx.mm">

+ 54 - 6
gameplay/src/Button.cpp

@@ -5,11 +5,24 @@ namespace gameplay
 {
 
 Button::Button()
+    : _gamepadButtonIndex(NULL)
 {
 }
 
 Button::~Button()
 {
+    if (_gamepadButtonIndex)
+        SAFE_DELETE(_gamepadButtonIndex);
+}
+
+Button* Button::create(const char* id, Theme::Style* style)
+{
+    Button* button = new Button();
+
+    button->_id = id;
+    button->_style = style;
+
+    return button;
 }
 
 Button* Button::create(Theme::Style* style, Properties* properties)
@@ -23,21 +36,56 @@ Button* Button::create(Theme::Style* style, Properties* properties)
 bool Button::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
 {
     if (!isEnabled())
-    {
         return false;
-    }
 
     switch (evt)
     {
     case Touch::TOUCH_PRESS:
-        setState(Control::ACTIVE);
+        if (x > _clipBounds.x && x <= _clipBounds.x + _clipBounds.width &&
+            y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height)
+        {
+            _contactIndex = (int) contactIndex;
+
+            setState(Control::ACTIVE);
+
+            notifyListeners(Listener::PRESS);
+
+            return _consumeInputEvents;
+        }
+        else
+        {
+            setState(Control::NORMAL);
+        }
         break;
+
     case Touch::TOUCH_RELEASE:
-        setState(Control::NORMAL);
+        _contactIndex = INVALID_CONTACT_INDEX;
+        notifyListeners(Listener::RELEASE);
+        if (x > _clipBounds.x && x <= _clipBounds.x + _clipBounds.width &&
+            y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height)
+        {
+            setState(Control::FOCUS);
+
+            notifyListeners(Listener::CLICK);
+
+            return _consumeInputEvents;
+        }
+        else
+        {
+            setState(Control::NORMAL);
+        }
         break;
+
+    case Touch::TOUCH_MOVE:
+        return _consumeInputEvents;
     }
 
-    return Control::touchEvent(evt, x, y, contactIndex);
+    return false;
 }
 
-}
+const char* Button::getType() const
+{
+    return "button";
+}
+
+}

+ 21 - 1
gameplay/src/Button.h

@@ -32,6 +32,19 @@ namespace gameplay
 class Button : public Label
 {
     friend class Container;
+    friend class Gamepad;
+
+public:
+
+    /**
+     * Create a new button control.
+     *
+     * @param id The control's ID.
+     * @param style The control's style.
+     *
+     * @return The new button.
+     */
+    static Button* create(const char* id, Theme::Style* style);
 
 protected:
 
@@ -69,14 +82,21 @@ protected:
      */
     bool touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
 
+    /**
+     * @see Control::getType
+     */
+    const char* getType() const;
+
 private:
 
     /**
      * Constructor.
      */
     Button(const Button& copy);
+
+    int* _gamepadButtonIndex;
 };
 
 }
 
-#endif
+#endif

+ 17 - 0
gameplay/src/CheckBox.cpp

@@ -19,6 +19,18 @@ CheckBox::~CheckBox()
 
 }
 
+CheckBox* CheckBox::create(const char* id, Theme::Style* style)
+{
+    GP_ASSERT(style);
+
+    CheckBox* checkBox = new CheckBox();
+    if (id)
+        checkBox->_id = id;
+    checkBox->setStyle(style);
+
+    return checkBox;
+}
+
 CheckBox* CheckBox::create(Theme::Style* style, Properties* properties)
 {
     GP_ASSERT(properties);
@@ -159,4 +171,9 @@ void CheckBox::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
     spriteBatch->draw(pos.x, pos.y, size.x, size.y, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color, _viewportClipBounds);
 }
 
+const char* CheckBox::getType() const
+{
+    return "checkBox";
+}
+
 }

+ 19 - 4
gameplay/src/CheckBox.h

@@ -37,6 +37,16 @@ class CheckBox : public Button
 
 public:
 
+    /**
+     * Create a new check box control.
+     *
+     * @param id The control's ID.
+     * @param style The control's style.
+     *
+     * @return The new check box.
+     */
+    static CheckBox* create(const char* id, Theme::Style* style);
+
     /**
      * Gets whether this checkbox is checked.
      *
@@ -66,6 +76,11 @@ public:
      */
     const Vector2& getImageSize() const;
 
+    /**
+     * @see Control::getType
+     */
+    const char* getType() const;
+
     /**
      * Add a listener to be notified of specific events affecting
      * this control.  Event types can be OR'ed together.
@@ -130,7 +145,7 @@ protected:
      * @param clip The container position this control is relative to.
      */
     void drawImages(SpriteBatch* spriteBatch, const Rectangle& clip);
-
+
     /**
      * Whether this checkbox is currently checked.
      */
@@ -140,11 +155,11 @@ protected:
      * The size to draw the checkbox icon, if different from its size in the texture.
      */
     Vector2 _imageSize;
-
+
     /**
      * The Theme::ThemeImage to display for the checkbox.
      */
-    Theme::ThemeImage* _image;
+    Theme::ThemeImage* _image;
 
 private:
 
@@ -156,4 +171,4 @@ private:
 
 }
 
-#endif
+#endif

+ 82 - 17
gameplay/src/Container.cpp

@@ -31,7 +31,7 @@ Container::Container()
       _scrollingVelocity(Vector2::zero()), _scrollingFriction(1.0f),
       _scrollingRight(false), _scrollingDown(false),
       _scrollingMouseVertically(false), _scrollingMouseHorizontally(false),
-      _scrollBarOpacityClip(NULL), _zIndexDefault(0), _totalWidth(0), _totalHeight(0)
+      _scrollBarOpacityClip(NULL), _zIndexDefault(0), _focusIndexDefault(0), _focusIndexMax(0), _totalWidth(0), _totalHeight(0)
 {
 }
 
@@ -49,6 +49,17 @@ Container::~Container()
     SAFE_RELEASE(_layout);
 }
 
+Container* Container::create(const char* id, Theme::Style* style, Layout::Type layoutType)
+{
+    GP_ASSERT(style);
+
+    Container* container = Container::create(layoutType);
+    if (id)
+        container->_id = id;
+    container->_style = style;
+    return container;
+}
+
 Container* Container::create(Layout::Type type)
 {
     Layout* layout = NULL;
@@ -160,6 +171,15 @@ void Container::addControls(Theme* theme, Properties* properties)
             {
                 control->setZIndex(_zIndexDefault++);
             }
+
+            if (control->getFocusIndex() == -1)
+            {
+                control->setFocusIndex(_focusIndexDefault++);
+            }
+
+            int focusIndex = control->getFocusIndex();
+            if (focusIndex > _focusIndexMax)
+                _focusIndexMax = focusIndex;
         }
 
         // Get the next control.
@@ -313,6 +333,11 @@ Animation* Container::getAnimation(const char* id) const
     return NULL;
 }
 
+const char* Container::getType() const
+{
+    return "container";
+}
+
 void Container::update(const Control* container, const Vector2& offset)
 {
     // Update this container's viewport.
@@ -395,7 +420,8 @@ void Container::draw(SpriteBatch* spriteBatch, const Rectangle& clip, bool needs
 
         spriteBatch->begin();
 
-        if (_scrollBarBounds.height > 0)
+        if (_scrollBarBounds.height > 0 &&
+            ((_scroll & SCROLL_VERTICAL) == SCROLL_VERTICAL))
         {
             const Rectangle& topRegion = _scrollBarTopCap->getRegion();
             const Theme::UVs& topUVs = _scrollBarTopCap->getUVs();
@@ -428,7 +454,8 @@ void Container::draw(SpriteBatch* spriteBatch, const Rectangle& clip, bool needs
             spriteBatch->draw(bounds.x, bounds.y, bounds.width, bounds.height, bottomUVs.u1, bottomUVs.v1, bottomUVs.u2, bottomUVs.v2, bottomColor, clipRegion);
         }
 
-        if (_scrollBarBounds.width > 0)
+        if (_scrollBarBounds.width > 0 &&
+            ((_scroll & SCROLL_HORIZONTAL) == SCROLL_HORIZONTAL))
         {
             const Rectangle& leftRegion = _scrollBarLeftCap->getRegion();
             const Theme::UVs& leftUVs = _scrollBarLeftCap->getUVs();
@@ -518,17 +545,40 @@ bool Container::keyEvent(Keyboard::KeyEvent evt, int key)
             continue;
         }
 
-        if (control->isContainer() || control->getState() == Control::FOCUS)
+        if (control->getState() == Control::FOCUS)
         {
             if (control->keyEvent(evt, key))
+            {
                 return _consumeInputEvents;
+            }
+            else if (evt == Keyboard::KEY_CHAR && key == Keyboard::KEY_TAB)
+            {
+                // Shift focus to next control.
+                int focusIndex = control->getFocusIndex() + 1; // Index to search for.
+                if (focusIndex > _focusIndexMax)
+                {
+                    focusIndex = 0;
+                }
+                control->setState(Control::NORMAL);
+
+                std::vector<Control*>::const_iterator itt;
+                for (itt = _controls.begin(); itt < _controls.end(); itt++)
+                {
+                    Control* nextControl = *itt;
+                    if (nextControl->getFocusIndex() == focusIndex)
+                    {
+                        nextControl->setState(Control::FOCUS);
+                        return _consumeInputEvents;
+                    }
+                }
+            }
         }
     }
 
     return false;
 }
 
-bool Container::isContainer()
+bool Container::isContainer() const
 {
     return true;
 }
@@ -873,6 +923,12 @@ bool Container::mouseEventScroll(Mouse::MouseEvent evt, int x, int y, int wheelD
 
         case Mouse::MOUSE_WHEEL:
             _scrollPosition.y += (_totalHeight / 10.0f) * wheelDelta;
+            if (_scrollBarOpacityClip && _scrollBarOpacityClip->isPlaying())
+            {
+                _scrollBarOpacityClip->stop();
+                _scrollBarOpacityClip = NULL;
+            }
+            _scrollBarOpacity = 1.0f;
             return _consumeInputEvents;
     }
 
@@ -917,7 +973,9 @@ bool Container::pointerEvent(bool mouse, char evt, int x, int y, int data)
             boundsY += offset->y;
         }
 
-        if (control->getState() != Control::NORMAL ||
+        Control::State currentState = control->getState();
+        if ((control->isContainer() && currentState == Control::FOCUS) ||
+            (currentState != Control::NORMAL && control->_contactIndex == data) ||
             ((evt == Touch::TOUCH_PRESS ||
               evt == Mouse::MOUSE_PRESS_LEFT_BUTTON ||
               evt == Mouse::MOUSE_PRESS_MIDDLE_BUTTON ||
@@ -941,25 +999,32 @@ bool Container::pointerEvent(bool mouse, char evt, int x, int y, int data)
         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) ||
+        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;
+            eventConsumed = true;
         }
     }
 
+    switch (evt)
+    {
+    case Touch::TOUCH_PRESS:
+        if (x > _clipBounds.x && x <= _clipBounds.x + _clipBounds.width &&
+            y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height)
+        {
+            setState(Control::FOCUS);
+        }
+        else
+        {
+            setState(Control::NORMAL);
+            return false;
+        }
+        break;
+    }
+
     return (_consumeInputEvents | eventConsumed);
 }
 

+ 20 - 3
gameplay/src/Container.h

@@ -45,7 +45,6 @@ namespace gameplay
  */
 class Container : public Control
 {
-    friend class DropDownList;
 
 public:
 
@@ -65,6 +64,17 @@ public:
         SCROLL_BOTH = SCROLL_HORIZONTAL | SCROLL_VERTICAL
     };
 
+    /**
+     * Create a new container.
+     *
+     * @param id The container's ID.
+     * @param style The container's style.
+     * @param layoutType The container's layout type.
+     *
+     * @return The new container.
+     */
+    static Container* create(const char* id, Theme::Style* style, Layout::Type layoutType = Layout::LAYOUT_ABSOLUTE);
+
     /**
      * Get this container's layout.
      *
@@ -167,6 +177,11 @@ public:
      */
     Animation* getAnimation(const char* id = NULL) const;
 
+    /**
+     * @see Control::getType
+     */
+    const char* getType() const;
+
     /**
      * @see AnimationTarget#getAnimationPropertyComponentCount
      */
@@ -274,10 +289,10 @@ protected:
 
     /**
      * Returns whether this control is a container.
-     * 
+     *
      * @return true if this is a container, false if not.
      */
-    bool isContainer();
+    bool isContainer() const;
 
     /**
      * Returns whether this container or any of its controls have been modified and require an update.
@@ -458,6 +473,8 @@ private:
 
     AnimationClip* _scrollBarOpacityClip;
     int _zIndexDefault;
+    int _focusIndexDefault;
+    int _focusIndexMax;
 
     float _totalWidth;
     float _totalHeight;

+ 63 - 16
gameplay/src/Control.cpp

@@ -6,7 +6,9 @@ namespace gameplay
 {
 
 Control::Control()
-    : _id(""), _state(Control::NORMAL), _dirty(true), _consumeInputEvents(true), _listeners(NULL), _style(NULL), _styleOverridden(false), _skin(NULL)
+    : _id(""), _state(Control::NORMAL), _bounds(Rectangle::empty()), _clipBounds(Rectangle::empty()), _viewportClipBounds(Rectangle::empty()),
+    _clearBounds(Rectangle::empty()), _dirty(true), _consumeInputEvents(true), _listeners(NULL), _contactIndex(INVALID_CONTACT_INDEX),
+    _styleOverridden(false), _skin(NULL)
 {
 }
 
@@ -51,6 +53,15 @@ void Control::initialize(Theme::Style* style, Properties* properties)
         _zIndex = -1;
     }
 
+    if (properties->exists("focusIndex"))
+    {
+        _focusIndex = properties->getInt("focusIndex");
+    }
+    else
+    {
+        _focusIndex = -1;
+    }
+
     Vector2 position;
     Vector2 size;
     if (properties->exists("position"))
@@ -74,8 +85,6 @@ void Control::initialize(Theme::Style* style, Properties* properties)
     }
     setBounds(Rectangle(position.x, position.y, size.x, size.y));
 
-    _state = Control::getState(properties->getString("state"));
-
     const char* id = properties->getId();
     if (id)
         _id = id;
@@ -108,18 +117,23 @@ void Control::initialize(Theme::Style* style, Properties* properties)
         else if (spaceName == "MARGIN")
         {
             setMargin(innerSpace->getFloat("top"), innerSpace->getFloat("bottom"),
-                      innerSpace->getFloat("left"), innerSpace->getFloat("right"));
+                innerSpace->getFloat("left"), innerSpace->getFloat("right"));
         }
         else if (spaceName == "PADDING")
         {
             setPadding(innerSpace->getFloat("top"), innerSpace->getFloat("bottom"),
-                       innerSpace->getFloat("left"), innerSpace->getFloat("right"));
+                innerSpace->getFloat("left"), innerSpace->getFloat("right"));
         }
 
         innerSpace = properties->getNextNamespace();
     }
 }
 
+void initialize(const char* id, Theme::Style* style, const Vector2& position, const Vector2& size)
+{
+
+}
+
 const char* Control::getID() const
 {
     return _id.c_str();
@@ -630,6 +644,16 @@ void Control::setZIndex(int zIndex)
     }
 }
 
+int Control::getFocusIndex() const
+{
+    return _focusIndex;
+}
+
+void Control::setFocusIndex(int focusIndex)
+{
+    _focusIndex = focusIndex;
+}
+
 void Control::addListener(Control::Listener* listener, int eventFlags)
 {
     GP_ASSERT(listener);
@@ -683,26 +707,46 @@ void Control::addSpecificListener(Control::Listener* listener, Listener::EventTy
 bool Control::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
 {
     if (!isEnabled())
-    {
         return false;
-    }
 
     switch (evt)
     {
     case Touch::TOUCH_PRESS:
-        notifyListeners(Listener::PRESS);
-        return _consumeInputEvents;
+        // 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 (x > _clipBounds.x && x <= _clipBounds.x + _clipBounds.width &&
+            y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height)
+        {
+            _contactIndex = (int) contactIndex;
+
+            notifyListeners(Listener::PRESS);
+
+            return _consumeInputEvents;
+        }
+        else
+        {
+            // If this control was in focus, it's not any more.
+            _state = NORMAL;
+            _contactIndex = INVALID_CONTACT_INDEX;
+        }
+        break;
             
     case Touch::TOUCH_RELEASE:
+
+        _contactIndex = INVALID_CONTACT_INDEX;
+
         // Always trigger Listener::RELEASE
         notifyListeners(Listener::RELEASE);
 
         // Only trigger Listener::CLICK if both PRESS and RELEASE took place within the control's bounds.
-        if (x > 0 && x <= _bounds.width &&
-            y > 0 && y <= _bounds.height)
+        if (x > _clipBounds.x && x <= _clipBounds.x + _clipBounds.width &&
+            y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height)
         {
-            notifyListeners(Listener::CLICK);
+            // Leave this control in the FOCUS state.
+            notifyListeners(Listener::CLICK);
         }
+
         return _consumeInputEvents;
     }
 
@@ -954,7 +998,7 @@ bool Control::isDirty()
     return _dirty;
 }
 
-bool Control::isContainer()
+bool Control::isContainer() const
 {
     return false;
 }
@@ -998,6 +1042,11 @@ Theme::ThemeImage* Control::getImage(const char* id, State state)
     return imageList->getImage(id);
 }
 
+const char* Control::getType() const
+{
+    return "control";
+}
+
 // Implementation of AnimationHandler
 unsigned int Control::getAnimationPropertyComponentCount(int propertyId) const
 {
@@ -1084,9 +1133,7 @@ void Control::setAnimationPropertyValue(int propertyId, AnimationValue* value, f
         _dirty = true;
         break;
     case ANIMATE_OPACITY:
-        _dirty = true;
-        break;
-    default:
+        _dirty = true;
         break;
     }
 }

+ 52 - 2
gameplay/src/Control.h

@@ -25,6 +25,7 @@ class Control : public Ref, public AnimationTarget
     friend class AbsoluteLayout;
     friend class VerticalLayout;
     friend class FlowLayout;
+    friend class Gamepad;
 
 public:
 
@@ -683,6 +684,20 @@ public:
      */
     void setZIndex(int zIndex);
 
+    /**
+     * Get this control's focus index.
+     *
+     * @return This control's focus index.
+     */
+    int getFocusIndex() const;
+
+    /**
+     * Set this control's focus index.
+     *
+     * @param focusIndex The new focus index.
+     */
+    void setFocusIndex(int focusIndex);
+
     /**
      * Add a listener to be notified of specific events affecting
      * this control.  Event types can be OR'ed together.
@@ -695,6 +710,13 @@ public:
      */
     virtual void addListener(Control::Listener* listener, int eventFlags);
 
+    /**
+     * Gets the type of the Control and returns it as a string.
+     *
+     * @return The string of the Control type, all in lower-case.
+     */
+    virtual const char* getType() const;
+
     /**
      * @see AnimationTarget#getAnimationPropertyComponentCount
      */
@@ -712,6 +734,11 @@ public:
 
 protected:
 
+    /**
+     *  Constant value representing an unset or invalid contact index.
+     */
+    static const int INVALID_CONTACT_INDEX = -1;
+
     /**
      * Constructor.
      */
@@ -807,16 +834,29 @@ protected:
     virtual void draw(SpriteBatch* spriteBatch, const Rectangle& clip, bool needsClear, bool cleared, float targetHeight);
 
     /**
-     * Initialize properties common to STATE_ALL Controls.
+     * Initialize properties common to all Controls from a Properties object.
+     *
+     * @param style The style to apply to this control.
+     * @param properties The properties to set on this control.
      */
     virtual void initialize(Theme::Style* style, Properties* properties);
 
+    /**
+     * Initialize properties common to all Controls.
+     *
+     * @param id This control's ID.
+     * @param style The style to apply to this control.
+     * @param position This control's position.
+     * @param size This control's size.
+     */
+    //virtual void initialize(const char* id, Theme::Style* style, const Vector2& position, const Vector2& size);
+
     /**
      * Container and classes that extend it should implement this and return true.
      *
      * @return true if this object is of class Container, false otherwise.
      */
-    virtual bool isContainer();
+    virtual bool isContainer() const;
 
     /**
      * Returns whether this control has been modified and requires an update.
@@ -949,6 +989,16 @@ protected:
      */
     int _zIndex;
 
+    /**
+     * The contact index assigned to this control.
+     */
+    int _contactIndex;
+
+    /**
+     * The focus order of the control.
+     */
+    int _focusIndex;
+
 private:
 
     /*

+ 57 - 9
gameplay/src/Form.cpp

@@ -39,6 +39,43 @@ Form::~Form()
     }
 }
 
+Form* Form::create(const char* id, Theme::Style* style, Layout::Type layoutType)
+{
+    GP_ASSERT(style);
+
+    Layout* layout;
+    switch (layoutType)
+    {
+    case Layout::LAYOUT_ABSOLUTE:
+        layout = AbsoluteLayout::create();
+        break;
+    case Layout::LAYOUT_FLOW:
+        layout = FlowLayout::create();
+        break;
+    case Layout::LAYOUT_VERTICAL:
+        layout = VerticalLayout::create();
+        break;
+    default:
+        GP_ERROR("Unsupported layout type '%d'.", layoutType);
+        break;
+    }
+
+    Form* form = new Form();
+    if (id)
+        form->_id = id;
+    form->_style = style;
+    form->_layout = layout;
+    form->_theme = style->getTheme();
+
+    // Get default projection matrix.
+    Game* game = Game::getInstance();
+    Matrix::createOrthographicOffCenter(0, game->getWidth(), game->getHeight(), 0, 0, 1, &form->_defaultProjectionMatrix);
+
+    __forms.push_back(form);
+
+    return form;
+}
+
 Form* Form::create(const char* url)
 {
     // Load Form from .form file.
@@ -99,9 +136,7 @@ Form* Form::create(const char* url)
     }
     else
     {
-        Theme::Style::Overlay* overlay = Theme::Style::Overlay::create();
-        style = new Theme::Style(theme, "", 1.0f / theme->_texture->getWidth(), 1.0f / theme->_texture->getHeight(),
-            Theme::Margin::empty(), Theme::Border::empty(), overlay, overlay, overlay, overlay);
+        style = theme->getEmptyStyle();
     }
     form->initialize(style, formProperties);
 
@@ -159,6 +194,11 @@ Form* Form::getForm(const char* id)
     return NULL;
 }
 
+Theme* Form::getTheme() const
+{
+    return _theme;
+}
+
 void Form::setSize(float width, float height)
 {
     if (_autoWidth)
@@ -482,6 +522,11 @@ void Form::draw()
     }
 }
 
+const char* Form::getType() const
+{
+    return "form";
+}
+
 void Form::initializeQuad(Mesh* mesh)
 {
     // Release current model.
@@ -519,6 +564,7 @@ bool Form::touchEventInternal(Touch::TouchEvent evt, int x, int y, unsigned int
 {
     // Check for a collision with each Form in __forms.
     // Pass the event on.
+    bool eventConsumed = false;
     std::vector<Form*>::const_iterator it;
     for (it = __forms.begin(); it < __forms.end(); it++)
     {
@@ -540,7 +586,7 @@ bool Form::touchEventInternal(Touch::TouchEvent evt, int x, int y, unsigned int
                          point.y >= bounds.y &&
                          point.y <= bounds.y + bounds.height))
                     {
-                        return form->touchEvent(evt, point.x - bounds.x, bounds.height - point.y - bounds.y, contactIndex);
+                        eventConsumed |= form->touchEvent(evt, point.x - bounds.x, bounds.height - point.y - bounds.y, contactIndex);
                     }
                 }
             }
@@ -556,13 +602,13 @@ bool Form::touchEventInternal(Touch::TouchEvent evt, int x, int y, unsigned int
                         y <= bounds.y + bounds.height))
                 {
                     // Pass on the event's position relative to the form.
-                    return form->touchEvent(evt, x - bounds.x, y - bounds.y, contactIndex);
+                    eventConsumed |= form->touchEvent(evt, x - bounds.x, y - bounds.y, contactIndex);
                 }
             }
         }
     }
 
-    return false;
+    return eventConsumed;
 }
 
 bool Form::keyEventInternal(Keyboard::KeyEvent evt, int key)
@@ -584,6 +630,8 @@ bool Form::keyEventInternal(Keyboard::KeyEvent evt, int key)
 
 bool Form::mouseEventInternal(Mouse::MouseEvent evt, int x, int y, int wheelDelta)
 {
+    bool eventConsumed = false;
+
     std::vector<Form*>::const_iterator it;
     for (it = __forms.begin(); it < __forms.end(); it++)
     {
@@ -608,7 +656,7 @@ bool Form::mouseEventInternal(Mouse::MouseEvent evt, int x, int y, int wheelDelt
                          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);
+                        eventConsumed |= form->mouseEvent(evt, point.x - bounds.x, bounds.height - point.y - bounds.y, wheelDelta);
                     }
                 }
             }
@@ -627,13 +675,13 @@ bool Form::mouseEventInternal(Mouse::MouseEvent evt, int x, int y, int wheelDelt
                         y <= bounds.y + bounds.height))
                 {
                     // Pass on the event's position relative to the form.
-                    return form->mouseEvent(evt, x - bounds.x, y - bounds.y, wheelDelta);
+                    eventConsumed |= form->mouseEvent(evt, x - bounds.x, y - bounds.y, wheelDelta);
                 }
             }
         }
     }
 
-    return false;
+    return eventConsumed;
 }
 
 bool Form::projectPoint(int x, int y, Vector3* point)

+ 20 - 2
gameplay/src/Form.h

@@ -55,10 +55,21 @@ public:
      * where the URL is of the format "<file-path>.<extension>#<namespace-id>/<namespace-id>/.../<namespace-id>"
      * (and "#<namespace-id>/<namespace-id>/.../<namespace-id>" is optional). 
      * 
-     * @param url The URL pointing to the Properties object defining the animation data. 
+     * @param url The URL pointing to the Properties object defining the form. 
      */
     static Form* create(const char* url);
 
+    /**
+     * Create a new Form.
+     *
+     * @param id The Form's ID.
+     * @param style The Form's style.
+     * @param layoutType The form's layout type.
+     *
+     * @return The new Form.
+     */
+    static Form* create(const char* id, Theme::Style* style, Layout::Type layoutType = Layout::LAYOUT_ABSOLUTE);
+
     /**
      * Get a form from its ID.
      *
@@ -68,6 +79,8 @@ public:
      */
     static Form* getForm(const char* id);
 
+    Theme* getTheme() const;
+
     /**
      * Set the desired size of this form.
      *
@@ -143,6 +156,11 @@ public:
      */
     void draw();
 
+    /**
+     * @see Control::getType
+     */
+    const char* getType() const;
+
 private:
     
     /**
@@ -224,4 +242,4 @@ private:
 
 }
 
-#endif
+#endif

+ 1 - 0
gameplay/src/FrameBuffer.cpp

@@ -86,6 +86,7 @@ FrameBuffer* FrameBuffer::create(const char* id, unsigned int width, unsigned in
     frameBuffer->_renderTargets = renderTargets;
 
     frameBuffer->setRenderTarget(renderTarget, 0);
+    SAFE_RELEASE(renderTarget);
 
     __frameBuffers.push_back(frameBuffer);
 

+ 76 - 2
gameplay/src/Game.cpp

@@ -20,7 +20,8 @@ Game::Game()
     : _initialized(false), _state(UNINITIALIZED), 
       _frameLastFPS(0), _frameCount(0), _frameRate(0), 
       _clearDepth(1.0f), _clearStencil(0), _properties(NULL),
-      _animationController(NULL), _audioController(NULL), _physicsController(NULL), _audioListener(NULL)
+      _animationController(NULL), _audioController(NULL), _physicsController(NULL), _audioListener(NULL), 
+      _gamepadCount(0), _gamepads(NULL)
 {
     GP_ASSERT(__gameInstance == NULL);
     __gameInstance = this;
@@ -95,7 +96,7 @@ bool Game::startup()
     setViewport(Rectangle(0.0f, 0.0f, (float)_width, (float)_height));
     RenderState::initialize();
     FrameBuffer::initialize();
-
+    
     _animationController = new AnimationController();
     _animationController->initialize();
 
@@ -105,6 +106,8 @@ bool Game::startup()
     _physicsController = new PhysicsController();
     _physicsController->initialize();
 
+    loadGamepad();
+    
     _state = RUNNING;
 
     return true;
@@ -122,6 +125,15 @@ void Game::shutdown()
         Platform::signalShutdown();
         finalize();
 
+        if (_gamepads)
+        {
+            for (unsigned int i = 0; i < _gamepadCount; i++)
+            {
+                SAFE_DELETE(_gamepads[i]);
+            }
+            SAFE_DELETE_ARRAY(_gamepads);
+        }
+
         _animationController->finalize();
         SAFE_DELETE(_animationController);
 
@@ -207,6 +219,12 @@ void Game::frame()
         // Update the physics.
         _physicsController->update(elapsedTime);
 
+        if (_gamepads)
+        {
+            for (unsigned int i = 0; i < _gamepadCount; i++)
+                _gamepads[i]->update();
+        }
+
         // Application Update.
         update(elapsedTime);
 
@@ -215,6 +233,12 @@ void Game::frame()
 
         // Graphics Rendering.
         render(elapsedTime);
+        
+        if (_gamepads)
+        {
+            for (unsigned int i = 0; i < _gamepadCount; i++)
+                _gamepads[i]->render();
+        }
 
         // Update FPS.
         ++_frameCount;
@@ -305,6 +329,10 @@ void Game::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactI
 {
 }
 
+void Game::gamepadEvent(Gamepad::GamepadEvent evt, Gamepad* gamepad, unsigned int index)
+{
+}
+
 void Game::schedule(float timeOffset, TimeListener* timeListener, void* cookie)
 {
     GP_ASSERT(_timeEvents);
@@ -386,4 +414,50 @@ bool Game::TimeEvent::operator<(const TimeEvent& v) const
     return time > v.time;
 }
 
+Gamepad* Game::createGamepad(const char* gamepadFormPath)
+{
+    GP_ASSERT(gamepadFormPath);
+
+    Gamepad* gamepad = new Gamepad(gamepadFormPath);
+    GP_ASSERT(gamepad);
+
+    if (!_gamepads)
+    {
+        _gamepadCount++;
+        _gamepads = new Gamepad*[_gamepadCount];
+        _gamepads[0] = gamepad;
+    }
+    else
+    {
+        int oldSize = _gamepadCount;
+        _gamepadCount++;
+        Gamepad** tempGamepads = new Gamepad*[_gamepadCount];
+        memcpy(tempGamepads, _gamepads, sizeof(Gamepad*) * oldSize);
+        tempGamepads[oldSize] = gamepad;
+        
+        SAFE_DELETE_ARRAY(_gamepads);
+        _gamepads = tempGamepads;
+    }
+
+    return gamepad;
+}
+
+void Game::loadGamepad()
+{
+    if (_properties)
+    {
+        // Check if there is a virtual keyboard included in the .config file.
+        // If there is, try to create it and assign it to "player one".
+        Properties* gamepadProperties = _properties->getNamespace("gamepad", true);
+        if (gamepadProperties && gamepadProperties->exists("form"))
+        {
+            const char* gamepadFormPath = gamepadProperties->getString("form");
+            GP_ASSERT(gamepadFormPath);
+
+            Gamepad* gamepad = createGamepad(gamepadFormPath);
+            GP_ASSERT(gamepad);
+        }
+    }
+}
+
 }

+ 33 - 0
gameplay/src/Game.h

@@ -13,6 +13,7 @@
 #include "Rectangle.h"
 #include "Vector4.h"
 #include "TimeListener.h"
+#include "Gamepad.h"
 
 namespace gameplay
 {
@@ -22,6 +23,7 @@ namespace gameplay
  */
 class Game
 {
+
 public:
 
     /**
@@ -267,6 +269,15 @@ public:
      */
     virtual bool mouseEvent(Mouse::MouseEvent evt, int x, int y, int wheelDelta);
 
+    /**
+     * Gamepad callback on gamepad events.
+     *
+     * @param evt The gamepad event that occured.
+     * @param gamepad the gamepad the event occured on
+     * @param index The joystick or button index that triggered the event.
+     */
+    virtual void gamepadEvent(Gamepad::GamepadEvent evt, Gamepad* gamepad, unsigned int index);
+
     /**
      * Sets muli-touch is to be enabled/disabled. Default is disabled.
      *
@@ -299,6 +310,21 @@ public:
      */
     void schedule(float timeOffset, TimeListener* timeListener, void* cookie = 0);
 
+    /** 
+     * Creates a Gamepad object from a .form file.
+     *
+     * @param playerIndex
+     * @param formPath
+     */
+    Gamepad* createGamepad(const char* gamepadFormPath);
+
+    /**
+     * Gets the gamepad for the specified player index.
+     *
+     * @param playerIndex The player index to get the gamepad for (0 <= playerIndex <= 3) 
+     */
+    inline Gamepad* getGamepad(unsigned int playerIndex = 0);
+
 protected:
 
     /**
@@ -405,6 +431,11 @@ private:
      */
     void loadConfig();
 
+    /**
+     * Loads a gamepad from the configuration file.
+     */
+    void loadGamepad();
+
     bool _initialized;                          // If game has initialized yet.
     State _state;                               // The game state.
     static double _pausedTimeLast;              // The last time paused.
@@ -423,6 +454,8 @@ private:
     AudioController* _audioController;          // Controls audio sources that are playing in the game.
     PhysicsController* _physicsController;      // Controls the simulation of a physics scene and entities.
     AudioListener* _audioListener;              // The audio listener in 3D space.
+    unsigned int _gamepadCount;
+    Gamepad** _gamepads;
     std::priority_queue<TimeEvent, std::vector<TimeEvent>, std::less<TimeEvent> >* _timeEvents; // Contains the scheduled time events.
 
     // Note: Do not add STL object member variables on the stack; this will cause false memory leaks to be reported.

+ 11 - 1
gameplay/src/Game.inl

@@ -72,4 +72,14 @@ inline void Game::displayKeyboard(bool display)
     Platform::displayKeyboard(display);
 }
 
-}
+inline Gamepad* Game::getGamepad(unsigned int playerIndex)
+{
+    GP_ASSERT(playerIndex < _gamepadCount);
+
+    if (_gamepads)
+        return _gamepads[playerIndex];
+    else
+        return NULL;
+}
+
+}

+ 181 - 0
gameplay/src/Gamepad.cpp

@@ -0,0 +1,181 @@
+#include "Base.h"
+#include "Gamepad.h"
+#include "Joystick.h"
+#include "Game.h"
+
+namespace gameplay
+{
+
+Gamepad::Gamepad()
+    : _playerIndex(-1), _joystickValues(NULL), _joystickCount(0), _buttonStates(NULL), _buttonCount(0), _gamepadForm(NULL)
+{
+}
+
+Gamepad::Gamepad(const char* formPath)
+    : _playerIndex(-1), _joystickValues(NULL), _joystickCount(0), _buttonStates(NULL), _buttonCount(0), _gamepadForm(NULL)
+{
+    GP_ASSERT(formPath);
+
+    _gamepadForm = Form::create(formPath);
+    GP_ASSERT(_gamepadForm);
+
+    _gamepadForm->setConsumeInputEvents(false);
+
+    getGamepadControls(_gamepadForm);
+
+    if (_joystickCount > 0)
+    {
+        _joystickValues = new Vector2*[_joystickCount];
+        for (unsigned int i = 0; i < _joystickCount; i++)
+        {
+            _joystickValues[i] = new Vector2();
+        }
+    }
+    if (_buttonCount > 0)
+    {
+        _buttonStates = new ButtonState*[_buttonCount];
+        for (unsigned int i = 0; i < _buttonCount; i++)
+        {
+            _buttonStates[i] = new ButtonState();
+        }
+    }
+}
+
+void Gamepad::getGamepadControls(Form* form)
+{
+    std::vector<Control*> controls = form->getControls();
+    std::vector<Control*>::iterator itr = controls.begin();
+
+    for (; itr != controls.end(); itr++)
+    {
+        Control* control = *itr;
+        GP_ASSERT(control);
+
+        if (std::strcmp("container", control->getType()) == 0)
+        {
+            getGamepadControls((Form*) control);
+        }
+        else if (std::strcmp("joystick", control->getType()) == 0)
+        {
+            control->addListener(this, Control::Listener::PRESS | Control::Listener::VALUE_CHANGED | Control::Listener::RELEASE);
+            Joystick* joystick = (Joystick*) control;
+            
+            if (!joystick->_gamepadJoystickIndex)
+                joystick->_gamepadJoystickIndex = new int[1];
+            
+            *joystick->_gamepadJoystickIndex = _joystickCount;
+            _joystickCount++;
+        }
+        else if (std::strcmp("button", control->getType()) == 0)
+        {
+            control->addListener(this, Control::Listener::PRESS | Control::Listener::RELEASE);
+            Button* button = (Button*) control;
+            
+            if (!button->_gamepadButtonIndex)
+                button->_gamepadButtonIndex = new int[1];
+
+            *button->_gamepadButtonIndex = _buttonCount;
+            _buttonCount++;
+        }
+    }
+}
+
+Gamepad::~Gamepad()
+{
+    if (_joystickValues)
+    {
+        for (unsigned int i = 0; i < _joystickCount; i++)
+            SAFE_DELETE(_joystickValues[i]);
+        SAFE_DELETE_ARRAY(_joystickValues);
+    }
+
+    if (_buttonStates)
+    {
+        for (unsigned int i = 0; i < _buttonCount; i++)
+           SAFE_DELETE(_buttonStates[i]);
+        SAFE_DELETE_ARRAY(_buttonStates);
+    }
+
+    if (_gamepadForm)
+        SAFE_RELEASE(_gamepadForm);
+}
+
+void Gamepad::update()
+{
+    if (_gamepadForm && _gamepadForm->isEnabled())
+    {
+        _gamepadForm->update();
+    }
+}
+
+void Gamepad::render()
+{
+    if (_gamepadForm && _gamepadForm->isEnabled())
+    {
+        _gamepadForm->draw();
+    }
+}
+
+void Gamepad::controlEvent(Control* control, Control::Listener::EventType evt)
+{
+    if (_gamepadForm && _gamepadForm->isEnabled())
+    {
+        if (std::strcmp("joystick", control->getType()) == 0)
+        {
+            int joystickIndex = *(((Joystick*) control)->_gamepadJoystickIndex);
+            switch(evt)
+            {
+                case Control::Listener::PRESS:
+                case Control::Listener::VALUE_CHANGED:
+                    _joystickValues[joystickIndex]->set(((Joystick*)control)->getValue());
+                    break;
+                case Control::Listener::RELEASE:
+                    _joystickValues[joystickIndex]->set(0.0f, 0.0f);
+                    break;
+            }
+            Game::getInstance()->gamepadEvent(JOYSTICK_EVENT, this, joystickIndex);
+        }
+        else if (std::strcmp("button", control->getType()) == 0)
+        {
+            int buttonIndex = *(((Button*) control)->_gamepadButtonIndex);
+            switch(evt)
+            {
+                case Control::Listener::PRESS:
+                    *_buttonStates[buttonIndex] = BUTTON_PRESSED;
+                    break;
+                case Control::Listener::RELEASE:
+                    *_buttonStates[buttonIndex] = BUTTON_RELEASED;
+                    break;
+            }
+            Game::getInstance()->gamepadEvent(BUTTON_EVENT, this, buttonIndex);
+        }
+    }
+}
+
+Gamepad::ButtonState Gamepad::getButtonState(unsigned int buttonId) const
+{
+    GP_ASSERT(buttonId < _buttonCount);
+
+    return *_buttonStates[buttonId];
+}
+
+bool Gamepad::isJoystickActive(unsigned int joystickId) const
+{
+    GP_ASSERT(joystickId < _joystickCount);
+
+    return !(_joystickValues[joystickId]->isZero());
+}
+
+const Vector2& Gamepad::getJoystickValue(unsigned int joystickId) const
+{
+    GP_ASSERT(joystickId < _joystickCount);
+
+    return *_joystickValues[joystickId];
+}
+
+bool Gamepad::isVirtual() const
+{
+    return (_gamepadForm && _gamepadForm->isEnabled());
+}
+
+}

+ 113 - 0
gameplay/src/Gamepad.h

@@ -0,0 +1,113 @@
+#ifndef GAMEPAD_H_
+#define GAMEPAD_H_
+
+#include "Form.h"
+#include "Button.h"
+
+namespace gameplay
+{
+
+/**
+ * Defines an interface for handling gamepad support.
+ */
+class Gamepad : public Control::Listener
+{
+    friend class Game;
+
+public:
+
+    enum GamepadEvent
+    {
+        JOYSTICK_EVENT,
+        BUTTON_EVENT
+    };
+
+    enum ButtonState
+    {
+        BUTTON_PRESSED = gameplay::Button::Listener::PRESS, 
+        BUTTON_RELEASED = gameplay::Button::Listener::RELEASE
+    };
+    
+    /** 
+     * Gets the current state of the specified button.
+     */
+    ButtonState getButtonState(unsigned int buttonId) const;
+
+    /**
+     * Gets whether the specified joystick's state is active or not.
+     * 
+     * @param joystickId The unique integer ID of the joystick to set.
+     * @return Whether the given joystick is active or not.
+     */
+    bool isJoystickActive(unsigned int joystickId) const;
+
+    /**
+     * Returns the specified joystick's value as a Vector2.
+     *
+     * @param joystickId The unique integer ID of the joystick to set.
+     * @return A Vector2 of the joystick displacement for the specified joystick.
+     */
+    const Vector2& getJoystickValue(unsigned int joystickId) const;
+
+    /** 
+     * Listener for Control events on the gamepads Joystick or Buttons.
+     */
+    void controlEvent(Control* control, Control::Listener::EventType evt);
+
+    /**
+     * Returns whether the gamepad is represented with a UI form or not.
+     */
+    bool isVirtual() const;
+
+private:
+
+    /**
+     * Constructor.
+     */
+    Gamepad();
+    
+    /**
+     * Constructor.
+     * Create a gamepad from the specified formPath.
+     *
+     * @param formPath
+     */ 
+    Gamepad(const char* formPath);
+
+    /**
+     * Copy constructor.
+     */
+    Gamepad(const Gamepad& copy);
+
+    /** 
+     * Destructor.
+     */
+    virtual ~Gamepad();
+    
+    /** 
+     * Gets the Joystick and Button Control object's from the specified form.
+     */
+    void getGamepadControls(Form* form);
+
+    /**
+     * Updates the gamepad.
+     */
+    void update();
+
+    /**
+     * Renders the gamepad if it is based on a form and if the form is enabled.
+     */
+    void render();
+
+    int _playerIndex;
+    Vector2** _joystickValues;
+    unsigned int _joystickCount;
+    ButtonState** _buttonStates;
+    unsigned int _buttonCount;
+
+    Form* _gamepadForm;
+};
+
+}
+
+#endif

+ 77 - 47
gameplay/src/Joystick.cpp

@@ -1,12 +1,10 @@
 #include "Base.h"
 #include "Joystick.h"
 
-#define INVALID_CONTACT_INDEX ((unsigned int)-1)
-
 namespace gameplay
 {
 
-Joystick::Joystick() : _contactIndex(INVALID_CONTACT_INDEX), _absolute(true)
+Joystick::Joystick() : _absolute(true), _gamepadJoystickIndex(NULL)
 {
 }
 
@@ -16,6 +14,20 @@ Joystick::Joystick(const Joystick& copy)
 
 Joystick::~Joystick()
 {
+    if (_gamepadJoystickIndex)
+        SAFE_DELETE(_gamepadJoystickIndex);
+}
+
+Joystick* Joystick::create(const char* id, Theme::Style* style)
+{
+    GP_ASSERT(style);
+
+    Joystick* joystick = new Joystick();
+    if (id)
+        joystick->_id = id;
+    joystick->setStyle(style);
+
+    return joystick;
 }
 
 Joystick* Joystick::create(Theme::Style* style, Properties* properties)
@@ -40,6 +52,11 @@ void Joystick::initialize(Theme::Style* style, Properties* properties)
     }
     _radius = properties->getFloat("radius");
 
+    if (properties->exists("absolute"))
+    {
+        setAbsolute(properties->getBool("absolute"));
+    }
+
     Vector4 v;
     if (properties->getVector4("region", &v))
     {
@@ -84,10 +101,12 @@ bool Joystick::touchEvent(Touch::TouchEvent touchEvent, int x, int y, unsigned i
                 _region.y = y + _bounds.y - _region.height * 0.5f;
             }
 
-            if ((dx >= -_radius && dx <= _radius) && (dy >= -_radius && dy <= _radius) && 
-                _contactIndex == INVALID_CONTACT_INDEX)
+            if ((dx >= -_radius && dx <= _radius) && (dy >= -_radius && dy <= _radius))
             {
-                _contactIndex = contactIndex;
+                _contactIndex = (int) contactIndex;
+
+                notifyListeners(Listener::PRESS);
+
                 _displacement.set(0.0f, 0.0f);
                 
                 Vector2 value(0.0f, 0.0f);
@@ -99,67 +118,71 @@ bool Joystick::touchEvent(Touch::TouchEvent touchEvent, int x, int y, unsigned i
                 }
 
                 _state = ACTIVE;
+
+                return _consumeInputEvents;
+            }
+            else
+            {
+                _state = NORMAL;
             }
+            break;
         }
         case Touch::TOUCH_MOVE:
         {
-            if (_contactIndex == contactIndex)
+            float dx = x - ((!_absolute) ? _region.x - _bounds.x : 0.0f) - _region.width * 0.5f;
+            float dy = -(y - ((!_absolute) ? _region.y - _bounds.y : 0.0f) - _region.height * 0.5f);
+            if (((dx * dx) + (dy * dy)) <= (_radius * _radius))
             {
-                float dx = x - ((!_absolute) ? _region.x - _bounds.x : 0.0f) - _region.width * 0.5f;
-                float dy = -(y - ((!_absolute) ? _region.y - _bounds.y : 0.0f) - _region.height * 0.5f);
-                if (((dx * dx) + (dy * dy)) <= (_radius * _radius))
-                {
-                    GP_ASSERT(_radius);
-                    Vector2 value(dx, dy);
-                    value.scale(1.0f / _radius);
-                    if (_value != value)
-                    {
-                        _value.set(value);
-                        _dirty = true;
-                        notifyListeners(Control::Listener::VALUE_CHANGED);
-                    }
-                }
-                else
+                GP_ASSERT(_radius);
+                Vector2 value(dx, dy);
+                value.scale(1.0f / _radius);
+                if (_value != value)
                 {
-                    Vector2 value(dx, dy);
-                    value.normalize();
-                    value.scale(_radius);
-                    value.normalize();
-                    if (_value != value)
-                    {
-                        _value.set(value);
-                        _dirty = true;
-                        notifyListeners(Control::Listener::VALUE_CHANGED);
-                    }
+                    _value.set(value);
+                    _dirty = true;
+                    notifyListeners(Control::Listener::VALUE_CHANGED);
                 }
-
-                _displacement.set(dx, dy);
             }
-        }
-        break;
-        case Touch::TOUCH_RELEASE:
-        {
-            if (_contactIndex == contactIndex)
+            else
             {
-                // Reset displacement and direction vectors.
-                _contactIndex = INVALID_CONTACT_INDEX;
-                _displacement.set(0.0f, 0.0f);
-                
-                Vector2 value(0.0f, 0.0f);
+                Vector2 value(dx, dy);
+                value.normalize();
+                value.scale(_radius);
+                value.normalize();
                 if (_value != value)
                 {
                     _value.set(value);
                     _dirty = true;
                     notifyListeners(Control::Listener::VALUE_CHANGED);
                 }
+            }
 
-                _state = NORMAL;
+            _displacement.set(dx, dy);
+
+            return _consumeInputEvents;
+        }
+        case Touch::TOUCH_RELEASE:
+        {
+            _contactIndex = INVALID_CONTACT_INDEX;
+
+            // Reset displacement and direction vectors.
+            _displacement.set(0.0f, 0.0f);
+
+            Vector2 value(0.0f, 0.0f);
+            if (_value != value)
+            {
+                _value.set(value);
+                _dirty = true;
+                notifyListeners(Control::Listener::VALUE_CHANGED);
             }
+
+            _state = NORMAL;
+
+            return _consumeInputEvents;
         }
-        break;
     }
 
-    return Control::touchEvent(touchEvent, x, y, contactIndex);
+    return false;
 }
 
 void Joystick::update(const Control* container, const Vector2& offset)
@@ -226,4 +249,11 @@ void Joystick::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
     spriteBatch->end();
 }
 
+const char* Joystick::getType() const
+{
+    return "joystick";
+}
+
+
+
 }

+ 17 - 1
gameplay/src/Joystick.h

@@ -12,8 +12,19 @@ namespace gameplay
 class Joystick : public Control
 {
     friend class Container;
+    friend class Gamepad;
 
 public:
+
+    /**
+     * Create a new joystick control.
+     *
+     * @param id The control's ID.
+     * @param style The control's style.
+     *
+     * @return The new joystick.
+     */
+    static Joystick* create(const char* id, Theme::Style* style);
     
     /**
      * Add a listener to be notified of specific events affecting
@@ -69,6 +80,11 @@ public:
      */
     inline bool isAbsolute() const;
 
+    /**
+     * @see Control::getType
+     */
+    const char* getType() const;
+
 protected:
     
     /**
@@ -135,11 +151,11 @@ private:
     Joystick(const Joystick& copy);
 
     float _radius;
-    unsigned int _contactIndex;
     bool _absolute;
     Vector2 _displacement;
     Vector2 _value;
     Rectangle _region;
+    int* _gamepadJoystickIndex;
 };
 
 }

+ 21 - 1
gameplay/src/Label.cpp

@@ -16,11 +16,26 @@ Label::~Label()
 {
 }
 
+Label* Label::create(const char* id, Theme::Style* style)
+{
+    GP_ASSERT(style);
+
+    Label* label = new Label();
+    if (id)
+        label->_id = id;
+    label->setStyle(style);
+
+    return label;
+}
+
 Label* Label::create(Theme::Style* style, Properties* properties)
 {
     Label* label = new Label();
     label->initialize(style, properties);
     label->_consumeInputEvents = false;
+    
+    // Ensure that labels cannot receive focus.
+    label->_focusIndex = -2;
 
     return label;
 }
@@ -97,4 +112,9 @@ void Label::drawText(const Rectangle& clip)
     _dirty = false;
 }
 
-}
+const char* Label::getType() const
+{
+    return "label";
+}
+
+}

+ 16 - 1
gameplay/src/Label.h

@@ -33,6 +33,16 @@ class Label : public Control
 
 public:
 
+    /**
+     * Create a new label control.
+     *
+     * @param id The control's ID.
+     * @param style The control's style.
+     *
+     * @return The new label.
+     */
+    static Label* create(const char*id, Theme::Style* style);
+
     /**
      * Set the text for this label to display.
      *
@@ -47,6 +57,11 @@ public:
      */
     const char* getText();
 
+    /**
+     * @see Control::getType
+     */
+    const char* getType() const;
+
     /**
      * Add a listener to be notified of specific events affecting
      * this control.  Event types can be OR'ed together.
@@ -132,4 +147,4 @@ private:
 
 }
 
-#endif
+#endif

+ 18 - 1
gameplay/src/RadioButton.cpp

@@ -24,6 +24,18 @@ RadioButton::~RadioButton()
     }
 }
 
+RadioButton* RadioButton::create(const char* id, Theme::Style* style)
+{
+    GP_ASSERT(style);
+
+    RadioButton* radioButton = new RadioButton();
+    if (id)
+        radioButton->_id = id;
+    radioButton->setStyle(style);
+
+    return radioButton;
+}
+
 RadioButton* RadioButton::create(Theme::Style* style, Properties* properties)
 {
     GP_ASSERT(properties);
@@ -186,4 +198,9 @@ void RadioButton::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
     spriteBatch->draw(pos.x, pos.y, size.x, size.y, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color, _viewportClipBounds);
 }
 
-}
+const char* RadioButton::getType() const
+{
+    return "radioButton";
+}
+
+}

+ 17 - 1
gameplay/src/RadioButton.h

@@ -38,6 +38,17 @@ class RadioButton : public Button
     friend class Container;
 
 public:
+
+    /**
+     * Create a new radio button control.
+     *
+     * @param id The control's ID.
+     * @param style The control's style.
+     *
+     * @return The new radio button.
+     */
+    static RadioButton* create(const char* id, Theme::Style* style);
+
     /**
      * Get whether this radio button is currently selected.
      *
@@ -60,6 +71,11 @@ public:
      */
     const Vector2& getImageSize() const;
 
+    /**
+     * @see Control::getType
+     */
+    const char* getType() const;
+
     /**
      * Add a listener to be notified of specific events affecting
      * this control.  Event types can be OR'ed together.
@@ -159,4 +175,4 @@ private:
 
 }
 
-#endif
+#endif

+ 74 - 2
gameplay/src/Slider.cpp

@@ -3,6 +3,9 @@
 namespace gameplay
 {
 
+// Fraction of slider to scroll when mouse scrollwheel is used.
+static const float SCROLL_FRACTION = 0.1f;
+
 Slider::Slider() : _minImage(NULL), _maxImage(NULL), _trackImage(NULL), _markerImage(NULL)
 {
 }
@@ -11,6 +14,18 @@ Slider::~Slider()
 {
 }
 
+Slider* Slider::create(const char* id, Theme::Style* style)
+{
+    GP_ASSERT(style);
+
+    Slider* slider = new Slider();
+    if (id)
+        slider->_id = id;
+    slider->setStyle(style);
+
+    return slider;
+}
+
 Slider* Slider::create(Theme::Style* style, Properties* properties)
 {
     GP_ASSERT(properties);
@@ -133,13 +148,65 @@ bool Slider::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contac
 
         if (evt == Touch::TOUCH_RELEASE)
         {
-            _state = NORMAL;
+            _state = FOCUS;
             _dirty = true;
         }
         break;
     }
+    
+    if (evt == Touch::TOUCH_MOVE)
+        return _consumeInputEvents;
+    else
+        return Control::touchEvent(evt, x, y, contactIndex);
+}
+
+bool Slider::mouseEvent(Mouse::MouseEvent evt, int x, int y, int wheelDelta)
+{
+    if (!isEnabled())
+    {
+        return false;
+    }
 
-    return Control::touchEvent(evt, x, y, contactIndex);
+    switch (evt)
+    {
+        case Mouse::MOUSE_PRESS_LEFT_BUTTON:
+            return touchEvent(Touch::TOUCH_PRESS, x, y, 0);
+
+        case Mouse::MOUSE_MOVE:
+            return touchEvent(Touch::TOUCH_MOVE, x, y, 0);
+
+        case Mouse::MOUSE_RELEASE_LEFT_BUTTON:
+            return touchEvent(Touch::TOUCH_RELEASE, x, y, 0);
+
+        case Mouse::MOUSE_WHEEL:
+        {
+            if (_state == FOCUS)
+            {
+                float total = _max - _min;
+                float oldValue = _value;
+                _value += (total * SCROLL_FRACTION) * wheelDelta;
+            
+                if (_value > _max)
+                    _value = _max;
+                else if (_value < _min)
+                    _value = _min;
+
+                if (_value != oldValue)
+                {
+                    notifyListeners(Listener::VALUE_CHANGED);
+                }
+
+                _dirty = true;
+                return _consumeInputEvents;
+            }
+            break;
+        }
+
+        default:
+            break;
+    }
+
+    return false;
 }
 
 void Slider::update(const Control* container, const Vector2& offset)
@@ -208,4 +275,9 @@ void Slider::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
     spriteBatch->draw(pos.x, pos.y, markerRegion.width, markerRegion.height, marker.u1, marker.v1, marker.u2, marker.v2, markerColor, _viewportClipBounds);
 }
 
+const char* Slider::getType() const
+{
+    return "slider";
+}
+
 }

+ 29 - 0
gameplay/src/Slider.h

@@ -35,6 +35,16 @@ class Slider : public Label
 
 public:
 
+    /**
+     * Create a new slider control.
+     *
+     * @param id The control's ID.
+     * @param style The control's style.
+     *
+     * @return The new slider.
+     */
+    static Slider* create(const char* id, Theme::Style* style);
+
     /**
      * Set the minimum value that can be set on this slider.
      *
@@ -93,6 +103,11 @@ public:
      */
     float getValue();
 
+    /**
+     * @see Control::getType
+     */
+    const char* getType() const;
+
     /**
      * Add a listener to be notified of specific events affecting
      * this control.  Event types can be OR'ed together.
@@ -141,6 +156,20 @@ protected:
      */
     bool touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
 
+    /**
+     * 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
+     */
+    bool mouseEvent(Mouse::MouseEvent evt, int x, int y, int wheelDelta);
+
     /**
      * Draw the images associated with this control.
      *

+ 0 - 25
gameplay/src/SpriteBatch.cpp

@@ -256,17 +256,6 @@ void SpriteBatch::draw(const Vector3& position, const Vector3& right, const Vect
     float u1, float v1, float u2, float v2, const Vector4& color, const Vector2& rotationPoint, float rotationAngle)
 {
     // Calculate the vertex positions.
-    //static Vector3 p[4];
-
-    // Pre-optimized:
-    /*
-    p[0] = position - 0.5f * width * right - 0.5f * height * forward;
-    p[1] = position + 0.5f * width * right - 0.5f * height * forward;
-    p[2] = p[0] + height * forward;
-    p[3] = p[1] + height * forward;
-    */
-    
-    // Optimized:
     Vector3 tRight(right);
     tRight *= width * 0.5f;
     Vector3 tForward(forward);
@@ -288,11 +277,6 @@ void SpriteBatch::draw(const Vector3& position, const Vector3& right, const Vect
     p3 += tForward;
 
     // Calculate the rotation point.
-    
-    // Pre-optimized:
-    //Vector3 rp = p[0] + (rotationPoint.x * width * right) + (rotationPoint.y * height * forward);
-
-    // Optimized:
     Vector3 rp = p0;
     tRight = right;
     tRight *= width * rotationPoint.x;
@@ -306,15 +290,6 @@ void SpriteBatch::draw(const Vector3& position, const Vector3& right, const Vect
     static Matrix rotation;
     Matrix::createRotation(u, rotationAngle, &rotation);
 
-    // Pre-optimized:
-    /*
-    p[0] = (rotation * (p[0] - rp)) + rp;
-    p[1] = (rotation * (p[1] - rp)) + rp;
-    p[2] = (rotation * (p[2] - rp)) + rp;
-    p[3] = (rotation * (p[3] - rp)) + rp;
-    */
-
-    // Optimized:
     p0 -= rp;
     p0 *= rotation;
     p0 += rp;

+ 60 - 25
gameplay/src/TextBox.cpp

@@ -16,6 +16,18 @@ TextBox::~TextBox()
 {
 }
 
+TextBox* TextBox::create(const char* id, Theme::Style* style)
+{
+    GP_ASSERT(style);
+
+    TextBox* textBox = new TextBox();
+    if (id)
+        textBox->_id = id;
+    textBox->setStyle(style);
+
+    return textBox;
+}
+
 TextBox* TextBox::create(Theme::Style* style, Properties* properties)
 {
     TextBox* textBox = new TextBox();
@@ -49,36 +61,35 @@ bool TextBox::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int conta
     switch (evt)
     {
     case Touch::TOUCH_PRESS: 
-        if (_state == NORMAL)
-        {
-            _state = ACTIVE;
-            Game::getInstance()->displayKeyboard(true);
-            _dirty = true;
-            return _consumeInputEvents;
-        }
-        else if (x > _clipBounds.x && x <= _clipBounds.x + _clipBounds.width &&
+        if (x > _clipBounds.x && x <= _clipBounds.x + _clipBounds.width &&
                  y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height)
         {
-            setCaretLocation(x, y);
+            _contactIndex = (int) contactIndex;
+
+            if (_state == NORMAL)
+                Game::getInstance()->displayKeyboard(true);
+            else
+                setCaretLocation(x, y);
+
+            _state = ACTIVE;
             _dirty = true;
-            return _consumeInputEvents;
         }
         else
         {
+            _contactIndex = INVALID_CONTACT_INDEX;
             _state = NORMAL;
             Game::getInstance()->displayKeyboard(false);
             _dirty = true;
-            return _consumeInputEvents;
+            return false;
         }
         break;
     case Touch::TOUCH_MOVE:
-        if (_state == FOCUS &&
+        if (_state == ACTIVE &&
             x > _clipBounds.x && x <= _clipBounds.x + _clipBounds.width &&
             y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height)
         {
             setCaretLocation(x, y);
             _dirty = true;
-            return _consumeInputEvents;
         }
         break;
     case Touch::TOUCH_RELEASE:
@@ -87,16 +98,14 @@ bool TextBox::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int conta
         {
             setCaretLocation(x, y);
             _state = FOCUS;
-            _dirty = true;
-            return _consumeInputEvents;
         }
         else
         {
             _state = NORMAL;
             Game::getInstance()->displayKeyboard(false);
-            _dirty = true;
-            return _consumeInputEvents;
         }
+        _contactIndex = INVALID_CONTACT_INDEX;
+        _dirty = true;
         break;
     }
 
@@ -156,7 +165,7 @@ bool TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
                         Font::Justify textAlignment = getTextAlignment(_state);
                         bool rightToLeft = getTextRightToLeft(_state);
 
-                        unsigned int textIndex = font->getIndexAtLocation(_text.c_str(), _textBounds, fontSize, _caretLocation, &_caretLocation,
+                        int textIndex = font->getIndexAtLocation(_text.c_str(), _textBounds, fontSize, _caretLocation, &_caretLocation,
                             textAlignment, true, rightToLeft);
                         font->getLocationAtIndex(_text.c_str(), _textBounds, fontSize, &_caretLocation, textIndex - 1,
                             textAlignment, true, rightToLeft);
@@ -187,10 +196,15 @@ bool TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
                         unsigned int fontSize = getFontSize(_state);
                         Font::Justify textAlignment = getTextAlignment(_state);
                         bool rightToLeft = getTextRightToLeft(_state);
-
+                        _prevCaretLocation.set(_caretLocation);
                         _caretLocation.y -= fontSize;
-                        font->getIndexAtLocation(_text.c_str(), _textBounds, fontSize, _caretLocation, &_caretLocation,
+                        int textIndex = font->getIndexAtLocation(_text.c_str(), _textBounds, fontSize, _caretLocation, &_caretLocation,
                             textAlignment, true, rightToLeft);
+                        if (textIndex == -1)
+                        {
+                            _caretLocation.set(_prevCaretLocation);
+                        }
+
                         _dirty = true;
                         consume = true;
                         break;
@@ -202,10 +216,15 @@ bool TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
                         unsigned int fontSize = getFontSize(_state);
                         Font::Justify textAlignment = getTextAlignment(_state);
                         bool rightToLeft = getTextRightToLeft(_state);
-
+                        _prevCaretLocation.set(_caretLocation);
                         _caretLocation.y += fontSize;
-                        font->getIndexAtLocation(_text.c_str(), _textBounds, fontSize, _caretLocation, &_caretLocation,
+                        int textIndex = font->getIndexAtLocation(_text.c_str(), _textBounds, fontSize, _caretLocation, &_caretLocation,
                             textAlignment, true, rightToLeft);
+                        if (textIndex == -1)
+                        {
+                            _caretLocation.set(_prevCaretLocation);
+                        }
+
                         _dirty = true;
                         consume = true;
                         break;
@@ -224,6 +243,12 @@ bool TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
 
                 int textIndex = font->getIndexAtLocation(_text.c_str(), _textBounds, fontSize, _caretLocation, &_caretLocation,
                     textAlignment, true, rightToLeft);
+                if (textIndex == -1)
+                {
+                    textIndex = 0;
+                    font->getLocationAtIndex(_text.c_str(), _textBounds, fontSize, &_caretLocation, 0,
+                        textAlignment, true, rightToLeft);
+                }
 
                 switch (key)
                 {
@@ -237,13 +262,17 @@ bool TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
                                 textAlignment, true, rightToLeft);
 
                             _dirty = true;
-                            consume = true;
                         }
+                        consume = true;
                         break;
                     }
                     case Keyboard::KEY_RETURN:
                         // TODO: Handle line-break insertion correctly.
                         break;
+                    case Keyboard::KEY_ESCAPE:
+                        break;
+                    case Keyboard::KEY_TAB:
+                        break;
                     default:
                     {
                         // Insert character into string.
@@ -293,6 +322,7 @@ bool TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
                 }
 
                 notifyListeners(Listener::TEXT_CHANGED);
+                break;
             }
         }
     }
@@ -312,7 +342,7 @@ void TextBox::update(const Control* container, const Vector2& offset)
 
 void TextBox::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
 {
-    if (_state == FOCUS)
+    if (_state == ACTIVE || _state == FOCUS)
     {
         // Draw the cursor at its current location.
         GP_ASSERT(_caretImage);
@@ -352,4 +382,9 @@ void TextBox::setCaretLocation(int x, int y)
     }
 }
 
-}
+const char* TextBox::getType() const
+{
+    return "textBox";
+}
+
+}

+ 16 - 1
gameplay/src/TextBox.h

@@ -39,6 +39,16 @@ class TextBox : public Label
 
 public:
 
+    /**
+     * Create a new text box control.
+     *
+     * @param id The control's ID.
+     * @param style The control's style.
+     *
+     * @return The new text box.
+     */
+    static TextBox* create(const char* id, Theme::Style* style);
+
     /**
      * Add a listener to be notified of specific events affecting
      * this control.  Event types can be OR'ed together.
@@ -58,6 +68,11 @@ public:
      */
     int getLastKeypress();
 
+    /**
+     * @see Control::getType
+     */
+    const char* getType() const;
+
 protected:
 
     /**
@@ -165,4 +180,4 @@ private:
 
 }
 
-#endif
+#endif

+ 29 - 17
gameplay/src/Theme.h

@@ -135,7 +135,6 @@ namespace gameplay
 class Theme: public Ref
 {
     friend class Control;
-    friend class Container;
     friend class Form;
     friend class Skin;
 
@@ -294,6 +293,35 @@ public:
         Vector4 _color;
     };
 
+    /**
+     * Creates a theme using the data from the Properties object defined at the specified URL, 
+     * where the URL is of the format "<file-path>.<extension>#<namespace-id>/<namespace-id>/.../<namespace-id>"
+     * (and "#<namespace-id>/<namespace-id>/.../<namespace-id>" is optional). 
+     * 
+     * @param url The URL pointing to the Properties object defining the theme. 
+     */
+    static Theme* create(const char* url);
+
+    /**
+     * Get a style by its ID.
+     *
+     * @param id The style ID.
+     *
+     * @return The style with the specified ID, or NULL if it does not exist.
+     */
+    Theme::Style* getStyle(const char* id) const;
+
+    /**
+     * Get the empty style.  Used when a control does not specify a style.
+     * This is especially useful for containers that are being used only for
+     * layout and positioning, and have no background or border themselves.
+     * The empty style has no border, background, margin, padding, images, etc..
+     * Any needed properties can be set on the control directly.
+     *
+     * @return The empty style.
+     */
+    Theme::Style* getEmptyStyle();
+
 private:
 
     /**
@@ -406,22 +434,6 @@ private:
      */
     ~Theme();
 
-    /**
-     * Creates an instance of a Theme using the data from the Properties object defined at the specified URL, 
-     * where the URL is of the format "<file-path>.<extension>#<namespace-id>/<namespace-id>/.../<namespace-id>"
-     * (and "#<namespace-id>/<namespace-id>/.../<namespace-id>" is optional). 
-     * 
-     * @param url The URL pointing to the Properties object defining the theme.
-     *
-     * @return A new Theme.
-     */
-    static Theme* create(const char* url);
-
-    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;

+ 5 - 0
gameplay/src/ThemeStyle.cpp

@@ -39,6 +39,11 @@ Theme::Style::~Style()
         SAFE_RELEASE(_overlays[i]);
     }
 }
+
+Theme* Theme::Style::getTheme() const
+{
+    return _theme;
+}
     
 const char* Theme::Style::getId() const
 {

+ 9 - 0
gameplay/src/ThemeStyle.h

@@ -25,6 +25,15 @@ class Theme::Style
     friend class Container;
     friend class Form;
 
+public:
+
+    /**
+     * Get the theme this style belongs to.
+     *
+     * @return The theme this style belongs to.
+     */
+    Theme* getTheme() const;
+
 private:
 
     /**

+ 7 - 9
gameplay/src/VerticalLayout.h

@@ -13,11 +13,16 @@ namespace gameplay
  */
 class VerticalLayout : public Layout
 {
-    friend class Form;
-    friend class Container;
 
 public:
 
+    /**
+     * Create a VerticalLayout.
+     *
+     * @return a VerticalLayout object.
+     */
+    static VerticalLayout* create();
+
     /**
      * Set whether this layout will start laying out controls from the bottom of the container.
      * This setting defaults to 'false', meaning controls will start at the top.
@@ -52,13 +57,6 @@ protected:
      */
     virtual ~VerticalLayout();
 
-    /**
-     * Create a VerticalLayout.
-     *
-     * @return a VerticalLayout object.
-     */
-    static VerticalLayout* create();
-
     /**
      * Update the controls contained by the specified container.
      *

+ 1 - 0
gameplay/src/gameplay.h

@@ -7,6 +7,7 @@
 #include "Mouse.h"
 #include "FileSystem.h"
 #include "Bundle.h"
+#include "Gamepad.h"
 
 // Math
 #include "Rectangle.h"