BsGUILayoutX.cpp 14 KB

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