Explorar el Código

Better way of calculating size of nested layouts

Marko Pintera hace 12 años
padre
commit
6845cc477b

+ 11 - 2
BansheeEngine/Include/BsGUILayout.h

@@ -1,6 +1,7 @@
 #pragma once
 
 #include "BsPrerequisites.h"
+#include "CmInt2.h"
 
 namespace BansheeEngine
 {
@@ -84,17 +85,25 @@ namespace BansheeEngine
 		UINT32 getNumChildren() const;
 
 		/**
-		 * @brief	Re-arranges the elements to fit the layout. You shouldn't need to call this manually
+		 * @brief	Re-arranges the elements to fit the layout. (Internal use only)
 		 */
 		void _update(UINT32 x, UINT32 y, UINT32 width, UINT32 height, UINT32 depth);
 
+		UINT32 _getOptimalWidth() const { return mOptimalWidth; }
+		UINT32 _getOptimalHeight() const { return mOptimalHeight; }
+
 		void _markAsDirty() { mIsDirty = true; }
 		bool _isDirty();
 
 	protected:
-		std::vector<GUILayoutEntry> mChildren;		
+		std::vector<GUILayoutEntry> mChildren;	
+		std::vector<CM::Int2> mOptimalSizes;
+		UINT32 mOptimalWidth;
+		UINT32 mOptimalHeight;
+
 		bool mIsDirty;
 
+		virtual void updateOptimalSizes() = 0;
 		virtual void updateInternal(UINT32 x, UINT32 y, UINT32 width, UINT32 height, UINT32 depth) = 0;
 	};
 }

+ 1 - 0
BansheeEngine/Include/BsGUILayoutX.h

@@ -12,6 +12,7 @@ namespace BansheeEngine
 		~GUILayoutX() {};
 
 	protected:
+		void updateOptimalSizes();
 		void updateInternal(UINT32 x, UINT32 y, UINT32 width, UINT32 height, UINT32 depth);
 	};
 }

+ 1 - 0
BansheeEngine/Include/BsGUILayoutY.h

@@ -12,6 +12,7 @@ namespace BansheeEngine
 		~GUILayoutY() {};
 
 	protected:
+		void updateOptimalSizes();
 		void updateInternal(UINT32 x, UINT32 y, UINT32 width, UINT32 height, UINT32 depth);
 	};
 }

+ 2 - 1
BansheeEngine/Source/BsGUILayout.cpp

