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:
mConsoleFunctions.cc
up at the top, add:
guiControl.h
right after the line horizResizeFloating, ///< fixed width, add:
after GuiControl::setHeight(), add:
change GuiControl::parentResized() to this:
after ConsoleMethod( GuiControl, getPosition, const char*, 2, 2, ""), add:
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);
}About the author
#2
11/18/2007 (5:15 am)
edit: holy $%#!! The floating is great! :) Thanks again Orion! :) works perfectly! 
Associate Fyodor -bank- Osokin
Dedicated Logic
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 :
should be updated. If using only the "vertResizeFit", then if should be 6 for horiz and 5 for vert.
And, your
should be read as:
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 :)