2
0

BsGUILayoutX.cpp 12 KB

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