BsGUITreeView.cpp 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356
  1. //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
  2. //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
  3. #include "GUI/BsGUITreeView.h"
  4. #include "GUI/BsGUILayout.h"
  5. #include "GUI/BsGUITexture.h"
  6. #include "GUI/BsGUIButton.h"
  7. #include "GUI/BsGUILabel.h"
  8. #include "GUI/BsGUISpace.h"
  9. #include "GUI/BsCGUIWidget.h"
  10. #include "GUI/BsGUIToggle.h"
  11. #include "GUI/BsGUITreeViewEditBox.h"
  12. #include "GUI/BsGUIMouseEvent.h"
  13. #include "GUI/BsGUISkin.h"
  14. #include "GUI/BsGUICommandEvent.h"
  15. #include "GUI/BsGUIVirtualButtonEvent.h"
  16. #include "GUI/BsGUIScrollArea.h"
  17. #include "GUI/BsDragAndDropManager.h"
  18. #include "Utility/BsTime.h"
  19. using namespace std::placeholders;
  20. namespace bs
  21. {
  22. const UINT32 GUITreeView::ELEMENT_EXTRA_SPACING = 3;
  23. const UINT32 GUITreeView::INDENT_SIZE = 10;
  24. const UINT32 GUITreeView::INITIAL_INDENT_OFFSET = 16;
  25. const UINT32 GUITreeView::DRAG_MIN_DISTANCE = 3;
  26. const float GUITreeView::AUTO_EXPAND_DELAY_SEC = 0.5f;
  27. const float GUITreeView::SCROLL_AREA_HEIGHT_PCT = 0.1f;
  28. const UINT32 GUITreeView::SCROLL_SPEED_PX_PER_SEC = 100;
  29. const Color GUITreeView::CUT_COLOR = Color(1.0f, 1.0f, 1.0f, 0.3f);
  30. const Color GUITreeView::DISABLED_COLOR = Color(1.0f, 1.0f, 1.0f, 0.6f);
  31. GUITreeView::TreeElement::TreeElement()
  32. : mParent(nullptr), mFoldoutBtn(nullptr), mElement(nullptr), mSortedIdx(0), mIsExpanded(false), mIsSelected(false)
  33. , mIsHighlighted(false), mIsVisible(true), mIsCut(false), mIsDisabled(false)
  34. { }
  35. GUITreeView::TreeElement::~TreeElement()
  36. {
  37. assert(mChildren.empty());
  38. if(mFoldoutBtn != nullptr)
  39. GUIElement::destroy(mFoldoutBtn);
  40. if(mElement != nullptr)
  41. GUIElement::destroy(mElement);
  42. }
  43. bool GUITreeView::TreeElement::isParentRec(TreeElement* element) const
  44. {
  45. TreeElement* curParent = mParent;
  46. while(curParent != nullptr)
  47. {
  48. if(curParent == element)
  49. return true;
  50. curParent = curParent->mParent;
  51. }
  52. return false;
  53. }
  54. GUITreeView::TreeElement* GUITreeView::InteractableElement::getTreeElement() const
  55. {
  56. if(!isTreeElement())
  57. return nullptr;
  58. UINT32 sortedIdx = (index - 1) / 2;
  59. auto findIter = std::find_if(parent->mChildren.begin(), parent->mChildren.end(),
  60. [&](const TreeElement* x) { return x->mSortedIdx == sortedIdx; });
  61. if(findIter != parent->mChildren.end())
  62. return *findIter;
  63. return nullptr;
  64. }
  65. GUITreeView::GUITreeView(const String& backgroundStyle, const String& elementBtnStyle,
  66. const String& foldoutBtnStyle, const String& selectionBackgroundStyle, const String& highlightBackgroundStyle,
  67. const String& editBoxStyle, const String& dragHighlightStyle, const String& dragSepHighlightStyle, const GUIDimensions& dimensions)
  68. : GUIElementContainer(dimensions), mBackgroundStyle(backgroundStyle), mElementBtnStyle(elementBtnStyle)
  69. , mFoldoutBtnStyle(foldoutBtnStyle), mSelectionBackgroundStyle(selectionBackgroundStyle)
  70. , mHighlightBackgroundStyle(highlightBackgroundStyle), mEditBoxStyle(editBoxStyle)
  71. , mDragHighlightStyle(dragHighlightStyle), mDragSepHighlightStyle(dragSepHighlightStyle), mIsElementSelected(false)
  72. , mIsElementHighlighted(false), mEditElement(nullptr), mNameEditBox(nullptr), mDragInProgress(false)
  73. , mDragHighlight(nullptr), mDragSepHighlight(nullptr), mScrollState(ScrollState::None), mLastScrollTime(0.0f)
  74. , mMouseOverDragElement(nullptr), mMouseOverDragElementTime(0.0f)
  75. {
  76. if(mBackgroundStyle == StringUtil::BLANK)
  77. mBackgroundStyle = "TreeViewBackground";
  78. if(mElementBtnStyle == StringUtil::BLANK)
  79. mElementBtnStyle = "TreeViewElementBtn";
  80. if(mFoldoutBtnStyle == StringUtil::BLANK)
  81. mFoldoutBtnStyle = "Expand";
  82. if(mSelectionBackgroundStyle == StringUtil::BLANK)
  83. mSelectionBackgroundStyle = "TreeViewSelectionBackground";
  84. if (mHighlightBackgroundStyle == StringUtil::BLANK)
  85. mHighlightBackgroundStyle = "TreeViewHighlightBackground";
  86. if(mEditBoxStyle == StringUtil::BLANK)
  87. mEditBoxStyle = "TreeViewEditBox";
  88. if(mDragHighlightStyle == StringUtil::BLANK)
  89. mDragHighlightStyle = "TreeViewElementHighlight";
  90. if(mDragSepHighlightStyle == StringUtil::BLANK)
  91. mDragSepHighlightStyle = "TreeViewElementSepHighlight";
  92. mBackgroundImage = GUITexture::create(mBackgroundStyle);
  93. mNameEditBox = GUITreeViewEditBox::create(mEditBoxStyle);
  94. mNameEditBox->setVisible(false);
  95. mNameEditBox->onInputConfirmed.connect(std::bind(&GUITreeView::onEditAccepted, this));
  96. mNameEditBox->onInputCanceled.connect(std::bind(&GUITreeView::onEditCanceled, this));
  97. mNameEditBox->onFocusLost.connect(std::bind(&GUITreeView::onEditFocusLost, this));
  98. mDragHighlight = GUITexture::create(mDragHighlightStyle);
  99. mDragSepHighlight = GUITexture::create(mDragSepHighlightStyle);
  100. mDragHighlight->setVisible(false);
  101. mDragSepHighlight->setVisible(false);
  102. mDragHighlight->_setElementDepth(2);
  103. mDragSepHighlight->_setElementDepth(2);
  104. _registerChildElement(mBackgroundImage);
  105. _registerChildElement(mNameEditBox);
  106. _registerChildElement(mDragHighlight);
  107. _registerChildElement(mDragSepHighlight);
  108. }
  109. GUITreeView::~GUITreeView()
  110. {
  111. }
  112. void GUITreeView::_update()
  113. {
  114. // Attempt to auto-expand elements we are dragging over
  115. if(acceptDragAndDrop())
  116. {
  117. const InteractableElement* element = findElementUnderCoord(mDragPosition);
  118. temporarilyExpandElement(element);
  119. }
  120. // NOTE - Instead of iterating through every visible element and comparing it with internal values,
  121. // I might just want to add callbacks to SceneManager that notify me of any changes and then only perform
  122. // update if anything is actually dirty
  123. updateTreeElementHierarchy();
  124. // Attempt to scroll if needed
  125. if(mScrollState != ScrollState::None)
  126. {
  127. GUIScrollArea* scrollArea = findParentScrollArea();
  128. if(scrollArea != nullptr)
  129. {
  130. float curTime = gTime().getTime();
  131. float timeDiff = curTime - mLastScrollTime;
  132. float secondsPerPixel = 1.0f / SCROLL_SPEED_PX_PER_SEC;
  133. switch(mScrollState)
  134. {
  135. case ScrollState::TransitioningUp:
  136. mScrollState = ScrollState::Up;
  137. mLastScrollTime = curTime;
  138. break;
  139. case ScrollState::TransitioningDown:
  140. mScrollState = ScrollState::Down;
  141. mLastScrollTime = curTime;
  142. break;
  143. case ScrollState::Up:
  144. {
  145. UINT32 scrollAmount = (UINT32)Math::floorToInt(timeDiff / secondsPerPixel);
  146. mLastScrollTime += scrollAmount * secondsPerPixel;
  147. scrollArea->scrollUpPx(scrollAmount);
  148. }
  149. break;
  150. case ScrollState::Down:
  151. {
  152. UINT32 scrollAmount = (UINT32)Math::floorToInt(timeDiff / secondsPerPixel);
  153. mLastScrollTime += scrollAmount * secondsPerPixel;
  154. scrollArea->scrollDownPx(scrollAmount);
  155. }
  156. break;
  157. case ScrollState::None:
  158. break;
  159. }
  160. }
  161. }
  162. }
  163. bool GUITreeView::_mouseEvent(const GUIMouseEvent& event)
  164. {
  165. if(event.getType() == GUIMouseEventType::MouseUp)
  166. {
  167. if(DragAndDropManager::instance().isDragInProgress())
  168. return false;
  169. const InteractableElement* element = findElementUnderCoord(event.getPosition());
  170. TreeElement* treeElement = nullptr;
  171. if(element != nullptr && element->isTreeElement())
  172. {
  173. treeElement = element->getTreeElement();
  174. }
  175. if (treeElement != nullptr)
  176. {
  177. bool onFoldout = false;
  178. if (treeElement->mFoldoutBtn != nullptr)
  179. onFoldout = treeElement->mFoldoutBtn->_getClippedBounds().contains(event.getPosition());
  180. bool onEditElement = false;
  181. if (mEditElement != nullptr)
  182. onEditElement = treeElement == mEditElement;
  183. if (!onFoldout && !onEditElement)
  184. {
  185. if (event.getButton() == GUIMouseButton::Left)
  186. {
  187. if (event.isCtrlDown())
  188. {
  189. selectElement(treeElement);
  190. }
  191. else if (event.isShiftDown())
  192. {
  193. if (isSelectionActive())
  194. {
  195. TreeElement* selectionRoot = mSelectedElements[0].element;
  196. unselectAll();
  197. auto iterStartFind = std::find_if(mVisibleElements.begin(), mVisibleElements.end(),
  198. [&](const InteractableElement& x) { return x.parent == selectionRoot->mParent; });
  199. bool foundStart = false;
  200. bool foundEnd = false;
  201. for (; iterStartFind != mVisibleElements.end(); ++iterStartFind)
  202. {
  203. if (!iterStartFind->isTreeElement())
  204. continue;
  205. TreeElement* curElem = iterStartFind->getTreeElement();
  206. if (curElem == selectionRoot)
  207. {
  208. foundStart = true;
  209. break;
  210. }
  211. }
  212. auto iterEndFind = std::find_if(mVisibleElements.begin(), mVisibleElements.end(),
  213. [&](const InteractableElement& x) { return &x == element; });
  214. if (iterEndFind != mVisibleElements.end())
  215. foundEnd = true;
  216. if (foundStart && foundEnd)
  217. {
  218. if (iterStartFind < iterEndFind)
  219. {
  220. for (; iterStartFind != (iterEndFind + 1); ++iterStartFind)
  221. {
  222. if (iterStartFind->isTreeElement())
  223. selectElement(iterStartFind->getTreeElement());
  224. }
  225. }
  226. else if (iterEndFind < iterStartFind)
  227. {
  228. for (; iterEndFind != (iterStartFind + 1); ++iterEndFind)
  229. {
  230. if (iterEndFind->isTreeElement())
  231. selectElement(iterEndFind->getTreeElement());
  232. }
  233. }
  234. else
  235. selectElement(treeElement);
  236. }
  237. if (!foundStart || !foundEnd)
  238. selectElement(treeElement);
  239. }
  240. else
  241. {
  242. selectElement(treeElement);
  243. }
  244. }
  245. else
  246. {
  247. bool doRename = false;
  248. if (isSelectionActive())
  249. {
  250. for (auto& selectedElem : mSelectedElements)
  251. {
  252. if (selectedElem.element == treeElement)
  253. {
  254. doRename = true;
  255. break;
  256. }
  257. }
  258. }
  259. unselectAll();
  260. selectElement(treeElement);
  261. if (doRename)
  262. renameSelected();
  263. }
  264. _markLayoutAsDirty();
  265. return true;
  266. }
  267. else if (event.getButton() == GUIMouseButton::Right)
  268. {
  269. unselectAll();
  270. selectElement(treeElement);
  271. _markLayoutAsDirty();
  272. return true;
  273. }
  274. }
  275. }
  276. else
  277. {
  278. unselectAll();
  279. return true;
  280. }
  281. }
  282. else if(event.getType() == GUIMouseEventType::MouseDragStart)
  283. {
  284. clearPing();
  285. mDragStartPosition = event.getPosition();
  286. }
  287. else if(event.getType() == GUIMouseEventType::MouseDrag)
  288. {
  289. UINT32 dist = mDragStartPosition.manhattanDist(event.getPosition());
  290. if(!DragAndDropManager::instance().isDragInProgress())
  291. {
  292. if(dist > DRAG_MIN_DISTANCE && mEditElement == nullptr)
  293. {
  294. const InteractableElement* element = findElementUnderCoord(mDragStartPosition);
  295. Vector<TreeElement*> draggedElements;
  296. if(element != nullptr && element->isTreeElement())
  297. {
  298. // If multiple elements are selected, drag all of them
  299. TreeElement* treeElement = element->getTreeElement();
  300. auto iterFind = std::find_if(mSelectedElements.begin(), mSelectedElements.end(),
  301. [&] (const SelectedElement& x) { return x.element == treeElement; });
  302. if (iterFind != mSelectedElements.end())
  303. {
  304. for (auto& entry : mSelectedElements)
  305. draggedElements.push_back(entry.element);
  306. }
  307. else
  308. draggedElements.push_back(treeElement);
  309. }
  310. dragAndDropStart(draggedElements);
  311. mDragPosition = event.getPosition();
  312. mDragInProgress = true;
  313. mScrollState = ScrollState::None;
  314. _markLayoutAsDirty();
  315. }
  316. }
  317. }
  318. else if(event.getType() == GUIMouseEventType::MouseDragAndDropDragged)
  319. {
  320. if(acceptDragAndDrop())
  321. {
  322. clearPing();
  323. mDragPosition = event.getPosition();
  324. mDragInProgress = true;
  325. _markLayoutAsDirty();
  326. if(mBottomScrollBounds.contains(mDragPosition))
  327. {
  328. if(mScrollState != ScrollState::Down)
  329. mScrollState = ScrollState::TransitioningDown;
  330. }
  331. else if(mTopScrollBounds.contains(mDragPosition))
  332. {
  333. if(mScrollState != ScrollState::Up)
  334. mScrollState = ScrollState::TransitioningUp;
  335. }
  336. else
  337. mScrollState = ScrollState::None;
  338. return true;
  339. }
  340. }
  341. else if(event.getType() == GUIMouseEventType::MouseDragAndDropDropped)
  342. {
  343. if(acceptDragAndDrop())
  344. {
  345. const InteractableElement* element = findElementUnderCoord(event.getPosition());
  346. TreeElement* treeElement = nullptr;
  347. if(element != nullptr)
  348. {
  349. if(element->isTreeElement())
  350. treeElement = element->getTreeElement();
  351. else
  352. treeElement = element->parent;
  353. }
  354. unselectAll();
  355. dragAndDropEnded(treeElement);
  356. mDragInProgress = false;
  357. return true;
  358. }
  359. }
  360. else if(event.getType() == GUIMouseEventType::MouseOut)
  361. {
  362. mDragInProgress = false;
  363. _markLayoutAsDirty();
  364. }
  365. else if(event.getType() == GUIMouseEventType::MouseDragAndDropLeft)
  366. {
  367. mDragInProgress = false;
  368. _markLayoutAsDirty();
  369. }
  370. return false;
  371. }
  372. bool GUITreeView::_commandEvent(const GUICommandEvent& ev)
  373. {
  374. if(ev.getType() == GUICommandEventType::MoveUp || ev.getType() == GUICommandEventType::SelectUp)
  375. {
  376. TreeElement* topMostElement = getTopMostSelectedElement();
  377. auto topMostIter = std::find_if(mVisibleElements.begin(), mVisibleElements.end(),
  378. [&] (const InteractableElement& x) { return x.getTreeElement() == topMostElement; });
  379. if(topMostIter != mVisibleElements.end() && topMostIter != mVisibleElements.begin())
  380. {
  381. do
  382. {
  383. topMostIter--;
  384. } while (!topMostIter->isTreeElement() && topMostIter != mVisibleElements.begin());
  385. if(topMostIter->isTreeElement())
  386. {
  387. if(ev.getType() == GUICommandEventType::MoveUp)
  388. unselectAll();
  389. TreeElement* treeElement = topMostIter->getTreeElement();
  390. selectElement(treeElement);
  391. scrollToElement(treeElement, false);
  392. }
  393. }
  394. }
  395. else if(ev.getType() == GUICommandEventType::MoveDown || ev.getType() == GUICommandEventType::SelectDown)
  396. {
  397. TreeElement* bottoMostElement = getBottomMostSelectedElement();
  398. auto bottomMostIter = std::find_if(mVisibleElements.begin(), mVisibleElements.end(),
  399. [&] (const InteractableElement& x) { return x.getTreeElement() == bottoMostElement; });
  400. if(bottomMostIter != mVisibleElements.end())
  401. {
  402. do
  403. {
  404. bottomMostIter++;
  405. } while (bottomMostIter != mVisibleElements.end() && !bottomMostIter->isTreeElement());
  406. if(bottomMostIter != mVisibleElements.end() && bottomMostIter->isTreeElement())
  407. {
  408. if(ev.getType() == GUICommandEventType::MoveDown)
  409. unselectAll();
  410. TreeElement* treeElement = bottomMostIter->getTreeElement();
  411. selectElement(treeElement);
  412. scrollToElement(treeElement, false);
  413. }
  414. }
  415. }
  416. return GUIElementContainer::_commandEvent(ev);
  417. }
  418. bool GUITreeView::isSelectionActive() const
  419. {
  420. return mIsElementSelected && mSelectedElements.size() > 0;
  421. }
  422. void GUITreeView::selectElement(TreeElement* element, bool triggerEvents)
  423. {
  424. clearPing();
  425. auto iterFind = std::find_if(mSelectedElements.begin(), mSelectedElements.end(),
  426. [&] (const SelectedElement& x) { return x.element == element; });
  427. if(iterFind == mSelectedElements.end())
  428. {
  429. GUITexture* background = GUITexture::create(mSelectionBackgroundStyle);
  430. background->_setElementDepth(3);
  431. _registerChildElement(background);
  432. element->mIsSelected = true;
  433. mSelectedElements.push_back(SelectedElement(element, background));
  434. mIsElementSelected = true;
  435. if(triggerEvents)
  436. selectionChanged();
  437. }
  438. }
  439. void GUITreeView::unselectElement(TreeElement* element, bool triggerEvents)
  440. {
  441. clearPing();
  442. auto iterFind = std::find_if(mSelectedElements.begin(), mSelectedElements.end(),
  443. [&] (const SelectedElement& x) { return x.element == element; });
  444. if(iterFind != mSelectedElements.end())
  445. {
  446. iterFind->element->mIsSelected = false;
  447. GUIElement::destroy(iterFind->background);
  448. mSelectedElements.erase(iterFind);
  449. _markLayoutAsDirty();
  450. if(triggerEvents)
  451. selectionChanged();
  452. }
  453. mIsElementSelected = mSelectedElements.size() > 0;
  454. }
  455. void GUITreeView::unselectAll(bool sendEvent)
  456. {
  457. clearPing();
  458. for(auto& selectedElem : mSelectedElements)
  459. {
  460. selectedElem.element->mIsSelected = false;
  461. GUIElement::destroy(selectedElem.background);
  462. }
  463. mSelectedElements.clear();
  464. mIsElementSelected = false;
  465. _markLayoutAsDirty();
  466. if (sendEvent)
  467. selectionChanged();
  468. }
  469. void GUITreeView::renameSelected()
  470. {
  471. if (isSelectionActive() && mEditElement == nullptr)
  472. {
  473. clearPing();
  474. enableEdit(mSelectedElements[0].element);
  475. unselectAll();
  476. }
  477. }
  478. void GUITreeView::deleteSelection()
  479. {
  480. if (isSelectionActive())
  481. {
  482. auto isChildOf = [&](const TreeElement* parent, const TreeElement* child)
  483. {
  484. const TreeElement* elem = child;
  485. while (elem != nullptr && elem != parent)
  486. elem = elem->mParent;
  487. return elem == parent;
  488. };
  489. // Ensure we don't unnecessarily try to delete children if their
  490. // parent is getting deleted anyway
  491. Vector<TreeElement*> elementsToDelete;
  492. for (UINT32 i = 0; i < (UINT32)mSelectedElements.size(); i++)
  493. {
  494. bool hasDeletedParent = false;
  495. for (UINT32 j = 0; j < (UINT32)mSelectedElements.size(); j++)
  496. {
  497. if (i == j)
  498. continue;
  499. if (isChildOf(mSelectedElements[j].element, mSelectedElements[i].element))
  500. {
  501. hasDeletedParent = true;
  502. break;
  503. }
  504. }
  505. if (!hasDeletedParent)
  506. elementsToDelete.push_back(mSelectedElements[i].element);
  507. }
  508. clearPing();
  509. unselectAll();
  510. for (auto& elem : elementsToDelete)
  511. deleteTreeElement(elem);
  512. }
  513. }
  514. void GUITreeView::ping(TreeElement* element)
  515. {
  516. clearPing();
  517. expandToElement(element);
  518. scrollToElement(element, true);
  519. GUITexture* background = GUITexture::create(mHighlightBackgroundStyle);
  520. background->_setElementDepth(2);
  521. _registerChildElement(background);
  522. element->mIsHighlighted = true;
  523. mHighlightedElement.element = element;
  524. mHighlightedElement.background = background;
  525. mIsElementHighlighted = true;
  526. _markLayoutAsDirty();
  527. }
  528. void GUITreeView::clearPing()
  529. {
  530. if (!mIsElementHighlighted)
  531. return;
  532. mHighlightedElement.element->mIsHighlighted = false;
  533. GUIElement::destroy(mHighlightedElement.background);
  534. mHighlightedElement.element = nullptr;
  535. mHighlightedElement.background = nullptr;
  536. mIsElementHighlighted = false;
  537. _markLayoutAsDirty();
  538. }
  539. void GUITreeView::expandToElement(TreeElement* element)
  540. {
  541. if (element->mIsVisible || element->mParent == nullptr)
  542. return;
  543. Stack<TreeElement*> todo;
  544. TreeElement* parent = element->mParent;
  545. while (parent != nullptr)
  546. {
  547. if (!parent->mIsExpanded)
  548. todo.push(parent);
  549. if (parent->mIsVisible)
  550. break;
  551. parent = parent->mParent;
  552. }
  553. while (!todo.empty())
  554. {
  555. TreeElement* curElement = todo.top();
  556. todo.pop();
  557. expandElement(curElement);
  558. }
  559. }
  560. void GUITreeView::expandElement(TreeElement* element, bool toggleButton)
  561. {
  562. if(element->mIsExpanded)
  563. return;
  564. element->mIsExpanded = true;
  565. if(element->mParent == nullptr || (element->mParent->mIsVisible && element->mParent->mIsExpanded))
  566. {
  567. Stack<TreeElement*> todo;
  568. todo.push(element);
  569. while(!todo.empty())
  570. {
  571. TreeElement* curElem = todo.top();
  572. todo.pop();
  573. curElem->mIsVisible = true;
  574. updateElementGUI(curElem);
  575. if(curElem->mIsExpanded)
  576. {
  577. for(auto& child : curElem->mChildren)
  578. todo.push(child);
  579. }
  580. }
  581. }
  582. if (toggleButton && element->mFoldoutBtn != nullptr)
  583. element->mFoldoutBtn->toggleOn();
  584. }
  585. void GUITreeView::collapseElement(TreeElement* element, bool toggleButton)
  586. {
  587. if (!element->mIsExpanded || element == &getRootElement())
  588. return;
  589. element->mIsExpanded = false;
  590. updateElementGUI(element);
  591. if(element->mParent == nullptr || (element->mParent->mIsVisible && element->mParent->mIsExpanded))
  592. {
  593. Stack<TreeElement*> todo;
  594. for(auto& child : element->mChildren)
  595. todo.push(child);
  596. while(!todo.empty())
  597. {
  598. TreeElement* curElem = todo.top();
  599. todo.pop();
  600. curElem->mIsVisible = false;
  601. if(curElem->mIsSelected)
  602. unselectElement(curElem);
  603. updateElementGUI(curElem);
  604. if(curElem->mIsExpanded)
  605. {
  606. for(auto& child : curElem->mChildren)
  607. todo.push(child);
  608. }
  609. }
  610. }
  611. if (toggleButton && element->mFoldoutBtn != nullptr)
  612. element->mFoldoutBtn->toggleOff();
  613. }
  614. void GUITreeView::updateElementGUI(TreeElement* element)
  615. {
  616. if(element == &getRootElement())
  617. return;
  618. if(element->mIsVisible)
  619. {
  620. HString name(element->mName);
  621. if(element->mElement == nullptr)
  622. {
  623. element->mElement = GUILabel::create(name, mElementBtnStyle);
  624. _registerChildElement(element->mElement);
  625. }
  626. if (element->mIsCut)
  627. {
  628. Color cutTint = element->mTint;
  629. cutTint.a = CUT_COLOR.a;
  630. element->mElement->setTint(cutTint);
  631. }
  632. else if(element->mIsDisabled)
  633. {
  634. Color disabledTint = element->mTint;
  635. disabledTint.a = DISABLED_COLOR.a;
  636. element->mElement->setTint(disabledTint);
  637. }
  638. else
  639. element->mElement->setTint(element->mTint);
  640. if(element->mChildren.size() > 0)
  641. {
  642. if(element->mFoldoutBtn == nullptr)
  643. {
  644. element->mFoldoutBtn = GUIToggle::create(GUIContent(HString("")), mFoldoutBtnStyle);
  645. _registerChildElement(element->mFoldoutBtn);
  646. element->mFoldoutBtn->onToggled.connect(std::bind(&GUITreeView::elementToggled, this, element, _1));
  647. if(element->mIsExpanded)
  648. element->mFoldoutBtn->toggleOn();
  649. }
  650. }
  651. else
  652. {
  653. if(element->mFoldoutBtn != nullptr)
  654. {
  655. GUIElement::destroy(element->mFoldoutBtn);
  656. element->mFoldoutBtn = nullptr;
  657. }
  658. }
  659. element->mElement->setContent(GUIContent(name));
  660. }
  661. else
  662. {
  663. if(element->mElement != nullptr)
  664. {
  665. GUIElement::destroy(element->mElement);
  666. element->mElement = nullptr;
  667. }
  668. if(element->mFoldoutBtn != nullptr)
  669. {
  670. GUIElement::destroy(element->mFoldoutBtn);
  671. element->mFoldoutBtn = nullptr;
  672. }
  673. if(element->mIsSelected && element->mIsExpanded)
  674. unselectElement(element);
  675. }
  676. _markLayoutAsDirty();
  677. }
  678. void GUITreeView::elementToggled(TreeElement* element, bool toggled)
  679. {
  680. clearPing();
  681. if(toggled)
  682. expandElement(element, false);
  683. else
  684. collapseElement(element, false);
  685. }
  686. void GUITreeView::onEditAccepted()
  687. {
  688. TreeElement* elem = mEditElement;
  689. disableEdit(true);
  690. selectElement(elem);
  691. }
  692. void GUITreeView::onEditCanceled()
  693. {
  694. if (mEditElement != nullptr)
  695. {
  696. TreeElement* elem = mEditElement;
  697. disableEdit(false);
  698. selectElement(elem);
  699. }
  700. }
  701. void GUITreeView::onEditFocusLost()
  702. {
  703. if (mEditElement != nullptr)
  704. disableEdit(false);
  705. }
  706. void GUITreeView::enableEdit(TreeElement* element)
  707. {
  708. assert(mEditElement == nullptr);
  709. mEditElement = element;
  710. mNameEditBox->setVisible(true);
  711. mNameEditBox->setText(element->mName);
  712. mNameEditBox->setFocus(true);
  713. if(element->mElement != nullptr)
  714. element->mElement->setVisible(false);
  715. }
  716. void GUITreeView::disableEdit(bool applyChanges)
  717. {
  718. assert(mEditElement != nullptr);
  719. if(mEditElement->mElement != nullptr)
  720. mEditElement->mElement->setVisible(true);
  721. if(applyChanges)
  722. {
  723. String newName = mNameEditBox->getText();
  724. renameTreeElement(mEditElement, newName);
  725. }
  726. mNameEditBox->setFocus(false);
  727. mNameEditBox->setVisible(false);
  728. mEditElement = nullptr;
  729. }
  730. Vector2I GUITreeView::_getOptimalSize() const
  731. {
  732. struct UpdateTreeElement
  733. {
  734. UpdateTreeElement(const TreeElement* element, UINT32 indent)
  735. :element(element), indent(indent)
  736. { }
  737. const TreeElement* element;
  738. UINT32 indent;
  739. };
  740. Vector2I optimalSize;
  741. if (_getDimensions().fixedWidth() && _getDimensions().fixedHeight())
  742. {
  743. optimalSize.x = _getDimensions().minWidth;
  744. optimalSize.y = _getDimensions().minHeight;
  745. }
  746. else
  747. {
  748. Stack<UpdateTreeElement> todo;
  749. todo.push(UpdateTreeElement(&getRootElementConst(), 0));
  750. while(!todo.empty())
  751. {
  752. UpdateTreeElement currentUpdateElement = todo.top();
  753. const TreeElement* current = currentUpdateElement.element;
  754. todo.pop();
  755. INT32 yOffset = 0;
  756. if(current->mElement != nullptr)
  757. {
  758. Vector2I curOptimalSize = current->mElement->_getOptimalSize();
  759. optimalSize.x = std::max(optimalSize.x,
  760. (INT32)(INITIAL_INDENT_OFFSET + curOptimalSize.x + currentUpdateElement.indent * INDENT_SIZE));
  761. yOffset = curOptimalSize.y + ELEMENT_EXTRA_SPACING;
  762. }
  763. optimalSize.y += yOffset;
  764. for(auto& child : current->mChildren)
  765. {
  766. if(!child->mIsVisible)
  767. continue;
  768. todo.push(UpdateTreeElement(child, currentUpdateElement.indent + 1));
  769. }
  770. }
  771. if(_getDimensions().fixedWidth())
  772. optimalSize.x = _getDimensions().minWidth;
  773. else
  774. {
  775. if(_getDimensions().minWidth > 0)
  776. optimalSize.x = std::max((INT32)_getDimensions().minWidth, optimalSize.x);
  777. if(_getDimensions().maxWidth > 0)
  778. optimalSize.x = std::min((INT32)_getDimensions().maxWidth, optimalSize.x);
  779. }
  780. if (_getDimensions().fixedHeight())
  781. optimalSize.y = _getDimensions().minHeight;
  782. else
  783. {
  784. if(_getDimensions().minHeight > 0)
  785. optimalSize.y = std::max((INT32)_getDimensions().minHeight, optimalSize.y);
  786. if(_getDimensions().maxHeight > 0)
  787. optimalSize.y = std::min((INT32)_getDimensions().maxHeight, optimalSize.y);
  788. }
  789. }
  790. return optimalSize;
  791. }
  792. void GUITreeView::updateClippedBounds()
  793. {
  794. mClippedBounds = mLayoutData.area;
  795. mClippedBounds.clip(mLayoutData.clipRect);
  796. }
  797. void GUITreeView::_updateLayoutInternal(const GUILayoutData& data)
  798. {
  799. struct UpdateTreeElement
  800. {
  801. UpdateTreeElement(TreeElement* element, UINT32 indent)
  802. :element(element), indent(indent)
  803. { }
  804. TreeElement* element;
  805. UINT32 indent;
  806. };
  807. mVisibleElements.clear();
  808. Stack<UpdateTreeElement> todo;
  809. todo.push(UpdateTreeElement(&getRootElement(), 0));
  810. // NOTE - Instead of iterating through all elements, try to find those within the clip rect
  811. // and only iterate through those. Others should somehow be marked in-active (similar to GUIElement::isDisabled()?)
  812. Vector<TreeElement*> tempOrderedElements;
  813. Vector2I offset(data.area.x, data.area.y);
  814. while(!todo.empty())
  815. {
  816. UpdateTreeElement currentUpdateElement = todo.top();
  817. TreeElement* current = currentUpdateElement.element;
  818. UINT32 indent = currentUpdateElement.indent;
  819. todo.pop();
  820. INT32 btnHeight = 0;
  821. INT32 yOffset = 0;
  822. if(current->mElement != nullptr)
  823. {
  824. Vector2I elementSize = current->mElement->_getOptimalSize();
  825. btnHeight = elementSize.y;
  826. mVisibleElements.push_back(InteractableElement(current->mParent, current->mSortedIdx * 2 + 0, Rect2I(data.area.x, offset.y, data.area.width, ELEMENT_EXTRA_SPACING)));
  827. mVisibleElements.push_back(InteractableElement(current->mParent, current->mSortedIdx * 2 + 1, Rect2I(data.area.x, offset.y + ELEMENT_EXTRA_SPACING, data.area.width, btnHeight)));
  828. offset.x = data.area.x + INITIAL_INDENT_OFFSET + indent * INDENT_SIZE;
  829. offset.y += ELEMENT_EXTRA_SPACING;
  830. GUILayoutData childData = data;
  831. childData.area.x = offset.x;
  832. childData.area.y = offset.y;
  833. childData.area.width = elementSize.x;
  834. childData.area.height = elementSize.y;
  835. current->mElement->_setLayoutData(childData);
  836. yOffset = btnHeight;
  837. }
  838. if(current->mFoldoutBtn != nullptr)
  839. {
  840. Vector2I elementSize = current->mFoldoutBtn->_getOptimalSize();
  841. offset.x -= std::min((INT32)INITIAL_INDENT_OFFSET, elementSize.x + 2);
  842. Vector2I myOffset = offset;
  843. myOffset.y += 1;
  844. if(elementSize.y > btnHeight)
  845. {
  846. UINT32 diff = elementSize.y - btnHeight;
  847. float half = diff * 0.5f;
  848. myOffset.y -= Math::floorToInt(half);
  849. }
  850. GUILayoutData childData = data;
  851. childData.area.x = myOffset.x;
  852. childData.area.y = myOffset.y;
  853. childData.area.width = elementSize.x;
  854. childData.area.height = elementSize.y;
  855. current->mFoldoutBtn->_setLayoutData(childData);
  856. }
  857. offset.y += yOffset;
  858. tempOrderedElements.resize(current->mChildren.size(), nullptr);
  859. for(auto& child : current->mChildren)
  860. {
  861. tempOrderedElements[child->mSortedIdx] = child;
  862. }
  863. for(auto iter = tempOrderedElements.rbegin(); iter != tempOrderedElements.rend(); ++iter)
  864. {
  865. TreeElement* child = *iter;
  866. if(!child->mIsVisible)
  867. continue;
  868. todo.push(UpdateTreeElement(child, indent + 1));
  869. }
  870. }
  871. UINT32 remainingHeight = (UINT32)std::max(0, (INT32)data.area.height - (offset.y - data.area.y));
  872. if(remainingHeight > 0)
  873. mVisibleElements.push_back(InteractableElement(&getRootElement(), (UINT32)getRootElement().mChildren.size() * 2, Rect2I(data.area.x, offset.y, data.area.width, remainingHeight)));
  874. for(auto selectedElem : mSelectedElements)
  875. {
  876. GUILabel* targetElement = selectedElem.element->mElement;
  877. if (targetElement == nullptr)
  878. continue;
  879. GUILayoutData childData = data;
  880. childData.area.y = targetElement->_getLayoutData().area.y;
  881. childData.area.height = targetElement->_getLayoutData().area.height;
  882. selectedElem.background->_setLayoutData(childData);
  883. }
  884. if (mIsElementHighlighted)
  885. {
  886. GUILabel* targetElement = mHighlightedElement.element->mElement;
  887. if (targetElement != nullptr)
  888. {
  889. GUILayoutData childData = data;
  890. childData.area.y = targetElement->_getLayoutData().area.y;
  891. childData.area.height = targetElement->_getLayoutData().area.height;
  892. mHighlightedElement.background->_setLayoutData(childData);
  893. }
  894. }
  895. if(mEditElement != nullptr)
  896. {
  897. GUILabel* targetElement = mEditElement->mElement;
  898. if (targetElement != nullptr)
  899. {
  900. UINT32 remainingWidth = (UINT32)std::max(0, (((INT32)data.area.width) - (offset.x - data.area.x)));
  901. GUILayoutData childData = data;
  902. childData.area = targetElement->_getLayoutData().area;
  903. childData.area.width = remainingWidth;
  904. mNameEditBox->_setLayoutData(childData);
  905. }
  906. }
  907. if(mDragInProgress)
  908. {
  909. const InteractableElement* interactableElement = findElementUnderCoord(mDragPosition);
  910. if(interactableElement == nullptr)
  911. {
  912. mDragHighlight->setVisible(false);
  913. mDragSepHighlight->setVisible(false);
  914. }
  915. else
  916. {
  917. if(interactableElement->isTreeElement())
  918. {
  919. mDragSepHighlight->setVisible(false);
  920. mDragHighlight->setVisible(true);
  921. GUILayoutData childData = data;
  922. childData.area = interactableElement->bounds;
  923. mDragHighlight->_setLayoutData(childData);
  924. }
  925. else
  926. {
  927. mDragHighlight->setVisible(false);
  928. mDragSepHighlight->setVisible(true);
  929. GUILayoutData childData = data;
  930. childData.area = interactableElement->bounds;
  931. mDragSepHighlight->_setLayoutData(childData);
  932. }
  933. }
  934. }
  935. else
  936. {
  937. mDragHighlight->setVisible(false);
  938. mDragSepHighlight->setVisible(false);
  939. }
  940. // Update scroll bounds
  941. UINT32 scrollHeight = (UINT32)Math::roundToInt(data.clipRect.height * SCROLL_AREA_HEIGHT_PCT);
  942. mTopScrollBounds.x = data.clipRect.x;
  943. mTopScrollBounds.y = data.clipRect.y;
  944. mTopScrollBounds.width = data.clipRect.width;
  945. mTopScrollBounds.height = scrollHeight;
  946. mBottomScrollBounds.x = data.clipRect.x;
  947. mBottomScrollBounds.y = data.clipRect.y + data.clipRect.height - scrollHeight;
  948. mBottomScrollBounds.width = data.clipRect.width;
  949. mBottomScrollBounds.height = scrollHeight;
  950. }
  951. const GUITreeView::InteractableElement* GUITreeView::findElementUnderCoord(const Vector2I& coord) const
  952. {
  953. for(auto& element : mVisibleElements)
  954. {
  955. if(element.bounds.contains(coord))
  956. {
  957. return &element;
  958. }
  959. }
  960. return nullptr;
  961. }
  962. GUITreeView::TreeElement* GUITreeView::getTopMostSelectedElement() const
  963. {
  964. auto topMostElement = mVisibleElements.end();
  965. for(auto& selectedElement : mSelectedElements)
  966. {
  967. auto iterFind = std::find_if(mVisibleElements.begin(), mVisibleElements.end(),
  968. [&] (const InteractableElement& x) { return x.getTreeElement() == selectedElement.element; });
  969. if(iterFind != mVisibleElements.end())
  970. {
  971. if(topMostElement == mVisibleElements.end())
  972. topMostElement = iterFind;
  973. else
  974. {
  975. if(iterFind->bounds.y < topMostElement->bounds.y)
  976. topMostElement = iterFind;
  977. }
  978. }
  979. }
  980. if(topMostElement != mVisibleElements.end())
  981. return topMostElement->getTreeElement();
  982. else
  983. return nullptr;
  984. }
  985. GUITreeView::TreeElement* GUITreeView::getBottomMostSelectedElement() const
  986. {
  987. auto botMostElement = mVisibleElements.end();
  988. for(auto& selectedElement : mSelectedElements)
  989. {
  990. auto iterFind = std::find_if(mVisibleElements.begin(), mVisibleElements.end(),
  991. [&] (const InteractableElement& x) { return x.getTreeElement() == selectedElement.element; });
  992. if(iterFind != mVisibleElements.end())
  993. {
  994. if(botMostElement == mVisibleElements.end())
  995. botMostElement = iterFind;
  996. else
  997. {
  998. if((iterFind->bounds.y + iterFind->bounds.height) > (botMostElement->bounds.y + botMostElement->bounds.height))
  999. botMostElement = iterFind;
  1000. }
  1001. }
  1002. }
  1003. if(botMostElement != mVisibleElements.end())
  1004. return botMostElement->getTreeElement();
  1005. else
  1006. return nullptr;
  1007. }
  1008. void GUITreeView::closeTemporarilyExpandedElements()
  1009. {
  1010. temporarilyExpandElement(nullptr);
  1011. }
  1012. void GUITreeView::temporarilyExpandElement(const InteractableElement* mouseOverElement)
  1013. {
  1014. TreeElement* treeElement = nullptr;
  1015. if(mouseOverElement != nullptr && mouseOverElement->isTreeElement())
  1016. treeElement = mouseOverElement->getTreeElement();
  1017. if(treeElement == nullptr || treeElement != mMouseOverDragElement)
  1018. {
  1019. while(!mAutoExpandedElements.empty())
  1020. {
  1021. TreeElement* autoExpandedElement = mAutoExpandedElements.top();
  1022. bool unexpandElement = false;
  1023. if(mouseOverElement != nullptr && mouseOverElement->parent != nullptr)
  1024. {
  1025. if(mouseOverElement->parent != autoExpandedElement && !mouseOverElement->parent->isParentRec(autoExpandedElement))
  1026. unexpandElement = true;
  1027. else
  1028. break;
  1029. }
  1030. else
  1031. unexpandElement = true;
  1032. if(unexpandElement)
  1033. {
  1034. collapseElement(autoExpandedElement);
  1035. mAutoExpandedElements.pop();
  1036. }
  1037. }
  1038. mMouseOverDragElement = treeElement;
  1039. mMouseOverDragElementTime = gTime().getTime();
  1040. }
  1041. else
  1042. {
  1043. if(mMouseOverDragElement != nullptr && !mMouseOverDragElement->mIsExpanded)
  1044. {
  1045. float timeDiff = gTime().getTime() - mMouseOverDragElementTime;
  1046. if(timeDiff >= AUTO_EXPAND_DELAY_SEC)
  1047. {
  1048. mAutoExpandedElements.push(mMouseOverDragElement);
  1049. expandElement(mMouseOverDragElement);
  1050. }
  1051. }
  1052. }
  1053. }
  1054. void GUITreeView::scrollToElement(TreeElement* element, bool center)
  1055. {
  1056. if(element->mElement == nullptr)
  1057. return;
  1058. GUIScrollArea* scrollArea = findParentScrollArea();
  1059. if(scrollArea == nullptr)
  1060. return;
  1061. if(center)
  1062. {
  1063. Rect2I myBounds = _getClippedBounds();
  1064. INT32 clipVertCenter = myBounds.y + (INT32)Math::roundToInt(myBounds.height * 0.5f);
  1065. INT32 elemVertCenter = element->mElement->_getLayoutData().area.y + (INT32)Math::roundToInt(element->mElement->_getLayoutData().area.height * 0.5f);
  1066. if(elemVertCenter > clipVertCenter)
  1067. scrollArea->scrollDownPx(elemVertCenter - clipVertCenter);
  1068. else
  1069. scrollArea->scrollUpPx(clipVertCenter - elemVertCenter);
  1070. }
  1071. else
  1072. {
  1073. Rect2I myBounds = _getClippedBounds();
  1074. INT32 elemVertTop = element->mElement->_getLayoutData().area.y;
  1075. INT32 elemVertBottom = element->mElement->_getLayoutData().area.y + element->mElement->_getLayoutData().area.height;
  1076. INT32 top = myBounds.y;
  1077. INT32 bottom = myBounds.y + myBounds.height;
  1078. if(elemVertTop < top)
  1079. scrollArea->scrollUpPx(top - elemVertTop);
  1080. else if(elemVertBottom > bottom)
  1081. scrollArea->scrollDownPx(elemVertBottom - bottom);
  1082. }
  1083. }
  1084. GUIScrollArea* GUITreeView::findParentScrollArea() const
  1085. {
  1086. GUIElementBase* parent = _getParent();
  1087. while(parent != nullptr)
  1088. {
  1089. if(parent->_getType() == GUIElementBase::Type::Element)
  1090. {
  1091. GUIElement* parentElement = static_cast<GUIElement*>(parent);
  1092. if(parentElement->_getElementType() == GUIElement::ElementType::ScrollArea)
  1093. {
  1094. GUIScrollArea* scrollArea = static_cast<GUIScrollArea*>(parentElement);
  1095. return scrollArea;
  1096. }
  1097. }
  1098. parent = parent->_getParent();
  1099. }
  1100. return nullptr;
  1101. }
  1102. const String& GUITreeView::getGUITypeName()
  1103. {
  1104. static String typeName = "SceneTreeView";
  1105. return typeName;
  1106. }
  1107. }