Bläddra i källkod

Better layout organization algorithm

Marko Pintera 12 år sedan
förälder
incheckning
80abe9c7ca
2 ändrade filer med 389 tillägg och 131 borttagningar
  1. 388 130
      BansheeEngine/Source/BsGUILayoutX.cpp
  2. 1 1
      CamelotClient/CmEditorWindow.cpp

+ 388 - 130
BansheeEngine/Source/BsGUILayoutX.cpp

@@ -10,170 +10,226 @@ namespace BansheeEngine
 {
 	void GUILayoutX::updateInternal(UINT32 x, UINT32 y, UINT32 width, UINT32 height, UINT32 depth)
 	{
-		// Calculate flexible space sizes
-		std::vector<UINT32> flexibleSpaceSizes;
-		UINT32 minimalTotalSize = 0;
+		UINT32 totalOptimalSize = 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));
+
+		// Set fixed-size elements and determine optimal size
+		UINT32 childIdx = 0;
 		for(auto& child : mChildren)
 		{
-			if(child.isFlexibleSpace())
+			if(child.isFixedSpace())
 			{
-				flexibleSpaceSizes.push_back(0);
-			}
-			else if(child.isFixedSpace())
-			{
-				minimalTotalSize += child.space->getSize();
+				elementSizes[childIdx] = child.space->getSize();
+				totalOptimalSize += child.space->getSize();
+				processedElements[childIdx] = true;
 			}
 			else if(child.isElement())
 			{
 				const GUILayoutOptions& layoutOptions = child.element->_getLayoutOptions();
 
 				if(layoutOptions.fixedWidth)
-					minimalTotalSize += layoutOptions.width;
+				{
+					totalOptimalSize += layoutOptions.width;
+					elementSizes[childIdx] = layoutOptions.width;
+					processedElements[childIdx] = true;
+				}
 				else
 				{
 					UINT32 optimalWidth = child.element->_getOptimalWidth();
 
-					if(layoutOptions.minHeight > 0)
+					if(layoutOptions.minWidth > 0)
 						optimalWidth = std::max(layoutOptions.minWidth, optimalWidth);
 
-					if(layoutOptions.maxHeight > 0)
+					if(layoutOptions.maxWidth > 0)
 						optimalWidth = std::min(layoutOptions.maxWidth, optimalWidth);
 
-					minimalTotalSize += optimalWidth;
+					elementSizes[childIdx] = optimalWidth;
+					totalOptimalSize += optimalWidth;
+					numNonClampedElements++;
 				}
 			}
-		}
-
-		UINT32 remainingFlexSpaceSize = (UINT32)std::max(0, (INT32)width - (INT32)minimalTotalSize);
-		float avgFlexibleSpaceSize = remainingFlexSpaceSize / (float)flexibleSpaceSizes.size();
-		
-		for(size_t i = 0; i < flexibleSpaceSizes.size(); i++)
-		{
-			UINT32 spaceSize = (UINT32)Math::CeilToInt(avgFlexibleSpaceSize);
-			spaceSize = std::min(remainingFlexSpaceSize, spaceSize);
+			else if(child.isLayout())
+			{
+				// Layout use any size that's available (quite possibly none), but it might be better to give them a certain minimum size.
+				numNonClampedElements++;
+			}
+			else if(child.isFlexibleSpace())
+			{
+				numFlexibleSpaces++;
+				numNonClampedElements++;
+			}
 
-			remainingFlexSpaceSize -= spaceSize;
-			flexibleSpaceSizes[i] = spaceSize;
+			childIdx++;
 		}
 
-		// Get a basic estimate of the average width
-		UINT32 totalWidth = 0;
-		UINT32 numFreeElems = 0;
-		float avgWidth = 0.0f;
-		UINT32 flexibleSpaceIdx = 0;
-		for(auto& child : mChildren)
+		// Our optimal size is larger than maximum allowed, so we need to reduce size of some elements
+		if(totalOptimalSize > width)
 		{
-			if(child.isElement())
+			UINT32 extraSize = totalOptimalSize - width;
+			UINT32 remainingSize = extraSize;
+
+			// Iterate until we reduce everything so it fits, while maintaining
+			// equal average sizes 
+			while(remainingSize > 0 && numNonClampedElements > 0)
 			{
-				const GUILayoutOptions& layoutOptions = child.element->_getLayoutOptions();
+				float avgSize = remainingSize / (float)numNonClampedElements;
 
-				if(layoutOptions.fixedWidth)
-				{
-					totalWidth += layoutOptions.width;
-				}
-				else
+				childIdx = 0;
+				for(auto& child : mChildren)
 				{
-					numFreeElems++;
-				}
-			}
-			else if(child.isFixedSpace())
-			{
-				totalWidth += child.space->getSize();
-			}
-			else if(child.isFlexibleSpace())
-			{
-				totalWidth += flexibleSpaceSizes[flexibleSpaceIdx];
-				flexibleSpaceIdx++;
-			}
-			else
-			{
-				numFreeElems++;
-			}
-		}
+					if(processedElements[childIdx])
+						continue;
 
