Kaynağa Gözat

Layout actual size calculation without requiring an update (Untested)
Updated GUIScrollArea so it's element positions/sizes can be properly retrieved without requiring an update (Untested)

Marko Pintera 11 yıl önce
ebeveyn
işleme
eb21414923

+ 9 - 0
BansheeEngine/Include/BsGUILayout.h

@@ -152,6 +152,15 @@ namespace BansheeEngine
 		 */
 		GUIArea* _getParentGUIArea() const { return mParentGUIArea; }
 
+		/**
+		 * @brief	Calculates the actual size of the layout taken up by all of its elements.
+		 *			
+		 * @note	Actual size means the bounds might be smaller or larger than the layout area itself.
+		 *			If larger that means certain portions of the child elements will be clipped, and if
+		 *			smaller certain portions of the layout area will be empty.
+		 */
+		virtual Vector2I _calcActualSize(Rect2I* elementAreas, UINT32 numElements) const = 0;
+
 	protected:
 		GUIArea* mParentGUIArea;
 

+ 5 - 0
BansheeEngine/Include/BsGUILayoutExplicit.h

@@ -30,6 +30,11 @@ namespace BansheeEngine
 		 * @copydoc	GUILayout::_getElementAreas
 		 */
 		void _getElementAreas(INT32 x, INT32 y, UINT32 width, UINT32 height, Rect2I* elementAreas, UINT32 numElements, const Vector<Vector2I>& optimalSizes) const;
+
+		/**
+		 * @copydoc	GUILayout::_calcActualSize
+		 */
+		virtual Vector2I _calcActualSize(Rect2I* elementAreas, UINT32 numElements) const;
 	protected:
 		/**
 		 * @brief	Positions/size all child layout elements based on the provided settings and their (previously calculated) optimal sizes.

+ 9 - 0
BansheeEngine/Include/BsGUILayoutUtility.h

@@ -27,5 +27,14 @@ namespace BansheeEngine
 		// of such custom container elements like tree view don't have method for calculating 
 		// element bounds implemented
 		static Rect2I calcBounds(const GUIElementBase* elem);
+
+		/**
+		 * @brief	Calculates the actual size of the layout taken up by all of its elements.
+		 *			
+		 * @note	Actual size means the bounds might be smaller or larger than the layout area itself.
+		 *			If larger that means certain portions of the child elements will be clipped, and if
+		 *			smaller certain portions of the layout area will be empty.
+		 */
+		static Vector2I calcActualSize(UINT32 width, UINT32 height, const GUILayout* layout);
 	};
 }

+ 5 - 0
BansheeEngine/Include/BsGUILayoutX.h

@@ -30,6 +30,11 @@ namespace BansheeEngine
 		 * @copydoc	GUILayout::_getElementAreas
 		 */
 		void _getElementAreas(INT32 x, INT32 y, UINT32 width, UINT32 height, Rect2I* elementAreas, UINT32 numElements, const Vector<Vector2I>& optimalSizes) const;
+
+		/**
+		 * @copydoc	GUILayout::_calcActualSize
+		 */
+		virtual Vector2I _calcActualSize(Rect2I* elementAreas, UINT32 numElements) const;
 	protected:
 		/**
 		 * @brief	Positions/size all child layout elements based on the provided settings and their (previously calculated) optimal sizes.

+ 5 - 0
BansheeEngine/Include/BsGUILayoutY.h

@@ -30,6 +30,11 @@ namespace BansheeEngine
 		 * @copydoc	GUILayout::_getElementAreas
 		 */
 		void _getElementAreas(INT32 x, INT32 y, UINT32 width, UINT32 height, Rect2I* elementAreas, UINT32 numElements, const Vector<Vector2I>& optimalSizes) const;
+
+		/**
+		 * @copydoc	GUILayout::_calcActualSize
+		 */
+		virtual Vector2I _calcActualSize(Rect2I* elementAreas, UINT32 numElements) const;
 	protected:
 		/**
 		 * @brief	Positions/size all child layout elements based on the provided settings and their (previously calculated) optimal sizes.

+ 15 - 2
BansheeEngine/Include/BsGUIScrollArea.h

@@ -170,6 +170,19 @@ namespace BansheeEngine
 		void _updateLayoutInternal(INT32 x, INT32 y, UINT32 width, UINT32 height,
 			Rect2I clipRect, UINT8 widgetDepth, UINT16 areaDepth);
 
+		/**
+		 * @copydoc	GUIElementContainer::_getElementAreas
+		 */
+		void _getElementAreas(INT32 x, INT32 y, UINT32 width, UINT32 height, Rect2I* elementAreas, UINT32 numElements, const Vector<Vector2I>& optimalSizes) const;
+
+		/**
+		 * @copydoc	GUIElementContainer::_getElementAreas
+		 *
+		 * @note	Also calculates some scroll area specific values.
+		 */
+		void _getElementAreas(INT32 x, INT32 y, UINT32 width, UINT32 height, Rect2I* elementAreas, UINT32 numElements, 
+			const Vector<Vector2I>& optimalSizes, Vector2I& visibleSize, Vector2I& contentSize) const;
+
 		ScrollBarType mVertBarType;
 		ScrollBarType mHorzBarType;
 		String mScrollBarStyle;
