Layout size auto calculation

So, I’ve got a layout and I push some elements into it. Is it possible to know the size of the layout when I finish adding the children? It seems the size of the layout is not automatically calculated. If that’s right then it’s quite dissappointing, since the purpose of the layout is foggy in this case.
We’d be happy if the ui containers would work like DOM-elements in HTML, e.g. when one <div> is inserted into another one, it’s possible to get the size of the outter one.

PS. I’m aware of Layout::getLayoutAccumulatedSize(), but it’s protected and it’s no available for all Widgets.

Anyone please?

This may not help, but I’m currently using this code below. It’s far from perfect, but does what I need for now.

This also takes into account setting margins on child widgets, and I haven’t determined the exact calculation as I’m haven’t looked into how they apply left and right margins of neighbor elements. The only issue I’m having is layouts where a child widget is scaled. If there are sub layouts then I update their bounds first, then the root layout. Hopefully they fix the way Layout works so that it does update its contentsize to fit all of its children, instead of having to set the size manually as it does now. Also hopefully they take into account scale or the affine transforms.

void GUIHelper::updateBoundsToFitChildren(Layout* layout)
{
    auto layoutType = layout->getLayoutType();
    float w = layout->getContentSize().width;
    float h = layout->getContentSize().height;

    if(layoutType == LayoutType::HORIZONTAL) {
        w = 0;
    } else if(layoutType == LayoutType::VERTICAL) {
        h = 0;
    }

    bool firstChild = true;
    auto childs = layout->getChildren();
    for(auto child : childs)
    {
        auto childWidget = dynamic_cast<Widget*>(child);
        if(childWidget)
        {
            auto childSize = childWidget->getBoundingBox().size;

            auto margin = childWidget->getLayoutParameter()->getMargin();
            auto marginWidth = MAX(margin.left, margin.right);
            auto marginHeight = MAX(margin.top, margin.bottom);

            if(true || firstChild) {
                marginWidth = margin.left + margin.right;
                marginHeight = margin.top + margin.bottom;
                firstChild = false;
            }

            if(layoutType == LayoutType::HORIZONTAL) {
                w += childSize.width + marginWidth;
                auto childHeight = childSize.height + marginHeight;
                h = MAX(h, childHeight);
            } else if(layoutType == LayoutType::VERTICAL) {
                auto childWidth = childSize.width + marginWidth;
                w = MAX(w, childWidth);
                h += childSize.height + marginHeight;
            }
        }
    }

    auto size = Size(w,h);
    layout->setContentSize(size);
}

Thanks.
I’ve written pretty similar helper functions by myself before I asked the question here.
Actually my post is intended to force the developers to make a built-in solution…

2 Likes

Something like this should also be available as a recursive method for simply getting a Size object describing a populated Layout with nested Layout elements (e.g., HBoxes within a VBox).

I’ll post a link to this thread in the feature wish list thread.

1 Like

FYI, just found Layout::getLayoutAccumulatedSize in UILayout.cpp:

Size Layout::getLayoutAccumulatedSize()const
{
const auto& children = this->getChildren();
Size layoutSize = Size::ZERO;
int widgetCount =0;
for(const auto& widget : children)
{
Layout layout = dynamic_cast<Layout>(widget);
if (nullptr != layout)
{
layoutSize = layoutSize + layout->getLayoutAccumulatedSize();
}
else
{
Widget w = dynamic_cast<Widget>(widget);
if (w)
{
widgetCount++;
Margin m = w->getLayoutParameter()->getMargin();
layoutSize = layoutSize + w->getContentSize() + Size(m.right + m.left, m.top + m.bottom) * 0.5;
}
}
}

//substract extra size
Type type = this->getLayoutType();
if (type == Type::HORIZONTAL)
{
    layoutSize = layoutSize - Size(0, layoutSize.height/widgetCount * (widgetCount-1));
}
if (type == Type::VERTICAL)
{
    layoutSize = layoutSize - Size(layoutSize.width/widgetCount * (widgetCount-1), 0);
}
return layoutSize;

}

Yep, it’s protected though (see first post @dotquid). I based my code off it. If you make that public it might work great for you. I found that with a variety of widget types and a few levels of hierarchy that it didn’t render as expected.

But hopefully it gets improved alongside the next major update to CocoStudio.

Dear all,
I had the same need and I wrote an inherited version of Layout class to manage this, and it worked like a charm :stuck_out_tongue_winking_eye: ! I don’t understand why this is not a Layout class option (setAutoSizeMode(true/false)). I put the code here :

Header :

#ifndef _AUTO_SIZE_LAYOUT_H_
#define _AUTO_SIZE_LAYOUT_H_

#include "cocos2d.h"
#include "ui/CocosGUI.h"

