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

Merge pull request #1683 from louis-mclaughlin/next

Added support for JoystickControl scaling
Sean Taylor преди 11 години
родител
ревизия
2fe37c3236
променени са 5 файла, в които са добавени 408 реда и са изтрити 171 реда
  1. 26 31
      gameplay/src/Control.cpp
  2. 49 1
      gameplay/src/Control.h
  3. 261 122
      gameplay/src/JoystickControl.cpp
  4. 64 9
      gameplay/src/JoystickControl.h
  5. 8 8
      samples/browser/res/common/gamepad.form

+ 26 - 31
gameplay/src/Control.cpp

@@ -4,40 +4,9 @@
 #include "Form.h"
 #include "Theme.h"
 
-#define BOUNDS_X_PERCENTAGE_BIT 1
-#define BOUNDS_Y_PERCENTAGE_BIT 2
-#define BOUNDS_WIDTH_PERCENTAGE_BIT 4
-#define BOUNDS_HEIGHT_PERCENTAGE_BIT 8
-
 namespace gameplay
 {
 
-static float parseCoord(const char* s, bool* isPercentage)
-{
-    const char* p;
-    if ((p = strchr(s, '%')) != NULL)
-    {
-        std::string value(s, (std::string::size_type)(p - s));
-        *isPercentage = true;
-        return (float)(atof(value.c_str()) * 0.01);
-    }
-    *isPercentage = false;
-    return (float)atof(s);
-}
-
-static bool parseCoordPair(const char* s, float* v1, float* v2, bool* v1Percentage, bool* v2Percentage)
-{
-    size_t len = strlen(s);
-    const char* s2 = strchr(s, ',');
-    if (s2 == NULL)
-        return false;
-    std::string v1Str(s, (std::string::size_type)(s2 - s));
-    std::string v2Str(s2 + 1);
-    *v1 = parseCoord(v1Str.c_str(), v1Percentage);
-    *v2 = parseCoord(v2Str.c_str(), v2Percentage);
-    return true;
-}
-
 Control::Control()
     : _id(""), _boundsBits(0), _dirtyBits(DIRTY_BOUNDS | DIRTY_STATE), _consumeInputEvents(true), _alignment(ALIGN_TOP_LEFT),
     _autoSize(AUTO_SIZE_BOTH), _listeners(NULL), _style(NULL), _visible(true), _opacity(0.0f), _zIndex(-1),
@@ -1900,4 +1869,30 @@ Control::Alignment Control::getAlignment(const char* alignment)
     return Control::ALIGN_TOP_LEFT;
 }
 
+float Control::parseCoord(const char* s, bool* isPercentage)
+{
+    const char* p;
+    if ((p = strchr(s, '%')) != NULL)
+    {
+        std::string value(s, (std::string::size_type)(p - s));
+        *isPercentage = true;
+        return (float)(atof(value.c_str()) * 0.01);
+    }
+    *isPercentage = false;
+    return (float)atof(s);
+}
+
+bool Control::parseCoordPair(const char* s, float* v1, float* v2, bool* v1Percentage, bool* v2Percentage)
+{
+    size_t len = strlen(s);
+    const char* s2 = strchr(s, ',');
+    if (s2 == NULL)
+        return false;
+    std::string v1Str(s, (std::string::size_type)(s2 - s));
+    std::string v2Str(s2 + 1);
+    *v1 = parseCoord(v1Str.c_str(), v1Percentage);
+    *v2 = parseCoord(v2Str.c_str(), v2Percentage);
+    return true;
+}
+
 }

+ 49 - 1
gameplay/src/Control.h

@@ -992,6 +992,31 @@ protected:
      */
     static const int DIRTY_STATE = 2;
 
+    /**
+     * Indicates that the x position of the control is a percentage.
+     */
+    static const int BOUNDS_X_PERCENTAGE_BIT = 1;
+
+    /**
+     * Indicates that the y position of the control is a percentage.
+     */
+    static const int BOUNDS_Y_PERCENTAGE_BIT = 2;
+
+    /**
+     * Indicates that the width of the control is a percentage.
+     */
+    static const int BOUNDS_WIDTH_PERCENTAGE_BIT = 4;
+
+    /**
+     * Indicates that the height of the control is a percentage.
+     */
+    static const int BOUNDS_HEIGHT_PERCENTAGE_BIT = 8;
+
+    /**
+     * Indicates that the radius of the control is a percentage.
+     */
+    static const int BOUNDS_RADIUS_PERCENTAGE_BIT = 16;
+
     /**
      * Constructor.
      */