@@ -181,8 +194,8 @@ namespace BansheeEngine
 		float mVertOffset;
 		float mHorzOffset;
 
-		UINT32 mClippedContentWidth, mClippedContentHeight;
-		UINT32 mContentWidth, mContentHeight;
+		Vector2I mVisibleSize;
+		Vector2I mContentSize;
 
 		static const UINT32 ScrollBarWidth;
 		static const UINT32 MinHandleSize;

+ 8 - 2
BansheeEngine/Source/BsGUILayoutExplicit.cpp

@@ -55,8 +55,6 @@ namespace BansheeEngine
 	{
 		UINT32 childIdx = 0;
 
-		mActualWidth = width;
-		mActualHeight = height;
 		for (auto& child : mChildren)
 		{
 			if (child->_getType() == GUIElementBase::Type::Element)
@@ -83,6 +81,14 @@ namespace BansheeEngine
 			childIdx++;
 		}
 
+		mActualWidth = 0; // Not relevant
+		mActualHeight = 0;
+
 		_markAsClean();
 	}
+
+	Vector2I GUILayoutExplicit::_calcActualSize(Rect2I* elementAreas, UINT32 numElements) const
+	{
+		return Vector2I(0, 0); // Not relevant
+	}
 }

+ 54 - 0
BansheeEngine/Source/BsGUILayoutUtility.cpp

@@ -2,6 +2,8 @@
 #include "BsGUIElementBase.h"
 #include "BsGUILayout.h"
 #include "BsGUIArea.h"
+#include "BsGUIElement.h"
+#include "BsGUIElementStyle.h"
 
 namespace BansheeEngine
 {
@@ -58,4 +60,56 @@ namespace BansheeEngine
 
 		return myArea;
 	}
+
+	Vector2I GUILayoutUtility::calcActualSize(UINT32 width, UINT32 height, const GUILayout* layout)
+	{
+		UINT32 numElements = (UINT32)layout->_getNumChildren();
+
+		Vector<Vector2I> optimalSizes;
+		for (UINT32 i = 0; i < numElements; i++)
+		{
+			GUIElementBase* child = layout->_getChild(i);
+			optimalSizes.push_back(GUILayoutUtility::calcOptimalSize(child));
+		}
+
+		Rect2I* elementAreas = nullptr;
+
+		if (numElements > 0)
+			elementAreas = stackConstructN<Rect2I>(numElements);
+
+		layout->_getElementAreas(0, 0, width, height, elementAreas, numElements, optimalSizes);
+		Rect2I* actualAreas = elementAreas; // We re-use the same array
+
+		for (UINT32 i = 0; i < numElements; i++)
+		{
+			GUIElementBase* child = layout->_getChild(i);
+			Rect2I childArea = elementAreas[i];
+
+			if (child->_getType() == GUIElementBase::Type::Layout)
+			{
+				Vector2I childActualSize = calcActualSize(childArea.width, childArea.height, static_cast<GUILayout*>(child));
+				actualAreas[i].width = (UINT32)childActualSize.x;
+				actualAreas[i].height = (UINT32)childActualSize.y;
+			}
+			else if (child->_getType() == GUIElementBase::Type::Element)
+			{
+				RectOffset padding = child->_getPadding();
+
+				actualAreas[i].width = elementAreas[i].width + padding.left + padding.right;
+				actualAreas[i].height = elementAreas[i].height + padding.top + padding.bottom;
+			}
+			else
+			{
+				actualAreas[i].width = elementAreas[i].width;
+				actualAreas[i].height = elementAreas[i].height;
+			}
+		}
+
+		Vector2I actualSize = layout->_calcActualSize(actualAreas, numElements);
+
+		if (elementAreas != nullptr)
+			stackDeallocLast(elementAreas);
+
+		return actualSize;
+	}
 }

+ 25 - 7
BansheeEngine/Source/BsGUILayoutX.cpp

@@ -402,9 +402,8 @@ namespace BansheeEngine
 
 		// Now that we have all the areas, actually assign them
 		UINT32 childIdx = 0;
+		Rect2I* actualSizes = elementAreas; // We re-use the same array
 
-		mActualWidth = 0;
-		mActualHeight = 0;
 		for(auto& child : mChildren)
 		{
 			Rect2I childArea = elementAreas[childIdx];
@@ -427,9 +426,9 @@ namespace BansheeEngine
 				newClipRect.clip(clipRect);
 				element->_updateLayoutInternal(offset.x, offset.y, childArea.width, childArea.height, newClipRect, widgetDepth, areaDepth);
 
-				mActualHeight = std::max(mActualHeight, (UINT32)childArea.height);
+				actualSizes[childIdx].height = childArea.height + child->_getPadding().top + child->_getPadding().bottom;
 			}
-			else if(child->_getType() == GUIElementBase::Type::Layout)
+			else if (child->_getType() == GUIElementBase::Type::Layout)
 			{
 				GUILayout* layout = static_cast<GUILayout*>(child);
 
@@ -437,17 +436,36 @@ namespace BansheeEngine
 				newClipRect.clip(clipRect);
 				layout->_updateLayoutInternal(childArea.x, childArea.y, childArea.width, height, newClipRect, widgetDepth, areaDepth);
 
-				UINT32 childHeight = layout->_getActualHeight();
-				mActualHeight = std::max(mActualHeight, childHeight);
+				actualSizes[childIdx].height = layout->_getActualHeight();
 			}