using namespace cocos2d;
using namespace cocos2d::ui;

class OAutoSizeLayout : public Layout
{
public:
	CREATE_FUNC(OAutoSizeLayout);

	virtual bool init() override;

	// I don't understand why but in C++ when you override one prototype, it hides all other prototypes of the same method !
	//	=> so override all prtotypes the same way
	virtual void addChild(Node* child)override;
	virtual void addChild(Node * child, int localZOrder)override;
	virtual void addChild(Node* child, int localZOrder, int tag) override;
	virtual void addChild(Node* child, int localZOrder, const std::string &name) override;

protected:
	void ResizeFromChildAdded(Node* child);
};

#endif

Implementation :

#include "OAutoSizeLayout.h"

USING_NS_CC;
using namespace cocos2d::ui;

bool OAutoSizeLayout::init()
{
	bool ret = Layout::init();
	if (ret)
		setAnchorPoint(Vec2::ANCHOR_MIDDLE);
	return ret;
}

void OAutoSizeLayout::addChild(Node *child, int zOrder, int tag)
{
	// 1. Do the base work
	Layout::addChild(child, zOrder, tag);
	// 2. Do the auto size additionnal work
	ResizeFromChildAdded(child);
}

void OAutoSizeLayout::addChild(Node* child)
{
	// 1. Do the base work
	Layout::addChild(child);
	// 2. Do the auto size additionnal work
	ResizeFromChildAdded(child);
}

void OAutoSizeLayout::addChild(Node * child, int localZOrder)
{
	// 1. Do the base work
	Layout::addChild(child, localZOrder);
	// 2. Do the auto size additionnal work
	ResizeFromChildAdded(child);
}

void OAutoSizeLayout::addChild(Node* child, int zOrder, const std::string &name)
{
	// 1. Do the base work
	Layout::addChild(child, zOrder, name);
	// 2. Do the auto size additionnal work
	ResizeFromChildAdded(child);
}

void OAutoSizeLayout::ResizeFromChildAdded(Node* child)
{
	const Size& childSize = child->getContentSize();
	CCASSERT(childSize.width != 0 && childSize.height != 0, "When using OAutoSizeLayout, any child added must have its ContentSize set!");
	Widget* childAsWidget = dynamic_cast<Widget*>(child);
	const LayoutParameter* childLOP = childAsWidget->getLayoutParameter();
	if (childAsWidget != nullptr)
	{
		CCASSERT(nullptr != childLOP, "When using OAutoSizeLayout, any Widget child added must have its LayoutParameter set!");
	}
	float hTotalMargin = childLOP != nullptr ? childLOP->getMargin().left + childLOP->getMargin().right : 0;
	float vTotalMargin = childLOP != nullptr ? childLOP->getMargin().top + childLOP->getMargin().bottom : 0;
	float newWidth = -1;
	float newHeigth = -1;
	if (getLayoutType() == Type::HORIZONTAL)
	{
		// Horizontal arrangement :		1. width is the sum of children widths (+ margins)
		//								2. Height is the greatest heigth (+ margins)
		newWidth = _contentSize.width + hTotalMargin + childSize.width;
		newHeigth = _contentSize.height;
		if (newHeigth < vTotalMargin + childSize.height)
			newHeigth = vTotalMargin + childSize.height;
	}
	else if (getLayoutType() == Type::VERTICAL)
	{
		// Horizontal arrangement :		1. height is the sum of children heightss (+ margins)
		//								2. width is the greatest width (+ margins)
		newWidth = _contentSize.width;
		newHeigth = _contentSize.height + vTotalMargin + childSize.height;
		if (newWidth < hTotalMargin + childSize.width)
			newWidth = hTotalMargin + childSize.width;
	}
	if (newWidth >= 0)
		setContentSize(Size(newWidth, newHeigth));
}

I hope this helps somebody :sunglasses: !

1 Like

Hi all !

One more time, this morning, I asked myself why I made an inherited class which permit to have a layout whose size is update each time a child is added in it…, And I retreive the same discussions about this :joy: !

So if it can be useful to anyone, I made an update of my inherited AutoSizeLayout class to permit to set a background image in it : this is the code if somebody is interested (by the way if somebody understood why the Layout method getLayoutAccumulatedSize() used margin * 0.5 to calculate the size, I will be happy to know why :fearful:)

So this is the new header

#ifndef _AUTO_SIZE_LAYOUT_H_
#define _AUTO_SIZE_LAYOUT_H_

#include "cocos2d.h"
#include "ui/CocosGUI.h"

using namespace cocos2d;
using namespace cocos2d::ui;
using namespace std;

namespace myStore {

class AutoSizeLayout : public Layout
{
public:
	//CREATE_FUNC(AutoSizeLayout);
	AutoSizeLayout(Type loType) : Layout()
	{ 
		init(); 
		setLayoutType(loType);
	};