@@ -10,7 +10,7 @@ using namespace CamelotFramework;
 namespace BansheeEngine
 {
 	GUILayout::GUILayout()
-		:mIsDirty(false)
+		:mIsDirty(false), mOptimalWidth(0), mOptimalHeight(0)
 	{
 
 	}
@@ -261,6 +261,7 @@ namespace BansheeEngine
 
 	void GUILayout::_update(UINT32 x, UINT32 y, UINT32 width, UINT32 height, UINT32 depth)
 	{
+		updateOptimalSizes(); // We calculate optimal sizes of all layouts as a pre-processing step, as they are requested often during update
 		updateInternal(x, y, width, height, depth);
 		mIsDirty = false;
 	}

+ 155 - 101
BansheeEngine/Source/BsGUILayoutX.cpp

@@ -8,12 +8,84 @@ using namespace CamelotFramework;
 
 namespace BansheeEngine
 {
+	void GUILayoutX::updateOptimalSizes()
+	{
+		// Update all children first, otherwise we can't determine out own optimal size
+		for(auto& child : mChildren)
+		{
+			if(child.isLayout())
+				updateOptimalSizes();
+		}
+
+		if(mChildren.size() != mOptimalSizes.size())
+			mOptimalSizes.resize(mChildren.size());
+
+		mOptimalWidth = 0;
+		mOptimalHeight = 0;
+
+		UINT32 childIdx = 0;
+		for(auto& child : mChildren)
+		{
+			UINT32 optimalWidth = 0;
+			UINT32 optimalHeight = 0;
+
+			if(child.isFixedSpace())
+			{
+				optimalWidth = child.space->getSize();
+			}
+			else if(child.isElement())
+			{
+				const GUILayoutOptions& layoutOptions = child.element->_getLayoutOptions();
+
+				if(layoutOptions.fixedWidth)
+				{
+					optimalWidth = layoutOptions.width;
+				}
+				else
+				{
+					optimalWidth = child.element->_getOptimalWidth();
+
+					if(layoutOptions.minWidth > 0)
+						optimalWidth = std::max(layoutOptions.minWidth, optimalWidth);
+
+					if(layoutOptions.maxWidth > 0)
+						optimalWidth = std::min(layoutOptions.maxWidth, optimalWidth);
+				}
+
+				if(layoutOptions.fixedHeight)
+					optimalHeight = layoutOptions.height;
+				else
+				{
+					optimalHeight = child.element->_getOptimalHeight();
+
+					if(layoutOptions.minHeight > 0)
+						optimalHeight = std::max(layoutOptions.minHeight, optimalHeight);
+
+					if(layoutOptions.maxHeight > 0)
+						optimalHeight = std::min(layoutOptions.maxHeight, optimalHeight);
+				}
+			}
+			else if(child.isLayout())
+			{
+				optimalWidth = child.layout->_getOptimalWidth();
+			}
+
+			mOptimalSizes[childIdx].x = optimalWidth;
+			mOptimalWidth += optimalWidth;
+
+			mOptimalSizes[childIdx].y = optimalHeight;
+			mOptimalHeight = std::max(mOptimalHeight, optimalHeight);
+
+			childIdx++;
+		}
+	}
+
 	void GUILayoutX::updateInternal(UINT32 x, UINT32 y, UINT32 width, UINT32 height, UINT32 depth)
 	{
-		UINT32 totalOptimalSize = 0;
+		UINT32 totalOptimalSize = _getOptimalWidth();
+		UINT32 totalNonClampedSize = 0;
 		UINT32 numNonClampedElements = 0;
 		UINT32 numFlexibleSpaces = 0;
-		UINT32 numLayouts = 0;
 
 		bool* processedElements = CM_NEW_ARRAY(bool, (UINT32)mChildren.size(), ScratchAlloc);
 		memset(processedElements, 0, mChildren.size() * sizeof(bool));
@@ -24,14 +96,14 @@ namespace BansheeEngine
 		float* elementScaleWeights = CM_NEW_ARRAY(float, (UINT32)mChildren.size(), ScratchAlloc);
 		memset(elementScaleWeights, 0, mChildren.size() * sizeof(float));
 
-		// Set fixed-size elements and determine optimal size
+		// Set initial sizes, count number of children per type and mark fixed elements as already processed
 		UINT32 childIdx = 0;
 		for(auto& child : mChildren)
 		{
+			elementSizes[childIdx] = mOptimalSizes[childIdx].x;
+
 			if(child.isFixedSpace())
 			{
-				elementSizes[childIdx] = child.space->getSize();
-				totalOptimalSize += child.space->getSize();
 				processedElements[childIdx] = true;
 			}
 			else if(child.isElement())
@@ -39,30 +111,17 @@ namespace BansheeEngine
 				const GUILayoutOptions& layoutOptions = child.element->_getLayoutOptions();
 
 				if(layoutOptions.fixedWidth)
-				{
-					totalOptimalSize += layoutOptions.width;
-					elementSizes[childIdx] = layoutOptions.width;
 					processedElements[childIdx] = true;
-				}
 				else
 				{
-					UINT32 optimalWidth = child.element->_getOptimalWidth();
-
-					if(layoutOptions.minWidth > 0)
-						optimalWidth = std::max(layoutOptions.minWidth, optimalWidth);
-
-					if(layoutOptions.maxWidth > 0)
-						optimalWidth = std::min(layoutOptions.maxWidth, optimalWidth);
-
-					elementSizes[childIdx] = optimalWidth;
-					totalOptimalSize += optimalWidth;
 					numNonClampedElements++;
+					totalNonClampedSize += elementSizes[childIdx];
 				}
 			}
 			else if(child.isLayout())
 			{
-				numLayouts++;
 				numNonClampedElements++;
+				totalNonClampedSize += elementSizes[childIdx];
 			}
 			else if(child.isFlexibleSpace())
 			{
@@ -73,25 +132,58 @@ namespace BansheeEngine
 			childIdx++;
 		}
 
-		// Determine layout size. We could just calculate optimal size of all elements in the layout
-		// but I feel that's an overkill. Instead I just use the average size.
-		childIdx = 0;
-		UINT32 layoutSize = (UINT32)Math::CeilToInt(totalOptimalSize / (float)numLayouts);
-		for(auto& child : mChildren)
+		// If there is some room left, calculate flexible space sizes (since they will fill up all that extra room)
+		if(width > totalOptimalSize)
 		{
-			if(child.isLayout())
+			UINT32 extraSize = width - totalOptimalSize;
+			UINT32 remainingSize = extraSize;
+
+			// Flexible spaces always expand to fill up all unused space
+			if(numFlexibleSpaces > 0)
 			{
-				elementSizes[childIdx] += layoutSize;
-				totalOptimalSize += layoutSize;
+				float avgSize = remainingSize / (float)numFlexibleSpaces;
+
+				childIdx = 0;
+				for(auto& child : mChildren)
+				{
+					if(processedElements[childIdx])
+					{
+						childIdx++;
+						continue;
+					}
+
+					UINT32 extraWidth = std::min((UINT32)Math::CeilToInt(avgSize), remainingSize);
+					UINT32 elementWidth = elementSizes[childIdx] + extraWidth;
+
+					// Clamp if needed
+					if(child.isFlexibleSpace())
+					{
+						processedElements[childIdx] = true;
+						numNonClampedElements--;
+						elementSizes[childIdx] = elementWidth;
+
+						remainingSize = (UINT32)std::max(0, (INT32)remainingSize - (INT32)extraWidth);
+					}
+
+					childIdx++;
+				}
+
+				totalOptimalSize = width;
 			}
-			childIdx++;
 		}
 
-		// Determine weight scale for every element
+		// Determine weight scale for every element. When scaling elements up/down they will be scaled based on this weight.
+		// Weight is to ensure all elements are scaled fairly, so elements that are large will get effected more than smaller elements.
 		childIdx = 0;
-		float invOptimalSize = 1.0f / totalOptimalSize;
+		float invOptimalSize = 1.0f / totalNonClampedSize;
 		for(auto& child : mChildren)
 		{
+			if(processedElements[childIdx])
+			{
+				childIdx++;
+				continue;
+			}
+
 			elementScaleWeights[childIdx] = invOptimalSize * elementSizes[childIdx];
 
 			childIdx++;
@@ -104,7 +196,7 @@ namespace BansheeEngine
 			UINT32 remainingSize = extraSize;
 
 			// Iterate until we reduce everything so it fits, while maintaining
-			// equal average sizes 
+			// equal average sizes using the weights we calculated earlier
 			while(remainingSize > 0 && numNonClampedElements > 0)
 			{
 				UINT32 totalRemainingSize = remainingSize;
@@ -113,7 +205,10 @@ namespace BansheeEngine
 				for(auto& child : mChildren)
 				{
 					if(processedElements[childIdx])
+					{
+						childIdx++;
 						continue;
+					}
 
 					float avgSize = totalRemainingSize * elementScaleWeights[childIdx];
 					
@@ -170,80 +265,54 @@ namespace BansheeEngine
 			UINT32 extraSize = width - totalOptimalSize;
 			UINT32 remainingSize = extraSize;
 
-			// Flexible spaces always expand to fill up all unused space, so we have a special case for them
-			if(numFlexibleSpaces > 0)
+			// Iterate until we reduce everything so it fits, while maintaining
+			// equal average sizes using the weights we calculated earlier
+			while(remainingSize > 0 && numNonClampedElements > 0)
 			{
-				float avgSize = remainingSize / (float)numFlexibleSpaces;
+				UINT32 totalRemainingSize = remainingSize;
 
 				childIdx = 0;
 				for(auto& child : mChildren)
 				{
 					if(processedElements[childIdx])
+					{
+						childIdx++;
 						continue;
+					}
 
+					float avgSize = totalRemainingSize * elementScaleWeights[childIdx];
 					UINT32 extraWidth = std::min((UINT32)Math::CeilToInt(avgSize), remainingSize);
 					UINT32 elementWidth = elementSizes[childIdx] + extraWidth;
 
 					// Clamp if needed
-					if(child.isFlexibleSpace())
-					{
-						processedElements[childIdx] = true;
-						elementSizes[childIdx] = elementWidth;
-
-						remainingSize = (UINT32)std::max(0, (INT32)remainingSize - (INT32)extraWidth);
-					}
-
-					childIdx++;
-				}
-			}
-			else // Otherwise, just expand individual elements, as well as their constraints allow it (equal to reduce algorithm above)
-			{
-				// Iterate until we reduce everything so it fits, while maintaining
-				// equal average sizes 
-				while(remainingSize > 0 && numNonClampedElements > 0)
-				{
-					UINT32 totalRemainingSize = remainingSize;
-
-					childIdx = 0;
-					for(auto& child : mChildren)
+					if(child.isElement())
 					{
-						if(processedElements[childIdx])
-							continue;
-
-						float avgSize = totalRemainingSize * elementScaleWeights[childIdx];
-						UINT32 extraWidth = std::min((UINT32)Math::CeilToInt(avgSize), remainingSize);
-						UINT32 elementWidth = elementSizes[childIdx] + extraWidth;
+						const GUILayoutOptions& layoutOptions = child.element->_getLayoutOptions();
 
-						// Clamp if needed
-						if(child.isElement())
+						if(layoutOptions.maxWidth > 0 && elementWidth > layoutOptions.maxWidth)
 						{
-							const GUILayoutOptions& layoutOptions = child.element->_getLayoutOptions();
-
-							if(layoutOptions.maxWidth > 0 && elementWidth > layoutOptions.maxWidth)
-							{
-								extraWidth = elementSizes[childIdx] - layoutOptions.maxWidth;
-								elementWidth = layoutOptions.maxWidth;
-
-								processedElements[childIdx] = true;
-								numNonClampedElements--;
-							}
+							extraWidth = elementSizes[childIdx] - layoutOptions.maxWidth;
+							elementWidth = layoutOptions.maxWidth;
 
-							elementSizes[childIdx] = elementWidth;
-							remainingSize = (UINT32)std::max(0, (INT32)remainingSize - (INT32)extraWidth);
-						}
-						else if(child.isLayout())
-						{
-							elementSizes[childIdx] = elementWidth;
-							remainingSize = (UINT32)std::max(0, (INT32)remainingSize - (INT32)extraWidth);
-						}
-						else if(child.isFlexibleSpace())
-						{
 							processedElements[childIdx] = true;
 							numNonClampedElements--;
 						}
 
-						childIdx++;
+						elementSizes[childIdx] = elementWidth;
+						remainingSize = (UINT32)std::max(0, (INT32)remainingSize - (INT32)extraWidth);
+					}
+					else if(child.isLayout())
+					{
+						elementSizes[childIdx] = elementWidth;
+						remainingSize = (UINT32)std::max(0, (INT32)remainingSize - (INT32)extraWidth);
 					}
+					else if(child.isFlexibleSpace())
+					{
+						processedElements[childIdx] = true;
+						numNonClampedElements--;
+					}
+
+					childIdx++;
 				}
 			}
 		}
@@ -259,22 +328,7 @@ namespace BansheeEngine
 			if(child.isElement())
 			{
 				child.element->_setWidth(elementWidth);
-
-				const GUILayoutOptions& layoutOptions = child.element->_getLayoutOptions();
-				if(layoutOptions.fixedHeight)
-					child.element->_setHeight(layoutOptions.height);
-				else
-				{
-					UINT32 optimalHeight = child.element->_getOptimalHeight();
-
-					if(layoutOptions.minHeight > 0)
-						optimalHeight = std::max(layoutOptions.minHeight, optimalHeight);
-
-					if(layoutOptions.maxHeight > 0)
-						optimalHeight = std::min(layoutOptions.maxHeight, optimalHeight);
-
-					child.element->_setHeight(optimalHeight);
-				}
+				child.element->_setHeight(mOptimalSizes[childIdx].y);
 
 				UINT32 yOffset = (UINT32)Math::CeilToInt((height - child.element->_getHeight()) * 0.5f);
 

+ 5 - 0
BansheeEngine/Source/BsGUILayoutY.cpp

@@ -2,6 +2,11 @@
 
 namespace BansheeEngine
 {
+	void GUILayoutY::updateOptimalSizes()
+	{
+
+	}
+
 	void GUILayoutY::updateInternal(UINT32 x, UINT32 y, UINT32 width, UINT32 height, UINT32 depth)
 	{