[Snippet] Auto-sized buttons & text boxes, layout boxes

scrawl

10-09-2012 13:51:02

This snippet allows you to create dynamically sized buttons & text boxes (depending on the text displayed on it) entirely in layout files. I found it very useful, mostly due to the fact that the length of the text on the button can depend on the localisation used. I have also made a HBox widget, that automatically manages the positions & sizes of sub-widgets. Let's say you have a button with a text next to it:

[ Button text ] TextBox text

If the length of the button text increases (and the button width increases), the TextBox should be displaced to the right. This is automatically done when you use the HBox :)

You can also do lots of other things, e.g. a dynamically sized button bar aligned to the right of a window:

| ------------------------------- ( SPACER ) ---------------------------------------- | [ Button 1 ] [ Button 2 ]

This would be achieved by using an empty widget on the left, with the "HStretch" user string set to "true". HStretch means to occupy all the available space, while normally the HBox asks the sub-widgets how much space they need (if they are inherited from AutoSizedWidget, like AutoSizedButton and AutoSizedTextBox)

I found such a method of layouting (which is very common in other toolkits like GTK and QT) lacking in MyGUI, hence I made this. :)

I didn't get around yet to fill in the VBox widget, but it should be basically the same as HBox, just vertically.

It should also be possible to stack boxes inside each other (i.e. a box inside another box), I haven't tested that though.

Here are some example layouts:

2 buttons aligned to the right
<Widget type="HBox" skin="" position="160 370 380 24">
<Widget type="Widget"> <!-- spacer -->
<UserString key="HStretch" value="true"/>
</Widget>

<Widget type="AutoSizedButton" skin="MW_Button" name="CreateButton">
<Property key="Caption" value="#{sCreate}"/>
</Widget>
<Widget type="AutoSizedButton" skin="MW_Button" name="CancelButton">
<Property key="Caption" value="#{sCancel}"/>
</Widget>
</Widget>


TextBox next to a Button
<Widget type="HBox" position="4 172 350 24">
<Widget type="AutoSizedButton" skin="MW_Button" name="ShadowsDebug"/>
<Widget type="AutoSizedTextBox" skin="SandText">
<Property key="Caption" value="Debug overlay"/>
</Widget>
</Widget>


And lastly, here is the widget code you need:

Updated code see the last post

Don't forget to do this at the start of your app:


MyGUI::FactoryManager::getInstance().registerFactory<HBox>("Widget");
MyGUI::FactoryManager::getInstance().registerFactory<VBox>("Widget");
MyGUI::FactoryManager::getInstance().registerFactory<AutoSizedTextBox>("Widget");
MyGUI::FactoryManager::getInstance().registerFactory<AutoSizedButton>("Widget");


I hope someone finds this useful.

scrawl

18-09-2012 16:24:43

Added some more goodies!

- Implemented VBox
- Properties for HBox and VBox:
- AutoResize: automatically adjusts size of the box so that it fits all elements perfectly
- Padding: outer padding around contained widgets
- Spacing: adds space between contained widgets

Source


void AutoSizedWidget::notifySizeChange (MyGUI::Widget* w)
{
if (w->getParent () != 0)
{
Box* b = dynamic_cast<Box*>(w->getParent());
if (b)
b->notifyChildrenSizeChanged ();
else
{
if (mExpandDirection == MyGUI::Align::Left)
{
int hdiff = getRequestedSize ().width - w->getSize().width;
w->setPosition(w->getPosition() - MyGUI::IntPoint(hdiff, 0));
}
w->setSize(getRequestedSize ());
}
}
}


MyGUI::IntSize AutoSizedTextBox::getRequestedSize()
{
return getTextSize();
}

void AutoSizedTextBox::setCaption(const MyGUI::UString& _value)
{
TextBox::setCaption(_value);

notifySizeChange (this);
}

void AutoSizedTextBox::setPropertyOverride(const std::string& _key, const std::string& _value)
{
if (_key == "ExpandDirection")
{
mExpandDirection = MyGUI::Align::parse (_value);
}
else
{
TextBox::setPropertyOverride (_key, _value);
}
}


MyGUI::IntSize AutoSizedButton::getRequestedSize()
{
MyGUI::IntSize size = getTextSize() + MyGUI::IntSize(24,0);
size.height = std::max(24, size.height);
return size;
}

void AutoSizedButton::setCaption(const MyGUI::UString& _value)
{
Button::setCaption(_value);

notifySizeChange (this);
}

void AutoSizedButton::setPropertyOverride(const std::string& _key, const std::string& _value)
{
if (_key == "ExpandDirection")
{
mExpandDirection = MyGUI::Align::parse (_value);
}
else
{
Button::setPropertyOverride (_key, _value);
}
}

Box::Box()
: mSpacing(4)
, mPadding(0)
, mAutoResize(false)
{

}

void Box::notifyChildrenSizeChanged ()
{
align();
}