@@ -1264,7 +1289,7 @@ protected:
      * @param id The ID of the image to retrieve.
      * @param state The state to get this image from.
      *
-     * @return The requested Theme::ThemeImage, or NULL if none was found.
+     * @return The requested Theme::ThemeImage, or an empty image from the controls theme if none was found.
      */
     Theme::ThemeImage* getImage(const char* id, State state);
 
@@ -1307,6 +1332,29 @@ protected:
      */
     static Alignment getAlignment(const char* alignment);
 
+    /**
+     * Converts a string in the format of either 'N' or 'N%' (where N is a number)
+     * into its value and sets a flag to indicate if it should be treated as a percentage.
+     *
+     * @param s A string in the format of either 'N' or 'N%' (where N is a number)
+     * @param isPercentage Set to true if the returned value should be treated as a percentage
+     * @return The number contained within the string
+     */
+    static float parseCoord(const char* s, bool* isPercentage);
+
+    /**
+     * Converts a string in the format of either 'N, N' or 'N%, N%' (where N is a number)
+     * into its values and sets flags to indicate which numbers should be treated as percentages
+     *
+     * @param s A string in the format of either 'N, N' or 'N%, N%' (where N is a number)
+     * @param v1 Set to the value of the first number in the string
+     * @param v1 Set to the value of the second number in the string
+     * @param v1Percentage Set to true if the first number should be treated as a percentage
+     * @param v2Percentage Set to true if the second number should be treated as a percentage
+     * @return True if the string was parsed
+     */
+    static bool parseCoordPair(const char* s, float* v1, float* v2, bool* v1Percentage, bool* v2Percentage);
+
     /** 
      * The Control's ID.
      */ 

+ 261 - 122
gameplay/src/JoystickControl.cpp

@@ -4,16 +4,21 @@
 namespace gameplay
 {
 
-JoystickControl::JoystickControl() : _radius(1.0f), _relative(true), _innerSize(NULL), _outerSize(NULL), _index(0)
+JoystickControl::JoystickControl() : _radiusPixels(1.0f), _relative(true), _innerSizePixels(NULL), _outerSizePixels(NULL), _index(0),
+    _innerRegionCoord(NULL), _outerRegionCoord(NULL), _innerRegionCoordBoundsBits(0), _outerRegionCoordBoundsBits(0), _radiusCoord(_radiusPixels)
 {
 }
 
 JoystickControl::~JoystickControl()
 {
-    if (_innerSize)
-        SAFE_DELETE(_innerSize);
-    if (_outerSize)
-        SAFE_DELETE(_outerSize);
+    if (_innerSizePixels)
+        SAFE_DELETE(_innerSizePixels);
+    if (_outerSizePixels)
+        SAFE_DELETE(_outerSizePixels);
+    if(_innerRegionCoord)
+        SAFE_DELETE_ARRAY(_innerRegionCoord);
+    if(_outerRegionCoord)
+        SAFE_DELETE_ARRAY(_outerRegionCoord);
 }
 
 JoystickControl* JoystickControl::create(const char* id, Theme::Style* style)
@@ -41,30 +46,66 @@ const Vector2& JoystickControl::getValue() const
     return _value;
 }
 
-void JoystickControl::setInnerRegionSize(const Vector2& size)
+void JoystickControl::setInnerRegionSize(const Vector2& size, bool isWidthPercentage, bool isHeightPercentage)
 {
-    if (_innerSize)
-        _innerSize->set(size);
+    if (_innerSizePixels)
+    {
+        if(!_innerRegionCoord)
+        {
+            _innerRegionCoord = new Vector2();
+        }
+
+        setRegion(size, *_innerRegionCoord, _innerRegionCoordBoundsBits, isWidthPercentage, isHeightPercentage);
+        updateAbsoluteSizes();
+    }
 }
 
