Ver Fonte

Merge pull request #191 from blackberry-gaming/next-ablake

Updates to UI API
Sean Paul Taylor há 13 anos atrás
pai
commit
9c4b8fe353

+ 3 - 14
gameplay/src/Button.cpp

@@ -3,13 +3,12 @@
 
 namespace gameplay
 {
-    Button::Button() : _callback(NULL)
+    Button::Button()
     {
     }
 
     Button::~Button()
     {
-        SAFE_DELETE(_callback);
     }
 
     Button* Button::create(Theme::Style* style, Properties* properties)
@@ -32,23 +31,13 @@ namespace gameplay
         case Touch::TOUCH_PRESS:
             _state = Control::ACTIVE;
             _dirty = true;
-            return _consumeTouchEvents;
+            break;
         case Touch::TOUCH_RELEASE:
-            if (_callback &&
-                x > 0 && x <= _bounds.width &&
-                y > 0 && y <= _bounds.height)
-            {
-                // Button-clicked callback.
-                _callback->trigger(this);
-                setState(Control::NORMAL);
-                _dirty = true;
-                return _consumeTouchEvents;
-            }
             _dirty = true;
             setState(Control::NORMAL);
             break;
         }
 
-        return _consumeTouchEvents;
+        return Control::touchEvent(evt, x, y, contactIndex);
     }
 }

+ 0 - 59
gameplay/src/Button.h

@@ -26,20 +26,6 @@ class Button : public Label
 {
     friend class Container;
 
-    class Callback;
-
-public:
-    /**
-     * Set a callback method on this button.  The callback will be triggered when the button is
-     * clicked -- i.e. consecutive TOUCH_PRESS and TOUCH_RELEASE events that both fall within
-     * the bounds of the button.  
-     *
-     * @param instance The object to call the method on.
-     * @param callbackMethod The method to call.
-     */
-    template <class ClassType>
-    void setCallback(ClassType* instance, void (ClassType::*callbackMethod)(Control*));
-
 protected:
     /**
      * Create a button with a given style and properties.
@@ -68,55 +54,10 @@ protected:
     Button();
     virtual ~Button();
 
-    Callback* _callback;    // The callback method pointer interface.
-
 private:
     Button(const Button& copy);
-
-    /**
-     * Abstract callback interface.
-     */
-    class Callback
-    {
-    public:
-        virtual ~Callback() { }
-        virtual void trigger(Control* button) = 0;
-    };
-
-    /**
-     * Implementation of the callback interface for a specific class.
-     */
-    template <class ClassType>
-    class CallbackImpl : public Callback
-    {
-        typedef void (ClassType::*CallbackMethod)(Control*);
-    public:
-        CallbackImpl(ClassType* instance, CallbackMethod method);
-        void trigger(Control* control);
-    private:
-        ClassType* _instance;
-        CallbackMethod _method;
-    };
 };
 
-template <class ClassType>
-Button::CallbackImpl<ClassType>::CallbackImpl(ClassType* instance, CallbackMethod method)
-    : _instance(instance), _method(method)
-{
-}
-
-template <class ClassType>
-void Button::setCallback(ClassType* instance, void (ClassType::*callbackMethod)(Control*))
-{
-    _callback = new CallbackImpl<ClassType>(instance, callbackMethod);
-}
-
-template <class ClassType>
-void Button::CallbackImpl<ClassType>::trigger(Control* control)
-{
-    (_instance->*_method)(control);
-}
-
 }
 
 #endif

+ 16 - 18
gameplay/src/CheckBox.cpp

@@ -50,6 +50,17 @@ const Vector2& CheckBox::getIconSize() const
     return _iconSize;
 }
 
+void CheckBox::addListener(Control::Listener* listener, int eventFlags)
+{
+    if ((eventFlags & Listener::TEXT_CHANGED) == Listener::TEXT_CHANGED)
+    {
+        WARN("TEXT_CHANGED event is not applicable to CheckBox.");
+        eventFlags &= ~Listener::TEXT_CHANGED;
+    }
+
+    Control::addListener(listener, eventFlags);
+}
+
 bool CheckBox::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
 {
     if (!isEnabled())
@@ -67,6 +78,7 @@ bool CheckBox::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int cont
                     y > 0 && y <= _bounds.height)
                 {
                     _checked = !_checked;
+                    alertListeners(Listener::VALUE_CHANGED);
                 }
             }
         }
@@ -89,8 +101,8 @@ void CheckBox::update(const Rectangle& clip)
     }
     float iconWidth = size.x;
 
-    _clip.x += iconWidth;
-    _clip.width -= iconWidth;
+    _textBounds.x += iconWidth;
+    _textBounds.width -= iconWidth;
 }
 
 void CheckBox::drawSprites(SpriteBatch* spriteBatch, const Rectangle& clip)