+			else
+				actualSizes[childIdx].height = childArea.height;
 
-			mActualWidth += childArea.width + child->_getPadding().left + child->_getPadding().right;
+			actualSizes[childIdx].x = childArea.width + child->_getPadding().left + child->_getPadding().right;
 			childIdx++;
 		}
 
+		Vector2I actualSize = _calcActualSize(actualSizes, numElements);
+		mActualWidth = (UINT32)actualSize.x;
+		mActualHeight = (UINT32)actualSize.y;
+
 		if(elementAreas != nullptr)
 			stackDeallocLast(elementAreas);
 
 		_markAsClean();
 	}
+
+	Vector2I GUILayoutX::_calcActualSize(Rect2I* elementAreas, UINT32 numElements) const
+	{
+		Vector2I actualArea;
+		for (UINT32 i = 0; i < numElements; i++)
+		{
+			Rect2I childArea = elementAreas[i];
+
+			actualArea.x = childArea.width;
+			actualArea.y += std::max(actualArea.x, childArea.width);
+		}
+
+		return actualArea;
+	}
 }

+ 24 - 5
BansheeEngine/Source/BsGUILayoutY.cpp

@@ -399,9 +399,8 @@ namespace BansheeEngine
 
 		// Now that we have all the areas, actually assign them
 		UINT32 childIdx = 0;
+		Rect2I* actualSizes = elementAreas; // We re-use the same array
 
-		mActualWidth = 0;
-		mActualHeight = 0;
 		for(auto& child : mChildren)
 		{
 			Rect2I childArea = elementAreas[childIdx];
@@ -425,7 +424,7 @@ namespace BansheeEngine
 				newClipRect.clip(clipRect);
 				element->_updateLayoutInternal(offset.x, offset.y, childArea.width, childArea.height, newClipRect, widgetDepth, areaDepth);
 
-				mActualWidth = std::max(mActualWidth, (UINT32)childArea.width);
+				actualSizes[childIdx].width = childArea.width + element->_getPadding().left + element->_getPadding().right;
 			}
 			else if(child->_getType() == GUIElementBase::Type::Layout)
 			{
@@ -435,16 +434,36 @@ namespace BansheeEngine
 				newClipRect.clip(clipRect);
 				layout->_updateLayoutInternal(childArea.x, childArea.y, width, childArea.height, newClipRect, widgetDepth, areaDepth);
 
-				mActualWidth = std::max(mActualWidth, layout->_getActualWidth());
+				actualSizes[childIdx].width = layout->_getActualWidth();
 			}
+			else
+				actualSizes[childIdx].width = childArea.width;
 
-			mActualHeight += childArea.height + child->_getPadding().top + child->_getPadding().bottom;
+			actualSizes[childIdx].height = childArea.height + child->_getPadding().top + child->_getPadding().bottom;
 			childIdx++;
 		}
 
+		Vector2I actualSize = _calcActualSize(actualSizes, numElements);
+		mActualWidth = (UINT32)actualSize.x;
+		mActualHeight = (UINT32)actualSize.y;
+
 		if (elementAreas != nullptr)
 			stackDeallocLast(elementAreas);
 
 		_markAsClean();
 	}
+
+	Vector2I GUILayoutY::_calcActualSize(Rect2I* elementAreas, UINT32 numElements) const
+	{
+		Vector2I actualArea;
+		for (UINT32 i = 0; i < numElements; i++)
+		{
+			Rect2I childArea = elementAreas[i];
+
+			actualArea.x = std::max(actualArea.x, childArea.width);
+			actualArea.y += childArea.height;
+		}
+
+		return actualArea;
+	}
 }

+ 173 - 147
BansheeEngine/Source/BsGUIScrollArea.cpp

@@ -8,6 +8,7 @@
 #include "BsGUIScrollBarVert.h"
 #include "BsGUIScrollBarHorz.h"
 #include "BsGUIMouseEvent.h"
+#include "BsGUILayoutUtility.h"
 #include "BsException.h"
 
 using namespace std::placeholders;
@@ -21,10 +22,18 @@ namespace BansheeEngine
 	GUIScrollArea::GUIScrollArea(ScrollBarType vertBarType, ScrollBarType horzBarType, 
 		const String& scrollBarStyle, const String& scrollAreaStyle, const GUILayoutOptions& layoutOptions)
 		:GUIElementContainer(layoutOptions), mVertScroll(nullptr), mHorzScroll(nullptr), mVertOffset(0), mHorzOffset(0),