-const Vector2& JoystickControl::getInnerRegionSize() const
+const Vector2& JoystickControl::getInnerRegionSize(bool* isWidthPercentage, bool* isHeightPercentage) const
 {
-    if (_innerSize)
-        return *_innerSize;
+    if(isWidthPercentage)
+    {
+        *isWidthPercentage = _innerSizePixels && (_innerRegionCoord && _innerRegionCoordBoundsBits & BOUNDS_WIDTH_PERCENTAGE_BIT || Control::isXPercentage());
+    }
+
+    if(isHeightPercentage)
+    {
+        *isHeightPercentage = _innerSizePixels && (_innerRegionCoord && _innerRegionCoordBoundsBits & BOUNDS_HEIGHT_PERCENTAGE_BIT || Control::isYPercentage());
+    }
+
+    if (_innerSizePixels)
+        return *_innerSizePixels;
     else
         return Vector2::zero();
 }
 
-void JoystickControl::setOuterRegionSize(const Vector2& size)
+void JoystickControl::setOuterRegionSize(const Vector2& size, bool isWidthPercentage, bool isHeightPercentage)
 {
-    if (_outerSize)
-        _outerSize->set(size);
+    if (_outerSizePixels)
+    {
+        if(!_outerRegionCoord)
+        {
+            _outerRegionCoord = new Vector2();
+        }
+
+        setRegion(size, *_outerRegionCoord, _outerRegionCoordBoundsBits, isWidthPercentage, isHeightPercentage);
+        updateAbsoluteSizes();
+    }
 }
 
-const Vector2& JoystickControl::getOuterRegionSize() const
+const Vector2& JoystickControl::getOuterRegionSize(bool* isWidthPercentage, bool* isHeightPercentage) const
 {
-    if (_outerSize)
-        return *_outerSize;
+    if(isWidthPercentage)
+    {
+        *isWidthPercentage = _outerSizePixels && (_outerRegionCoord && _outerRegionCoordBoundsBits & BOUNDS_WIDTH_PERCENTAGE_BIT || Control::isXPercentage());
+    }
+
+    if(isHeightPercentage)
+    {
+        *isHeightPercentage = _outerSizePixels && (_outerRegionCoord && _outerRegionCoordBoundsBits & BOUNDS_HEIGHT_PERCENTAGE_BIT || Control::isYPercentage());
+    }
+
+    if (_outerSizePixels)
+        return *_outerSizePixels;
     else
         return Vector2::zero();
 }
@@ -84,88 +125,171 @@ unsigned int JoystickControl::getIndex() const
     return _index;
 }
 