@@ -123,28 +135,14 @@ void CheckBox::drawSprites(SpriteBatch* spriteBatch, const Rectangle& clip)
         if (_checked)
         {
             const Theme::UVs on = icon->getOnUVs();
-            spriteBatch->draw(pos.x, pos.y, size.x, size.y, on.u1, on.v1, on.u2, on.v2, color);
+            spriteBatch->draw(pos.x, pos.y, size.x, size.y, on.u1, on.v1, on.u2, on.v2, color, _clip);
         }
         else
         {
             const Theme::UVs off = icon->getOffUVs();
-            spriteBatch->draw(pos.x, pos.y, size.x, size.y, off.u1, off.v1, off.u2, off.v2, color);
+            spriteBatch->draw(pos.x, pos.y, size.x, size.y, off.u1, off.v1, off.u2, off.v2, color, _clip);
         }
     }
 }
 
-void CheckBox::drawText(const Rectangle& clip)
-{
-    // TODO: Batch all labels that use the same font.
-    Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
-    Font* font = overlay->getFont();
-    
-    // Draw the text.
-    font->begin();
-    font->drawText(_text.c_str(), _clip, overlay->getTextColor(), overlay->getFontSize(), overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
-    font->end();
-
-    _dirty = false;
-}
-
 }

+ 12 - 7
gameplay/src/CheckBox.h

@@ -51,6 +51,18 @@ public:
      */
     const Vector2& getIconSize() const;
 
+    /**
+     * Add a listener to be notified of specific events affecting
+     * this control.  Event types can be OR'ed together.
+     * E.g. To listen to touch-press and touch-release events,
+     * pass <code>Control::Listener::TOUCH | Control::Listener::RELEASE</code>
+     * as the second parameter.
+     *
+     * @param listener The listener to add.
+     * @param eventFlags The events to listen for.
+     */
+    virtual void addListener(Control::Listener* listener, int eventFlags);
+
 protected:
     CheckBox();
     ~CheckBox();
@@ -95,13 +107,6 @@ protected:
      */
     void drawSprites(SpriteBatch* spriteBatch, const Rectangle& clip);
 
-    /**
-     * Draw this control's text.
-     *
-     * @param position The container position this control is relative to.
-     */
-    void drawText(const Rectangle& clip);
-
     bool _checked;      // Whether this checkbox is currently checked.
     Vector2 _iconSize;  // The size to draw the checkbox icon, if different from its size in the texture.
 

+ 113 - 2
gameplay/src/Control.cpp

@@ -5,7 +5,7 @@ namespace gameplay
 {
     Control::Control()
         : _id(""), _state(Control::NORMAL), _position(Vector2::zero()), _size(Vector2::zero()), _bounds(Rectangle::empty()), _clip(Rectangle::empty()),
-          _autoWidth(true), _autoHeight(true), _dirty(true), _consumeTouchEvents(true)
+          _autoWidth(true), _autoHeight(true), _dirty(true), _consumeTouchEvents(true), _listeners(NULL)
     {
     }
 
@@ -15,6 +15,15 @@ namespace gameplay
 
     Control::~Control()
     {
+        if (_listeners)
+        {
+            for (ListenerMap::const_iterator itr = _listeners->begin(); itr != _listeners->end(); itr++)
+            {
+                std::list<Listener*>* list = itr->second;
+                SAFE_DELETE(list);
+            }
+            SAFE_DELETE(_listeners);
+        }
     }
 
     void Control::init(Theme::Style* style, Properties* properties)
@@ -76,6 +85,11 @@ namespace gameplay
 
     void Control::setStyle(Theme::Style* style)
     {
+        if (style != _style)
+        {
+            _dirty = true;
+        }
+
         _style = style;
     }
 
@@ -136,9 +150,78 @@ namespace gameplay
         return _consumeTouchEvents;
     }
 
+    void Control::addListener(Control::Listener* listener, int eventFlags)
+    {
+        if ((eventFlags & Listener::PRESS) == Listener::PRESS)
+        {
+            addSpecificListener(listener, Listener::PRESS);
+        }
+
+        if ((eventFlags & Listener::RELEASE) == Listener::RELEASE)
+        {
+            addSpecificListener(listener, Listener::RELEASE);
+        }
+
+        if ((eventFlags & Listener::CLICK) == Listener::CLICK)
+        {
+            addSpecificListener(listener, Listener::CLICK);
+        }
+
+        if ((eventFlags & Listener::VALUE_CHANGED) == Listener::VALUE_CHANGED)
+        {
+            addSpecificListener(listener, Listener::VALUE_CHANGED);
+        }
+
+        if ((eventFlags & Listener::TEXT_CHANGED) == Listener::TEXT_CHANGED)
+        {
+            addSpecificListener(listener, Listener::TEXT_CHANGED);
+        }
+    }
+
+    void Control::addSpecificListener(Control::Listener* listener, Listener::EventType eventType)
+    {
+        if (!_listeners)
+        {
+            _listeners = new std::map<Listener::EventType, std::list<Listener*>*>();
+        }
+
+        ListenerMap::const_iterator itr = _listeners->find(eventType);
+        if (itr == _listeners->end())
+        {
+            _listeners->insert(std::make_pair(eventType, new std::list<Listener*>()));
+            itr = _listeners->find(eventType);
+        }
+
+        std::list<Listener*>* listenerList = itr->second;
+        listenerList->push_back(listener);
+    }
+
     bool Control::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
     {
-        // Empty stub to be implemented by Button and its descendents.
+        if (!isEnabled())
+        {
+            return false;
+        }
+
+        switch (evt)
+        {
+        case Touch::TOUCH_PRESS:
+            alertListeners(Listener::PRESS);
+            break;
+            
+        case Touch::TOUCH_RELEASE:
+            // Always trigger Listener::RELEASE
+            alertListeners(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)
+            {
+                alertListeners(Listener::CLICK);
+            }
+            break;
+        }
+
         return _consumeTouchEvents;
     }
 
