Game Development Community

dev|Pro Game Development Curriculum

Fixing GUI relative sizing

by Tim "Zear" Hammock · 02/20/2002 (9:47 am) · 6 comments

Relative sizing is broken. To see what I mean:

Create a GuiControl and put a button in it. Set the button's horizSizing and vertSizing to relative and click apply. Now grab the lower right resize knob on the GuiControl and drag it in and out furiously. It won't take very long for the button inside to become hopelessly mis-sized and mis-placed.

Why does this happen? Because relative sizing bases its calculations on the current position and extents of the two objects. When the outer component is sized down then back up, the ratios are subtly changed with each resize, until they no longer reflect the original measurements at all. Ack.

My solution? Keep information about the original relationship between the bounds of the parent and the bounds of the child, then use that ratio, along with the new parent size to calculate the new child position and size. Efficient? Elegant? Maybe, maybe not.

The advantage of this approach is simplicity, backward compatibility, and the fact that it works. The extra data carried along is small, and is only carried by components derived from GuiControl, so it shouldn't cause too much trouble.

If you have a more elegant approach, let me know. Meanwhile this works for now. I am working on a more comprehensive solution to this and other problems I see (see the TCP UI discussion), but that is for later.

On to the code. I have added a method to GuiControl that is not strictly necessary for this issue, but it adds flexibility for other components I have. Anyway:

First, open guiControl.h. After the line
RectI	mBounds;
add
RectI	mOriginalBounds;
After the line
virtual void parentResized(const Point2I &oldParentExtent, const Point2I &newParentExtent);
add
virtual RectI translateBounds(const Point2I &oldParentExtent, const Point2I &newParentExtent, RectI bounds);
virtual Point2I getOriginalExtent();
Then in guiControl.cc, add this line to the end of the contructor
mOriginalBounds = RectI(Point2I(-1, 0), Point2I(0, 0));
(if you don't know, the construcor is the method GuiControl::GuiControl() right near the top of the file).

Next find the function
void GuiControl::parentResized(const Point2I &oldParentExtent, const Point2I &newParentExtent)
and replace the entire function with these three functions
Point2I GuiControl::getOriginalExtent()
{
	if(mOriginalBounds.point.x == -1)
		return mBounds.extent;
	else
		return mOriginalBounds.extent;
}

void GuiControl::parentResized(const Point2I &oldParentExtent, const Point2I &newParentExtent)
{
	if(mOriginalBounds.point.x == -1)
		mOriginalBounds = RectI(mBounds.point, mBounds.extent);
	
	RectI newbounds = translateBounds(oldParentExtent, newParentExtent, mBounds);
	resize(newbounds.point, newbounds.extent);
}

RectI GuiControl::translateBounds(const Point2I &oldParentExtent, const Point2I &newParentExtent, RectI bounds)
{
	S32 deltaX = newParentExtent.x - oldParentExtent.x;
	S32 deltaY = newParentExtent.y - oldParentExtent.y;

	if (mHorizSizing == horizResizeCenter)
		bounds.point.x = (newParentExtent.x - mBounds.extent.x) >> 1;
	else if (mHorizSizing == horizResizeWidth)
		bounds.extent.x += deltaX;
	else if (mHorizSizing == horizResizeLeft)
		bounds.point.x += deltaX;
	else if (mHorizSizing == horizResizeRelative && oldParentExtent.x != 0)
	{
		S32 newLeft = (mOriginalBounds.point.x * newParentExtent.x) / getParent()->getOriginalExtent().x;
		S32 newRight = ((mOriginalBounds.point.x + mOriginalBounds.extent.x) * newParentExtent.x) / getParent()->getOriginalExtent().x;

		bounds.point.x = newLeft;
		bounds.extent.x = newRight - newLeft;
	}

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

		bounds.point.y = newTop;
		bounds.extent.y = newBottom - newTop;
	}

	return bounds;
}
That's pretty much it. You will notice that I am simply keeping a copy of the original bounds of each object. Then when the resize is done, the relative code uses the original extents of both objects instead of the current ones. There is also a problem with the width and height resizing settings. It would make more sense to just set the position value to zero and the extent to the extent of the parent, but doing this breaks the gui editor, so I'll deal with that later. Have fun.

#1
04/04/2002 (3:23 pm)
:)
#2
07/19/2006 (7:06 pm)
Thank you, thank you, thank you, I was trying a different method but this method is so much better, still relevent in 1.4 and should be added to HEAD.
#3
09/18/2006 (8:12 am)
Doesn't work at all for me on TGE 1.4. Moreover, I can no longer resize the canvas, making the gui editor completely unusable.
#4
06/26/2007 (4:04 pm)
@Alberto
This resource is excellent even in TGE 1.5.2. There is only one change needed to make it up to date.

From both the new getOriginalExtent() and parentResized() functions change this
if(mOriginalBounds.point.x == -1)
to this
if(mOriginalBounds.point.x == 0)

This resource's technique also needs to be applied to GuiFrameSetCtrl::rebalance()
#5
07/02/2007 (8:35 pm)
By the way I finished extending this resource to fix the effects of resizing on GuiFrameSetCtrl (the gui tables). You can see my code for that here. I no longer have any problems with gui resizing in TGE 1.5.2. HINT: These changes should go into the next release of TGE!
#6
11/18/2011 (7:27 pm)
Anyone have experiences at running this almost 10 years old code snippet in the current 1.7.2 build.

I am trying to make it work, but results are unpredictable to say the least.
Randomly re-sizing main application window sometimes totally miss-place gui controls, or sometimes do the placing right.

Did anyone manage to make it work?