-		UINT32 leftoverWidth = (UINT32)std::max(0, (INT32)width - (INT32)totalWidth);
-		float averageWidth = leftoverWidth / (float) numFreeElems;
+					UINT32 extraWidth = std::min((UINT32)Math::CeilToInt(avgSize), remainingSize);
+					UINT32 elementWidth = (UINT32)std::max(0, (INT32)elementSizes[childIdx] - (INT32)extraWidth);
 
-		// Only assign elements with fixed, or clamped width
-		bool* processedElements = CM_NEW_ARRAY(bool, (UINT32)mChildren.size(), ScratchAlloc);
-		memset(processedElements, 0, mChildren.size() * sizeof(bool));
+					// Clamp if needed
+					if(child.isElement())
+					{
+						const GUILayoutOptions& layoutOptions = child.element->_getLayoutOptions();
+
+						if(elementWidth == 0)
+						{
+							processedElements[childIdx] = true;
+							numNonClampedElements--;
+						}
+						else if(layoutOptions.minWidth > 0 && elementWidth < layoutOptions.minWidth)
+						{
+							elementWidth = layoutOptions.minWidth;
+							
+							processedElements[childIdx] = true;
+							numNonClampedElements--;
+						}
+
+						extraWidth = elementSizes[childIdx] - elementWidth;
+						elementSizes[childIdx] = elementWidth;
+						remainingSize = (UINT32)std::max(0, (INT32)remainingSize - (INT32)extraWidth);
+					}
+					else if(child.isLayout())
+					{
+						if(elementWidth == 0)
+						{
+							processedElements[childIdx] = true;
+							numNonClampedElements--;
+						}
+
+						extraWidth = elementWidth - elementSizes[childIdx];
+						elementSizes[childIdx] = elementWidth;
+						remainingSize = (UINT32)std::max(0, (INT32)remainingSize - (INT32)extraWidth);
+					}
+					else if(child.isFlexibleSpace())
+					{
+						elementSizes[childIdx] = 0;
+						processedElements[childIdx] = true;
+						numNonClampedElements--;
+					}
 