@@ -146,6 +229,22 @@ namespace gameplay
     {
     }
 
+    void Control::alertListeners(Listener::EventType eventType)
+    {
+        if (_listeners)
+        {
+            ListenerMap::const_iterator itr = _listeners->find(eventType);
+            if (itr != _listeners->end())
+            {
+                std::list<Listener*>* listenerList = itr->second;
+                for (std::list<Listener*>::iterator listenerItr = listenerList->begin(); listenerItr != listenerList->end(); listenerItr++)
+                {
+                    (*listenerItr)->controlEvent(this, eventType);
+                }
+            }
+        }
+    }
+
     void Control::update(const Rectangle& clip)
     {
         // Calculate the bounds.
@@ -185,6 +284,8 @@ namespace gameplay
         width = _size.x - border.left - padding.left - border.right - padding.right;
         height = _size.y - border.top - padding.top - border.bottom - padding.bottom;
 
+        _textBounds.set(x, y, width, height);
+
         clipX2 = clip.x + clip.width;
         x2 = x + width;
         if (x2 > clipX2)
@@ -199,6 +300,16 @@ namespace gameplay
             height = clipY2 - y;
         }
 
+        if (x < clip.x)
+        {
+            x = clip.x;
+        }
+
+        if (y < clip.y)
+        {
+            y = clip.y;
+        }
+
         _clip.set(x, y, width, height);
     }
 

+ 40 - 1
gameplay/src/Control.h

@@ -35,6 +35,23 @@ public:
         DISABLED
     };
 
+    class Listener
+    {
+    public:
+        enum EventType
+        {
+            PRESS           = 0x01,
+            RELEASE         = 0x02,
+            CLICK           = 0x04,
+            VALUE_CHANGED   = 0x08,
+            TEXT_CHANGED    = 0x10,
+            //MOUSE           = PRESS | RELEASE | CLICK,
+            //ALL             = MOUSE | VALUE_CHANGED | TEXT_CHANGED 
+        };
+
+        virtual void controlEvent(Control* control, EventType evt) = 0;
+    };
+
     /**
      * Get this control's ID string.
      *
@@ -158,6 +175,18 @@ public:
      */
     Theme::Style* getStyle() const;
 
+    /**
+     * Add a listener to be notified of specific events affecting
+     * this control.  Event types can be OR'ed together.
+     * E.g. To listen to touch-press and touch-release events,
+     * pass <code>Control::Listener::TOUCH | Control::Listener::RELEASE</code>
+     * as the second parameter.
+     *
+     * @param listener The listener to add.
+     * @param eventFlags The events to listen for.
+     */
+    virtual void addListener(Control::Listener* listener, int eventFlags);
+
 protected:
     Control();
     virtual ~Control();
@@ -246,17 +275,27 @@ protected:
      */
     static State getStateFromString(const char* state);
 
+    /**
+     * Alert all listeners of a specific event.
+     */
+    void alertListeners(Listener::EventType eventType);
+
+    void addSpecificListener(Control::Listener* listener, Listener::EventType eventType);
+
     std::string _id;
     State _state;           // Determines overlay used during draw().
     Vector2 _position;      // Position, relative to parent container's clipping window.
     Vector2 _size;          // Desired size.  Will be clipped.
     Rectangle _bounds;      // The position and size of this control, relative to parent container's bounds, including border and padding, after clipping.
-    Rectangle _clip;        // Clipping window of this control's content.
+    Rectangle _textBounds;  // The position and size of this control's content, before clipping.  Used for text alignment.
+    Rectangle _clip;        // Clipping window of this control's content, after clipping.
     bool _autoWidth;
     bool _autoHeight;
     bool _dirty;
     bool _consumeTouchEvents;
     Theme::Style* _style;
+    typedef std::map<Listener::EventType, std::list<Listener*>*> ListenerMap;
+    ListenerMap* _listeners;
 
 private:
     Control(const Control& copy);

+ 9 - 2
gameplay/src/Font.cpp

@@ -289,7 +289,7 @@ void Font::drawText(const char* text, int x, int y, const Vector4& color, unsign
     }
 }
 