-		mContentWidth(0), mContentHeight(0), mClippedContentWidth(0), mClippedContentHeight(0), mVertBarType(vertBarType), mHorzBarType(horzBarType),
-		mScrollBarStyle(scrollBarStyle)
+		mVertBarType(vertBarType), mHorzBarType(horzBarType), mScrollBarStyle(scrollBarStyle)
 	{
 		mContentLayout = &addLayoutYInternal(this);
+
+		mHorzScroll = GUIScrollBarHorz::create(mScrollBarStyle);
+		mVertScroll = GUIScrollBarVert::create(mScrollBarStyle);
+
+		_registerChildElement(mHorzScroll);
+		_registerChildElement(mVertScroll);
+
+		mHorzScroll->onScrollPositionChanged.connect(std::bind(&GUIScrollArea::horzScrollUpdate, this, _1));
+		mVertScroll->onScrollPositionChanged.connect(std::bind(&GUIScrollArea::vertScrollUpdate, this, _1));
 	}
 
 	GUIScrollArea::~GUIScrollArea()
@@ -42,136 +51,187 @@ namespace BansheeEngine
 		mClippedBounds = bounds;
 	}
 
-	void GUIScrollArea::_updateLayoutInternal(INT32 x, INT32 y, UINT32 width, UINT32 height,
-		Rect2I clipRect, UINT8 widgetDepth, UINT16 areaDepth)
+	void GUIScrollArea::_getElementAreas(INT32 x, INT32 y, UINT32 width, UINT32 height, Rect2I* elementAreas, UINT32 numElements, const Vector<Vector2I>& optimalSizes) const
+	{
+		Vector2I visibleSize, contentSize;
+		_getElementAreas(x, y, width, height, elementAreas, numElements, optimalSizes, visibleSize, contentSize);
+	}
+
+	void GUIScrollArea::_getElementAreas(INT32 x, INT32 y, UINT32 width, UINT32 height, Rect2I* elementAreas, UINT32 numElements, 
+		const Vector<Vector2I>& optimalSizes, Vector2I& visibleSize, Vector2I& contentSize) const
 	{
-		// We want elements to use their optimal height, since scroll area
-		// technically provides "infinite" space
-		UINT32 contentLayoutWidth = width;
-		if(mHorzBarType != ScrollBarType::NeverShow)
-			contentLayoutWidth = mContentLayout->_getOptimalSize().x;
+		assert(mChildren.size() == numElements && numElements == 3);
+
+		UINT32 layoutIdx = 0;
+		UINT32 horzScrollIdx = 0;
+		UINT32 vertScrollIdx = 0;
+		UINT32 idx = 0;
+		for (auto& child : mChildren)
+		{
+			if (child == mContentLayout)
+				layoutIdx = idx;
 
-		UINT32 contentLayoutHeight = height;
-		if(mVertBarType != ScrollBarType::NeverShow)
-			contentLayoutHeight = mContentLayout->_getOptimalSize().y;
+			if (child == mHorzScroll)
+				horzScrollIdx = idx;
 
-		UINT32 maxedContentLayoutWidth = std::max(contentLayoutWidth, mWidth);
-		UINT32 maxedContentLayoutHeight = std::max(contentLayoutHeight, mHeight);
+			if (child == mVertScroll)
+				vertScrollIdx = idx;
 
-		mContentLayout->_updateLayoutInternal(x, y, maxedContentLayoutWidth, maxedContentLayoutHeight, 
-			clipRect, widgetDepth, areaDepth);
+			idx++;
+		}
 
-		mContentWidth = mContentLayout->_getActualWidth();
-		mContentHeight = mContentLayout->_getActualHeight();
+		// Calculate content layout bounds
 
-		mClippedContentWidth = width;
-		mClippedContentHeight = height;
+		//// We want elements to use their optimal height, since scroll area
+		//// technically provides "infinite" space
+		UINT32 optimalContentWidth = width;
+		if (mHorzBarType != ScrollBarType::NeverShow)
+			optimalContentWidth = optimalSizes[layoutIdx].x;
 
-		Rect2I layoutClipRect = clipRect;
-		bool addHorzScrollbar = (mHorzBarType == ScrollBarType::ShowIfDoesntFit && mContentWidth > mWidth) || 
+		UINT32 optimalContentHeight = height;
+		if (mVertBarType != ScrollBarType::NeverShow)
+			optimalContentHeight = optimalSizes[layoutIdx].y;
+
+		UINT32 layoutWidth = std::max(optimalContentWidth, width);
+		UINT32 layoutHeight = std::max(optimalContentHeight, height);
+
+		contentSize = GUILayoutUtility::calcActualSize(layoutWidth, layoutHeight, mContentLayout);
+		visibleSize = Vector2I(width, height);
+
+		bool addHorzScrollbar = (mHorzBarType == ScrollBarType::ShowIfDoesntFit && contentSize.x > visibleSize.x) ||
 			mHorzBarType == ScrollBarType::AlwaysShow && mHorzBarType != ScrollBarType::NeverShow;
 
 		bool hasHorzScrollbar = false;
 		bool hasVertScrollbar = false;
-		if(addHorzScrollbar)
-		{ 
+		if (addHorzScrollbar)
+		{
 			// Make room for scrollbar
-			mClippedContentHeight = (UINT32)std::max(0, (INT32)height - (INT32)ScrollBarWidth);
-			layoutClipRect.height = mClippedContentHeight;
-			hasHorzScrollbar = true;
+			visibleSize.y = (UINT32)std::max(0, (INT32)height - (INT32)ScrollBarWidth);
 
 			if (mVertBarType == ScrollBarType::NeverShow)
-				maxedContentLayoutHeight = mClippedContentHeight;
+				layoutHeight = (UINT32)visibleSize.y;
 			else
-				maxedContentLayoutHeight = std::max(contentLayoutHeight, mClippedContentHeight); // Never go below optimal size
-
-			mContentLayout->_updateLayoutInternal(x - Math::floorToInt(mHorzOffset), y, 
-				maxedContentLayoutWidth, maxedContentLayoutHeight, layoutClipRect, widgetDepth, areaDepth);
+				layoutHeight = std::max(optimalContentHeight, (UINT32)visibleSize.y); // Never go below optimal size
 
-			mContentWidth = mContentLayout->_getActualWidth();
-			mContentHeight = mContentLayout->_getActualHeight();
+			contentSize = GUILayoutUtility::calcActualSize(layoutWidth, layoutHeight, mContentLayout);
+			hasHorzScrollbar = true;
 		}
 
-		bool addVertScrollbar = (mVertBarType == ScrollBarType::ShowIfDoesntFit && mContentHeight > mClippedContentHeight) || 
+		bool addVertScrollbar = (mVertBarType == ScrollBarType::ShowIfDoesntFit && contentSize.y > visibleSize.y) ||
 			mVertBarType == ScrollBarType::AlwaysShow && mVertBarType != ScrollBarType::NeverShow;
 
-		if(addVertScrollbar)
+		if (addVertScrollbar)
 		{
 			// Make room for scrollbar
-			mClippedContentWidth = (UINT32)std::max(0, (INT32)width - (INT32)ScrollBarWidth);
-			layoutClipRect.width = mClippedContentWidth;
-			hasVertScrollbar = true;
-
+			visibleSize.x = (UINT32)std::max(0, (INT32)width - (INT32)ScrollBarWidth);
+			
 			if (mHorzBarType == ScrollBarType::NeverShow)
-				maxedContentLayoutWidth = mClippedContentWidth;
+				layoutWidth = (UINT32)visibleSize.x;
 			else
-				maxedContentLayoutWidth = std::max(contentLayoutWidth, mClippedContentWidth); // Never go below optimal size
+				layoutWidth = std::max(optimalContentWidth, (UINT32)visibleSize.x); // Never go below optimal size
 
-			if(hasHorzScrollbar)
-			{
-				mContentLayout->_updateLayoutInternal(x - Math::floorToInt(mHorzOffset), y - Math::floorToInt(mVertOffset), 
-					maxedContentLayoutWidth, maxedContentLayoutHeight,
-					layoutClipRect, widgetDepth, areaDepth);
-			}
-			else
-			{
-				mContentLayout->_updateLayoutInternal(x, y - Math::floorToInt(mVertOffset), 
-					maxedContentLayoutWidth, maxedContentLayoutHeight,
-					layoutClipRect, widgetDepth, areaDepth);
-			}
-
-			mContentWidth = mContentLayout->_getActualWidth();
-			mContentHeight = mContentLayout->_getActualHeight();
+			contentSize = GUILayoutUtility::calcActualSize(layoutWidth, layoutHeight, mContentLayout);
+			hasVertScrollbar = true;
 
-			if(!hasHorzScrollbar) // Since width has been reduced, we need to check if we require the horizontal scrollbar
+			if (!hasHorzScrollbar) // Since width has been reduced, we need to check if we require the horizontal scrollbar
 			{
-				addHorzScrollbar = (mHorzBarType == ScrollBarType::ShowIfDoesntFit && mContentWidth > mClippedContentWidth) && mHorzBarType != ScrollBarType::NeverShow;
+				addHorzScrollbar = (mHorzBarType == ScrollBarType::ShowIfDoesntFit && contentSize.x > visibleSize.x) && mHorzBarType != ScrollBarType::NeverShow;
 
-				if(addHorzScrollbar)
+				if (addHorzScrollbar)
 				{
 					// Make room for scrollbar
-					mClippedContentHeight = (UINT32)std::max(0, (INT32)height - (INT32)ScrollBarWidth);
-					layoutClipRect.height = mClippedContentHeight;
+					visibleSize.y = (UINT32)std::max(0, (INT32)height - (INT32)ScrollBarWidth);
 
 					if (mVertBarType == ScrollBarType::NeverShow)
-						maxedContentLayoutHeight = mClippedContentHeight;
+						layoutHeight = (UINT32)visibleSize.y;
 					else
-						maxedContentLayoutHeight = std::max(contentLayoutHeight, mClippedContentHeight); // Never go below optimal size
-
-					mContentLayout->_updateLayoutInternal(x - Math::floorToInt(mHorzOffset), y - Math::floorToInt(mVertOffset), 
-						maxedContentLayoutWidth, maxedContentLayoutHeight,
-						layoutClipRect, widgetDepth, areaDepth);
-
-					mContentWidth = mContentLayout->_getActualWidth();
-					mContentHeight = mContentLayout->_getActualHeight();
+						layoutHeight = std::max(optimalContentHeight, (UINT32)visibleSize.y); // Never go below optimal size
 
+					contentSize = GUILayoutUtility::calcActualSize(layoutWidth, layoutHeight, mContentLayout);
 					hasHorzScrollbar = true;
 				}
 			}
 		}
 
-		// Add/remove/update vertical scrollbar as needed
-		if((mVertBarType == ScrollBarType::ShowIfDoesntFit && mContentHeight > mClippedContentHeight) || mVertBarType == ScrollBarType::AlwaysShow &&
-			mVertBarType != ScrollBarType::NeverShow)
-		{
-			if(mVertScroll == nullptr)
-			{
-				mVertScroll = GUIScrollBarVert::create(mScrollBarStyle);
-
-				_registerChildElement(mVertScroll);
-
-				mVertScroll->onScrollPositionChanged.connect(std::bind(&GUIScrollArea::vertScrollUpdate, this, _1));
-			}
+		elementAreas[layoutIdx] = Rect2I(x - Math::floorToInt(mHorzOffset), y - Math::floorToInt(mVertOffset), layoutWidth, layoutHeight);
 
+		// Calculate vertical scrollbar bounds
+		if (hasVertScrollbar)
+		{
 			INT32 scrollBarOffset = (UINT32)std::max(0, (INT32)width - (INT32)ScrollBarWidth);
 			UINT32 scrollBarHeight = height;
-			if(hasHorzScrollbar)
+			if (hasHorzScrollbar)
 				scrollBarHeight = (UINT32)std::max(0, (INT32)scrollBarHeight - (INT32)ScrollBarWidth);
 
-			Vector2I offset(x + scrollBarOffset, y);
-			mVertScroll->setOffset(offset);
-			mVertScroll->setWidth(ScrollBarWidth);
-			mVertScroll->setHeight(scrollBarHeight);
+			elementAreas[vertScrollIdx] = Rect2I(x + scrollBarOffset, y, ScrollBarWidth, scrollBarHeight);
+		}
+		else
+		{
+			elementAreas[vertScrollIdx] = Rect2I(x + layoutWidth, y, 0, 0);
+		}
+
+		// Calculate horizontal scrollbar bounds
+		if (hasHorzScrollbar)
+		{
+			INT32 scrollBarOffset = (UINT32)std::max(0, (INT32)height - (INT32)ScrollBarWidth);
+			UINT32 scrollBarWidth = width;
+			if (hasVertScrollbar)
+				scrollBarWidth = (UINT32)std::max(0, (INT32)scrollBarWidth - (INT32)ScrollBarWidth);
+
+			elementAreas[horzScrollIdx] = Rect2I(x, y + scrollBarOffset, scrollBarWidth, ScrollBarWidth);
+		}
+		else
+		{
+			elementAreas[horzScrollIdx] = Rect2I(x, y + layoutHeight, 0, 0);
+		}
+	}
+
+	void GUIScrollArea::_updateLayoutInternal(INT32 x, INT32 y, UINT32 width, UINT32 height,
+		Rect2I clipRect, UINT8 widgetDepth, UINT16 areaDepth)
+	{
+		UINT32 numElements = (UINT32)mChildren.size();
+		Rect2I* elementAreas = nullptr;
+
+		if (numElements > 0)
+			elementAreas = stackConstructN<Rect2I>(numElements);
+
+		Vector<Vector2I> optimalSizes;
+		UINT32 layoutIdx = 0;
+		UINT32 horzScrollIdx = 0;
+		UINT32 vertScrollIdx = 0;
+		for (UINT32 i = 0; i < numElements; i++)
+		{
+			GUIElementBase* child = _getChild(i);
+			optimalSizes.push_back(GUILayoutUtility::calcOptimalSize(child));
+
+			if (child == mContentLayout)
+				layoutIdx = i;
+
+			if (child == mHorzScroll)
+				horzScrollIdx = i;
+
+			if (child == mVertScroll)
+				vertScrollIdx = i;
+		}
+
+		_getElementAreas(x, y, width, height, elementAreas, numElements, optimalSizes, mVisibleSize, mContentSize);
+
+		Rect2I& layoutBounds = elementAreas[layoutIdx];
+		Rect2I& horzScrollBounds = elementAreas[horzScrollIdx];
+		Rect2I& vertScrollBounds = elementAreas[vertScrollIdx];
+
+		// Layout
+		Rect2I layoutClipRect = clipRect;
+		layoutClipRect.width = (UINT32)mVisibleSize.x;
+		layoutClipRect.height = (UINT32)mVisibleSize.y;
+		mContentLayout->_updateLayoutInternal(layoutBounds.x, layoutBounds.y,
+			layoutBounds.width, layoutBounds.height, layoutClipRect, widgetDepth, areaDepth);
+
+		// Vertical scrollbar
+		{
+			mVertScroll->setOffset(Vector2I(vertScrollBounds.x, vertScrollBounds.y));
+			mVertScroll->setWidth(vertScrollBounds.width);
+			mVertScroll->setHeight(vertScrollBounds.height);
 			mVertScroll->_setAreaDepth(areaDepth);
 			mVertScroll->_setWidgetDepth(widgetDepth);
 
@@ -180,55 +240,28 @@ namespace BansheeEngine
 			mVertScroll->_setClipRect(elemClipRect);
 
 			// This element is not a child of any layout so we treat it as a root element
-			Rect2I scrollBarLayoutClipRect(clipRect.x + scrollBarOffset, clipRect.y, clippedScrollbarWidth, clipRect.height);
-			mVertScroll->_updateLayout(offset.x, offset.y, ScrollBarWidth, scrollBarHeight, scrollBarLayoutClipRect, widgetDepth, areaDepth);
+			Rect2I scrollBarLayoutClipRect(clipRect.x + (vertScrollBounds.x - x), clipRect.y + (vertScrollBounds.y - y), clippedScrollbarWidth, clipRect.height);
+			mVertScroll->_updateLayout(vertScrollBounds.x, vertScrollBounds.y, vertScrollBounds.width, vertScrollBounds.height, scrollBarLayoutClipRect, widgetDepth, areaDepth);
 
 			// Set new handle size and update position to match the new size
-			UINT32 newHandleSize = (UINT32)Math::floorToInt(mVertScroll->getMaxHandleSize() * (scrollBarHeight / (float)mContentHeight));
+			UINT32 newHandleSize = (UINT32)Math::floorToInt(mVertScroll->getMaxHandleSize() * (vertScrollBounds.height / (float)mContentSize.y));
 			newHandleSize = std::max(newHandleSize, MinHandleSize);
 
-			UINT32 scrollableHeight = (UINT32)std::max(0, INT32(mContentHeight) - INT32(scrollBarHeight));
+			UINT32 scrollableHeight = (UINT32)std::max(0, INT32(mContentSize.y) - INT32(vertScrollBounds.height));
 			float newScrollPct = 0.0f;
 
-			if(scrollableHeight > 0)
+			if (scrollableHeight > 0)
 				newScrollPct = mVertOffset / scrollableHeight;
 
 			mVertScroll->setHandleSize(newHandleSize);
 			mVertScroll->setScrollPos(newScrollPct);
 		}
-		else
-		{
-			if(mVertScroll != nullptr)
-			{
-				GUIElement::destroy(mVertScroll);
-				mVertScroll = nullptr;
-			}
-
-			mVertOffset = 0.0f;
-		}
 
-		// Add/remove/update horizontal scrollbar as needed
-		if((mHorzBarType == ScrollBarType::ShowIfDoesntFit && mContentWidth > mClippedContentWidth) || mHorzBarType == ScrollBarType::AlwaysShow &&
-			mHorzBarType != ScrollBarType::NeverShow)
-		{ 
-			if(mHorzScroll == nullptr)
-			{
-				mHorzScroll = GUIScrollBarHorz::create(mScrollBarStyle);
-
-				_registerChildElement(mHorzScroll);
-
-				mHorzScroll->onScrollPositionChanged.connect(std::bind(&GUIScrollArea::horzScrollUpdate, this, _1));
-			}
-
-			INT32 scrollBarOffset = (UINT32)std::max(0, (INT32)height - (INT32)ScrollBarWidth);
-			UINT32 scrollBarWidth = width;
-			if(hasVertScrollbar)
-				scrollBarWidth = (UINT32)std::max(0, (INT32)scrollBarWidth - (INT32)ScrollBarWidth);
-
-			Vector2I offset(x, y + scrollBarOffset);
-			mHorzScroll->setOffset(offset);
-			mHorzScroll->setWidth(scrollBarWidth);
-			mHorzScroll->setHeight(ScrollBarWidth);
+		// Horizontal scrollbar
+		{
+			mHorzScroll->setOffset(Vector2I(horzScrollBounds.x, horzScrollBounds.y));
+			mHorzScroll->setWidth(horzScrollBounds.width);
+			mHorzScroll->setHeight(horzScrollBounds.height);
 			mHorzScroll->_setAreaDepth(areaDepth);
 			mHorzScroll->_setWidgetDepth(widgetDepth);
 
@@ -237,32 +270,25 @@ namespace BansheeEngine
 			mHorzScroll->_setClipRect(elemClipRect);
 
 			// This element is not a child of any layout so we treat it as a root element
-			Rect2I scrollBarLayoutClipRect(clipRect.x, clipRect.y + scrollBarOffset, clipRect.width, clippedScrollbarHeight);
-			mHorzScroll->_updateLayout(offset.x, offset.y, scrollBarWidth, ScrollBarWidth, scrollBarLayoutClipRect, widgetDepth, areaDepth);
+			Rect2I scrollBarLayoutClipRect(clipRect.x + (horzScrollBounds.x - x), clipRect.y + (horzScrollBounds.y - y), clipRect.width, clippedScrollbarHeight);
+			mHorzScroll->_updateLayout(horzScrollBounds.x, horzScrollBounds.y, horzScrollBounds.width, horzScrollBounds.height, scrollBarLayoutClipRect, widgetDepth, areaDepth);
 
 			// Set new handle size and update position to match the new size
-			UINT32 newHandleSize = (UINT32)Math::floorToInt(mHorzScroll->getMaxHandleSize() * (scrollBarWidth / (float)mContentWidth));
+			UINT32 newHandleSize = (UINT32)Math::floorToInt(mHorzScroll->getMaxHandleSize() * (horzScrollBounds.width / (float)mContentSize.x));
 			newHandleSize = std::max(newHandleSize, MinHandleSize);
 
-			UINT32 scrollableWidth = (UINT32)std::max(0, INT32(mContentWidth) - INT32(scrollBarWidth));
+			UINT32 scrollableWidth = (UINT32)std::max(0, INT32(mContentSize.x) - INT32(horzScrollBounds.width));
 			float newScrollPct = 0.0f;
-			
-			if(scrollableWidth > 0)
+
+			if (scrollableWidth > 0)
 				newScrollPct = mHorzOffset / scrollableWidth;
 
 			mHorzScroll->setHandleSize(newHandleSize);
 			mHorzScroll->setScrollPos(newScrollPct);
 		}
-		else
-		{
-			if(mHorzScroll != nullptr)
-			{
-				GUIElement::destroy(mHorzScroll);
-				mHorzScroll = nullptr;
-			}
 
-			mHorzOffset = 0.0f;
-		}
+		if (elementAreas != nullptr)
+			stackDeallocLast(elementAreas);
 	}
 
 	void GUIScrollArea::vertScrollUpdate(float scrollPos)
@@ -277,7 +303,7 @@ namespace BansheeEngine
 
 	void GUIScrollArea::scrollToVertical(float pct)
 	{
-		UINT32 scrollableHeight = (UINT32)std::max(0, INT32(mContentHeight) - INT32(mClippedContentHeight));
+		UINT32 scrollableHeight = (UINT32)std::max(0, INT32(mContentSize.y) - INT32(mVisibleSize.y));
 		mVertOffset = scrollableHeight * Math::clamp01(pct);
 
 		markContentAsDirty();
@@ -285,7 +311,7 @@ namespace BansheeEngine
 
 	void GUIScrollArea::scrollToHorizontal(float pct)
 	{
-		UINT32 scrollableWidth = (UINT32)std::max(0, INT32(mContentWidth) - INT32(mClippedContentWidth));
+		UINT32 scrollableWidth = (UINT32)std::max(0, INT32(mContentSize.x) - INT32(mVisibleSize.x));
 		mHorzOffset = scrollableWidth * Math::clamp01(pct);
 
 		markContentAsDirty();
@@ -295,7 +321,7 @@ namespace BansheeEngine
 	{
 		if(mVertScroll != nullptr)
 		{
-			UINT32 scrollableSize = (UINT32)std::max(0, INT32(mContentHeight) - INT32(mClippedContentHeight));
+			UINT32 scrollableSize = (UINT32)std::max(0, INT32(mContentSize.y) - INT32(mVisibleSize.y));
 
 			float offset = 0.0f;
 			if(scrollableSize > 0)
@@ -309,7 +335,7 @@ namespace BansheeEngine
 	{
 		if(mVertScroll != nullptr)
 		{
-			UINT32 scrollableSize = (UINT32)std::max(0, INT32(mContentHeight) - INT32(mClippedContentHeight));
+			UINT32 scrollableSize = (UINT32)std::max(0, INT32(mContentSize.y) - INT32(mVisibleSize.y));
 
 			float offset = 0.0f;
 			if(scrollableSize > 0)
@@ -323,7 +349,7 @@ namespace BansheeEngine
 	{
 		if(mHorzScroll != nullptr)
 		{
-			UINT32 scrollableSize = (UINT32)std::max(0, INT32(mContentWidth) - INT32(mClippedContentWidth));
+			UINT32 scrollableSize = (UINT32)std::max(0, INT32(mContentSize.x) - INT32(mVisibleSize.x));
 
 			float offset = 0.0f;
 			if(scrollableSize > 0)
@@ -337,7 +363,7 @@ namespace BansheeEngine
 	{
 		if(mHorzScroll != nullptr)
 		{
-			UINT32 scrollableSize = (UINT32)std::max(0, INT32(mContentWidth) - INT32(mClippedContentWidth));
+			UINT32 scrollableSize = (UINT32)std::max(0, INT32(mContentSize.x) - INT32(mVisibleSize.x));
 
 			float offset = 0.0f;
 			if(scrollableSize > 0)
@@ -378,7 +404,7 @@ namespace BansheeEngine
 			// Mouse wheel only scrolls on the Y axis
 			if(mVertScroll != nullptr)
 			{
-				UINT32 scrollableHeight = (UINT32)std::max(0, INT32(mContentWidth) - INT32(mClippedContentHeight));
+				UINT32 scrollableHeight = (UINT32)std::max(0, INT32(mContentSize.y) - INT32(mVisibleSize.y));
 				float additionalScroll = (float)WheelScrollAmount / scrollableHeight;
 
 				mVertScroll->scroll(additionalScroll * ev.getWheelScrollAmount());

+ 0 - 1
TODO.txt

@@ -16,7 +16,6 @@ C#:
 Dialog.Show(title, text, btn1 text, btn1 callback, btn2 text, btn2 callback, btn3 text, btn3 callback)
 ProgressBar.Show(float percent) / ProgressBar.Hide()
 
-
 Add C# wrappers GUIElement bounds and visible bounds (with ability to set non-visible bounds)
 
 Got a crash on shutdown that was caused by locking a mutex in an Event destructor. Event was Platform::onMouseCaptureChanged.