+void setBit(bool set, int& bitSetOut, int bit)
+{
+    if(set)
+    {
+        bitSetOut |= bit;
+    }
+    else
+    {
+        bitSetOut &= ~bit;
+    }
+}
+
+void JoystickControl::setRadius(float radius, bool isPercentage)
+{
+    _radiusCoord = radius;
+    setBit(isPercentage, _boundsBits, BOUNDS_RADIUS_PERCENTAGE_BIT);
+    updateAbsoluteSizes();
+}
+
+float JoystickControl::getRadius() const
+{
+    return _radiusCoord;
+}
+
+bool JoystickControl::isRadiusPercentage() const
+{
+    return _boundsBits & BOUNDS_RADIUS_PERCENTAGE_BIT;
+}
 
 void JoystickControl::initialize(const char* typeName, Theme::Style* style, Properties* properties)
 {
     Control::initialize(typeName, style, properties);
 
-	if (!properties)
-	{
-		GP_WARN("JoystickControl creation without properties object is unsupported.");
-		return;
-	}
-
-	Control::State state = getState();
-
-	if (!properties->exists("radius"))
-	{
-		GP_WARN("JoystickControl: required attribute 'radius' is missing.");
-	}
-	else
-	{
-		_radius = properties->getFloat("radius");
-		if (_radius < 1.0f)
-			_radius = 1.0f;
-	}
-
-	if (properties->exists("relative"))
-	{
-		setRelative(properties->getBool("relative"));
-	}
-	else
-	{
-		setRelative(false);
-	}
-
-	Theme::ThemeImage* inner = getImage("inner", state);
-	if (inner)
-	{
-		_innerSize = new Vector2();
-		Vector2 innerSize;
-		if (properties->getVector2("innerRegion", &innerSize))
-		{
-			_innerSize->set(innerSize.x, innerSize.y);
-		}
-		else
-		{
-			const Rectangle& rect = inner->getRegion();
-			_innerSize->set(rect.width, rect.height);
-		}
-	}
-
-	Theme::ThemeImage* outer = getImage("outer", state);
-	if (outer)
-	{
-		_outerSize = new Vector2();
-		Vector2 outerSize;
-		if (properties->getVector2("outerRegion", &outerSize))
-		{
-			_outerSize->set(outerSize.x, outerSize.y);
-		}
-		else
-		{
-			const Rectangle& rect = outer->getRegion();
-			_outerSize->set(rect.width, rect.height);
-		}
-		_screenRegion.width = _outerSize->x;
-		_screenRegion.height = _outerSize->y;
-	}
-	else
-	{
-		if (inner)
-		{
-			const Rectangle& rect = inner->getRegion();
-			_screenRegion.width = rect.width;
-			_screenRegion.height = rect.height;
-		}
-		else
-		{
-			_screenRegion.width = _radius * 2.0f;
-			_screenRegion.height = _screenRegion.width;
-		}
-	}
-
-	_index = properties->getInt("index");
+    if (!properties)
+    {
+        GP_WARN("JoystickControl creation without properties object is unsupported.");
+        return;
+    }
+
+    const char* radiusId = "radius";
+    if (!properties->exists(radiusId))
+    {
+        GP_WARN("JoystickControl: required attribute 'radius' is missing.");
+    }
+    else
+    {
+        const char* radiusStr = properties->getString(radiusId);
+        bool isPercentage = false;
+        _radiusCoord = parseCoord(radiusStr, &isPercentage);
+        setBit(isPercentage, _boundsBits, BOUNDS_RADIUS_PERCENTAGE_BIT);
+    }
+
+    const char* relativeId = "relative";
+    if (properties->exists(relativeId))
+    {
+        setRelative(properties->getBool(relativeId));
+    }
+    else
+    {
+        setRelative(false);
+    }
+
+    const char* innerRegionId = "innerRegion";
+    if(properties->exists(innerRegionId))
+    {
+        _innerRegionCoord = new Vector2();
+        getRegion(*_innerRegionCoord, _innerRegionCoordBoundsBits, properties->getString(innerRegionId));
+    }
+
+    const char* outerRegionId = "outerRegion";
+    if(properties->exists(outerRegionId))
+    {
+        _outerRegionCoord = new Vector2();
+        getRegion(*_outerRegionCoord, _outerRegionCoordBoundsBits, properties->getString(outerRegionId));
+    }
+
+    _index = properties->getInt("index");
+}
+
+void JoystickControl::updateAbsoluteBounds(const Vector2& offset)
+{
+    Control::updateAbsoluteBounds(offset);
+    updateAbsoluteSizes();
+}
+
+void JoystickControl::setRegion(const Vector2& regionSizeIn, Vector2& regionSizeOut, int& regionBoundsBitsOut, bool isWidthPercentage, bool isHeightPercentage)
+{
+    regionSizeOut = regionSizeIn;
+    setBit(isWidthPercentage, regionBoundsBitsOut, BOUNDS_WIDTH_PERCENTAGE_BIT);
+    setBit(isHeightPercentage, regionBoundsBitsOut, BOUNDS_HEIGHT_PERCENTAGE_BIT);
+}
+
+void JoystickControl::getRegion(Vector2& regionOut, int & regionBoundsBitsOut, const char* regionPropertyId) const
+{
+    bool isWidthPercent = false;
+    bool isHeightPercent = false;
+    parseCoordPair(regionPropertyId, &regionOut.x, &regionOut.y, &isWidthPercent, &isHeightPercent);
+    setBit(isWidthPercent, regionBoundsBitsOut, BOUNDS_WIDTH_PERCENTAGE_BIT);
+    setBit(isHeightPercent, regionBoundsBitsOut, BOUNDS_HEIGHT_PERCENTAGE_BIT);
+}
+
+Vector2 JoystickControl::getPixelSize(const Vector2& region, const int regionBoundsBits) const
+{
+    Vector2 size;
+    size.x = regionBoundsBits & BOUNDS_WIDTH_PERCENTAGE_BIT ? _absoluteBounds.width * region.x : region.x;
+    size.y = regionBoundsBits & BOUNDS_HEIGHT_PERCENTAGE_BIT ? _absoluteBounds.height * region.y: region.y;
+    return size;
+}
+
+Vector2 JoystickControl::getPixelSize(const Theme::ThemeImage* image) const
+{
+    Rectangle rect = image->getRegion();
+    rect.width = isWidthPercentage() ? (_absoluteBounds.width / rect.width) * rect.width : rect.width;
+    rect.height = isHeightPercentage() ? (_absoluteBounds.height / rect.height) * rect.height : rect.height;
+    return Vector2(rect.width, rect.height);
+}
+
+Theme::ThemeImage * JoystickControl::getNonEmptyImage(const char* id, Control::State state)
+{
+    Theme::ThemeImage* image = getImage(id, state);
+    return strcmp(image->getId(), id) == 0 ? image : NULL;
+}
+
+void JoystickControl::updateAbsoluteSizes()
+{
+    const Control::State state = getState();
+
+    Theme::ThemeImage* innerImage = getNonEmptyImage("inner", state);
+    const bool innerRegionDefined = _innerRegionCoord || innerImage;
+
+    if(innerRegionDefined)
+    {
+        if(!_innerSizePixels)
+        {
+            _innerSizePixels = new Vector2();
+        }
+
+        *_innerSizePixels = _innerRegionCoord ? getPixelSize(*_innerRegionCoord, _innerRegionCoordBoundsBits) : getPixelSize(innerImage);
+        _screenRegionPixels.width = _innerSizePixels->x;
+        _screenRegionPixels.height = _innerSizePixels->y;
+    }
+
+    Theme::ThemeImage* outerImage = getNonEmptyImage("outer", state);
+    const bool outerRegionDefined = _outerRegionCoord || outerImage;
+
+    if (outerRegionDefined)
+    {
+        if(!_outerSizePixels)
+        {
+            _outerSizePixels = new Vector2();
+        }
+
+        *_outerSizePixels = _outerRegionCoord ? getPixelSize(*_outerRegionCoord, _outerRegionCoordBoundsBits) : getPixelSize(outerImage);
+        _screenRegionPixels.width = _outerSizePixels->x > _screenRegionPixels.width ? _outerSizePixels->x : _screenRegionPixels.width;
+        _screenRegionPixels.width = _outerSizePixels->y > _screenRegionPixels.height ? _outerSizePixels->y : _screenRegionPixels.height;
+    }
+
+    _radiusPixels = std::max(1.0f, _boundsBits & BOUNDS_RADIUS_PERCENTAGE_BIT ?
+                std::min(_screenRegionPixels.width, _screenRegionPixels.height) * _radiusCoord : _radiusCoord);
+
+    if (!innerRegionDefined && !outerRegionDefined)
+    {
+        _screenRegionPixels.width = _radiusPixels * 2.0f;
+        _screenRegionPixels.height = _screenRegionPixels.width;
+    }
 }
 
 void JoystickControl::addListener(Control::Listener* listener, int eventFlags)