-void Font::drawText(const char* text, const Rectangle& area, const Vector4& color, unsigned int size, Justify justify, bool wrap, bool rightToLeft)
+void Font::drawText(const char* text, const Rectangle& area, const Vector4& color, unsigned int size, Justify justify, bool wrap, bool rightToLeft, const Rectangle* clip)
 {
     if (size == 0)
         size = _size;
@@ -577,7 +577,14 @@ void Font::drawText(const char* text, const Rectangle& area, const Vector4& colo
                     // Draw this character.
                     if (draw)
                     {
-                        _batch->draw(xPos, yPos, g.width * scale, size, g.uvs[0], g.uvs[1], g.uvs[2], g.uvs[3], color);
+                        if (clip)
+                        {
+                            _batch->draw(xPos, yPos, g.width * scale, size, g.uvs[0], g.uvs[1], g.uvs[2], g.uvs[3], color, *clip);
+                        }
+                        else
+                        {
+                            _batch->draw(xPos, yPos, g.width * scale, size, g.uvs[0], g.uvs[1], g.uvs[2], g.uvs[3], color);
+                        }
                     }
                 }
                 xPos += g.width*scale + ((float)size*0.125f);

+ 5 - 4
gameplay/src/Font.h

@@ -141,15 +141,16 @@ public:
      * Clips text outside the viewport.  Optionally wraps text to fit within the width of the viewport.
      *
      * @param text The text to draw.
-     * @param clip The viewport area to draw within.  Text will be clipped outside this rectangle.
+     * @param area The viewport area to draw within.  Text will be clipped outside this rectangle.
      * @param color The color of text.
      * @param size The size to draw text (0 for default size).
      * @param justify Justification of text within the viewport.
      * @param wrap Wraps text to fit within the width of the viewport if true.
-     * @param rightToLeft
+     * @param rightToLeft Whether to draw text from right to left.
+     * @param clip A region to clip text within after applying justification to the viewport area.
      */
-    void drawText(const char* text, const Rectangle& clip, const Vector4& color,
-        unsigned int size = 0, Justify justify = ALIGN_TOP_LEFT, bool wrap = true, bool rightToLeft = false);
+    void drawText(const char* text, const Rectangle& area, const Vector4& color,
+        unsigned int size = 0, Justify justify = ALIGN_TOP_LEFT, bool wrap = true, bool rightToLeft = false, const Rectangle* clip = NULL);
 
     /**
      * Measures a string's width and height without alignment, wrapping or clipping.

+ 8 - 11
gameplay/src/Form.cpp

@@ -134,20 +134,17 @@ namespace gameplay
 
     void Form::setNode(Node* node)
     {
-        // Set this Form up to be 3D by initializing a quad, projection matrix and viewport.
-        if (!_quad)
+        _node = node;
+
+        if (_node && !_quad)
         {
+            // Set this Form up to be 3D by initializing a quad, projection matrix and viewport.
             setQuad(0.0f, 0.0f, _size.x, _size.y);
-        }
-
-        Matrix::createOrthographicOffCenter(0, _size.x, _size.y, 0, 0, 1, &_projectionMatrix);
-        _theme->setProjectionMatrix(_projectionMatrix);
-        _viewport = new Viewport(0, 0, _size.x, _size.y);
 
-        // Connect the new node.
-        _node = node;
-        if (_node)
-        {
+            Matrix::createOrthographicOffCenter(0, _size.x, _size.y, 0, 0, 1, &_projectionMatrix);
+            _theme->setProjectionMatrix(_projectionMatrix);
+            _viewport = new Viewport(0, 0, _size.x, _size.y);
+            
             _node->setModel(_quad);
         }
     }

+ 18 - 1
gameplay/src/Label.cpp

@@ -33,6 +33,23 @@ namespace gameplay
             _text = text;
         }
     }
+
+    void Label::addListener(Control::Listener* listener, int eventFlags)
+    {
+        if ((eventFlags & Listener::TEXT_CHANGED) == Listener::TEXT_CHANGED)
+        {
+            WARN("TEXT_CHANGED event is not applicable to this control.");
+            eventFlags &= ~Listener::TEXT_CHANGED;
+        }
+
+        if ((eventFlags & Listener::VALUE_CHANGED) == Listener::VALUE_CHANGED)
+        {
+            WARN("VALUE_CHANGED event is not applicable to this control.");
+            eventFlags &= ~Listener::VALUE_CHANGED;
+        }
+
+        Control::addListener(listener, eventFlags);
+    }
     
     void Label::setText(const char* text)
     {
@@ -58,7 +75,7 @@ namespace gameplay
 
         // Draw the text.
         font->begin();
-        font->drawText(_text.c_str(), _clip, overlay->getTextColor(), overlay->getFontSize(), overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
+        font->drawText(_text.c_str(), _textBounds, overlay->getTextColor(), overlay->getFontSize(), overlay->getTextAlignment(), true, overlay->getTextRightToLeft(), &_clip);
         font->end();
 
         _dirty = false;

+ 13 - 1
gameplay/src/Label.h

@@ -39,6 +39,18 @@ public:
      */
     const char* getText();
 
+    /**
+     * Add a listener to be notified of specific events affecting
+     * this control.  Event types can be OR'ed together.
+     * E.g. To listen to touch-press and touch-release events,
+     * pass <code>Control::Listener::TOUCH | Control::Listener::RELEASE</code>
+     * as the second parameter.
+     *
+     * @param listener The listener to add.
+     * @param eventFlags The events to listen for.
+     */
+    virtual void addListener(Control::Listener* listener, int eventFlags);
+
 protected:
     Label();
     virtual ~Label();
@@ -65,7 +77,7 @@ protected:
      */
     void drawText(const Rectangle& clip);
 
-    std::string _text;  // The text displayed by this label.
+    std::string _text;      // The text displayed by this label.
 
 private:
     Label(const Label& copy);

+ 3 - 0
gameplay/src/Node.cpp

@@ -36,12 +36,15 @@ Node::~Node()
         _audioSource->setNode(NULL);
     if (_particleEmitter)
         _particleEmitter->setNode(NULL);
+    if (_form)
+        _form->setNode(NULL);
 
     SAFE_RELEASE(_camera);
     SAFE_RELEASE(_light);
     SAFE_RELEASE(_model);
     SAFE_RELEASE(_audioSource);
     SAFE_RELEASE(_particleEmitter);
+    SAFE_RELEASE(_form);
     SAFE_DELETE(_physicsRigidBody);
 }
 

