BsGUILayoutY.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. #include "BsGUILayoutY.h"
  2. #include "BsGUIElement.h"
  3. #include "BsGUISpace.h"
  4. #include "CmMath.h"
  5. #include "CmInt2.h"
  6. using namespace CamelotFramework;
  7. namespace BansheeEngine
  8. {
  9. void GUILayoutY::_updateOptimalLayoutSizes()
  10. {
  11. // Update all children first, otherwise we can't determine out own optimal size
  12. for(auto& child : mChildren)
  13. {
  14. if(child->_getType() == GUIElementBase::Type::Layout)
  15. child->_updateOptimalLayoutSizes();
  16. }
  17. if(mChildren.size() != mOptimalSizes.size())
  18. mOptimalSizes.resize(mChildren.size());
  19. mOptimalWidth = 0;
  20. mOptimalHeight = 0;
  21. UINT32 childIdx = 0;
  22. for(auto& child : mChildren)
  23. {
  24. UINT32 optimalWidth = 0;
  25. UINT32 optimalHeight = 0;
  26. if(child->_getType() == GUIElementBase::Type::FixedSpace)
  27. {
  28. GUIFixedSpace* fixedSpace = static_cast<GUIFixedSpace*>(child);
  29. optimalHeight = fixedSpace->getSize();
  30. }
  31. else if(child->_getType() == GUIElementBase::Type::Element)
  32. {
  33. GUIElement* element = static_cast<GUIElement*>(child);
  34. const GUILayoutOptions& layoutOptions = element->_getLayoutOptions();
  35. if(layoutOptions.fixedHeight)
  36. {
  37. optimalHeight = layoutOptions.height;
  38. }
  39. else
  40. {
  41. optimalHeight = element->_getOptimalHeight();
  42. if(layoutOptions.minHeight > 0)
  43. optimalHeight = std::max(layoutOptions.minHeight, optimalHeight);
  44. if(layoutOptions.maxHeight > 0)
  45. optimalHeight = std::min(layoutOptions.maxHeight, optimalHeight);
  46. }
  47. if(layoutOptions.fixedWidth)
  48. optimalWidth = layoutOptions.width;
  49. else
  50. {
  51. optimalWidth = element->_getOptimalWidth();
  52. if(layoutOptions.minWidth > 0)
  53. optimalWidth = std::max(layoutOptions.minWidth, optimalWidth);
  54. if(layoutOptions.maxWidth > 0)
  55. optimalWidth = std::min(layoutOptions.maxWidth, optimalWidth);
  56. }
  57. }
  58. else if(child->_getType() == GUIElementBase::Type::Layout)
  59. {
  60. GUILayout* layout = static_cast<GUILayout*>(child);
  61. optimalHeight = layout->_getOptimalHeight();
  62. }
  63. mOptimalSizes[childIdx].y = optimalHeight;
  64. mOptimalHeight += optimalHeight;
  65. mOptimalSizes[childIdx].x = optimalWidth;
  66. mOptimalWidth = std::max(mOptimalWidth, optimalWidth);
  67. childIdx++;
  68. }
  69. }
  70. void GUILayoutY::_updateLayoutInternal(UINT32 x, UINT32 y, UINT32 width, UINT32 height, UINT8 widgetDepth, UINT16 areaDepth)
  71. {
  72. UINT32 totalOptimalSize = _getOptimalHeight();
  73. UINT32 totalNonClampedSize = 0;
  74. UINT32 numNonClampedElements = 0;
  75. UINT32 numFlexibleSpaces = 0;
  76. bool* processedElements = stackAllocN<bool>((UINT32)mChildren.size(), HID_Main);
  77. memset(processedElements, 0, mChildren.size() * sizeof(bool));
  78. UINT32* elementSizes = stackAllocN<UINT32>((UINT32)mChildren.size(), HID_Main);
  79. memset(elementSizes, 0, mChildren.size() * sizeof(UINT32));
  80. float* elementScaleWeights = stackAllocN<float>((UINT32)mChildren.size(), HID_Main);
  81. memset(elementScaleWeights, 0, mChildren.size() * sizeof(float));
  82. // Set initial sizes, count number of children per type and mark fixed elements as already processed
  83. UINT32 childIdx = 0;
  84. for(auto& child : mChildren)
  85. {
  86. elementSizes[childIdx] = mOptimalSizes[childIdx].y;
  87. if(child->_getType() == GUIElementBase::Type::FixedSpace)
  88. {
  89. processedElements[childIdx] = true;
  90. }
  91. else if(child->_getType() == GUIElementBase::Type::Element)
  92. {
  93. GUIElement* element = static_cast<GUIElement*>(child);
  94. const GUILayoutOptions& layoutOptions = element->_getLayoutOptions();
  95. if(layoutOptions.fixedHeight)
  96. processedElements[childIdx] = true;
  97. else
  98. {
  99. numNonClampedElements++;
  100. totalNonClampedSize += elementSizes[childIdx];
  101. }
  102. }
  103. else if(child->_getType() == GUIElementBase::Type::Layout)
  104. {
  105. numNonClampedElements++;
  106. totalNonClampedSize += elementSizes[childIdx];
  107. }
  108. else if(child->_getType() == GUIElementBase::Type::FlexibleSpace)
  109. {
  110. numFlexibleSpaces++;
  111. numNonClampedElements++;
  112. }
  113. childIdx++;
  114. }
  115. // If there is some room left, calculate flexible space sizes (since they will fill up all that extra room)
  116. if(height > totalOptimalSize)
  117. {
  118. UINT32 extraSize = height - totalOptimalSize;
  119. UINT32 remainingSize = extraSize;
  120. // Flexible spaces always expand to fill up all unused space
  121. if(numFlexibleSpaces > 0)
  122. {
  123. float avgSize = remainingSize / (float)numFlexibleSpaces;
  124. childIdx = 0;
  125. for(auto& child : mChildren)
  126. {
  127. if(processedElements[childIdx])
  128. {
  129. childIdx++;
  130. continue;
  131. }
  132. UINT32 extraHeight = std::min((UINT32)Math::CeilToInt(avgSize), remainingSize);
  133. UINT32 elementHeight = elementSizes[childIdx] + extraHeight;
  134. // Clamp if needed
  135. if(child->_getType() == GUIElementBase::Type::FlexibleSpace)
  136. {
  137. processedElements[childIdx] = true;
  138. numNonClampedElements--;
  139. elementSizes[childIdx] = elementHeight;
  140. remainingSize = (UINT32)std::max(0, (INT32)remainingSize - (INT32)extraHeight);
  141. }
  142. childIdx++;
  143. }
  144. totalOptimalSize = height;
  145. }
  146. }
  147. // Determine weight scale for every element. When scaling elements up/down they will be scaled based on this weight.
  148. // Weight is to ensure all elements are scaled fairly, so elements that are large will get effected more than smaller elements.
  149. childIdx = 0;
  150. float invOptimalSize = 1.0f / totalNonClampedSize;
  151. for(auto& child : mChildren)
  152. {
  153. if(processedElements[childIdx])
  154. {
  155. childIdx++;
  156. continue;
  157. }
  158. elementScaleWeights[childIdx] = invOptimalSize * elementSizes[childIdx];
  159. childIdx++;
  160. }
  161. // Our optimal size is larger than maximum allowed, so we need to reduce size of some elements
  162. if(totalOptimalSize > height)
  163. {
  164. UINT32 extraSize = totalOptimalSize - height;
  165. UINT32 remainingSize = extraSize;
  166. // Iterate until we reduce everything so it fits, while maintaining
  167. // equal average sizes using the weights we calculated earlier
  168. while(remainingSize > 0 && numNonClampedElements > 0)
  169. {
  170. UINT32 totalRemainingSize = remainingSize;
  171. childIdx = 0;
  172. for(auto& child : mChildren)
  173. {
  174. if(processedElements[childIdx])
  175. {
  176. childIdx++;
  177. continue;
  178. }
  179. float avgSize = totalRemainingSize * elementScaleWeights[childIdx];
  180. UINT32 extraHeight = std::min((UINT32)Math::CeilToInt(avgSize), remainingSize);
  181. UINT32 elementHeight = (UINT32)std::max(0, (INT32)elementSizes[childIdx] - (INT32)extraHeight);
  182. // Clamp if needed
  183. if(child->_getType() == GUIElementBase::Type::Element)
  184. {
  185. GUIElement* element = static_cast<GUIElement*>(child);
  186. const GUILayoutOptions& layoutOptions = element->_getLayoutOptions();
  187. if(elementHeight == 0)
  188. {
  189. processedElements[childIdx] = true;
  190. numNonClampedElements--;
  191. }
  192. else if(layoutOptions.minHeight > 0 && elementHeight < layoutOptions.minHeight)
  193. {
  194. elementHeight = layoutOptions.minHeight;
  195. processedElements[childIdx] = true;
  196. numNonClampedElements--;
  197. }
  198. extraHeight = elementSizes[childIdx] - elementHeight;
  199. elementSizes[childIdx] = elementHeight;
  200. remainingSize = (UINT32)std::max(0, (INT32)remainingSize - (INT32)extraHeight);
  201. }
  202. else if(child->_getType() == GUIElementBase::Type::Layout)
  203. {
  204. if(elementHeight == 0)
  205. {
  206. processedElements[childIdx] = true;
  207. numNonClampedElements--;
  208. }
  209. extraHeight = elementHeight - elementSizes[childIdx];
  210. elementSizes[childIdx] = elementHeight;
  211. remainingSize = (UINT32)std::max(0, (INT32)remainingSize - (INT32)extraHeight);
  212. }
  213. else if(child->_getType() == GUIElementBase::Type::FlexibleSpace)
  214. {
  215. elementSizes[childIdx] = 0;
  216. processedElements[childIdx] = true;
  217. numNonClampedElements--;
  218. }
  219. childIdx++;
  220. }
  221. }
  222. }
  223. else // We are smaller than the allowed maximum, so try to expand some elements
  224. {
  225. UINT32 extraSize = height - totalOptimalSize;
  226. UINT32 remainingSize = extraSize;
  227. // Iterate until we reduce everything so it fits, while maintaining
  228. // equal average sizes using the weights we calculated earlier
  229. while(remainingSize > 0 && numNonClampedElements > 0)
  230. {
  231. UINT32 totalRemainingSize = remainingSize;
  232. childIdx = 0;
  233. for(auto& child : mChildren)
  234. {
  235. if(processedElements[childIdx])
  236. {
  237. childIdx++;
  238. continue;
  239. }
  240. float avgSize = totalRemainingSize * elementScaleWeights[childIdx];
  241. UINT32 extraHeight = std::min((UINT32)Math::CeilToInt(avgSize), remainingSize);
  242. UINT32 elementHeight = elementSizes[childIdx] + extraHeight;
  243. // Clamp if needed
  244. if(child->_getType() == GUIElementBase::Type::Element)
  245. {
  246. GUIElement* element = static_cast<GUIElement*>(child);
  247. const GUILayoutOptions& layoutOptions = element->_getLayoutOptions();
  248. if(elementHeight == 0)
  249. {
  250. processedElements[childIdx] = true;
  251. numNonClampedElements--;
  252. }
  253. else if(layoutOptions.maxHeight > 0 && elementHeight > layoutOptions.maxHeight)
  254. {
  255. elementHeight = layoutOptions.maxHeight;
  256. processedElements[childIdx] = true;
  257. numNonClampedElements--;
  258. }
  259. extraHeight = elementHeight - elementSizes[childIdx];
  260. elementSizes[childIdx] = elementHeight;
  261. remainingSize = (UINT32)std::max(0, (INT32)remainingSize - (INT32)extraHeight);
  262. }
  263. else if(child->_getType() == GUIElementBase::Type::Layout)
  264. {
  265. elementSizes[childIdx] = elementHeight;
  266. remainingSize = (UINT32)std::max(0, (INT32)remainingSize - (INT32)extraHeight);
  267. }
  268. else if(child->_getType() == GUIElementBase::Type::FlexibleSpace)
  269. {
  270. processedElements[childIdx] = true;
  271. numNonClampedElements--;
  272. }
  273. childIdx++;
  274. }
  275. }
  276. }
  277. // Now that we have all the sizes, actually assign them
  278. // Also assign offsets, clip rectangles and depth
  279. UINT32 yOffset = 0;
  280. childIdx = 0;
  281. for(auto& child : mChildren)
  282. {
  283. UINT32 elementHeight = elementSizes[childIdx];
  284. if(child->_getType() == GUIElementBase::Type::Element)
  285. {
  286. GUIElement* element = static_cast<GUIElement*>(child);
  287. UINT32 elemWidth = mOptimalSizes[childIdx].x;
  288. const GUILayoutOptions& layoutOptions = element->_getLayoutOptions();
  289. if(!layoutOptions.fixedWidth)
  290. {
  291. elemWidth = width;
  292. if(layoutOptions.minWidth > 0 && elemWidth < layoutOptions.minWidth)
  293. elemWidth = layoutOptions.minWidth;
  294. if(layoutOptions.maxWidth > 0 && elemWidth > layoutOptions.maxWidth)
  295. elemWidth = layoutOptions.maxWidth;
  296. }
  297. element->_setWidth(elemWidth);
  298. element->_setHeight(elementHeight);
  299. UINT32 xOffset = (UINT32)Math::CeilToInt((width - element->_getHeight()) * 0.5f);
  300. Int2 offset(x + xOffset, y + yOffset);
  301. element->_setOffset(offset);
  302. element->_setWidgetDepth(widgetDepth);
  303. element->_setAreaDepth(areaDepth);
  304. UINT32 clippedWidth = (UINT32)std::min((INT32)element->_getWidth(), (INT32)width - (INT32)xOffset);
  305. UINT32 clippedHeight = (UINT32)std::min((INT32)element->_getHeight(), (INT32)height - (INT32)yOffset);
  306. element->_setClipRect(Rect(0, 0, clippedWidth, clippedHeight));
  307. }
  308. else if(child->_getType() == GUIElementBase::Type::Layout)
  309. {
  310. GUILayout* layout = static_cast<GUILayout*>(child);
  311. layout->_updateLayout(x, y + yOffset, width, elementHeight, widgetDepth, areaDepth);
  312. }
  313. yOffset += elementHeight;
  314. childIdx++;
  315. }
  316. stackDeallocLast(elementScaleWeights, HID_Main);
  317. stackDeallocLast(elementSizes, HID_Main);
  318. stackDeallocLast(processedElements, HID_Main);
  319. }
  320. }