BsGUITreeView.cpp 36 KB

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