+ 19 - 18
gameplay/src/RadioButton.cpp

@@ -65,6 +65,17 @@ const Vector2& RadioButton::getIconSize() const
     return _iconSize;
 }
 
+void RadioButton::addListener(Control::Listener* listener, int eventFlags)
+{
+    if ((eventFlags & Listener::TEXT_CHANGED) == Listener::TEXT_CHANGED)
+    {
+        WARN("TEXT_CHANGED event is not applicable to RadioButton.");
+        eventFlags &= ~Listener::TEXT_CHANGED;
+    }
+
+    Control::addListener(listener, eventFlags);
+}
+
 bool RadioButton::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
 {
     if (!isEnabled())
@@ -81,8 +92,12 @@ bool RadioButton::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int c
                 if (x > 0 && x <= _bounds.width &&
                     y > 0 && y <= _bounds.height)
                 {
-                    RadioButton::clearSelected(_groupId);
-                    _selected = true;
+                    if (!_selected)
+                    {
+                        RadioButton::clearSelected(_groupId);
+                        _selected = true;
+                        alertListeners(Listener::VALUE_CHANGED);
+                    }
                 }
             }
         }
@@ -158,22 +173,8 @@ void RadioButton::update(const Rectangle& clip)
     }
     float iconWidth = size.x;
 
-    _clip.x += iconWidth;
-    _clip.width -= iconWidth;
-}
-
-void RadioButton::drawText(const Rectangle& clip)
-{
-    // TODO: Batch all labels that use the same font.
-    Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
-    Font* font = overlay->getFont();
-    
-    // Draw the text.
-    font->begin();
-    font->drawText(_text.c_str(), _clip, overlay->getTextColor(), overlay->getFontSize(), overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
-    font->end();
-
-    _dirty = false;
+    _textBounds.x += iconWidth;
+    _textBounds.width -= iconWidth;
 }
 
 }

+ 12 - 2
gameplay/src/RadioButton.h

@@ -53,6 +53,18 @@ public:
      */
     const Vector2& getIconSize() const;
 
+    /**
+     * Add a listener to be notified of specific events affecting
+     * this control.  Event types can be OR'ed together.
+     * E.g. To listen to touch-press and touch-release events,
+     * pass <code>Control::Listener::TOUCH | Control::Listener::RELEASE</code>
+     * as the second parameter.
+     *
+     * @param listener The listener to add.
+     * @param eventFlags The events to listen for.
+     */
+    virtual void addListener(Control::Listener* listener, int eventFlags);
+
 protected:
     RadioButton();
     virtual ~RadioButton();
@@ -73,8 +85,6 @@ protected:
 
     void drawSprites(SpriteBatch* spriteBatch, const Rectangle& clip);
 
-    void drawText(const Rectangle& clip);
-
     // Clear the _selected flag of all radio buttons in the given group.
     static void clearSelected(const std::string& groupId);
 

+ 14 - 4
gameplay/src/Slider.cpp

@@ -64,6 +64,17 @@ void Slider::setValue(float value)
     _value = MATH_CLAMP(value, _min, _max);
 }
 
+void Slider::addListener(Control::Listener* listener, int eventFlags)
+{
+    if ((eventFlags & Listener::TEXT_CHANGED) == Listener::TEXT_CHANGED)
+    {
+        WARN("TEXT_CHANGED event is not applicable to Slider.");
+        eventFlags &= ~Listener::TEXT_CHANGED;
+    }
+
+    Control::addListener(listener, eventFlags);
+}
+
 bool Slider::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
 {
     if (!isEnabled())
@@ -75,7 +86,6 @@ bool Slider::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contac
     {
     case Touch::TOUCH_PRESS:
         _state = Control::ACTIVE;
-        _dirty = true;
         // Fall through to calculate new value.
 
     case Touch::TOUCH_MOVE:
@@ -121,16 +131,16 @@ bool Slider::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contac
             }
 
             // Call the callback if our value changed.
-            if (_callback && _value != oldValue)
+            if (_value != oldValue)
             {
-                _callback->trigger(this);
+                alertListeners(Listener::VALUE_CHANGED);
             }
 
             _dirty = true;
         }
     }
 
-    return _consumeTouchEvents;
+    return Control::touchEvent(evt, x, y, contactIndex);
 }
 
 void Slider::drawSprites(SpriteBatch* spriteBatch, const Rectangle& clip)

+ 3 - 1
gameplay/src/Slider.h

@@ -27,7 +27,7 @@ namespace gameplay
  *      text        = <string>                  // Text to display above, below or alongside the slider (depending on the style).
  *  }
  */
