BsGUISceneTreeView.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. #include "BsGUISceneTreeView.h"
  2. #include "BsGUIArea.h"
  3. #include "BsGUILayout.h"
  4. #include "BsGUITexture.h"
  5. #include "BsGUIButton.h"
  6. #include "BsGUILabel.h"
  7. #include "BsGUISpace.h"
  8. #include "BsGUIWidget.h"
  9. #include "BsGUIToggle.h"
  10. #include "BsGUIMouseEvent.h"
  11. #include "BsGUISkin.h"
  12. #include "CmSceneObject.h"
  13. #include "CmSceneManager.h"
  14. using namespace CamelotFramework;
  15. using namespace BansheeEngine;
  16. namespace BansheeEditor
  17. {
  18. const UINT32 GUISceneTreeView::ELEMENT_EXTRA_SPACING = 3;
  19. const UINT32 GUISceneTreeView::INDENT_SIZE = 10;
  20. const UINT32 GUISceneTreeView::INITIAL_INDENT_OFFSET = 16;
  21. GUISceneTreeView::TreeElement::TreeElement()
  22. :mParent(nullptr), mFoldoutBtn(nullptr), mElement(nullptr),
  23. mId(0), mIsExpanded(false), mSortedIdx(0), mIsDirty(false), mIsVisible(true)
  24. { }
  25. GUISceneTreeView::TreeElement::~TreeElement()
  26. {
  27. for(auto& child : mChildren)
  28. cm_delete(child);
  29. if(mFoldoutBtn != nullptr)
  30. GUIElement::destroy(mFoldoutBtn);
  31. if(mElement != nullptr)
  32. GUIElement::destroy(mElement);
  33. mChildren.clear();
  34. }
  35. GUISceneTreeView::GUISceneTreeView(GUIWidget& parent, GUIElementStyle* backgroundStyle, GUIElementStyle* elementBtnStyle,
  36. GUIElementStyle* foldoutBtnStyle, GUIElementStyle* selectionBackgroundStyle, const BS::GUILayoutOptions& layoutOptions)
  37. :GUIElementContainer(parent, layoutOptions), mBackgroundStyle(backgroundStyle),
  38. mElementBtnStyle(elementBtnStyle), mFoldoutBtnStyle(foldoutBtnStyle),
  39. mSelectedElement(nullptr), mSelectionBackground(nullptr), mSelectionBackgroundStyle(selectionBackgroundStyle)
  40. {
  41. if(mBackgroundStyle == nullptr)
  42. mBackgroundStyle = parent.getSkin().getStyle("TreeViewBackground");
  43. if(mElementBtnStyle == nullptr)
  44. mElementBtnStyle = parent.getSkin().getStyle("TreeViewElementBtn");
  45. if(mFoldoutBtnStyle == nullptr)
  46. mFoldoutBtnStyle = parent.getSkin().getStyle("TreeViewFoldoutBtn");
  47. if(mSelectionBackgroundStyle == nullptr)
  48. mSelectionBackgroundStyle = parent.getSkin().getStyle("TreeViewSelectionBackground");
  49. mBackgroundImage = GUITexture::create(parent, mBackgroundStyle);
  50. mSelectionBackground = GUITexture::create(parent, mSelectionBackgroundStyle);
  51. mSelectionBackground->disableRecursively();
  52. _registerChildElement(mBackgroundImage);
  53. _registerChildElement(mSelectionBackground);
  54. }
  55. GUISceneTreeView::~GUISceneTreeView()
  56. {
  57. }
  58. GUISceneTreeView* GUISceneTreeView::create(GUIWidget& parent, GUIElementStyle* backgroundStyle, GUIElementStyle* elementBtnStyle,
  59. GUIElementStyle* foldoutBtnStyle, GUIElementStyle* selectionBackgroundStyle)
  60. {
  61. return new (cm_alloc<GUISceneTreeView, PoolAlloc>()) GUISceneTreeView(parent, backgroundStyle, elementBtnStyle, foldoutBtnStyle,
  62. selectionBackgroundStyle, GUILayoutOptions::create(&GUISkin::DefaultStyle));
  63. }
  64. GUISceneTreeView* GUISceneTreeView::create(GUIWidget& parent, const GUIOptions& options, GUIElementStyle* backgroundStyle,
  65. GUIElementStyle* elementBtnStyle, GUIElementStyle* foldoutBtnStyle, GUIElementStyle* selectionBackgroundStyle)
  66. {
  67. return new (cm_alloc<GUISceneTreeView, PoolAlloc>()) GUISceneTreeView(parent, backgroundStyle, elementBtnStyle,
  68. foldoutBtnStyle, selectionBackgroundStyle, GUILayoutOptions::create(options, &GUISkin::DefaultStyle));
  69. }
  70. void GUISceneTreeView::update()
  71. {
  72. // NOTE - Instead of iterating through every visible element and comparing it with internal values,
  73. // I might just want to add callbacks to SceneManager that notify me of any changes and then only perform
  74. // update if anything is actually dirty
  75. struct UpdateTreeElement
  76. {
  77. UpdateTreeElement(TreeElement* element, UINT32 seqIdx, bool visible)
  78. :element(element), seqIdx(seqIdx), visible(visible)
  79. { }
  80. TreeElement* element;
  81. UINT32 seqIdx;
  82. bool visible;
  83. };
  84. HSceneObject root = CM::gSceneManager().getRootNode();
  85. mRootElement.mSceneObject = root;
  86. mRootElement.mId = root->getId();
  87. mRootElement.mSortedIdx = 0;
  88. mRootElement.mIsExpanded = true;
  89. Stack<UpdateTreeElement>::type todo;
  90. todo.push(UpdateTreeElement(&mRootElement, 0, true));
  91. while(!todo.empty())
  92. {
  93. UpdateTreeElement updateElement = todo.top();
  94. TreeElement* current = updateElement.element;
  95. HSceneObject currentSO = current->mSceneObject;
  96. todo.pop();
  97. // Check if SceneObject has changed in any way and update the tree element
  98. if(updateElement.visible)
  99. {
  100. bool completeMatch = (UINT32)current->mChildren.size() == currentSO->getNumChildren();
  101. // Early exit case - Most commonly there will be no changes between active and cached data so
  102. // we first do a quick check in order to avoid expensive comparison later
  103. if(completeMatch)
  104. {
  105. for(UINT32 i = 0; i < currentSO->getNumChildren(); i++)
  106. {
  107. UINT32 curId = currentSO->getChild(i)->getId();
  108. if(curId != current->mChildren[i]->mId)
  109. {
  110. completeMatch = false;
  111. break;
  112. }
  113. }
  114. }
  115. // Not a complete match, compare everything and insert/delete elements as needed
  116. if(!completeMatch)
  117. {
  118. Vector<TreeElement*>::type newChildren;
  119. mTempToDelete.resize(current->mChildren.size(), true);
  120. for(UINT32 i = 0; i < currentSO->getNumChildren(); i++)
  121. {
  122. HSceneObject currentSOChild = currentSO->getChild(i);
  123. UINT32 curId = currentSOChild->getId();
  124. bool found = false;
  125. for(UINT32 j = 0; j < current->mChildren.size(); j++)
  126. {
  127. TreeElement* currentChild = current->mChildren[j];
  128. if(curId == currentChild->mId)
  129. {
  130. mTempToDelete[j] = false;
  131. currentChild->mIsDirty = true;
  132. currentChild->mSortedIdx = (UINT32)newChildren.size();
  133. newChildren.push_back(currentChild);
  134. found = true;
  135. break;
  136. }
  137. }
  138. if(!found)
  139. {
  140. TreeElement* newChild = cm_new<TreeElement>();
  141. newChild->mParent = current;
  142. newChild->mSceneObject = currentSOChild;
  143. newChild->mId = currentSOChild->getId();
  144. newChild->mName = currentSOChild->getName();
  145. newChild->mSortedIdx = (UINT32)newChildren.size();
  146. newChild->mIsDirty = true;
  147. newChildren.push_back(newChild);
  148. }
  149. }
  150. for(UINT32 i = 0; i < current->mChildren.size(); i++)
  151. {
  152. if(!mTempToDelete[i])
  153. continue;
  154. cm_delete(current->mChildren[i]);
  155. }
  156. current->mChildren = newChildren;
  157. current->mIsDirty = true;
  158. }
  159. // Check if name needs updating
  160. const String& name = current->mSceneObject->getName();
  161. if(current->mName != name)
  162. {
  163. current->mName = name;
  164. current->mIsDirty = true;
  165. }
  166. // Calculate the sorted index of the element based on its name
  167. TreeElement* parent = current->mParent;
  168. if(current->mIsDirty && parent != nullptr)
  169. {
  170. for(UINT32 i = 0; i < (UINT32)parent->mChildren.size(); i++)
  171. {
  172. INT32 stringCompare = current->mName.compare(parent->mChildren[i]->mName);
  173. if(stringCompare > 0)
  174. {
  175. if(current->mSortedIdx < parent->mChildren[i]->mSortedIdx)
  176. std::swap(current->mSortedIdx, parent->mChildren[i]->mSortedIdx);
  177. }
  178. else if(stringCompare < 0)
  179. {
  180. if(current->mSortedIdx > parent->mChildren[i]->mSortedIdx)
  181. std::swap(current->mSortedIdx, parent->mChildren[i]->mSortedIdx);
  182. }
  183. }
  184. }
  185. }
  186. bool visibilityChanged = false;
  187. if(current->mIsVisible != updateElement.visible)
  188. {
  189. visibilityChanged = true;
  190. current->mIsVisible = updateElement.visible;
  191. current->mIsDirty = true;
  192. }
  193. if(current->mIsDirty && current != &mRootElement)
  194. {
  195. if(updateElement.visible)
  196. {
  197. HString name(toWString(current->mName));
  198. if(current->mElement == nullptr)
  199. {
  200. current->mElement = GUILabel::create(_getParentWidget(), name, mElementBtnStyle);
  201. _registerChildElement(current->mElement);
  202. }
  203. if(current->mChildren.size() > 0)
  204. {
  205. if(current->mFoldoutBtn == nullptr)
  206. {
  207. current->mFoldoutBtn = GUIToggle::create(_getParentWidget(), GUIContent(HString(L"")), mFoldoutBtnStyle);
  208. _registerChildElement(current->mFoldoutBtn);
  209. current->mFoldoutBtn->onToggled.connect(boost::bind(&GUISceneTreeView::elementToggled, this, current, _1));
  210. }
  211. }
  212. else
  213. {
  214. if(current->mFoldoutBtn != nullptr)
  215. {
  216. GUIElement::destroy(current->mFoldoutBtn);
  217. current->mFoldoutBtn = nullptr;
  218. }
  219. }
  220. current->mElement->setContent(GUIContent(name));
  221. }
  222. else
  223. {
  224. if(current->mElement != nullptr)
  225. {
  226. GUIElement::destroy(current->mElement);
  227. current->mElement = nullptr;
  228. }
  229. if(current->mFoldoutBtn != nullptr)
  230. {
  231. GUIElement::destroy(current->mFoldoutBtn);
  232. current->mFoldoutBtn = nullptr;
  233. }
  234. }
  235. markContentAsDirty();
  236. current->mIsDirty = false;
  237. }
  238. // Queue children for next iteration
  239. if(visibilityChanged || current->mIsVisible)
  240. {
  241. for(UINT32 i = 0; i < (UINT32)current->mChildren.size(); i++)
  242. {
  243. todo.push(UpdateTreeElement(current->mChildren[i], i, current->mIsVisible && current->mIsExpanded));
  244. }
  245. }
  246. }
  247. if(mSelectedElement != nullptr)
  248. {
  249. if(mSelectionBackground->_isDisabled())
  250. mSelectionBackground->enableRecursively();
  251. }
  252. else
  253. {
  254. if(!mSelectionBackground->_isDisabled())
  255. mSelectionBackground->disableRecursively();
  256. }
  257. }
  258. bool GUISceneTreeView::mouseEvent(const GUIMouseEvent& event)
  259. {
  260. if(event.getType() == GUIMouseEventType::MouseUp)
  261. {
  262. const GUISceneTreeView::InteractableElement* element = findElementUnderCoord(event.getPosition());
  263. if(element != nullptr && element->isTreeElement())
  264. mSelectedElement = interactableToRealElement(*element);
  265. markContentAsDirty();
  266. return true;
  267. }
  268. return false;
  269. }
  270. void GUISceneTreeView::elementToggled(TreeElement* element, bool toggled)
  271. {
  272. element->mIsExpanded = toggled;
  273. }
  274. Vector2I GUISceneTreeView::_getOptimalSize() const
  275. {
  276. struct UpdateTreeElement
  277. {
  278. UpdateTreeElement(const TreeElement* element, UINT32 indent)
  279. :element(element), indent(indent)
  280. { }
  281. const TreeElement* element;
  282. UINT32 indent;
  283. };
  284. Vector2I optimalSize;
  285. if(_getLayoutOptions().fixedWidth && _getLayoutOptions().fixedHeight)
  286. {
  287. optimalSize.x = _getLayoutOptions().width;
  288. optimalSize.y = _getLayoutOptions().height;
  289. }
  290. else
  291. {
  292. Stack<UpdateTreeElement>::type todo;
  293. todo.push(UpdateTreeElement(&mRootElement, 0));
  294. optimalSize.y += ELEMENT_EXTRA_SPACING;
  295. while(!todo.empty())
  296. {
  297. UpdateTreeElement currentUpdateElement = todo.top();
  298. const TreeElement* current = currentUpdateElement.element;
  299. todo.pop();
  300. INT32 yOffset = 0;
  301. if(current->mElement != nullptr)
  302. {
  303. Vector2I curOptimalSize = current->mElement->_getOptimalSize();
  304. optimalSize.x = std::max(optimalSize.x,
  305. (INT32)(INITIAL_INDENT_OFFSET + curOptimalSize.x + currentUpdateElement.indent * INDENT_SIZE));
  306. yOffset = curOptimalSize.y + ELEMENT_EXTRA_SPACING;
  307. }
  308. optimalSize.y += yOffset;
  309. for(auto& child : current->mChildren)
  310. {
  311. if(!child->mIsVisible)
  312. continue;
  313. todo.push(UpdateTreeElement(child, currentUpdateElement.indent + 1));
  314. }
  315. }
  316. if(_getLayoutOptions().fixedWidth)
  317. optimalSize.x = _getLayoutOptions().width;
  318. else
  319. {
  320. if(_getLayoutOptions().minWidth > 0)
  321. optimalSize.x = std::max((INT32)_getLayoutOptions().minWidth, optimalSize.x);
  322. if(_getLayoutOptions().maxWidth > 0)
  323. optimalSize.x = std::min((INT32)_getLayoutOptions().maxWidth, optimalSize.x);
  324. }
  325. if(_getLayoutOptions().fixedHeight)
  326. optimalSize.y = _getLayoutOptions().height;
  327. else
  328. {
  329. if(_getLayoutOptions().minHeight > 0)
  330. optimalSize.y = std::max((INT32)_getLayoutOptions().minHeight, optimalSize.y);
  331. if(_getLayoutOptions().maxHeight > 0)
  332. optimalSize.y = std::min((INT32)_getLayoutOptions().maxHeight, optimalSize.y);
  333. }
  334. }
  335. return optimalSize;
  336. }
  337. void GUISceneTreeView::updateClippedBounds()
  338. {
  339. Vector2I offset = _getOffset();
  340. mClippedBounds = RectI(offset.x, offset.y, _getWidth(), _getHeight());
  341. }
  342. void GUISceneTreeView::_updateLayoutInternal(INT32 x, INT32 y, UINT32 width, UINT32 height,
  343. RectI clipRect, UINT8 widgetDepth, UINT16 areaDepth)
  344. {
  345. struct UpdateTreeElement
  346. {
  347. UpdateTreeElement(TreeElement* element, UINT32 indent)
  348. :element(element), indent(indent)
  349. { }
  350. TreeElement* element;
  351. UINT32 indent;
  352. };
  353. mVisibleElements.clear();
  354. Stack<UpdateTreeElement>::type todo;
  355. todo.push(UpdateTreeElement(&mRootElement, 0));
  356. // NOTE - Instead of iterating through all elements, try to find those within the clip rect
  357. // and only iterate through those. Others should somehow be marked in-active (similar to GUIElement::isDisabled()?)
  358. Vector<TreeElement*>::type tempOrderedElements;
  359. Vector2I offset(x, y);
  360. while(!todo.empty())
  361. {
  362. UpdateTreeElement currentUpdateElement = todo.top();
  363. TreeElement* current = currentUpdateElement.element;
  364. UINT32 indent = currentUpdateElement.indent;
  365. todo.pop();
  366. if(current->mParent != nullptr && current->mSortedIdx == 0)
  367. {
  368. mVisibleElements.push_back(InteractableElement(current->mParent, 0, RectI(x, offset.y, width, ELEMENT_EXTRA_SPACING)));
  369. }
  370. INT32 btnHeight = 0;
  371. INT32 yOffset = 0;
  372. if(current->mElement != nullptr)
  373. {
  374. Vector2I elementSize = current->mElement->_getOptimalSize();
  375. btnHeight = elementSize.y;
  376. offset.x = INITIAL_INDENT_OFFSET + indent * INDENT_SIZE;
  377. current->mElement->_setOffset(offset);
  378. current->mElement->_setWidth(elementSize.x);
  379. current->mElement->_setHeight(elementSize.y);
  380. current->mElement->_setAreaDepth(areaDepth);
  381. current->mElement->_setWidgetDepth(widgetDepth);
  382. RectI elemClipRect(clipRect.x - offset.x, clipRect.y - offset.y, clipRect.width, clipRect.height);
  383. current->mElement->_setClipRect(elemClipRect);
  384. mVisibleElements.push_back(InteractableElement(current->mParent, current->mSortedIdx * 2 + 1, RectI(x, offset.y, width, btnHeight)));
  385. mVisibleElements.push_back(InteractableElement(current->mParent, current->mSortedIdx * 2 + 2, RectI(x, offset.y + btnHeight, width, ELEMENT_EXTRA_SPACING)));
  386. yOffset = btnHeight + ELEMENT_EXTRA_SPACING;
  387. }
  388. if(current->mFoldoutBtn != nullptr)
  389. {
  390. Vector2I elementSize = current->mFoldoutBtn->_getOptimalSize();
  391. offset.x -= std::min((INT32)INITIAL_INDENT_OFFSET, elementSize.y);
  392. Vector2I myOffset = offset;
  393. if(elementSize.y > btnHeight)
  394. {
  395. UINT32 diff = elementSize.y - btnHeight;
  396. float half = diff * 0.5f;
  397. myOffset.y -= Math::floorToInt(half);
  398. }
  399. current->mFoldoutBtn->_setOffset(myOffset);
  400. current->mFoldoutBtn->_setWidth(elementSize.x);
  401. current->mFoldoutBtn->_setHeight(elementSize.y);
  402. current->mFoldoutBtn->_setAreaDepth(areaDepth);
  403. current->mFoldoutBtn->_setWidgetDepth(widgetDepth);
  404. RectI elemClipRect(clipRect.x - myOffset.x, clipRect.y - myOffset.y, clipRect.width, clipRect.height);
  405. current->mFoldoutBtn->_setClipRect(elemClipRect);
  406. }
  407. offset.y += yOffset;
  408. tempOrderedElements.resize(current->mChildren.size(), nullptr);
  409. for(auto& child : current->mChildren)
  410. {
  411. tempOrderedElements[child->mSortedIdx] = child;
  412. }
  413. for(auto iter = tempOrderedElements.rbegin(); iter != tempOrderedElements.rend(); ++iter)
  414. {
  415. TreeElement* child = *iter;
  416. if(!child->mIsVisible)
  417. continue;
  418. todo.push(UpdateTreeElement(child, indent + 1));
  419. }
  420. }
  421. if(mSelectedElement != nullptr)
  422. {
  423. GUILabel* targetElement = mSelectedElement->mElement;
  424. Vector2I offset = targetElement->_getOffset();
  425. offset.x = x;
  426. mSelectionBackground->_setOffset(offset);
  427. mSelectionBackground->_setWidth(width);
  428. mSelectionBackground->_setHeight(targetElement->_getHeight());
  429. mSelectionBackground->_setAreaDepth(areaDepth + 1);
  430. mSelectionBackground->_setWidgetDepth(widgetDepth);
  431. RectI elemClipRect(clipRect.x - offset.x, clipRect.y - offset.y, clipRect.width, clipRect.height);
  432. mSelectionBackground->_setClipRect(elemClipRect);
  433. }
  434. }
  435. const GUISceneTreeView::InteractableElement* GUISceneTreeView::findElementUnderCoord(const CM::Vector2I& coord) const
  436. {
  437. for(auto& element : mVisibleElements)
  438. {
  439. if(element.bounds.contains(coord))
  440. {
  441. return &element;
  442. }
  443. }
  444. return nullptr;
  445. }
  446. GUISceneTreeView::TreeElement* GUISceneTreeView::interactableToRealElement(const GUISceneTreeView::InteractableElement& element)
  447. {
  448. if(!element.isTreeElement())
  449. return nullptr;
  450. UINT32 sortedIdx = (element.index - 1) / 2;
  451. auto findIter = std::find_if(element.parent->mChildren.begin(), element.parent->mChildren.end(),
  452. [&](const TreeElement* x) { return x->mSortedIdx == sortedIdx; });
  453. if(findIter != element.parent->mChildren.end())
  454. return *findIter;
  455. return nullptr;
  456. }
  457. const String& GUISceneTreeView::getGUITypeName()
  458. {
  459. static String typeName = "SceneTreeView";
  460. return typeName;
  461. }
  462. }