BsGUILayoutX.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. #include "BsGUILayoutX.h"
  2. #include "BsGUIElement.h"
  3. #include "BsGUISpace.h"
  4. #include "BsMath.h"
  5. #include "BsVector2I.h"
  6. #include "BsProfilerCPU.h"
  7. namespace BansheeEngine
  8. {
  9. GUILayoutX::GUILayoutX(const GUIDimensions& dimensions)
  10. : GUILayout(dimensions)
  11. { }
  12. LayoutSizeRange GUILayoutX::_calculateLayoutSizeRange() const
  13. {
  14. if (mIsDisabled)
  15. return LayoutSizeRange();
  16. Vector2I optimalSize;
  17. Vector2I minSize;
  18. for (auto& child : mChildren)
  19. {
  20. LayoutSizeRange sizeRange = child->_calculateLayoutSizeRange();
  21. if (child->_getType() == GUIElementBase::Type::FixedSpace)
  22. sizeRange.optimal.y = sizeRange.min.y = 0;
  23. UINT32 paddingX = child->_getPadding().left + child->_getPadding().right;
  24. UINT32 paddingY = child->_getPadding().top + child->_getPadding().bottom;
  25. optimalSize.x += sizeRange.optimal.x + paddingX;
  26. optimalSize.y = std::max((UINT32)optimalSize.y, sizeRange.optimal.y + paddingY);
  27. minSize.x += sizeRange.min.x + paddingX;
  28. minSize.y = std::max((UINT32)minSize.y, sizeRange.min.y + paddingY);
  29. }
  30. LayoutSizeRange sizeRange = _getDimensions().calculateSizeRange(optimalSize);
  31. sizeRange.min.x = std::max(sizeRange.min.x, minSize.x);
  32. sizeRange.min.y = std::max(sizeRange.min.y, minSize.y);
  33. return sizeRange;
  34. }
  35. void GUILayoutX::_updateOptimalLayoutSizes()
  36. {
  37. // Update all children first, otherwise we can't determine our own optimal size
  38. GUIElementBase::_updateOptimalLayoutSizes();
  39. if(mChildren.size() != mChildSizeRanges.size())
  40. mChildSizeRanges.resize(mChildren.size());
  41. Vector2I optimalSize;
  42. Vector2I minSize;
  43. UINT32 childIdx = 0;
  44. for(auto& child : mChildren)
  45. {
  46. LayoutSizeRange& childSizeRange = mChildSizeRanges[childIdx];
  47. childSizeRange = child->_getLayoutSizeRange();
  48. if (child->_getType() == GUIElementBase::Type::FixedSpace)
  49. {
  50. childSizeRange.optimal.y = 0;
  51. childSizeRange.min.y = 0;
  52. }
  53. UINT32 paddingX = child->_getPadding().left + child->_getPadding().right;
  54. UINT32 paddingY = child->_getPadding().top + child->_getPadding().bottom;
  55. optimalSize.x += childSizeRange.optimal.x + paddingX;
  56. optimalSize.y = std::max((UINT32)optimalSize.y, childSizeRange.optimal.y + paddingY);
  57. minSize.x += childSizeRange.min.x + paddingX;
  58. minSize.y = std::max((UINT32)minSize.y, childSizeRange.min.y + paddingY);
  59. childIdx++;
  60. }
  61. mSizeRange = _getDimensions().calculateSizeRange(optimalSize);
  62. mSizeRange.min.x = std::max(mSizeRange.min.x, minSize.x);
  63. mSizeRange.min.y = std::max(mSizeRange.min.y, minSize.y);
  64. }
  65. void GUILayoutX::_getElementAreas(const Rect2I& layoutArea, Rect2I* elementAreas, UINT32 numElements,
  66. const Vector<LayoutSizeRange>& sizeRanges, const LayoutSizeRange& mySizeRange) const
  67. {
  68. assert(mChildren.size() == numElements);
  69. UINT32 totalOptimalSize = mySizeRange.optimal.x;
  70. UINT32 totalNonClampedSize = 0;
  71. UINT32 numNonClampedElements = 0;
  72. UINT32 numFlexibleSpaces = 0;
  73. bool* processedElements = nullptr;
  74. float* elementScaleWeights = nullptr;
  75. if (mChildren.size() > 0)
  76. {
  77. processedElements = bs_stack_alloc<bool>((UINT32)mChildren.size());
  78. memset(processedElements, 0, mChildren.size() * sizeof(bool));
  79. elementScaleWeights = bs_stack_alloc<float>((UINT32)mChildren.size());
  80. memset(elementScaleWeights, 0, mChildren.size() * sizeof(float));
  81. }
  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. elementAreas[childIdx].width = sizeRanges[childIdx].optimal.x;
  87. if (child->_getType() == GUIElementBase::Type::FixedSpace)
  88. {
  89. processedElements[childIdx] = true;
  90. }
  91. else if (child->_getType() == GUIElementBase::Type::FlexibleSpace)
  92. {
  93. numFlexibleSpaces++;
  94. numNonClampedElements++;
  95. }
  96. else
  97. {
  98. const GUIDimensions& dimensions = child->_getDimensions();
  99. if (dimensions.fixedWidth())
  100. processedElements[childIdx] = true;
  101. else
  102. {
  103. if (elementAreas[childIdx].width > 0)
  104. {
  105. numNonClampedElements++;
  106. totalNonClampedSize += elementAreas[childIdx].width;
  107. }
  108. else
  109. processedElements[childIdx] = true;
  110. }
  111. }
  112. childIdx++;
  113. }
  114. // If there is some room left, calculate flexible space sizes (since they will fill up all that extra room)
  115. if ((UINT32)layoutArea.width > totalOptimalSize)
  116. {
  117. UINT32 extraSize = layoutArea.width - totalOptimalSize;
  118. UINT32 remainingSize = extraSize;
  119. // Flexible spaces always expand to fill up all unused space
  120. if (numFlexibleSpaces > 0)
  121. {
  122. float avgSize = remainingSize / (float)numFlexibleSpaces;
  123. childIdx = 0;
  124. for (auto& child : mChildren)
  125. {
  126. if (processedElements[childIdx])
  127. {
  128. childIdx++;
  129. continue;
  130. }
  131. UINT32 extraWidth = std::min((UINT32)Math::ceilToInt(avgSize), remainingSize);
  132. UINT32 elementWidth = elementAreas[childIdx].width + extraWidth;
  133. // Clamp if needed
  134. if (child->_getType() == GUIElementBase::Type::FlexibleSpace)
  135. {
  136. processedElements[childIdx] = true;
  137. numNonClampedElements--;
  138. elementAreas[childIdx].width = elementWidth;
  139. remainingSize = (UINT32)std::max(0, (INT32)remainingSize - (INT32)extraWidth);
  140. }
  141. childIdx++;
  142. }
  143. totalOptimalSize = layoutArea.width;
  144. }
  145. }
  146. // Determine weight scale for every element. When scaling elements up/down they will be scaled based on this weight.
  147. // Weight is to ensure all elements are scaled fairly, so elements that are large will get effected more than smaller elements.
  148. childIdx = 0;
  149. float invOptimalSize = 1.0f / totalNonClampedSize;
  150. for (auto& child : mChildren)
  151. {
  152. if (processedElements[childIdx])
  153. {
  154. childIdx++;
  155. continue;
  156. }
  157. elementScaleWeights[childIdx] = invOptimalSize * elementAreas[childIdx].width;
  158. childIdx++;
  159. }
  160. // Our optimal size is larger than maximum allowed, so we need to reduce size of some elements
  161. if (totalOptimalSize > (UINT32)layoutArea.width)
  162. {
  163. UINT32 extraSize = totalOptimalSize - layoutArea.width;
  164. UINT32 remainingSize = extraSize;
  165. // Iterate until we reduce everything so it fits, while maintaining
  166. // equal average sizes using the weights we calculated earlier
  167. while (remainingSize > 0 && numNonClampedElements > 0)
  168. {
  169. UINT32 totalRemainingSize = remainingSize;
  170. childIdx = 0;
  171. for (auto& child : mChildren)
  172. {
  173. if (processedElements[childIdx])
  174. {
  175. childIdx++;
  176. continue;
  177. }
  178. float avgSize = totalRemainingSize * elementScaleWeights[childIdx];
  179. UINT32 extraWidth = std::min((UINT32)Math::ceilToInt(avgSize), remainingSize);
  180. UINT32 elementWidth = (UINT32)std::max(0, (INT32)elementAreas[childIdx].width - (INT32)extraWidth);
  181. // Clamp if needed
  182. switch (child->_getType())
  183. {
  184. case GUIElementBase::Type::FlexibleSpace:
  185. elementAreas[childIdx].width = 0;
  186. processedElements[childIdx] = true;
  187. numNonClampedElements--;
  188. break;
  189. case GUIElementBase::Type::Element:
  190. case GUIElementBase::Type::Layout:
  191. case GUIElementBase::Type::Panel:
  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. break;
  210. }
  211. childIdx++;
  212. }
  213. }
  214. }
  215. else // We are smaller than the allowed maximum, so try to expand some elements
  216. {
  217. UINT32 extraSize = layoutArea.width - totalOptimalSize;
  218. UINT32 remainingSize = extraSize;
  219. // Iterate until we reduce everything so it fits, while maintaining
  220. // equal average sizes using the weights we calculated earlier
  221. while (remainingSize > 0 && numNonClampedElements > 0)
  222. {
  223. UINT32 totalRemainingSize = remainingSize;
  224. childIdx = 0;
  225. for (auto& child : mChildren)
  226. {
  227. if (processedElements[childIdx])
  228. {
  229. childIdx++;
  230. continue;
  231. }
  232. float avgSize = totalRemainingSize * elementScaleWeights[childIdx];
  233. UINT32 extraWidth = std::min((UINT32)Math::ceilToInt(avgSize), remainingSize);
  234. UINT32 elementWidth = elementAreas[childIdx].width + extraWidth;
  235. // Clamp if needed
  236. switch (child->_getType())
  237. {
  238. case GUIElementBase::Type::FlexibleSpace:
  239. processedElements[childIdx] = true;
  240. numNonClampedElements--;
  241. break;
  242. case GUIElementBase::Type::Element:
  243. case GUIElementBase::Type::Layout:
  244. case GUIElementBase::Type::Panel:
  245. {
  246. const LayoutSizeRange& childSizeRange = sizeRanges[childIdx];
  247. if (elementWidth == 0)
  248. {
  249. processedElements[childIdx] = true;
  250. numNonClampedElements--;
  251. }
  252. else if (childSizeRange.max.x > 0 && (INT32)elementWidth > childSizeRange.max.x)
  253. {
  254. elementWidth = childSizeRange.max.x;
  255. processedElements[childIdx] = true;
  256. numNonClampedElements--;
  257. }
  258. extraWidth = elementWidth - elementAreas[childIdx].width;
  259. elementAreas[childIdx].width = elementWidth;
  260. remainingSize = (UINT32)std::max(0, (INT32)remainingSize - (INT32)extraWidth);
  261. }
  262. break;
  263. }
  264. childIdx++;
  265. }
  266. }
  267. }
  268. // Compute offsets and height
  269. UINT32 xOffset = 0;
  270. childIdx = 0;
  271. for (auto& child : mChildren)
  272. {
  273. UINT32 elemWidth = elementAreas[childIdx].width;
  274. xOffset += child->_getPadding().left;
  275. UINT32 elemHeight = (UINT32)sizeRanges[childIdx].optimal.y;
  276. const GUIDimensions& dimensions = child->_getDimensions();
  277. if (!dimensions.fixedHeight())
  278. {
  279. elemHeight = layoutArea.height;
  280. if (dimensions.minHeight > 0 && elemHeight < dimensions.minHeight)
  281. elemHeight = dimensions.minHeight;
  282. if (dimensions.maxHeight > 0 && elemHeight > dimensions.maxHeight)
  283. elemHeight = dimensions.maxHeight;
  284. }
  285. elementAreas[childIdx].height = elemHeight;
  286. if (child->_getType() == GUIElementBase::Type::Element)
  287. {
  288. GUIElement* element = static_cast<GUIElement*>(child);
  289. UINT32 yPadding = element->_getPadding().top + element->_getPadding().bottom;
  290. INT32 yOffset = Math::ceilToInt(((INT32)layoutArea.height - (INT32)(elemHeight + yPadding)) * 0.5f);
  291. yOffset = std::max(0, yOffset);
  292. elementAreas[childIdx].x = layoutArea.x + xOffset;
  293. elementAreas[childIdx].y = layoutArea.y + yOffset;
  294. }
  295. else if (child->_getType() == GUIElementBase::Type::Layout || child->_getType() == GUIElementBase::Type::Panel)
  296. {
  297. GUILayout* layout = static_cast<GUILayout*>(child);
  298. elementAreas[childIdx].x = layoutArea.x + xOffset;
  299. elementAreas[childIdx].y = layoutArea.y;
  300. }
  301. xOffset += elemWidth + child->_getPadding().right;
  302. childIdx++;
  303. }
  304. if (elementScaleWeights != nullptr)
  305. bs_stack_free(elementScaleWeights);
  306. if (processedElements != nullptr)
  307. bs_stack_free(processedElements);
  308. }
  309. void GUILayoutX::_updateLayoutInternal(const GUILayoutData& data)
  310. {
  311. UINT32 numElements = (UINT32)mChildren.size();
  312. Rect2I* elementAreas = nullptr;
  313. if (numElements > 0)
  314. elementAreas = bs_stack_new<Rect2I>(numElements);
  315. _getElementAreas(data.area, elementAreas, numElements, mChildSizeRanges, mSizeRange);
  316. // Now that we have all the areas, actually assign them
  317. UINT32 childIdx = 0;
  318. GUILayoutData childData = data;
  319. for(auto& child : mChildren)
  320. {
  321. childData.area = elementAreas[childIdx];
  322. childData.clipRect = childData.area;
  323. childData.clipRect.clip(data.clipRect);
  324. child->_setLayoutData(childData);
  325. child->_updateLayoutInternal(childData);
  326. childIdx++;
  327. }
  328. if(elementAreas != nullptr)
  329. bs_stack_free(elementAreas);
  330. }
  331. Vector2I GUILayoutX::_calcActualSize(INT32 x, INT32 y, Rect2I* elementAreas, UINT32 numElements) const
  332. {
  333. Vector2I actualArea;
  334. for (UINT32 i = 0; i < numElements; i++)
  335. {
  336. Rect2I childArea = elementAreas[i];
  337. actualArea.x += childArea.width;
  338. actualArea.y = std::max(actualArea.y, childArea.height);
  339. }
  340. return actualArea;
  341. }
  342. GUILayoutX* GUILayoutX::create()
  343. {
  344. return bs_new<GUILayoutX>();
  345. }
  346. GUILayoutX* GUILayoutX::create(const GUIOptions& options)
  347. {
  348. return bs_new<GUILayoutX>(GUIDimensions::create(options));
  349. }
  350. }