-class Slider : public Button
+class Slider : public Label
 {
     friend class Container;
 
@@ -90,6 +90,8 @@ public:
      */
     float getValue();
 
+    void addListener(Control::Listener* listener, int eventFlags);
+
 protected:
     Slider();
     ~Slider();

+ 19 - 16
gameplay/src/TextBox.cpp

@@ -4,9 +4,7 @@
 namespace gameplay
 {
 
-static std::vector<TextBox*> __textBoxes;
-
-TextBox::TextBox()
+TextBox::TextBox() : _lastKeypress(0)
 {
 }
 
@@ -22,24 +20,13 @@ TextBox* TextBox::create(Theme::Style* style, Properties* properties)
 {
     TextBox* textBox = new TextBox();
     textBox->init(style, properties);
-    __textBoxes.push_back(textBox);
 
     return textBox;
 }
 
-TextBox* TextBox::getTextBox(const char* id)
+int TextBox::getLastKeypress()
 {
-    std::vector<TextBox*>::const_iterator it;
-    for (it = __textBoxes.begin(); it < __textBoxes.end(); it++)
-    {
-        TextBox* t = *it;
-        if (strcmp(id, t->getID()) == 0)
-        {
-            return t;
-        }
-    }
-
-    return NULL;
+    return _lastKeypress;
 }
 
 void TextBox::setCursorLocation(int x, int y)
@@ -57,6 +44,17 @@ void TextBox::setCursorLocation(int x, int y)
                        y - border.top - padding.top + _clip.y);
 }
 
+void TextBox::addListener(Control::Listener* listener, int eventFlags)
+{
+    if ((eventFlags & Listener::VALUE_CHANGED) == Listener::VALUE_CHANGED)
+    {
+        WARN("VALUE_CHANGED event is not applicable to TextBox.");
+        eventFlags &= ~Listener::VALUE_CHANGED;
+    }
+
+    Control::addListener(listener, eventFlags);
+}
+
 bool TextBox::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
 {   
     if (!isEnabled())
@@ -144,6 +142,7 @@ void TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
                         font->getLocationAtIndex(_text.c_str(), _clip, fontSize, &_cursorLocation, textIndex,
                             overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
                         _dirty = true;
+                        alertListeners(Listener::TEXT_CHANGED);
                         break;
                     }
                     case Keyboard::KEY_LEFT_ARROW:
@@ -241,9 +240,13 @@ void TextBox::keyEvent(Keyboard::KeyEvent evt, int key)
             
                     break;
                 }
+
+                alertListeners(Listener::TEXT_CHANGED);
             }
         }
     }
+
+    _lastKeypress = key;
 }
 
 void TextBox::update(const Rectangle& clip)

+ 43 - 8
gameplay/src/TextBox.h

@@ -9,11 +9,49 @@
 namespace gameplay
 {
 
+/**
+ * An editable text label.  Tap or click within the text box to bring up the
+ * virtual keyboard.
+ *
+ * Listeners can listen for a TEXT_CHANGED event, and then query the text box
+ * for the last keypress it received.
+ *
+ * The following properties are available for text boxes:
+ *
+ * label <Label ID>
+ * {
+ *      style       = <Style ID>
+ *      position    = <x, y>
+ *      size        = <width, height>
+ *      text        = <string>
+ * }
+ */
 class TextBox : public Label
 {
+    friend class Container;
+
 public:
+    /**
+     * Add a listener to be notified of specific events affecting
+     * this control.  Event types can be OR'ed together.
+     * E.g. To listen to touch-press and touch-release events,
+     * pass <code>Control::Listener::TOUCH | Control::Listener::RELEASE</code>
+     * as the second parameter.
+     *
+     * @param listener The listener to add.
+     * @param eventFlags The events to listen for.
+     */
+    virtual void addListener(Control::Listener* listener, int eventFlags);
+
+    int getLastKeypress();
+
+protected:
+    TextBox();
+    ~TextBox();
+
     static TextBox* create(Theme::Style* style, Properties* properties);
-    static TextBox* getTextBox(const char* id);
+
+    void setCursorLocation(int x, int y);
 
     bool touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
 
@@ -24,15 +62,12 @@ public:
     // Draw the cursor.
     void drawSprites(SpriteBatch* spriteBatch, const Rectangle& clip);
 
-private:
-    TextBox();
-    TextBox(const TextBox& copy);
-    ~TextBox();
-
-    void setCursorLocation(int x, int y);
-
     Vector2 _cursorLocation;
     unsigned int textIndex;
+    int _lastKeypress;
+
+private:
+    TextBox(const TextBox& copy);
 };
 
 }

+ 244 - 16
gameplay/src/Theme.h

@@ -1,7 +1,3 @@
-/*
- * Theme.h
- */
-
 #ifndef THEME_H_
 #define THEME_H_
 
