Browse Source

Implemented Y layout (untested)

Marko Pintera 12 years ago
parent
commit
a9975ece4c
1 changed files with 348 additions and 0 deletions
  1. 348 0
      BansheeEngine/Source/BsGUILayoutY.cpp

+ 348 - 0
BansheeEngine/Source/BsGUILayoutY.cpp

@@ -1,14 +1,362 @@
 #include "BsGUILayoutY.h"
 #include "BsGUILayoutY.h"
+#include "BsGUIElement.h"
+#include "BsGUISpace.h"
+#include "CmMath.h"
+#include "CmInt2.h"
+
+using namespace CamelotFramework;
 
 
 namespace BansheeEngine
 namespace BansheeEngine
 {
 {
 	void GUILayoutY::updateOptimalSizes()
 	void GUILayoutY::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())
+			{
+				optimalHeight = child.space->getSize();
+			}
+			else if(child.isElement())
+			{
+				const GUILayoutOptions& layoutOptions = child.element->_getLayoutOptions();
+
+				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);
+				}
 
 
+				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);
+				}
+			}
+			else if(child.isLayout())
+			{
+				optimalHeight = child.layout->_getOptimalHeight();
+			}
+
+			mOptimalSizes[childIdx].y = optimalHeight;
+			mOptimalHeight += optimalHeight;
+
+			mOptimalSizes[childIdx].x = optimalWidth;
+			mOptimalWidth = std::max(mOptimalWidth, optimalWidth);
+
+			childIdx++;
+		}
 	}
 	}
 
 
 	void GUILayoutY::updateInternal(UINT32 x, UINT32 y, UINT32 width, UINT32 height, UINT32 depth)
 	void GUILayoutY::updateInternal(UINT32 x, UINT32 y, UINT32 width, UINT32 height, UINT32 depth)
 	{
 	{
+		UINT32 totalOptimalSize = _getOptimalHeight();
+		UINT32 totalNonClampedSize = 0;
+		UINT32 numNonClampedElements = 0;
+		UINT32 numFlexibleSpaces = 0;
+
+		bool* processedElements = CM_NEW_ARRAY(bool, (UINT32)mChildren.size(), ScratchAlloc);
+		memset(processedElements, 0, mChildren.size() * sizeof(bool));
+
+		UINT32* elementSizes = CM_NEW_ARRAY(UINT32, (UINT32)mChildren.size(), ScratchAlloc);
+		memset(elementSizes, 0, mChildren.size() * sizeof(UINT32));
+
+		float* elementScaleWeights = CM_NEW_ARRAY(float, (UINT32)mChildren.size(), ScratchAlloc);
+		memset(elementScaleWeights, 0, mChildren.size() * sizeof(float));
+
+		// 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].y;
+
+			if(child.isFixedSpace())
+			{
+				processedElements[childIdx] = true;
+			}
+			else if(child.isElement())
+			{
+				const GUILayoutOptions& layoutOptions = child.element->_getLayoutOptions();
+
+				if(layoutOptions.fixedHeight)
+					processedElements[childIdx] = true;
+				else
+				{
+					numNonClampedElements++;
+					totalNonClampedSize += elementSizes[childIdx];
+				}
+			}
+			else if(child.isLayout())
+			{
+				numNonClampedElements++;
+				totalNonClampedSize += elementSizes[childIdx];
+			}
+			else if(child.isFlexibleSpace())
+			{
+				numFlexibleSpaces++;
+				numNonClampedElements++;
+			}
+
+			childIdx++;
+		}
+
+		// If there is some room left, calculate flexible space sizes (since they will fill up all that extra room)
+		if(height > totalOptimalSize)
+		{
+			UINT32 extraSize = height - totalOptimalSize;
+			UINT32 remainingSize = extraSize;
+
+			// Flexible spaces always expand to fill up all unused space
+			if(numFlexibleSpaces > 0)
+			{
+				float avgSize = remainingSize / (float)numFlexibleSpaces;
+
+				childIdx = 0;
+				for(auto& child : mChildren)
+				{
+					if(processedElements[childIdx])
+					{
+						childIdx++;
+						continue;
+					}
+
+					UINT32 extraHeight = std::min((UINT32)Math::CeilToInt(avgSize), remainingSize);
+					UINT32 elementHeight = elementSizes[childIdx] + extraHeight;
+
+					// Clamp if needed
+					if(child.isFlexibleSpace())
+					{
+						processedElements[childIdx] = true;
+						numNonClampedElements--;
+						elementSizes[childIdx] = elementHeight;
+
+						remainingSize = (UINT32)std::max(0, (INT32)remainingSize - (INT32)extraHeight);
+					}
+
+					childIdx++;
+				}
+
+				totalOptimalSize = height;
+			}
+		}
+
+		// 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 / totalNonClampedSize;
+		for(auto& child : mChildren)
+		{
+			if(processedElements[childIdx])
+			{
+				childIdx++;
+				continue;
+			}
+
+			elementScaleWeights[childIdx] = invOptimalSize * elementSizes[childIdx];
+
+			childIdx++;
+		}
+
+		// Our optimal size is larger than maximum allowed, so we need to reduce size of some elements
+		if(totalOptimalSize > height)
+		{
+			UINT32 extraSize = totalOptimalSize - height;
+			UINT32 remainingSize = extraSize;
+
+			// Iterate until we reduce everything so it fits, while maintaining
+			// equal average sizes using the weights we calculated earlier
+			while(remainingSize > 0 && numNonClampedElements > 0)
+			{
+				UINT32 totalRemainingSize = remainingSize;
+
+				childIdx = 0;
+				for(auto& child : mChildren)
+				{
+					if(processedElements[childIdx])
+					{
+						childIdx++;
+						continue;
+					}
+
+					float avgSize = totalRemainingSize * elementScaleWeights[childIdx];
+
+					UINT32 extraHeight = std::min((UINT32)Math::CeilToInt(avgSize), remainingSize);
+					UINT32 elementHeight = (UINT32)std::max(0, (INT32)elementSizes[childIdx] - (INT32)extraHeight);
+
+					// Clamp if needed
+					if(child.isElement())
+					{
+						const GUILayoutOptions& layoutOptions = child.element->_getLayoutOptions();
+
+						if(elementHeight == 0)
+						{
+							processedElements[childIdx] = true;
+							numNonClampedElements--;
+						}
+						else if(layoutOptions.minHeight > 0 && elementHeight < layoutOptions.minHeight)
+						{
+							elementHeight = layoutOptions.minHeight;
+
+							processedElements[childIdx] = true;
+							numNonClampedElements--;
+						}
+
+						extraHeight = elementSizes[childIdx] - elementHeight;
+						elementSizes[childIdx] = elementHeight;
+						remainingSize = (UINT32)std::max(0, (INT32)remainingSize - (INT32)extraHeight);
+					}
+					else if(child.isLayout())
+					{
+						if(elementHeight == 0)
+						{
+							processedElements[childIdx] = true;
+							numNonClampedElements--;
+						}
+
+						extraHeight = elementHeight - elementSizes[childIdx];
+						elementSizes[childIdx] = elementHeight;
+						remainingSize = (UINT32)std::max(0, (INT32)remainingSize - (INT32)extraHeight);
+					}
+					else if(child.isFlexibleSpace())
+					{
+						elementSizes[childIdx] = 0;
+						processedElements[childIdx] = true;
+						numNonClampedElements--;
+					}
+
+					childIdx++;
+				}
+			}
+		}
+		else // We are smaller than the allowed maximum, so try to expand some elements
+		{
+			UINT32 extraSize = height - totalOptimalSize;
+			UINT32 remainingSize = extraSize;
+
+			// Iterate until we reduce everything so it fits, while maintaining
+			// equal average sizes using the weights we calculated earlier
+			while(remainingSize > 0 && numNonClampedElements > 0)
+			{
+				UINT32 totalRemainingSize = remainingSize;
+
+				childIdx = 0;
+				for(auto& child : mChildren)
+				{
+					if(processedElements[childIdx])
+					{
+						childIdx++;
+						continue;
+					}
+
+					float avgSize = totalRemainingSize * elementScaleWeights[childIdx];
+					UINT32 extraHeight = std::min((UINT32)Math::CeilToInt(avgSize), remainingSize);
+					UINT32 elementHeight = elementSizes[childIdx] + extraHeight;
+
+					// Clamp if needed
+					if(child.isElement())
+					{
+						const GUILayoutOptions& layoutOptions = child.element->_getLayoutOptions();
+
+						if(elementHeight == 0)
+						{
+							processedElements[childIdx] = true;
+							numNonClampedElements--;
+						}
+						else if(layoutOptions.maxHeight > 0 && elementHeight > layoutOptions.maxHeight)
+						{
+							elementHeight = layoutOptions.maxHeight;
+
+							processedElements[childIdx] = true;
+							numNonClampedElements--;
+						}
+
+						extraHeight = elementSizes[childIdx] - elementHeight;
+						elementSizes[childIdx] = elementHeight;
+						remainingSize = (UINT32)std::max(0, (INT32)remainingSize - (INT32)extraHeight);
+					}
+					else if(child.isLayout())
+					{
+						elementSizes[childIdx] = elementHeight;
+						remainingSize = (UINT32)std::max(0, (INT32)remainingSize - (INT32)extraHeight);
+					}
+					else if(child.isFlexibleSpace())
+					{
+						processedElements[childIdx] = true;
+						numNonClampedElements--;
+					}
+
+					childIdx++;
+				}
+			}
+		}
+
+		// Now that we have all the sizes, actually assign them
+		// Also assign offsets, clip rectangles and depth
+		UINT32 yOffset = 0;
+		childIdx = 0;
+		for(auto& child : mChildren)
+		{
+			UINT32 elementHeight = elementSizes[childIdx];
+
+			if(child.isElement())
+			{
+				child.element->_setWidth(mOptimalSizes[childIdx].x);
+				child.element->_setHeight(elementHeight);
+
+				UINT32 xOffset = (UINT32)Math::CeilToInt((width - child.element->_getHeight()) * 0.5f);
+
+				Int2 offset(x + xOffset, y + yOffset);
+				child.element->_setOffset(offset);
+				child.element->_setDepth(depth);
+
+				UINT32 clippedWidth = (UINT32)std::min((INT32)child.element->_getWidth(), (INT32)width - offset.x);
+				UINT32 clippedHeight = (UINT32)std::min((INT32)child.element->_getHeight(), (INT32)height - offset.y);
+
+				child.element->_setClipRect(Rect(0, 0, clippedWidth, clippedHeight));
+			}
+			else if(child.isLayout())
+			{
+				child.layout->_update(x, y + yOffset, width, elementHeight, depth);
+			}
+
+			yOffset += elementHeight;
+			childIdx++;
+		}
 
 
+		CM_DELETE_ARRAY(processedElements, bool, (UINT32)mChildren.size(), ScratchAlloc);
+		CM_DELETE_ARRAY(elementSizes, UINT32, (UINT32)mChildren.size(), ScratchAlloc);
+		CM_DELETE_ARRAY(elementScaleWeights, float, (UINT32)mChildren.size(), ScratchAlloc);
 	}
 	}
 }
 }