Game Development Community

dev|Pro Game Development Curriculum

new GuiControl resizing mode: fit (in parent)

by Orion Elenzil · 11/12/2007 (1:45 pm) · 3 comments

This resource provides a new resizing mode for GuiControls: Fit (in parent).

When a control is in Fit mode,
it resizes so that it's the largest possible size while fitting in its parent and maintaining its aspect ratio.

Usage:
Create a control,
set the Horizontal sizing mode to "fit", and set the Vertical sizing mode to one of "top", "bottom", or "center".

When a control is in Fit mode, either the top & bottom edges or the left & right edges are guaranteed to be flush with the parent.
Where the other edges are aligned depends on the Vertical sizing mode - "top" is interpreted as "top or left", "bottom" as "bottom or right", and "center" as, well, "center".

Issues:
* Because GuiControl's sizes are stored as integers instead of floats, the aspect of the child control can drift a bit.
Sorry, i should fix this, but haven't. See http://www.garagegames.com/mg/forums/result.thread.php?qt=68153 for more discussion.

Bonuses:
in C++, we now have RectF::fitInside(),
in script we have mFitRectInRect(),
and for guiControls we have the one-shot GuiControl::fitInParent() in both C++ and script.

Heads-Ups:
this assumes familiarity with C++ and the TGE GuiControls system.
this was written on top of a fairly heavily modified TGE 1.3.5 codebase,
so while i expect it to compile & work on stock TGE 1.3 - 1.5, TGEA, TGB, What Have You, you might need to make some adjustments.


mRect.h
right after the line bool isValidRect().., add:
bool fitInside(const RectF& other, S32 align = 0);
and somewhere down near the bottom, add the core math:
// if degenerate, sets my dimensions to other's dimensions & returns false
inline bool RectF::fitInside(const RectF& other, S32 align)
{
   if (extent.x == 0.0f || extent.y == 0.0f || other.extent.x == 0.0f || other.extent.y == 0.0f)
   {
      extent = other.extent;
      return false;
   }


   F32 aspect      =       extent.x /       extent.y;
   F32 aspectOther = other.extent.x / other.extent.y;

   if (aspect > aspectOther)
   {
      // size
      extent.x   = other.extent.x;
      extent.y   = other.extent.x / aspect;

      // position
      point .x   = 0.0f;
      if (align < 0)
         point.y = 0.0f;
      else if (align == 0)
         point.y = (other.extent.y - extent.y) * 0.5f;
      else
         point.y =  other.extent.y - extent.y;
   }
   else
   {
      // size
      extent.y = other.extent.y;
      extent.x = other.extent.y * aspect;

      // position
      point .y   = 0.0f;
      if (align < 0)
         point.x = 0.0f;
      else if (align == 0)
         point.x = (other.extent.x - extent.x) * 0.5f;
      else
         point.x =  other.extent.x - extent.x;
   }

   point += other.point;

   return true;
}



mConsoleFunctions.cc
up at the top, add:
#include "math/mRect.h"
anywhere else (like the bottom), add:
ConsoleFunction( mFitRectInRect, const char*, 3, 4, "( int w1 int h1, [ int x2 int y2 ] int w2 int h2 [, align = -1, 0, 1]")
{
   char * ret = Con::getReturnBuffer(256);

   RectF r1;
   RectF r2;
   bool  includePosition = true;
   S32   align           = 0;

   dSscanf(argv[1], "%f %f", &r1.extent.x, &r1.extent.y);
   r1.point.set(0.0f, 0.0f);

   if (dSscanf(argv[2], "%f %f %f %f", &r2.point.x, &r2.point.y, &r2.extent.x, &r2.extent.y) < 4)
   {
      r2.extent       = r2.point;
      r2.point.set(0.0f, 0.0f);
      includePosition = false;
   }

   if (argc >= 3)
      align = dAtoi(argv[3]);

   r1.fitInside(r2, align);

   if (includePosition)
      dSprintf(ret, sizeof(ret), "%g %g %g %g", r1.point.x, r1.point.y, r1.extent.x, r1.extent.y);
   else
      dSprintf(ret, sizeof(ret), "%g %g"      ,                         r1.extent.x, r1.extent.y);

   return ret;
}