void Box::_setPropertyImpl(const std::string& _key, const std::string& _value)
{
if (_key == "Spacing")
mSpacing = MyGUI::utility::parseValue<int>(_value);
else if (_key == "Padding")
mPadding = MyGUI::utility::parseValue<int>(_value);
else if (_key == "AutoResize")
mAutoResize = MyGUI::utility::parseValue<bool>(_value);
}

void HBox::align ()
{
unsigned int count = getChildCount ();
size_t h_stretched_count = 0;
int total_width = 0;
int total_height = 0;
std::vector< std::pair<MyGUI::IntSize, bool> > sizes;

for (unsigned int i = 0; i < count; ++i)
{
MyGUI::Widget* w = getChildAt(i);
bool hstretch = w->getUserString ("HStretch") == "true";
h_stretched_count += hstretch;
AutoSizedWidget* aw = dynamic_cast<AutoSizedWidget*>(w);
if (aw)
{
sizes.push_back(std::make_pair(aw->getRequestedSize (), hstretch));
total_width += aw->getRequestedSize ().width;
total_height = std::max(total_height, aw->getRequestedSize ().height);
}
else
{
sizes.push_back (std::make_pair(w->getSize(), hstretch));
total_width += w->getSize().width;
}

if (i != count-1)
total_width += mSpacing;
}

if (mAutoResize && (total_width+mPadding*2 != getSize().width || total_height+mPadding*2 != getSize().height))
{
setSize(MyGUI::IntSize(total_width+mPadding*2, total_height+mPadding*2));
return;
}


int curX = 0;
for (unsigned int i = 0; i < count; ++i)
{
if (i == 0)
curX += mPadding;

MyGUI::Widget* w = getChildAt(i);

bool vstretch = w->getUserString ("VStretch") == "true";
int height = vstretch ? total_height : sizes[i].first.height;

MyGUI::IntCoord widgetCoord;
widgetCoord.left = curX;
widgetCoord.top = mPadding + (getSize().height-mPadding*2 - height) / 2;
int width = sizes[i].second ? sizes[i].first.width + (getSize().width-mPadding*2 - total_width)/h_stretched_count
: sizes[i].first.width;
widgetCoord.width = width;
widgetCoord.height = height;
w->setCoord(widgetCoord);
curX += width;

if (i != count-1)
curX += mSpacing;
}
}

void HBox::setPropertyOverride(const std::string& _key, const std::string& _value)
{
Box::_setPropertyImpl (_key, _value);
}

void HBox::setSize (const MyGUI::IntSize& _value)
{
MyGUI::Widget::setSize (_value);
align();
}

void HBox::setCoord (const MyGUI::IntCoord& _value)
{
MyGUI::Widget::setCoord (_value);
align();
}

void HBox::onWidgetCreated(MyGUI::Widget* _widget)
{
align();
}

void HBox::onWidgetDestroy(MyGUI::Widget* _widget)
{
align();
}

MyGUI::IntSize HBox::getRequestedSize ()
{
MyGUI::IntSize size(0,0);
for (unsigned int i = 0; i < getChildCount (); ++i)
{
AutoSizedWidget* w = dynamic_cast<AutoSizedWidget*>(getChildAt(i));
if (w)
{
MyGUI::IntSize requested = w->getRequestedSize ();
size.height = std::max(size.height, requested.height);
size.width = size.width + requested.width;
if (i != getChildCount()-1)
size.width += mSpacing;
}
else
{
MyGUI::IntSize requested = getChildAt(i)->getSize ();
size.height = std::max(size.height, requested.height);

if (getChildAt(i)->getUserString("HStretch") != "true")
size.width = size.width + requested.width;

if (i != getChildCount()-1)
size.width += mSpacing;
}
size.height += mPadding*2;
size.width += mPadding*2;
}
return size;
}




void VBox::align ()
{
unsigned int count = getChildCount ();
size_t v_stretched_count = 0;
int total_height = 0;
int total_width = 0;
std::vector< std::pair<MyGUI::IntSize, bool> > sizes;
for (unsigned int i = 0; i < count; ++i)
{
MyGUI::Widget* w = getChildAt(i);
bool vstretch = w->getUserString ("VStretch") == "true";
v_stretched_count += vstretch;
AutoSizedWidget* aw = dynamic_cast<AutoSizedWidget*>(w);
if (aw)
{
sizes.push_back(std::make_pair(aw->getRequestedSize (), vstretch));
total_height += aw->getRequestedSize ().height;
total_width = std::max(total_width, aw->getRequestedSize ().width);
}
else
{
sizes.push_back (std::make_pair(w->getSize(), vstretch));
total_height += w->getSize().height;
}

if (i != count-1)
total_height += mSpacing;
}

if (mAutoResize && (total_width+mPadding*2 != getSize().width || total_height+mPadding*2 != getSize().height))
{
setSize(MyGUI::IntSize(total_width+mPadding*2, total_height+mPadding*2));
return;
}


int curY = 0;
for (unsigned int i = 0; i < count; ++i)
{
if (i==0)
curY += mPadding;

MyGUI::Widget* w = getChildAt(i);

bool hstretch = w->getUserString ("HStretch") == "true";
int width = hstretch ? total_width : sizes[i].first.width;

MyGUI::IntCoord widgetCoord;
widgetCoord.top = curY;
widgetCoord.left = mPadding + (getSize().width-mPadding*2 - width) / 2;
int height = sizes[i].second ? sizes[i].first.height + (getSize().height-mPadding*2 - total_height)/v_stretched_count
: sizes[i].first.height;
widgetCoord.height = height;
widgetCoord.width = width;
w->setCoord(widgetCoord);
curY += height;

if (i != count-1)
curY += mSpacing;
}
}