	virtual bool init() override;

	// I don't understand why but in C++ when you override one prototype, it hides all other prototypes of the same method !
	//	=> so override all prototypes the same way
	virtual void addChild(Node* child)override;
	virtual void addChild(Node * child, int localZOrder)override;
	virtual void addChild(Node* child, int localZOrder, int tag) override;
	virtual void addChild(Node* child, int localZOrder, const string &name) override;

	void setBackGroundFrame(string frame);

protected:
	Size _childrenTotalSize;
	Size _backGroundSize;

	void ResizeFromChildAdded(Node* child);
	void updateContentSize();
};

}
#endif

and the new implementation

#include "AutoSizeLayout.h"

USING_NS_CC;
using namespace cocos2d::ui;

namespace myStore {

bool AutoSizeLayout::init()
{
	bool ret = Layout::init();
	if (ret)
	{
		setAnchorPoint(Vec2::ANCHOR_MIDDLE);
		_childrenTotalSize = Size::ZERO;
		_backGroundSize = Size::ZERO;
	}
	return ret;
}

void AutoSizeLayout::addChild(Node *child, int zOrder, int tag)
{
	// 1. Do the base work
	Layout::addChild(child, zOrder, tag);
	// 2. Do the auto size additionnal work
	ResizeFromChildAdded(child);
}

void AutoSizeLayout::addChild(Node* child)
{
	// 1. Do the base work
	Layout::addChild(child);
	// 2. Do the auto size additionnal work
	ResizeFromChildAdded(child);
}

void AutoSizeLayout::addChild(Node * child, int localZOrder)
{
	// 1. Do the base work
	Layout::addChild(child, localZOrder);
	// 2. Do the auto size additionnal work
	ResizeFromChildAdded(child);
}

void AutoSizeLayout::addChild(Node* child, int zOrder, const std::string &name)
{
	// 1. Do the base work
	Layout::addChild(child, zOrder, name);
	// 2. Do the auto size additionnal work
	ResizeFromChildAdded(child);
}

void AutoSizeLayout::ResizeFromChildAdded(Node* child)
{
	// OP : I don't understand why getLayoutAccumulatedSize returns the margin size * 0.5 !!
	// => so I can't use it for children total size !
	//Size s = getLayoutAccumulatedSize();

	const Size& childSize = child->getContentSize();
	CCASSERT(childSize.width != 0 && childSize.height != 0, "When using OAutoSizeLayout, any child added must have its ContentSize set!");
	Widget* childAsWidget = dynamic_cast<Widget*>(child);
	const LayoutParameter* childLOP = childAsWidget->getLayoutParameter();
	if (childAsWidget != nullptr)
	{
		CCASSERT(nullptr != childLOP, "When using OAutoSizeLayout, any Widget child added must have its LayoutParameter set!");
	}
	float hTotalMargin = childLOP != nullptr ? childLOP->getMargin().left + childLOP->getMargin().right : 0;
	float vTotalMargin = childLOP != nullptr ? childLOP->getMargin().top + childLOP->getMargin().bottom : 0;
	float newWidth = -1;
	float newHeigth = -1;
	if (getLayoutType() == Type::HORIZONTAL)
	{
		// Horizontal arrangement :		1. width is the sum of children widths (+ margins)
		//								2. Height is the greatest heigth (+ margins)
		_childrenTotalSize.width += hTotalMargin + childSize.width;
		if (_childrenTotalSize.height < vTotalMargin + childSize.height)
			_childrenTotalSize.height = vTotalMargin + childSize.height;
	}
	else if (getLayoutType() == Type::VERTICAL)
	{
		// Horizontal arrangement :		1. height is the sum of children heightss (+ margins)
		//								2. width is the greatest width (+ margins)
		_childrenTotalSize.height += vTotalMargin + childSize.height;
		if (_childrenTotalSize.width < hTotalMargin + childSize.width)
			_childrenTotalSize.width = hTotalMargin + childSize.width;
	}
	updateContentSize();
}

void AutoSizeLayout::updateContentSize()
{
	setContentSize(Size(_childrenTotalSize.width > _backGroundSize.width ? _childrenTotalSize.width : _backGroundSize.width,
						_childrenTotalSize.height > _backGroundSize.height ? _childrenTotalSize.height : _backGroundSize.height));
}

void AutoSizeLayout::setBackGroundFrame(string frame)
{
	setBackGroundImage(frame, Widget::TextureResType::PLIST);

	_backGroundSize = _backGroundImage->getContentSize();
	updateContentSize();
}

}

Again, I hope this helps… But since I had no reply, I think that it helsped nobody :sob:

BR

6 Likes