guiControl.h
right after the line horizResizeFloating, ///< fixed width, add:
horizResizeFit             ///< Fit In Parent Maintain Aspect - vert must be vertResizeBottom, vertResizeTop, vertResizeCenter
[code]

after the line [i]virtual void setHeight( S32 newHeight );[/i], add:
[code]
    /// Fit this control snugly in the parent w/o changing aspect.
    /// @param   align      -1 0 1
    virtual void fitInParent( S32 align );
[code]

[b]guiControl.cc[/b]
change this:
[code]
   { GuiControl::horizResizeFloating,        "floating"        }
};
static EnumTable gHorizSizingTable(8, &horzEnums[0]);
to this:
{ GuiControl::horizResizeFloating,        "floating"        },
   { GuiControl::horizResizeFit,             "fit"             }
};
static EnumTable gHorizSizingTable(9, &horzEnums[0]);

after GuiControl::setHeight(), add:
void GuiControl::setHeight( S32 newHeight )
{
   resize( mBounds.point, Point2I( mBounds.extent.x, newHeight ) );
}

void GuiControl::fitInParent( S32 align )
{
   GuiControl* parent = getParent();
   if (parent == NULL)
      return;

   Point2I ie1 = this  ->getExtent();
   Point2I ie2 = parent->getExtent();
   RectF r1;
   RectF r2;

   r1.point.set(0.0f, 0.0f);
   r1.extent.x = (F32)ie1.x;
   r1.extent.y = (F32)ie1.y;

   r2.point.set(0.0f, 0.0f);
   r2.extent.x = (F32)ie2.x;
   r2.extent.y = (F32)ie2.y;

   r1.fitInside(r2, align);

   Point2I ip1;
   Point2I ip2 = parent->getPosition();
   ip1.x = (S32)(r1.point .x + 0.5f);
   ip1.y = (S32)(r1.point .y + 0.5f);

   ie1.x = (S32)(r1.extent.x + 0.5f);
   ie1.y = (S32)(r1.extent.y + 0.5f);

   resize(ip1, ie1);
}