@@ -19,10 +15,107 @@ static const unsigned int MAX_OVERLAYS = 4;
 static const unsigned int MAX_OVERLAY_REGIONS = 9;
 
 /**
- * This class represents the appearance of an UI form described in a 
- * theme file. A theme file, at its simplest, contains a source texture atlas,
- * the texture coordinates of each control in its different mode. Once loaded,
- * the appearance properties can be retrieved using a style id and set on a UI control.
+ * A theme is created and stored as part of a form and represents its appearance.
+ * Once loaded, the appearance properties can be retrieved from their style IDs and set on other
+ * UI controls.  A Theme has one property, 'texture', which points to an image containing
+ * all the Icon, Cursor, Slider, and Container sprites used by the theme.  Each set of sprites within
+ * the texture is described in its own namespace.  The rest of the Theme consists of Style namespaces.
+ * A Style describes the border, margin, and padding of a Control, what icons and cursors (if any) are
+ * associated with a Control, and Font properties to apply to a Control's text.
+ *
+ * Below is an explanation of the properties that can be set within themes:
+ *
+ * theme
+ * {
+ *    texture = <Path to texture>
+ *
+ *    // Describes the images used for CheckBox and RadioButton icons.
+ *    icon <iconID>
+ *    {
+ *        size            =   <width, height>     // Size of this icon.
+ *        offPosition     =   <x, y>              // Position of the unchecked / unselected image.
+ *        onPosition      =   <x, y>              // Position of the checked / selected image.
+ *        color           =   <#ffffffff>         // Tint to apply to icon.
+ *    }
+ *   
+ *    cursor <cursorID>
+ *    {
+ *        region  =   <x, y, width, height>   // Region within the texture of cursor sprite.
+ *        color   =   <#ffffffff>             // Tint to apply to cursor.
+ *    }
+ *   
+ *    slider <sliderID>
+ *    {
+ *        minCapRegion    =   <x, y, width, height>   // Left / bottom slider cap.
+ *        maxCapRegion    =   <x, y, width, height>   // Right / top slider cap.
+ *        markerRegion    =   <x, y, width, height>   // Shows the slider's current position.
+ *        trackRegion     =   <x, y, width, height>   // Track the marker slides along.
+ *        color           =   <#ffffffff>             // Tint to apply to slider sprites.
+ *    }
+ *   
+ *    // Defines the border and background of a Control.
+ *    container <containerID>
+ *    {
+ *        // The corners and edges of the given region will be used as border sprites.
+ *        border
+ *        {
+ *            top     =   <int>   // Height of top border, top corners.
+ *            bottom  =   <int>   // Height of bottom border, bottom corners.
+ *            left    =   <int>   // Width of left border, left corners.
+ *            right   =   <int>   // Width of right border, right corners.
+ *        }
+ *       
+ *        region  =   <x, y, width, height>   // Total container region including entire border.
+ *        color   =   <#ffffffff>             // Tint to apply to container sprites.
+ *    }
+ *   
+ *    style <styleID>
+ *    {
+ *        // Layouts may make use of a style's margin to put space between adjacent controls.
+ *        margin
+ *        {
+ *            top     =   <int>
+ *            bottom  =   <int>
+ *            left    =   <int>
+ *            right   =   <int>        
+ *        }
+ *       
+ *        // Padding is the space between a control's border and its content.
+ *        padding
+ *        {
+ *            top     =   <int>
+ *            bottom  =   <int>
+ *            left    =   <int>
+ *            right   =   <int>        
+ *        }
+ *       
+ *        // This overlay is used when a control is enabled but not active or focused.
+ *        normal
+ *        {
+ *            container   =   <containerID>               // Container to use for border and background sprites.
+ *            checkBox    =   <iconID>                    // Icon to use for checkbox sprites.
+ *            radioButton =   <iconID>                    // Icon to use for radioButton sprites.
+ *            slider      =   <sliderID>                  // Slider to use for slider sprites.
+ *            mouseCursor =   <cursorID>                  // Cursor to use when the mouse is over this control.
+ *            textCursor  =   <cursorID>                  // Cursor to use within a textBox.
+ *            font        =   <Path to font>              // Font to use for rendering text.
+ *            fontSize    =   <int>                       // Size of text.
+ *            textColor   =   <#ffffffff>                 // Color of text.
+ *            alignment   =   <Text alignment constant>   // Member of Font::Justify enum.
+ *            rightToLeft =   <bool>                      // Whether to draw text from right to left.
+ *        }
+ *       
+ *        // This overlay is used when the control is in focus.
+ *        // If not specified, the 'normal' overlay will be used.
+ *        focus{}
+ *       
+ *        // This overlay is used when the control is active.
+ *        // (Touch or mouse is down within the control.)
+ *        // If not specified, the 'normal' overlay will be used.
+ *        active{}
+ *    }
+ * }
+ *
  */
 class Theme: public Ref
 {
@@ -58,6 +151,9 @@ private:
     SpriteBatch* getSpriteBatch() const;
 
 public:
+    /**
+     * Struct representing the UV coordinates of a rectangular image.
+     */
     typedef struct UVs
     {
         float u1;
@@ -66,6 +162,10 @@ public:
         float v2;
     } UVs;
 
+    /**
+     * Struct representing margin, border, and padding areas by
+     * the width or height of each side.
+     */
     typedef struct padding
     {
         float top;
@@ -76,15 +176,46 @@ public:
         padding() : top(0), bottom(0), left(0), right(0) {}
     } Margin, Border, Padding;
 
+    /**
+     * An icon with two images representing "on" and "off" states.
+     */
     class Icon : public Ref
     {
         friend class Theme;
 
     public:
+        /**
+         * Get this icon's ID.
+         *
+         * @return This icon's ID.
+         */
         const char* getId() const;
+
+        /**
+         * Get this icon's size.
+         *
+         * @return This icon's size.
+         */
         const Vector2& getSize() const;
+        /**
+         * Get this icon's color.
+         *
+         * @return This icon's color.
+         */
         const Vector4& getColor() const;
+
+        /**
+         * Get the UVs for this icon's off-state image.
+         *
+         * @return The UVs for this icon's off-state image.
+         */
         const Theme::UVs& getOffUVs() const;
+
+        /**
+         * Get the UVs for this icon's on-state image.
+         *
+         * @return The UVs for this icon's on-state image.
+         */
         const Theme::UVs& getOnUVs() const;
 
     private:
@@ -102,20 +233,83 @@ public:
         UVs _on;
     };
 
+    /**
+     * A set of four icons that make up a slider:
+     * two end-caps, a track, and a marker to be placed along the track.
+     */
     class SliderIcon : public Ref
     {
         friend class Theme;
 
     public:
+        /**
+         * Get this slider icon's ID.
+         *
+         * @return This slider icon's ID.
+         */
         const char* getId() const;
+
+        /**
+         * Get the UVs for this slider's min-cap image.
+         *
+         * @return The UVs for this slider's min-cap image.
+         */
         const Theme::UVs& getMinCapUVs() const;
+
+        /**
+         * Get the UVs for this slider's max-cap image.
+         *
+         * @return The UVs for this slider's max-cap image.
+         */
         const Theme::UVs& getMaxCapUVs() const;
+
+        /**
+         * Get the UVs for this slider's marker image.
+         *
+         * @return The UVs for this slider's marker image.
+         */
         const Theme::UVs& getMarkerUVs() const;
+
+        /**
+         * Get the UVs for this slider's track image.
+         *
+         * @return The UVs for this slider's track image.
+         */
         const Theme::UVs& getTrackUVs() const;
+
+        /**
+         * Get this slider's min-cap size.
+         *
+         * @return This slider's min-cap size.
+         */
         const Vector2& getMinCapSize() const;
+
+        /**
+         * Get this slider's max-cap size.
+         *
+         * @return This slider's max-cap size.
+         */
         const Vector2& getMaxCapSize() const;
+
+        /**
+         * Get this slider's marker size.
+         *
+         * @return This slider's marker size.
+         */
         const Vector2& getMarkerSize() const;
+
+        /**
+         * Get this slider's track size.
+         *
+         * @return This slider's track size.
+         */
         const Vector2& getTrackSize() const;
+
+        /**
+         * Get this slider's color.
+         *
+         * @return This slider's color.
+         */
         const Vector4& getColor() const;
 
     private:
@@ -149,16 +343,30 @@ public:
     public:
        /**
         * Returns the Id of this Cursor.
+        *
+        * @return This cursor's ID.
         */
         const char* getId() const;
 
        /**
         * Gets a UV coordinates computed from the texture region.
+        *
+        * @return This cursor's UVs.
         */
         const Theme::UVs& getUVs() const;
 
+        /**
+         * Gets this cursor's size.
+         *
+         * @return This cursor's size.
+         */
         const Vector2& getSize() const;
 
+        /**
+         * Gets this cursor's color.
+         *
+         * @return This cursor's color.
+         */
         const Vector4& getColor() const;
     
     private:
@@ -174,6 +382,9 @@ public:
         Vector4 _color;
     };
 
+    /**
+     * A container region defines the border and background of a control.
+     */
     class ContainerRegion : public Ref
     {
         friend class Theme;
@@ -186,19 +397,33 @@ public:
             BOTTOM_LEFT, BOTTOM, BOTTOM_RIGHT
         };
 
+        /**
+         * Gets this container area's ID.
+         *
+         * @return This container area's ID.
+         */
         const char* getId() const;
+
+        /**
+         * Gets this container area's border.
+         *
+         * @return This container area's border.
+         */
         const Theme::Border& getBorder() const;
-        const Theme::UVs& getUVs(ContainerArea area) const;
-        const Vector4& getColor() const;
-        
+
         /**
-         * Set the size of this ContainerRegion's border.
+         * Gets this container area's UVs.
          *
-         * When auto-size is set on width and/or height:
-         * Space added to the calculated (tightly bound) width and height.
+         * @return This container area's UVs.
+         */
+        const Theme::UVs& getUVs(ContainerArea area) const;
+
+        /**
+         * Gets this container area's color.
          *
+         * @return This container area's color.
          */
-        //void setBorder(float top, float bottom, float left, float right);
+        const Vector4& getColor() const;
 
     private:
         ContainerRegion(const Texture& texture, const Rectangle& region, const Theme::Border& border, const Vector4& color);
@@ -214,7 +439,10 @@ public:
     };
     
     /**
-     * This class represents the apperance of a control.
+     * This class represents the appearance of a control.  A style can have padding and margin values,
+     * as well as overlays for each of the control's states.  Each overlay in turn can reference
+     * the above classes to determine the border, background, cursor, and icon settings to use for
+     * a particular state.
      */
     class Style
     {