@@ -194,13 +318,13 @@ bool JoystickControl::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned i
                 // Get the displacement of the touch from the centre.
                 if (!_relative)
                 {
-                    dx = x - _screenRegion.width * 0.5f;
-                    dy = _screenRegion.height * 0.5f - y;
+                    dx = x - _screenRegionPixels.width * 0.5f;
+                    dy = _screenRegionPixels.height * 0.5f - y;
                 }
                 else
                 {
-                    _screenRegion.x = x + _bounds.x - _screenRegion.width * 0.5f;
-                    _screenRegion.y = y + _bounds.y - _screenRegion.height * 0.5f;
+                    _screenRegionPixels.x = x + _bounds.x - _screenRegionPixels.width * 0.5f;
+                    _screenRegionPixels.y = y + _bounds.y - _screenRegionPixels.height * 0.5f;
                 }
 
                 _displacement.set(dx, dy);
@@ -209,17 +333,17 @@ bool JoystickControl::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned i
                 // radius.
 
                 Vector2 value;
-                if ((fabs(_displacement.x) > _radius) || (fabs(_displacement.y) > _radius))
+                if ((fabs(_displacement.x) > _radiusPixels) || (fabs(_displacement.y) > _radiusPixels))
                 {
                     _displacement.normalize();
                     value.set(_displacement);
-                    _displacement.scale(_radius);
+                    _displacement.scale(_radiusPixels);
                 }
                 else
                 {
                     value.set(_displacement);
-                    GP_ASSERT(_radius);
-                    value.scale(1.0f / _radius);
+                    GP_ASSERT(_radiusPixels);
+                    value.scale(1.0f / _radiusPixels);
                 }
 
                 // Check if the value has changed. Won't this always be the case?
