BsGUIElementBase.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622
  1. //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
  2. //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
  3. #include "GUI/BsGUIElementBase.h"
  4. #include "GUI/BsGUILayout.h"
  5. #include "GUI/BsGUIPanel.h"
  6. #include "GUI/BsGUISpace.h"
  7. #include "GUI/BsGUIElement.h"
  8. #include "Error/BsException.h"
  9. #include "GUI/BsGUIWidget.h"
  10. namespace bs
  11. {
  12. GUIElementBase::GUIElementBase()
  13. : mParentWidget(nullptr), mAnchorParent(nullptr), mUpdateParent(nullptr), mParentElement(nullptr)
  14. , mFlags(GUIElem_Dirty)
  15. {
  16. }
  17. GUIElementBase::GUIElementBase(const GUIDimensions& dimensions)
  18. : mParentWidget(nullptr), mAnchorParent(nullptr), mUpdateParent(nullptr), mParentElement(nullptr)
  19. , mFlags(GUIElem_Dirty), mDimensions(dimensions)
  20. {
  21. }
  22. GUIElementBase::~GUIElementBase()
  23. {
  24. Vector<GUIElementBase*> childCopy = mChildren;
  25. for (auto& child : childCopy)
  26. {
  27. if (child->_getType() == Type::Element)
  28. {
  29. GUIElement* element = static_cast<GUIElement*>(child);
  30. GUIElement::destroy(element);
  31. }
  32. else if (child->_getType() == Type::Layout || child->_getType() == GUIElementBase::Type::Panel)
  33. {
  34. GUILayout* layout = static_cast<GUILayout*>(child);
  35. GUILayout::destroy(layout);
  36. }
  37. else if (child->_getType() == Type::FixedSpace)
  38. {
  39. GUIFixedSpace* space = static_cast<GUIFixedSpace*>(child);
  40. GUIFixedSpace::destroy(space);
  41. }
  42. else if (child->_getType() == Type::FlexibleSpace)
  43. {
  44. GUIFlexibleSpace* space = static_cast<GUIFlexibleSpace*>(child);
  45. GUIFlexibleSpace::destroy(space);
  46. }
  47. }
  48. }
  49. void GUIElementBase::setPosition(INT32 x, INT32 y)
  50. {
  51. mDimensions.x = x;
  52. mDimensions.y = y;
  53. // Note: I could call _markMeshAsDirty with a little more work. If parent is layout then this call can be ignored
  54. // and if it's a panel, we can immediately change the position without a full layout rebuild.
  55. _markLayoutAsDirty();
  56. }
  57. void GUIElementBase::setSize(UINT32 width, UINT32 height)
  58. {
  59. bool isFixedBefore = (mDimensions.flags & GUIDF_FixedWidth) != 0 && (mDimensions.flags & GUIDF_FixedHeight) != 0;
  60. mDimensions.flags |= GUIDF_FixedWidth | GUIDF_OverWidth | GUIDF_FixedHeight | GUIDF_OverHeight;
  61. mDimensions.minWidth = mDimensions.maxWidth = width;
  62. mDimensions.minHeight = mDimensions.maxHeight = height;
  63. bool isFixedAfter = (mDimensions.flags & GUIDF_FixedWidth) != 0 && (mDimensions.flags & GUIDF_FixedHeight) != 0;
  64. if (isFixedBefore != isFixedAfter)
  65. refreshChildUpdateParents();
  66. _markLayoutAsDirty();
  67. }
  68. void GUIElementBase::setWidth(UINT32 width)
  69. {
  70. bool isFixedBefore = (mDimensions.flags & GUIDF_FixedWidth) != 0 && (mDimensions.flags & GUIDF_FixedHeight) != 0;
  71. mDimensions.flags |= GUIDF_FixedWidth | GUIDF_OverWidth;
  72. mDimensions.minWidth = mDimensions.maxWidth = width;
  73. bool isFixedAfter = (mDimensions.flags & GUIDF_FixedWidth) != 0 && (mDimensions.flags & GUIDF_FixedHeight) != 0;
  74. if (isFixedBefore != isFixedAfter)
  75. refreshChildUpdateParents();
  76. _markLayoutAsDirty();
  77. }
  78. void GUIElementBase::setFlexibleWidth(UINT32 minWidth, UINT32 maxWidth)
  79. {
  80. if (maxWidth < minWidth)
  81. std::swap(minWidth, maxWidth);
  82. bool isFixedBefore = (mDimensions.flags & GUIDF_FixedWidth) != 0 && (mDimensions.flags & GUIDF_FixedHeight) != 0;
  83. mDimensions.flags |= GUIDF_OverWidth;
  84. mDimensions.flags &= ~GUIDF_FixedWidth;
  85. mDimensions.minWidth = minWidth;
  86. mDimensions.maxWidth = maxWidth;
  87. bool isFixedAfter = (mDimensions.flags & GUIDF_FixedWidth) != 0 && (mDimensions.flags & GUIDF_FixedHeight) != 0;
  88. if (isFixedBefore != isFixedAfter)
  89. refreshChildUpdateParents();
  90. _markLayoutAsDirty();
  91. }
  92. void GUIElementBase::setHeight(UINT32 height)
  93. {
  94. bool isFixedBefore = (mDimensions.flags & GUIDF_FixedWidth) != 0 && (mDimensions.flags & GUIDF_FixedHeight) != 0;
  95. mDimensions.flags |= GUIDF_FixedHeight | GUIDF_OverHeight;
  96. mDimensions.minHeight = mDimensions.maxHeight = height;
  97. bool isFixedAfter = (mDimensions.flags & GUIDF_FixedWidth) != 0 && (mDimensions.flags & GUIDF_FixedHeight) != 0;
  98. if (isFixedBefore != isFixedAfter)
  99. refreshChildUpdateParents();
  100. _markLayoutAsDirty();
  101. }
  102. void GUIElementBase::setFlexibleHeight(UINT32 minHeight, UINT32 maxHeight)
  103. {
  104. if (maxHeight < minHeight)
  105. std::swap(minHeight, maxHeight);
  106. bool isFixedBefore = (mDimensions.flags & GUIDF_FixedWidth) != 0 && (mDimensions.flags & GUIDF_FixedHeight) != 0;
  107. mDimensions.flags |= GUIDF_OverHeight;
  108. mDimensions.flags &= ~GUIDF_FixedHeight;
  109. mDimensions.minHeight = minHeight;
  110. mDimensions.maxHeight = maxHeight;
  111. bool isFixedAfter = (mDimensions.flags & GUIDF_FixedWidth) != 0 && (mDimensions.flags & GUIDF_FixedHeight) != 0;
  112. if (isFixedBefore != isFixedAfter)
  113. refreshChildUpdateParents();
  114. _markLayoutAsDirty();
  115. }
  116. void GUIElementBase::resetDimensions()
  117. {
  118. bool isFixedBefore = (mDimensions.flags & GUIDF_FixedWidth) != 0 && (mDimensions.flags & GUIDF_FixedHeight) != 0;
  119. mDimensions = GUIDimensions::create();
  120. bool isFixedAfter = (mDimensions.flags & GUIDF_FixedWidth) != 0 && (mDimensions.flags & GUIDF_FixedHeight) != 0;
  121. if (isFixedBefore != isFixedAfter)
  122. refreshChildUpdateParents();
  123. _markLayoutAsDirty();
  124. }
  125. Rect2I GUIElementBase::getBounds(GUIPanel* relativeTo)
  126. {
  127. if (relativeTo == nullptr)
  128. relativeTo = mAnchorParent;
  129. Rect2I anchorBounds;
  130. if (relativeTo != nullptr)
  131. anchorBounds = relativeTo->getGlobalBounds();
  132. if (mUpdateParent != nullptr && mUpdateParent->_isDirty() && mParentWidget != nullptr)
  133. mParentWidget->_updateLayout(mUpdateParent);
  134. Rect2I bounds = mLayoutData.area;
  135. bounds.x -= anchorBounds.x;
  136. bounds.y -= anchorBounds.y;
  137. return bounds;
  138. }
  139. void GUIElementBase::setBounds(const Rect2I& bounds)
  140. {
  141. setPosition(bounds.x, bounds.y);
  142. setWidth(bounds.width);
  143. setHeight(bounds.height);
  144. }
  145. Rect2I GUIElementBase::getGlobalBounds()
  146. {
  147. if (mUpdateParent != nullptr && mUpdateParent->_isDirty() && mParentWidget != nullptr)
  148. mParentWidget->_updateLayout(mUpdateParent);
  149. return mLayoutData.area;
  150. }
  151. Rect2I GUIElementBase::getVisibleBounds()
  152. {
  153. return getBounds();
  154. }
  155. void GUIElementBase::_markAsClean()
  156. {
  157. mFlags &= ~GUIElem_Dirty;
  158. }
  159. void GUIElementBase::_markLayoutAsDirty()
  160. {
  161. if(!_isVisible())
  162. return;
  163. if (mUpdateParent != nullptr)
  164. mUpdateParent->mFlags |= GUIElem_Dirty;
  165. else
  166. mFlags |= GUIElem_Dirty;
  167. }
  168. void GUIElementBase::_markContentAsDirty()
  169. {
  170. if (!_isVisible())
  171. return;
  172. if (mParentWidget != nullptr)
  173. mParentWidget->_markContentDirty(this);
  174. }
  175. void GUIElementBase::_markMeshAsDirty()
  176. {
  177. if(!_isVisible())
  178. return;
  179. if (mParentWidget != nullptr)
  180. mParentWidget->_markMeshDirty(this);
  181. }
  182. void GUIElementBase::setVisible(bool visible)
  183. {
  184. // No visibility states matter if object is not active
  185. if (!_isActive())
  186. return;
  187. bool visibleSelf = (mFlags & GUIElem_HiddenSelf) == 0;
  188. if (visibleSelf != visible)
  189. {
  190. // If making an element visible make sure to mark layout as dirty, as we didn't track any dirty flags while the element was inactive
  191. if (!visible)
  192. {
  193. mFlags |= GUIElem_HiddenSelf;
  194. _setVisible(false);
  195. }
  196. else
  197. {
  198. mFlags &= ~GUIElem_HiddenSelf;
  199. if (mParentElement == nullptr || mParentElement->_isVisible())
  200. _setVisible(true);
  201. }
  202. }
  203. }
  204. void GUIElementBase::_setVisible(bool visible)
  205. {
  206. bool isVisible = (mFlags & GUIElem_Hidden) == 0;
  207. if (isVisible == visible)
  208. return;
  209. if (!visible)
  210. {
  211. _markMeshAsDirty();
  212. mFlags |= GUIElem_Hidden;
  213. for (auto& child : mChildren)
  214. child->_setVisible(false);
  215. }
  216. else
  217. {
  218. bool childVisibleSelf = (mFlags & GUIElem_HiddenSelf) == 0;
  219. if (childVisibleSelf)
  220. {
  221. mFlags &= ~GUIElem_Hidden;
  222. _markLayoutAsDirty();
  223. for (auto& child : mChildren)
  224. child->_setVisible(true);
  225. }
  226. }
  227. }
  228. void GUIElementBase::setActive(bool active)
  229. {
  230. static const UINT8 ACTIVE_FLAGS = GUIElem_InactiveSelf | GUIElem_HiddenSelf;
  231. bool activeSelf = (mFlags & GUIElem_InactiveSelf) == 0;
  232. if (activeSelf != active)
  233. {
  234. if (!active)
  235. {
  236. mFlags |= ACTIVE_FLAGS;
  237. _setActive(false);
  238. _setVisible(false);
  239. }
  240. else
  241. {
  242. mFlags &= ~ACTIVE_FLAGS;
  243. if (mParentElement != nullptr)
  244. {
  245. if (mParentElement->_isActive())
  246. {
  247. _setActive(true);
  248. if (mParentElement->_isVisible())
  249. _setVisible(true);
  250. }
  251. }
  252. else
  253. {
  254. _setActive(true);
  255. _setVisible(true);
  256. }
  257. }
  258. }
  259. }
  260. void GUIElementBase::_setActive(bool active)
  261. {
  262. bool isActive = (mFlags & GUIElem_Inactive) == 0;
  263. if (isActive == active)
  264. return;
  265. if (!active)
  266. {
  267. _markLayoutAsDirty();
  268. mFlags |= GUIElem_Inactive;
  269. for (auto& child : mChildren)
  270. child->_setActive(false);
  271. }
  272. else
  273. {
  274. bool childActiveSelf = (mFlags & GUIElem_InactiveSelf) == 0;
  275. if (childActiveSelf)
  276. {
  277. mFlags &= ~GUIElem_Inactive;
  278. _markLayoutAsDirty();
  279. for (auto& child : mChildren)
  280. child->_setActive(true);
  281. }
  282. }
  283. }
  284. void GUIElementBase::setDisabled(bool disabled)
  285. {
  286. bool disabledSelf = (mFlags & GUIElem_DisabledSelf) != 0;
  287. if (disabledSelf != disabled)
  288. {
  289. if (!disabled)
  290. mFlags &= ~GUIElem_DisabledSelf;
  291. else
  292. mFlags |= GUIElem_DisabledSelf;
  293. _setDisabled(disabled);
  294. }
  295. }
  296. void GUIElementBase::_setDisabled(bool disabled)
  297. {
  298. bool isDisabled = (mFlags & GUIElem_Disabled) != 0;
  299. if (isDisabled == disabled)
  300. return;
  301. if (!disabled)
  302. {
  303. bool disabledSelf = (mFlags & GUIElem_DisabledSelf) != 0;
  304. if (!disabledSelf)
  305. {
  306. mFlags &= ~GUIElem_Disabled;
  307. for (auto& child : mChildren)
  308. child->_setDisabled(false);
  309. }
  310. }
  311. else
  312. {
  313. mFlags |= GUIElem_Disabled;
  314. for (auto& child : mChildren)
  315. child->_setDisabled(true);
  316. }
  317. if (_isVisible())
  318. _markContentAsDirty();
  319. }
  320. void GUIElementBase::_updateLayout(const GUILayoutData& data)
  321. {
  322. _updateOptimalLayoutSizes(); // We calculate optimal sizes of all layouts as a pre-processing step, as they are requested often during update
  323. _updateLayoutInternal(data);
  324. }
  325. void GUIElementBase::_updateOptimalLayoutSizes()
  326. {
  327. for(auto& child : mChildren)
  328. {
  329. child->_updateOptimalLayoutSizes();
  330. }
  331. }
  332. void GUIElementBase::_updateLayoutInternal(const GUILayoutData& data)
  333. {
  334. for(auto& child : mChildren)
  335. {
  336. child->_updateLayoutInternal(data);
  337. }
  338. }
  339. LayoutSizeRange GUIElementBase::_calculateLayoutSizeRange() const
  340. {
  341. const GUIDimensions& dimensions = _getDimensions();
  342. return dimensions.calculateSizeRange(_getOptimalSize());
  343. }
  344. LayoutSizeRange GUIElementBase::_getLayoutSizeRange() const
  345. {
  346. return _calculateLayoutSizeRange();
  347. }
  348. void GUIElementBase::_getElementAreas(const Rect2I& layoutArea, Rect2I* elementAreas, UINT32 numElements,
  349. const Vector<LayoutSizeRange>& sizeRanges, const LayoutSizeRange& mySizeRange) const
  350. {
  351. assert(mChildren.size() == 0);
  352. }
  353. void GUIElementBase::_setParent(GUIElementBase* parent)
  354. {
  355. if(mParentElement != parent)
  356. {
  357. mParentElement = parent;
  358. _updateAUParents();
  359. if (parent != nullptr)
  360. {
  361. if (_getParentWidget() != parent->_getParentWidget())
  362. _changeParentWidget(parent->_getParentWidget());
  363. }
  364. else
  365. _changeParentWidget(nullptr);
  366. }
  367. }
  368. void GUIElementBase::_registerChildElement(GUIElementBase* element)
  369. {
  370. assert(!element->_isDestroyed());
  371. GUIElementBase* parentElement = element->_getParent();
  372. if(parentElement != nullptr)
  373. {
  374. parentElement->_unregisterChildElement(element);
  375. }
  376. element->_setParent(this);
  377. mChildren.push_back(element);
  378. element->_setActive(_isActive());
  379. element->_setVisible(_isVisible());
  380. element->_setDisabled(_isDisabled());
  381. _markLayoutAsDirty();
  382. }
  383. void GUIElementBase::_unregisterChildElement(GUIElementBase* element)
  384. {
  385. bool foundElem = false;
  386. for(auto iter = mChildren.begin(); iter != mChildren.end(); ++iter)
  387. {
  388. GUIElementBase* child = *iter;
  389. if (child == element)
  390. {
  391. mChildren.erase(iter);
  392. element->_setParent(nullptr);
  393. foundElem = true;
  394. _markLayoutAsDirty();
  395. break;
  396. }
  397. }
  398. if(!foundElem)
  399. BS_EXCEPT(InvalidParametersException, "Provided element is not a part of this element.");
  400. }
  401. void GUIElementBase::_changeParentWidget(GUIWidget* widget)
  402. {
  403. assert(!_isDestroyed());
  404. if (mParentWidget != widget)
  405. {
  406. if (mParentWidget != nullptr)
  407. mParentWidget->_unregisterElement(this);
  408. if (widget != nullptr)
  409. widget->_registerElement(this);
  410. }
  411. mParentWidget = widget;
  412. for(auto& child : mChildren)
  413. {
  414. child->_changeParentWidget(widget);
  415. }
  416. _markLayoutAsDirty();
  417. }
  418. void GUIElementBase::_updateAUParents()
  419. {
  420. GUIElementBase* updateParent = nullptr;
  421. if (mParentElement != nullptr)
  422. {
  423. updateParent = mParentElement->findUpdateParent();
  424. // If parent is a panel then we can do an optimization and only update
  425. // one child instead of all of them, so change parent to that child.
  426. if (updateParent != nullptr && updateParent->_getType() == GUIElementBase::Type::Panel)
  427. {
  428. GUIElementBase* optimizedUpdateParent = this;
  429. while (optimizedUpdateParent->_getParent() != updateParent)
  430. optimizedUpdateParent = optimizedUpdateParent->_getParent();
  431. updateParent = optimizedUpdateParent;
  432. }
  433. }
  434. GUIPanel* anchorParent = nullptr;
  435. GUIElementBase* currentParent = mParentElement;
  436. while (currentParent != nullptr)
  437. {
  438. if (currentParent->_getType() == Type::Panel)
  439. {
  440. anchorParent = static_cast<GUIPanel*>(currentParent);
  441. break;
  442. }
  443. currentParent = currentParent->mParentElement;
  444. }
  445. setAnchorParent(anchorParent);
  446. setUpdateParent(updateParent);
  447. }
  448. GUIElementBase* GUIElementBase::findUpdateParent()
  449. {
  450. GUIElementBase* currentElement = this;
  451. while (currentElement != nullptr)
  452. {
  453. const GUIDimensions& parentDimensions = currentElement->_getDimensions();
  454. bool boundsDependOnChildren = !parentDimensions.fixedHeight() || !parentDimensions.fixedWidth();
  455. if (!boundsDependOnChildren)
  456. return currentElement;
  457. currentElement = currentElement->mParentElement;
  458. }
  459. return nullptr;
  460. }
  461. void GUIElementBase::refreshChildUpdateParents()
  462. {
  463. GUIElementBase* updateParent = findUpdateParent();
  464. for (auto& child : mChildren)
  465. {
  466. GUIElementBase* childUpdateParent = updateParent;
  467. // If parent is a panel then we can do an optimization and only update
  468. // one child instead of all of them, so change parent to that child.
  469. if (childUpdateParent != nullptr && childUpdateParent->_getType() == GUIElementBase::Type::Panel)
  470. {
  471. GUIElementBase* optimizedUpdateParent = child;
  472. while (optimizedUpdateParent->_getParent() != childUpdateParent)
  473. optimizedUpdateParent = optimizedUpdateParent->_getParent();
  474. childUpdateParent = optimizedUpdateParent;
  475. }
  476. child->setUpdateParent(childUpdateParent);
  477. }
  478. }
  479. void GUIElementBase::setAnchorParent(GUIPanel* anchorParent)
  480. {
  481. mAnchorParent = anchorParent;
  482. if (_getType() == Type::Panel)
  483. return;
  484. for (auto& child : mChildren)
  485. child->setAnchorParent(anchorParent);
  486. }
  487. void GUIElementBase::setUpdateParent(GUIElementBase* updateParent)
  488. {
  489. mUpdateParent = updateParent;
  490. const GUIDimensions& dimensions = _getDimensions();
  491. bool boundsDependOnChildren = !dimensions.fixedHeight() || !dimensions.fixedWidth();
  492. if (!boundsDependOnChildren)
  493. return;
  494. for (auto& child : mChildren)
  495. child->setUpdateParent(updateParent);
  496. }
  497. }