void VBox::setPropertyOverride(const std::string& _key, const std::string& _value)
{
Box::_setPropertyImpl (_key, _value);
}

void VBox::setSize (const MyGUI::IntSize& _value)
{
MyGUI::Widget::setSize (_value);
align();
}

void VBox::setCoord (const MyGUI::IntCoord& _value)
{
MyGUI::Widget::setCoord (_value);
align();
}

MyGUI::IntSize VBox::getRequestedSize ()
{
MyGUI::IntSize size(0,0);
for (unsigned int i = 0; i < getChildCount (); ++i)
{
AutoSizedWidget* w = dynamic_cast<AutoSizedWidget*>(getChildAt(i));
if (w)
{
MyGUI::IntSize requested = w->getRequestedSize ();
size.width = std::max(size.width, requested.width);
size.height = size.height + requested.height;
if (i != getChildCount()-1)
size.height += mSpacing;
}
else
{
MyGUI::IntSize requested = getChildAt(i)->getSize ();
size.width = std::max(size.width, requested.width);

if (getChildAt(i)->getUserString("VStretch") != "true")
size.height = size.height + requested.height;

if (i != getChildCount()-1)
size.height += mSpacing;
}
size.height += mPadding*2;
size.width += mPadding*2;
}
return size;
}

void VBox::onWidgetCreated(MyGUI::Widget* _widget)
{
align();
}

void VBox::onWidgetDestroy(MyGUI::Widget* _widget)
{
align();
}



Header


class AutoSizedWidget
{
public:
virtual MyGUI::IntSize getRequestedSize() = 0;

protected:
void notifySizeChange(MyGUI::Widget* w);

MyGUI::Align mExpandDirection;
};

class AutoSizedTextBox : public AutoSizedWidget, public MyGUI::TextBox
{
MYGUI_RTTI_DERIVED( AutoSizedTextBox )

public:
virtual MyGUI::IntSize getRequestedSize();
virtual void setCaption(const MyGUI::UString& _value);

protected:
virtual void setPropertyOverride(const std::string& _key, const std::string& _value);
};

class AutoSizedButton : public AutoSizedWidget, public MyGUI::Button
{
MYGUI_RTTI_DERIVED( AutoSizedButton )

public:
virtual MyGUI::IntSize getRequestedSize();
virtual void setCaption(const MyGUI::UString& _value);

protected:
virtual void setPropertyOverride(const std::string& _key, const std::string& _value);
};

/**
* @brief A container widget that automatically sizes its children
* @note the box being an AutoSizedWidget as well allows to put boxes inside a box
*/
class Box : public AutoSizedWidget
{
public:
Box();

void notifyChildrenSizeChanged();

protected:
virtual void align() = 0;

virtual void _setPropertyImpl(const std::string& _key, const std::string& _value);

int mSpacing; // how much space to put between elements

int mPadding; // outer padding

bool mAutoResize; // auto resize the box so that it exactly fits all elements
};

class HBox : public Box, public MyGUI::Widget
{
MYGUI_RTTI_DERIVED( HBox )

public:
virtual void setSize (const MyGUI::IntSize &_value);
virtual void setCoord (const MyGUI::IntCoord &_value);

protected:
virtual void align();
virtual MyGUI::IntSize getRequestedSize();

virtual void setPropertyOverride(const std::string& _key, const std::string& _value);

virtual void onWidgetCreated(MyGUI::Widget* _widget);
virtual void onWidgetDestroy(MyGUI::Widget* _widget);
};

class VBox : public Box, public MyGUI::Widget
{
MYGUI_RTTI_DERIVED( VBox)

public:
virtual void setSize (const MyGUI::IntSize &_value);
virtual void setCoord (const MyGUI::IntCoord &_value);

protected:
virtual void align();
virtual MyGUI::IntSize getRequestedSize();

virtual void setPropertyOverride(const std::string& _key, const std::string& _value);

virtual void onWidgetCreated(MyGUI::Widget* _widget);
virtual void onWidgetDestroy(MyGUI::Widget* _widget);
};