@@ -238,23 +362,23 @@ bool JoystickControl::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned i
         {
             if (_contactIndex == (int) contactIndex)
             {
-                float dx = x - ((_relative) ? _screenRegion.x - _bounds.x : 0.0f) - _screenRegion.width * 0.5f;
-                float dy = -(y - ((_relative) ? _screenRegion.y - _bounds.y : 0.0f) - _screenRegion.height * 0.5f);
+                float dx = x - ((_relative) ? _screenRegionPixels.x - _bounds.x : 0.0f) - _screenRegionPixels.width * 0.5f;
+                float dy = -(y - ((_relative) ? _screenRegionPixels.y - _bounds.y : 0.0f) - _screenRegionPixels.height * 0.5f);
 
                 _displacement.set(dx, dy);
 
                 Vector2 value;
-                if ((fabs(_displacement.x) > _radius) || (fabs(_displacement.y) > _radius))
+                if ((fabs(_displacement.x) > _radiusPixels) || (fabs(_displacement.y) > _radiusPixels))
                 {
                     _displacement.normalize();
                     value.set(_displacement);
-                    _displacement.scale(_radius);
+                    _displacement.scale(_radiusPixels);
                 }
                 else
                 {
                     value.set(_displacement);
-                    GP_ASSERT(_radius);
-                    value.scale(1.0f / _radius);
+                    GP_ASSERT(_radiusPixels);
+                    value.scale(1.0f / _radiusPixels);
                 }
 
                 if (_value != value)
@@ -294,57 +418,72 @@ bool JoystickControl::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned i
 
 unsigned int JoystickControl::drawImages(Form* form, const Rectangle& clip)
 {
-    Control::State state = getState();
+    const Control::State state = getState();
 
     unsigned int drawCalls = 0;
 
     // If the JoystickControl is not absolute, then only draw if it is active.
-    if (!_relative || (_relative && state == ACTIVE))
+    if ((_outerSizePixels || _innerSizePixels) && !_relative || (_relative && state == ACTIVE))
     {
         if (!_relative)
         {
-            _screenRegion.x = _viewportClipBounds.x + (_viewportClipBounds.width - _screenRegion.width) / 2.0f;
-            _screenRegion.y = _viewportClipBounds.y + (_viewportClipBounds.height - _screenRegion.height) / 2.0f;
+            _screenRegionPixels.x = _viewportClipBounds.x + (_viewportClipBounds.width - _screenRegionPixels.width) / 2.0f;
+            _screenRegionPixels.y = _viewportClipBounds.y + (_viewportClipBounds.height - _screenRegionPixels.height) / 2.0f;
         }
 
         SpriteBatch* batch = _style->getTheme()->getSpriteBatch();
         startBatch(form, batch);
 
         // Draw the outer image.
-        Theme::ThemeImage* outer = getImage("outer", state);
-        if (outer)
+        if (_outerSizePixels)
         {
+            Theme::ThemeImage* outer = getImage("outer", state);
             const Theme::UVs& uvs = outer->getUVs();
             const Vector4& color = outer->getColor();
+
+            Vector2 position(_screenRegionPixels.x, _screenRegionPixels.y);
+
+            if(_outerRegionCoord)
+            {
+                position.x += (_screenRegionPixels.width / 2) - (_outerSizePixels->x / 2);
+                position.y += (_screenRegionPixels.height / 2) - (_outerSizePixels->y / 2);
+            }
+
             if (_relative)
-                batch->draw(_screenRegion.x, _screenRegion.y, _outerSize->x, _outerSize->y, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color);
+                batch->draw(position.x, position.y, _outerSizePixels->x, _outerSizePixels->y, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color);
             else
-                batch->draw(_screenRegion.x, _screenRegion.y, _outerSize->x, _outerSize->y, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color, _viewportClipBounds);
+                batch->draw(position.x, position.y, _outerSizePixels->x, _outerSizePixels->y, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color, _viewportClipBounds);
             ++drawCalls;
         }
 
         // Draw the inner image.
-        Theme::ThemeImage* inner = getImage("inner", state);
-        if (inner)
+        if (_innerSizePixels)
         {
-            Vector2 position(_screenRegion.x, _screenRegion.y);
+            Theme::ThemeImage* inner = getImage("inner", state);
+            Vector2 position(_screenRegionPixels.x, _screenRegionPixels.y);
 
             // Adjust position to reflect displacement.
             position.x += _displacement.x;
             position.y += -_displacement.y;
 
+            if(_innerRegionCoord)
+            {
+                position.x += (_screenRegionPixels.width / 2) - (_innerSizePixels->x / 2);
+                position.y += (_screenRegionPixels.height / 2) - (_innerSizePixels->y / 2);
+            }
+
             // Get the uvs and color and draw.
             const Theme::UVs& uvs = inner->getUVs();
             const Vector4& color = inner->getColor();
             if (_relative)
-                batch->draw(position.x, position.y, _innerSize->x, _innerSize->y, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color);
+                batch->draw(position.x, position.y, _innerSizePixels->x, _innerSizePixels->y, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color);
             else
-                batch->draw(position.x, position.y, _innerSize->x, _innerSize->y, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color, _viewportClipBounds);
+                batch->draw(position.x, position.y, _innerSizePixels->x, _innerSizePixels->y, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color, _viewportClipBounds);
             ++drawCalls;
         }
 
         finishBatch(form, batch);
-    }
+}
 
     return drawCalls;
 }

+ 64 - 9
gameplay/src/JoystickControl.h

@@ -66,32 +66,42 @@ public:
      * inner image region defined.
      * 
      * @param size The size of the inner region of the joystick. (x, y) == (width, height)
+     * @param isWidthPercentage If the width value should be computed as a percentage of the relative size of this control
+     * @param isHeightPercentage If the height value should be computed as a percentage of the relative size of this control
      */
-    void setInnerRegionSize(const Vector2& size);
+    void setInnerRegionSize(const Vector2& size, bool isWidthPercentage = false, bool isHeightPercentage = false);
 
     /**
      * Gets the image size of the inner region of the joystick. Returns (0,0) if there is no inner image
      * region defined.
      * 
+     * @param isWidthPercentage Set to true if the width value is a percentage value of the relative size of this control
+     * @param isHeightPercentage Set to true if the height value is a percentage value of the relative size of this control
+     *
      * @return The image size of the inner region of the joystick. (x, y) == (width, height)
      */
-    const Vector2& getInnerRegionSize() const;
+    const Vector2& getInnerRegionSize(bool* isWidthPercentage = NULL, bool* isHeightPercentage = NULL) const;
 
     /**
      * Sets the image size of the outer region of the joystick. Does not do anything if there is no
      * outer image region defined.
      * 
      * @param size The size of the outer region of the joystick. (x, y) == (width, height)
+     * @param isWidthPercentage If the width value should be computed as a percentage of the relative size of this control
+     * @param isHeightPercentage If the height value should be computed as a percentage of the relative size of this control
      */
-    void setOuterRegionSize(const Vector2& size);
+    void setOuterRegionSize(const Vector2& size, bool isWidthPercentage = false, bool isHeightPercentage = false);
 
     /**
      * Gets the image size of the outer region of the joystick. Returns (0,0) if there is no outer image
      * region defined.
+     *
+     * @param isWidthPercentage Set to true if the width value is a percentage value of the relative size of this control
+     * @param isHeightPercentage Set to true if the height value is a percentage value of the relative size of this control
      * 
      * @return The image size of the outer region of the joystick. (x, y) == (width, height)
      */
-    const Vector2& getOuterRegionSize() const;
+    const Vector2& getOuterRegionSize(bool* isWidthPercentage = NULL, bool* isHeightPercentage = NULL) const;
 
     /**
      * Sets whether relative positioning is enabled or not.
@@ -118,6 +128,27 @@ public:
      */
     unsigned int getIndex() const;
 
+    /**
+     * Sets the radius of joystick motion
+     *
+     * @param isPercentage If the radius value is a percentage value of the relative size of this control
+     */
+    void setRadius(float radius, bool isPercentage = false);
+
+    /**
+     * Gets the radius of joystick motion
+     *
+     * @return The radius of joystick motion
+     */
+    float getRadius() const;
+
+    /**
+      * Determines if the radius of joystick motion is a percentage value of the relative size of this control
+      *
+     * @return True if the radius of joystick motion is a percentage value of the relative size of this control
+     */
+    bool isRadiusPercentage() const;
+
 protected:
     
     /**
@@ -134,7 +165,7 @@ protected:
      * Create a joystick control with a given style and properties.
      *
      * @param style The style to apply to this joystick.
-     * @param properties A properties object containing a definition of the joystick (optional).
+     * @param properties A properties object containing a definition of the joystick.
 	 *
      * @return The new joystick.
      */
@@ -159,6 +190,11 @@ protected:
      */
     bool touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
 
+    /**
+     * @see Control::updateAbsoluteBounds
+     */
+    void updateAbsoluteBounds(const Vector2& offset);
+
     /**
      * @see Control::drawImages
      */
@@ -171,13 +207,32 @@ private:
      */
     JoystickControl(const JoystickControl& copy);
 
-    float _radius; 
+    void setRegion(const Vector2& regionSizeIn, Vector2& regionSizeOut, int& regionBoundsBitsOut, bool isWidthPercentage, bool isHeightPercentage);
+
+    void getRegion(Vector2& regionOut, int & regionBoundsBitsOut, const char* regionPropertyId) const;
+
+    Vector2 getPixelSize(const Vector2& region, const int regionBoundsBits) const;
+
+    Vector2 getPixelSize(const Theme::ThemeImage* image) const;
+
+    Theme::ThemeImage * getNonEmptyImage(const char* id, Control::State state);
+
+    void updateAbsoluteSizes();
+
+    float _radiusCoord;
+    Vector2* _innerRegionCoord;
+    Vector2* _outerRegionCoord;
+    int _innerRegionCoordBoundsBits;
+    int _outerRegionCoordBoundsBits;
+
+    float _radiusPixels;
+    Vector2* _innerSizePixels;
+    Vector2* _outerSizePixels;
+    Rectangle _screenRegionPixels;
+
     bool _relative;
-    Rectangle _screenRegion;
     Vector2 _value;
     Vector2 _displacement;
-    Vector2* _innerSize;
-    Vector2* _outerSize;
     unsigned int _index;
 };
 

