BsGUILayoutX.cpp 15 KB

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