change GuiControl::parentResized() to this:
void GuiControl::parentResized(const Point2I &oldParentExtent, const Point2I &newParentExtent)
{
   Point2I newPosition = getPosition();
   Point2I newExtent = getExtent();

   S32 deltaX = newParentExtent.x - oldParentExtent.x;
   S32 deltaY = newParentExtent.y - oldParentExtent.y;

   if (mHorizSizing == horizResizeFit)
   {
      S32 align;
      if (mVertSizing == vertResizeTop)
         align = -1;
      else if (mVertSizing == vertResizeBottom)
         align =  1;
      else
         align =  0;

      fitInParent(align);
   }
   else
   {
      if (mHorizSizing == horizResizeCenter)
         newPosition.x = (newParentExtent.x - mBounds.extent.x) >> 1;
      else if (mHorizSizing == horizResizeWidth)
         newExtent.x += deltaX;
      else if (mHorizSizing == horizResizeLeft)
         newPosition.x += deltaX;
      else if (mHorizSizing == horizResizeRelative && oldParentExtent.x != 0)
      {
         S32 newLeft = (newPosition.x * newParentExtent.x) / oldParentExtent.x;
         S32 newRight = ((newPosition.x + newExtent.x) * newParentExtent.x) / oldParentExtent.x;

         newPosition.x = newLeft;
         newExtent.x = newRight - newLeft;
      }
      else if (mHorizSizing == horizResizeRightRelative && oldParentExtent.x != newPosition.x)
      {
         newExtent.x = newExtent.x * (newParentExtent.x - newPosition.x) / (oldParentExtent.x - newPosition.x);
      }
      else if (mHorizSizing == horizResizeLeftRelative && newPosition.x + newExtent.x != 0)
      {
         S32 newLeft = newPosition.x * (deltaX + newPosition.x + newExtent.x) / (newPosition.x + newExtent.x);
         S32 newRight = newPosition.x + newExtent.x + deltaX;

         newPosition.x = newLeft;
         newExtent.x = newRight - newLeft;
      }
      else if (mHorizSizing == horizResizeFloating && oldParentExtent.x != newExtent.x)
      {
         newPosition.x = newPosition.x * (newParentExtent.x - newExtent.x) / (oldParentExtent.x - newExtent.x);
      }

      if (mVertSizing == vertResizeCenter)
         newPosition.y = (newParentExtent.y - mBounds.extent.y) >> 1;
      else if (mVertSizing == vertResizeHeight)
         newExtent.y += deltaY;
      else if (mVertSizing == vertResizeTop)
         newPosition.y += deltaY;
      else if(mVertSizing == vertResizeRelative && oldParentExtent.y != 0)
      {
         S32 newTop = (newPosition.y * newParentExtent.y) / oldParentExtent.y;
         S32 newBottom = ((newPosition.y + newExtent.y) * newParentExtent.y) / oldParentExtent.y;

         newPosition.y = newTop;
         newExtent.y = newBottom - newTop;
      }
      else if (mVertSizing == vertResizeBottomRelative && oldParentExtent.y != newPosition.y)
      {
         newExtent.y = newExtent.y * (newParentExtent.y - newPosition.y) / (oldParentExtent.y - newPosition.y);
      }
      else if (mVertSizing == vertResizeTopRelative && newPosition.y + newExtent.y != 0)
      {
         S32 newTop = newPosition.y * (deltaY + newPosition.y + newExtent.y) / (newPosition.y + newExtent.y);
         S32 newBottom = newPosition.y + newExtent.y + deltaY;

         newPosition.y = newTop;
         newExtent.y = newBottom - newTop;
      }
      else if (mVertSizing == vertResizeFloating && oldParentExtent.y != newExtent.y)
      {
         newPosition.y = newPosition.y * (newParentExtent.y - newExtent.y) / (oldParentExtent.y - newExtent.y);
      }

      resize(newPosition, newExtent);
   }
}


after ConsoleMethod( GuiControl, getPosition, const char*, 2, 2, ""), add:
ConsoleMethod( GuiControl, fitInParent, void, 2, 3, "[align = -1, 0, 1]")
{
   S32 align = (argc >= 2) ? dAtoi(argv[2]) : 0;
   object->fitInParent(align);
}

#1
11/18/2007 (5:02 am)
yay! lovely!
But, the stock torque don't have horizResizeLeftRelative, horizResizeRightRelative and horizResizeFloating for horzEnums and vertResizeTopRelative, vertResizeBottomRelative and vertResizeFloating for vertEnums.
So, I just went ahead and added these.. Hopefully it don't needed anything else, as your "parentResized()" function included in full.
And, if using all of them, both :
static EnumTable gHorizSizingTable(9, &horzEnums[0]);
static EnumTable gVertSizingTable(8, &vertEnums[0]);
should be updated. If using only the "vertResizeFit", then if should be 6 for horiz and 5 for vert.

And, your
[b]mRect.h[/b]
right after the line bool isValidRect().., add:
should be read as:
[b]mRect.h[/b]
right after the line bool isValidRect() in [b]class RectF[/b] definition.., add:
as there is also isValidRect() for RectI, so someone can be confused if don't have enough torque-knowledge.

Building exe now.. I'm excited to see that in action! :) It solves LOTs of problems! You're always have something that very useful! Keep it up Orion!

Edit: just found that there is no vertResizeFit, edited text :)
#2
11/18/2007 (5:15 am)
edit: holy $%#!! The floating is great! :) Thanks again Orion! :) works perfectly!
#3
11/24/2007 (8:45 am)
Very nice, thank you very much!

I have it working nicely in TGE 1.5.2.