-		UINT32 childIdx = 0;
-		for(auto& child : mChildren)
+					childIdx++;
+				}
+			}
+		}
+		else // We are smaller than the allowed maximum, so try to expand some elements
 		{
-			if(child.isElement())
-			{
-				const GUILayoutOptions& layoutOptions = child.element->_getLayoutOptions();
+			UINT32 extraSize = width - totalOptimalSize;
+			UINT32 remainingSize = extraSize;
 
-				UINT32 elementWidth = 0;
+			// Flexible spaces always expand to fill up all unused space, so we have a special case for them
+			if(numFlexibleSpaces > 0)
+			{
+				float avgSize = remainingSize / (float)numFlexibleSpaces;
 
-				if(layoutOptions.fixedWidth)
+				childIdx = 0;
+				for(auto& child : mChildren)
 				{
-					elementWidth = layoutOptions.width;
-					processedElements[childIdx] = true;
-				}
-				else
-				{
-					UINT32 availableWidth = std::min((UINT32)Math::CeilToInt(averageWidth), leftoverWidth);
+					if(processedElements[childIdx])
+						continue;
+
+					UINT32 extraWidth = std::min((UINT32)Math::CeilToInt(avgSize), remainingSize);
+					UINT32 elementWidth = elementSizes[childIdx] + extraWidth;
 
-					// Clamp average to min max
-					if(layoutOptions.minWidth > 0 && availableWidth < layoutOptions.minWidth)
+					// Clamp if needed
+					if(child.isFlexibleSpace())
 					{
-						elementWidth = layoutOptions.minWidth;
-						leftoverWidth = (UINT32)std::max(0, (INT32)leftoverWidth - (INT32)elementWidth);
-						
 						processedElements[childIdx] = true;
-						numFreeElems--;
+						elementSizes[childIdx] = elementWidth;
+
+						remainingSize = (UINT32)std::max(0, (INT32)remainingSize - (INT32)extraWidth);
 					}
-					else if(layoutOptions.maxWidth > 0 && availableWidth > layoutOptions.maxWidth)
+
+					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)
+				{
+					float avgSize = remainingSize / (float)numNonClampedElements;
+
+					childIdx = 0;
+					for(auto& child : mChildren)
 					{
-						elementWidth = layoutOptions.maxWidth;
-						leftoverWidth = (UINT32)std::max(0, (INT32)leftoverWidth - (INT32)elementWidth);
-						
-						processedElements[childIdx] = true;
-						numFreeElems--;
+						if(processedElements[childIdx])
+							continue;
+
+						UINT32 extraWidth = std::min((UINT32)Math::CeilToInt(avgSize), remainingSize);
+						UINT32 elementWidth = elementSizes[childIdx] + extraWidth;
+
+						// Clamp if needed
+						if(child.isElement())
+						{
+							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--;
+							}
+
+							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++;
 					}
 				}
-
-				child.element->_setWidth(elementWidth);
 			}
-
-			childIdx++;
 		}
 
-		averageWidth = leftoverWidth / (float) numFreeElems;
-
-		// Assign free scaling elements now that we have a good estimate on average width
-		// Note: Our average value is JUST an estimate. It's hard to predict the actual value because
-		// of min/max constraints and would probably require a few iterations to get a somewhat accurate value,
-		// but I don't think that is worth the trouble. For that reason min/max clamping that might happen in 
-		// the next block might cause elements to either be a bit too small or too large for the layout.
-		// It is the assumption that such clamping won't happen often.
-		// 
-		// Also calculate offset and element height.
+		// Now that we have all the sizes, actually assign them
+		// Also assign offsets, clip rectangles and depth
 		UINT32 xOffset = 0;
-		flexibleSpaceIdx = 0;
 		childIdx = 0;
 		for(auto& child : mChildren)
 		{
+			UINT32 elementWidth = elementSizes[childIdx];
+
 			if(child.isElement())
 			{
-				const GUILayoutOptions& layoutOptions = child.element->_getLayoutOptions();
-
-				if(!processedElements[childIdx])
-				{
-					UINT32 elementWidth = std::min((UINT32)Math::CeilToInt(averageWidth), leftoverWidth);
-
-					if(layoutOptions.minWidth > 0 && elementWidth < layoutOptions.minWidth)
-						elementWidth = layoutOptions.minWidth;
-					else if(layoutOptions.maxWidth > 0 && elementWidth > layoutOptions.maxWidth)
-						elementWidth = layoutOptions.maxWidth;
-
-					leftoverWidth = (UINT32)std::max(0, (INT32)leftoverWidth - (INT32)elementWidth);
-
-					child.element->_setWidth(elementWidth);
-				}
+				child.element->_setWidth(elementWidth);
 
+				const GUILayoutOptions& layoutOptions = child.element->_getLayoutOptions();
 				if(layoutOptions.fixedHeight)
 					child.element->_setHeight(layoutOptions.height);
 				else
@@ -199,31 +255,233 @@ namespace BansheeEngine
 				UINT32 clippedHeight = (UINT32)std::min((INT32)child.element->_getHeight(), (INT32)height - offset.y);
 
 				child.element->_setClipRect(Rect(0, 0, clippedWidth, clippedHeight));
-
-				xOffset += child.element->_getWidth();
 			}
 			else if(child.isLayout())
 			{
-				UINT32 elementWidth = std::min((UINT32)Math::CeilToInt(averageWidth), leftoverWidth);
-				leftoverWidth = (UINT32)std::max(0, (INT32)leftoverWidth - (INT32)elementWidth);
-
 				child.layout->_update(x + xOffset, y, elementWidth, height, depth);
-
-				xOffset += elementWidth;
-			}
-			else if(child.isFixedSpace())
-			{
-				xOffset += child.space->getSize();
-			}
-			else if(child.isFlexibleSpace())
-			{
-				xOffset += flexibleSpaceSizes[flexibleSpaceIdx];
-				flexibleSpaceIdx++;
 			}
 
+			xOffset += elementWidth;
 			childIdx++;
 		}
 
 		CM_DELETE_ARRAY(processedElements, bool, (UINT32)mChildren.size(), ScratchAlloc);
+		CM_DELETE_ARRAY(elementSizes, UINT32, (UINT32)mChildren.size(), ScratchAlloc);
+
+		//// Calculate flexible space sizes
+		//std::vector<UINT32> flexibleSpaceSizes;
+		//UINT32 minimalTotalSize = 0;
+		//for(auto& child : mChildren)
+		//{
+		//	if(child.isFlexibleSpace())
+		//	{
+		//		flexibleSpaceSizes.push_back(0);
+		//	}
+		//	else if(child.isFixedSpace())
+		//	{
+		//		minimalTotalSize += child.space->getSize();
+		//	}
+		//	else if(child.isElement())
+		//	{
+		//		const GUILayoutOptions& layoutOptions = child.element->_getLayoutOptions();
+
+		//		if(layoutOptions.fixedWidth)
+		//			minimalTotalSize += layoutOptions.width;
+		//		else
+		//		{
+		//			UINT32 optimalWidth = child.element->_getOptimalWidth();
+
+		//			if(layoutOptions.minHeight > 0)
+		//				optimalWidth = std::max(layoutOptions.minWidth, optimalWidth);
+
+		//			if(layoutOptions.maxHeight > 0)
+		//				optimalWidth = std::min(layoutOptions.maxWidth, optimalWidth);
+
+		//			minimalTotalSize += optimalWidth;
+		//		}
+		//	}
+		//}
+
+		//UINT32 remainingFlexSpaceSize = (UINT32)std::max(0, (INT32)width - (INT32)minimalTotalSize);
+		//float avgFlexibleSpaceSize = remainingFlexSpaceSize / (float)flexibleSpaceSizes.size();
+		//
+		//for(size_t i = 0; i < flexibleSpaceSizes.size(); i++)
+		//{
+		//	UINT32 spaceSize = (UINT32)Math::CeilToInt(avgFlexibleSpaceSize);
+		//	spaceSize = std::min(remainingFlexSpaceSize, spaceSize);
+
+		//	remainingFlexSpaceSize -= spaceSize;
+		//	flexibleSpaceSizes[i] = spaceSize;
+		//}
+
+		//// Get a basic estimate of the average width
+		//UINT32 totalWidth = 0;
+		//UINT32 numFreeElems = 0;
+		//float avgWidth = 0.0f;
+		//UINT32 flexibleSpaceIdx = 0;
+		//for(auto& child : mChildren)
+		//{
+		//	if(child.isElement())
+		//	{
+		//		const GUILayoutOptions& layoutOptions = child.element->_getLayoutOptions();
+
+		//		if(layoutOptions.fixedWidth)
+		//		{
+		//			totalWidth += layoutOptions.width;
+		//		}
+		//		else
+		//		{
+		//			numFreeElems++;
+		//		}
+		//	}
+		//	else if(child.isFixedSpace())
+		//	{
+		//		totalWidth += child.space->getSize();
+		//	}
+		//	else if(child.isFlexibleSpace())
+		//	{
+		//		totalWidth += flexibleSpaceSizes[flexibleSpaceIdx];
+		//		flexibleSpaceIdx++;
+		//	}
+		//	else
+		//	{
+		//		numFreeElems++;
+		//	}
+		//}
+
+		//UINT32 leftoverWidth = (UINT32)std::max(0, (INT32)width - (INT32)totalWidth);
+		//float averageWidth = leftoverWidth / (float) numFreeElems;
+
+		//// Only assign elements with fixed, or clamped width
+		//bool* processedElements = CM_NEW_ARRAY(bool, (UINT32)mChildren.size(), ScratchAlloc);
+		//memset(processedElements, 0, mChildren.size() * sizeof(bool));
+
+		//UINT32 childIdx = 0;
+		//for(auto& child : mChildren)
+		//{
+		//	if(child.isElement())
+		//	{
+		//		const GUILayoutOptions& layoutOptions = child.element->_getLayoutOptions();
+
+		//		UINT32 elementWidth = 0;
+
+		//		if(layoutOptions.fixedWidth)
+		//		{
+		//			elementWidth = layoutOptions.width;
+		//			processedElements[childIdx] = true;
+		//		}
+		//		else
+		//		{
+		//			UINT32 availableWidth = std::min((UINT32)Math::CeilToInt(averageWidth), leftoverWidth);
+
+		//			// Clamp average to min max
+		//			if(layoutOptions.minWidth > 0 && availableWidth < layoutOptions.minWidth)
+		//			{
+		//				elementWidth = layoutOptions.minWidth;
+		//				leftoverWidth = (UINT32)std::max(0, (INT32)leftoverWidth - (INT32)elementWidth);
+		//				
+		//				processedElements[childIdx] = true;
+		//				numFreeElems--;
+		//			}
+		//			else if(layoutOptions.maxWidth > 0 && availableWidth > layoutOptions.maxWidth)
+		//			{
+		//				elementWidth = layoutOptions.maxWidth;
+		//				leftoverWidth = (UINT32)std::max(0, (INT32)leftoverWidth - (INT32)elementWidth);
+		//				
+		//				processedElements[childIdx] = true;
+		//				numFreeElems--;
+		//			}
+		//		}
+
+		//		child.element->_setWidth(elementWidth);
+		//	}
+
+		//	childIdx++;
+		//}
+
+		//averageWidth = leftoverWidth / (float) numFreeElems;
+
+		//// Assign free scaling elements now that we have a good estimate on average width
+		//// Note: Our average value is JUST an estimate. It's hard to predict the actual value because
+		//// of min/max constraints and would probably require a few iterations to get a somewhat accurate value,
+		//// but I don't think that is worth the trouble. For that reason min/max clamping that might happen in 
+		//// the next block might cause elements to either be a bit too small or too large for the layout.
+		//// It is the assumption that such clamping won't happen often.
+		//// 
+		//// Also calculate offset and element height.
+		//UINT32 xOffset = 0;
+		//flexibleSpaceIdx = 0;
+		//childIdx = 0;
+		//for(auto& child : mChildren)
+		//{
+		//	if(child.isElement())
+		//	{
+		//		const GUILayoutOptions& layoutOptions = child.element->_getLayoutOptions();
+
+		//		if(!processedElements[childIdx])
+		//		{
+		//			UINT32 elementWidth = std::min((UINT32)Math::CeilToInt(averageWidth), leftoverWidth);
+
+		//			if(layoutOptions.minWidth > 0 && elementWidth < layoutOptions.minWidth)
+		//				elementWidth = layoutOptions.minWidth;
+		//			else if(layoutOptions.maxWidth > 0 && elementWidth > layoutOptions.maxWidth)
+		//				elementWidth = layoutOptions.maxWidth;
+
+		//			leftoverWidth = (UINT32)std::max(0, (INT32)leftoverWidth - (INT32)elementWidth);
+
+		//			child.element->_setWidth(elementWidth);
+		//		}
+
+		//		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);
+		//		}
+
+		//		UINT32 yOffset = (UINT32)Math::CeilToInt((height - 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));
+
+		//		xOffset += child.element->_getWidth();
+		//	}
+		//	else if(child.isLayout())
+		//	{
+		//		UINT32 elementWidth = std::min((UINT32)Math::CeilToInt(averageWidth), leftoverWidth);
+		//		leftoverWidth = (UINT32)std::max(0, (INT32)leftoverWidth - (INT32)elementWidth);
+
+		//		child.layout->_update(x + xOffset, y, elementWidth, height, depth);
+
+		//		xOffset += elementWidth;
+		//	}
+		//	else if(child.isFixedSpace())
+		//	{
+		//		xOffset += child.space->getSize();
+		//	}
+		//	else if(child.isFlexibleSpace())
+		//	{
+		//		xOffset += flexibleSpaceSizes[flexibleSpaceIdx];
+		//		flexibleSpaceIdx++;
+		//	}
+
+		//	childIdx++;
+		//}
+
+		//CM_DELETE_ARRAY(processedElements, bool, (UINT32)mChildren.size(), ScratchAlloc);
 	}
 }

+ 1 - 1
CamelotClient/CmEditorWindow.cpp

@@ -61,7 +61,7 @@ namespace BansheeEditor
 		otherLayout.addElement(mDbgLabel);
 
 		//GUIFixedSpace& space = otherLayout.addSpace(10); // Due to bug in MSVC compiler I need to store return value
-		GUIFlexibleSpace& space3 = otherLayout.addFlexibleSpace();
+		//GUIFlexibleSpace& space3 = otherLayout.addFlexibleSpace();
 		otherLayout.addElement(GUIWindowFrame::create(*mGUI, GUILayoutOptions::expandableX(20, 100)));
 		//GUIFixedSpace& space2 = otherLayout.addSpace(10);
 		otherLayout.addElement(GUIWindowFrame::create(*mGUI));