+ 8 - 8
samples/browser/res/common/gamepad.form

@@ -7,27 +7,27 @@ form VIRTUAL GAMEPAD
     container left
     {
         alignment = ALIGN_BOTTOM_LEFT
-        size = 300, 300
+        size = 23%, 41%
         consumeInputEvents = false
         
         joystick
         {
             style = joystickStyle
-            size = 256, 256
+            size = 85%, 85%
             alignment = ALIGN_VCENTER_HCENTER
-            radius = 32
+            radius = 12.5%
         }
     }
 
     container right
     {
         alignment = ALIGN_BOTTOM_RIGHT
-        size = 256, 256
+        size = 20%, 35%
 		consumeInputEvents = false
 		
         container inner
         {
-            size = 230, 230
+            size = 90%, 90%
             alignment = ALIGN_VCENTER_HCENTER
             consumeInputEvents = false
             
@@ -35,7 +35,7 @@ form VIRTUAL GAMEPAD
             {
                 mapping = BUTTON_A
                 style = buttonAStyle
-                size = 128, 128
+                size = 50%, 50%
                 alignment = ALIGN_BOTTOM_LEFT
             }
 
@@ -43,9 +43,9 @@ form VIRTUAL GAMEPAD
             {
                 mapping = BUTTON_B
                 style = buttonBStyle
-                size = 128, 128
+                size = 50%, 50%
                 alignment = ALIGN_TOP_RIGHT
             }
         }
     }
-}
+}