BsGUISceneTreeView.cpp 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985
  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 "BsGUITreeViewEditBox.h"
  11. #include "BsGUIMouseEvent.h"
  12. #include "BsGUISkin.h"
  13. #include "BsGUICommandEvent.h"
  14. #include "CmSceneObject.h"
  15. #include "CmSceneManager.h"
  16. #include "BsCmdEditPlainFieldGO.h"
  17. #include "BsDragAndDropManager.h"
  18. #include "BsCmdReparentSO.h"
  19. using namespace CamelotFramework;
  20. using namespace BansheeEngine;
  21. namespace BansheeEditor
  22. {
  23. const UINT32 GUISceneTreeView::ELEMENT_EXTRA_SPACING = 3;
  24. const UINT32 GUISceneTreeView::INDENT_SIZE = 10;
  25. const UINT32 GUISceneTreeView::INITIAL_INDENT_OFFSET = 16;
  26. const UINT32 GUISceneTreeView::DRAG_MIN_DISTANCE = 3;
  27. GUISceneTreeView::DraggedSceneObjects::DraggedSceneObjects(UINT32 numObjects)
  28. :numObjects(numObjects)
  29. {
  30. objects = cm_newN<HSceneObject>(numObjects);
  31. }
  32. GUISceneTreeView::DraggedSceneObjects::~DraggedSceneObjects()
  33. {
  34. cm_deleteN(objects, numObjects);
  35. objects = nullptr;
  36. }
  37. GUISceneTreeView::TreeElement::TreeElement()
  38. :mParent(nullptr), mFoldoutBtn(nullptr), mElement(nullptr), mIsSelected(false),
  39. mId(0), mIsExpanded(false), mSortedIdx(0), mIsDirty(false), mIsVisible(true)
  40. { }
  41. GUISceneTreeView::TreeElement::~TreeElement()
  42. {
  43. for(auto& child : mChildren)
  44. cm_delete(child);
  45. if(mFoldoutBtn != nullptr)
  46. GUIElement::destroy(mFoldoutBtn);
  47. if(mElement != nullptr)
  48. GUIElement::destroy(mElement);
  49. mChildren.clear();
  50. }
  51. GUISceneTreeView::GUISceneTreeView(GUIWidget& parent, GUIElementStyle* backgroundStyle, GUIElementStyle* elementBtnStyle,
  52. GUIElementStyle* foldoutBtnStyle, GUIElementStyle* selectionBackgroundStyle, GUIElementStyle* editBoxStyle,
  53. BS::GUIElementStyle* dragHighlightStyle, BS::GUIElementStyle* dragSepHighlightStyle, const GUILayoutOptions& layoutOptions)
  54. :GUIElementContainer(parent, layoutOptions), mBackgroundStyle(backgroundStyle),
  55. mElementBtnStyle(elementBtnStyle), mFoldoutBtnStyle(foldoutBtnStyle), mEditBoxStyle(editBoxStyle), mEditElement(nullptr), mIsElementSelected(false),
  56. mNameEditBox(nullptr), mSelectionBackgroundStyle(selectionBackgroundStyle), mDragInProgress(nullptr), mDragHighlightStyle(dragHighlightStyle),
  57. mDragSepHighlightStyle(dragSepHighlightStyle), mDragHighlight(nullptr), mDragSepHighlight(nullptr)
  58. {
  59. if(mBackgroundStyle == nullptr)
  60. mBackgroundStyle = parent.getSkin().getStyle("TreeViewBackground");
  61. if(mElementBtnStyle == nullptr)
  62. mElementBtnStyle = parent.getSkin().getStyle("TreeViewElementBtn");
  63. if(mFoldoutBtnStyle == nullptr)
  64. mFoldoutBtnStyle = parent.getSkin().getStyle("TreeViewFoldoutBtn");
  65. if(mSelectionBackgroundStyle == nullptr)
  66. mSelectionBackgroundStyle = parent.getSkin().getStyle("TreeViewSelectionBackground");
  67. if(mEditBoxStyle == nullptr)
  68. mEditBoxStyle = parent.getSkin().getStyle("TreeViewEditBox");
  69. if(mDragHighlightStyle == nullptr)
  70. mDragHighlightStyle = parent.getSkin().getStyle("TreeViewElementHighlight");
  71. if(mDragSepHighlightStyle == nullptr)
  72. mDragSepHighlightStyle = parent.getSkin().getStyle("TreeViewElementSepHighlight");
  73. mBackgroundImage = GUITexture::create(parent, mBackgroundStyle);
  74. mNameEditBox = GUITreeViewEditBox::create(parent, mEditBoxStyle);
  75. mNameEditBox->disableRecursively();
  76. mNameEditBox->onInputConfirmed.connect(boost::bind(&GUISceneTreeView::onEditAccepted, this));
  77. mNameEditBox->onInputCanceled.connect(boost::bind(&GUISceneTreeView::onEditCanceled, this));
  78. mDragHighlight = GUITexture::create(parent, mDragHighlightStyle);
  79. mDragSepHighlight = GUITexture::create(parent, mDragSepHighlightStyle);
  80. mDragHighlight->disableRecursively();
  81. mDragSepHighlight->disableRecursively();
  82. _registerChildElement(mBackgroundImage);
  83. _registerChildElement(mNameEditBox);
  84. _registerChildElement(mDragHighlight);
  85. _registerChildElement(mDragSepHighlight);
  86. }
  87. GUISceneTreeView::~GUISceneTreeView()
  88. {
  89. }
  90. GUISceneTreeView* GUISceneTreeView::create(GUIWidget& parent, GUIElementStyle* backgroundStyle, GUIElementStyle* elementBtnStyle,
  91. GUIElementStyle* foldoutBtnStyle, GUIElementStyle* selectionBackgroundStyle, GUIElementStyle* editBoxStyle, GUIElementStyle* dragHighlightStyle,
  92. GUIElementStyle* dragSepHighlightStyle)
  93. {
  94. return new (cm_alloc<GUISceneTreeView, PoolAlloc>()) GUISceneTreeView(parent, backgroundStyle, elementBtnStyle, foldoutBtnStyle,
  95. selectionBackgroundStyle, editBoxStyle, dragHighlightStyle, dragSepHighlightStyle, GUILayoutOptions::create(&GUISkin::DefaultStyle));
  96. }
  97. GUISceneTreeView* GUISceneTreeView::create(GUIWidget& parent, const GUIOptions& options, GUIElementStyle* backgroundStyle,
  98. GUIElementStyle* elementBtnStyle, GUIElementStyle* foldoutBtnStyle, GUIElementStyle* selectionBackgroundStyle,
  99. GUIElementStyle* editBoxStyle, GUIElementStyle* dragHighlightStyle, GUIElementStyle* dragSepHighlightStyle)
  100. {
  101. return new (cm_alloc<GUISceneTreeView, PoolAlloc>()) GUISceneTreeView(parent, backgroundStyle, elementBtnStyle,
  102. foldoutBtnStyle, selectionBackgroundStyle, editBoxStyle, dragHighlightStyle, dragSepHighlightStyle, GUILayoutOptions::create(options, &GUISkin::DefaultStyle));
  103. }
  104. void GUISceneTreeView::update()
  105. {
  106. // NOTE - Instead of iterating through every visible element and comparing it with internal values,
  107. // I might just want to add callbacks to SceneManager that notify me of any changes and then only perform
  108. // update if anything is actually dirty
  109. struct UpdateTreeElement
  110. {
  111. UpdateTreeElement(TreeElement* element, UINT32 seqIdx, bool visible)
  112. :element(element), seqIdx(seqIdx), visible(visible)
  113. { }
  114. TreeElement* element;
  115. UINT32 seqIdx;
  116. bool visible;
  117. };
  118. HSceneObject root = CM::gSceneManager().getRootNode();
  119. mRootElement.mSceneObject = root;
  120. mRootElement.mId = root->getId();
  121. mRootElement.mSortedIdx = 0;
  122. mRootElement.mIsExpanded = true;
  123. Stack<UpdateTreeElement>::type todo;
  124. todo.push(UpdateTreeElement(&mRootElement, 0, true));
  125. while(!todo.empty())
  126. {
  127. UpdateTreeElement updateElement = todo.top();
  128. TreeElement* current = updateElement.element;
  129. HSceneObject currentSO = current->mSceneObject;
  130. todo.pop();
  131. // Check if SceneObject has changed in any way and update the tree element
  132. if(updateElement.visible)
  133. {
  134. bool completeMatch = (UINT32)current->mChildren.size() == currentSO->getNumChildren();
  135. // Early exit case - Most commonly there will be no changes between active and cached data so
  136. // we first do a quick check in order to avoid expensive comparison later
  137. if(completeMatch)
  138. {
  139. for(UINT32 i = 0; i < currentSO->getNumChildren(); i++)
  140. {
  141. UINT32 curId = currentSO->getChild(i)->getId();
  142. if(curId != current->mChildren[i]->mId)
  143. {
  144. completeMatch = false;
  145. break;
  146. }
  147. }
  148. }
  149. // Not a complete match, compare everything and insert/delete elements as needed
  150. if(!completeMatch)
  151. {
  152. Vector<TreeElement*>::type newChildren;
  153. mTempToDelete.resize(current->mChildren.size());
  154. for(UINT32 i = 0; i < (UINT32)current->mChildren.size(); i++)
  155. mTempToDelete[i] = true;
  156. for(UINT32 i = 0; i < currentSO->getNumChildren(); i++)
  157. {
  158. HSceneObject currentSOChild = currentSO->getChild(i);
  159. UINT32 curId = currentSOChild->getId();
  160. bool found = false;
  161. for(UINT32 j = 0; j < current->mChildren.size(); j++)
  162. {
  163. TreeElement* currentChild = current->mChildren[j];
  164. if(curId == currentChild->mId)
  165. {
  166. mTempToDelete[j] = false;
  167. currentChild->mIsDirty = true;
  168. currentChild->mSortedIdx = (UINT32)newChildren.size();
  169. newChildren.push_back(currentChild);
  170. found = true;
  171. break;
  172. }
  173. }
  174. if(!found)
  175. {
  176. TreeElement* newChild = cm_new<TreeElement>();
  177. newChild->mParent = current;
  178. newChild->mSceneObject = currentSOChild;
  179. newChild->mId = currentSOChild->getId();
  180. newChild->mName = currentSOChild->getName();
  181. newChild->mSortedIdx = (UINT32)newChildren.size();
  182. newChild->mIsDirty = true;
  183. newChildren.push_back(newChild);
  184. }
  185. }
  186. for(UINT32 i = 0; i < current->mChildren.size(); i++)
  187. {
  188. if(!mTempToDelete[i])
  189. continue;
  190. if(current->mChildren[i]->mIsSelected)
  191. unselectElement(current->mChildren[i]);
  192. cm_delete(current->mChildren[i]);
  193. }
  194. current->mChildren = newChildren;
  195. current->mIsDirty = true;
  196. }
  197. // Check if name needs updating
  198. const String& name = current->mSceneObject->getName();
  199. if(current->mName != name)
  200. {
  201. current->mName = name;
  202. current->mIsDirty = true;
  203. }
  204. // Calculate the sorted index of the element based on its name
  205. TreeElement* parent = current->mParent;
  206. if(current->mIsDirty && parent != nullptr)
  207. {
  208. for(UINT32 i = 0; i < (UINT32)parent->mChildren.size(); i++)
  209. {
  210. INT32 stringCompare = current->mName.compare(parent->mChildren[i]->mName);
  211. if(stringCompare > 0)
  212. {
  213. if(current->mSortedIdx < parent->mChildren[i]->mSortedIdx)
  214. std::swap(current->mSortedIdx, parent->mChildren[i]->mSortedIdx);
  215. }
  216. else if(stringCompare < 0)
  217. {
  218. if(current->mSortedIdx > parent->mChildren[i]->mSortedIdx)
  219. std::swap(current->mSortedIdx, parent->mChildren[i]->mSortedIdx);
  220. }
  221. }
  222. }
  223. }
  224. bool visibilityChanged = false;
  225. if(current->mIsVisible != updateElement.visible)
  226. {
  227. visibilityChanged = true;
  228. current->mIsVisible = updateElement.visible;
  229. current->mIsDirty = true;
  230. }
  231. if(current->mIsDirty && current != &mRootElement)
  232. {
  233. if(updateElement.visible)
  234. {
  235. HString name(toWString(current->mName));
  236. if(current->mElement == nullptr)
  237. {
  238. current->mElement = GUILabel::create(_getParentWidget(), name, mElementBtnStyle);
  239. _registerChildElement(current->mElement);
  240. }
  241. if(current->mChildren.size() > 0)
  242. {
  243. if(current->mFoldoutBtn == nullptr)
  244. {
  245. current->mFoldoutBtn = GUIToggle::create(_getParentWidget(), GUIContent(HString(L"")), mFoldoutBtnStyle);
  246. _registerChildElement(current->mFoldoutBtn);
  247. current->mFoldoutBtn->onToggled.connect(boost::bind(&GUISceneTreeView::elementToggled, this, current, _1));
  248. }
  249. }
  250. else
  251. {
  252. if(current->mFoldoutBtn != nullptr)
  253. {
  254. GUIElement::destroy(current->mFoldoutBtn);
  255. current->mFoldoutBtn = nullptr;
  256. }
  257. }
  258. current->mElement->setContent(GUIContent(name));
  259. }
  260. else
  261. {
  262. if(current->mElement != nullptr)
  263. {
  264. GUIElement::destroy(current->mElement);
  265. current->mElement = nullptr;
  266. }
  267. if(current->mFoldoutBtn != nullptr)
  268. {
  269. GUIElement::destroy(current->mFoldoutBtn);
  270. current->mFoldoutBtn = nullptr;
  271. }
  272. if(visibilityChanged && current->mIsSelected)
  273. unselectElement(current);
  274. }
  275. markContentAsDirty();
  276. current->mIsDirty = false;
  277. }
  278. // Queue children for next iteration
  279. if(visibilityChanged || current->mIsVisible)
  280. {
  281. for(UINT32 i = 0; i < (UINT32)current->mChildren.size(); i++)
  282. {
  283. todo.push(UpdateTreeElement(current->mChildren[i], i, current->mIsVisible && current->mIsExpanded));
  284. }
  285. }
  286. }
  287. }
  288. bool GUISceneTreeView::mouseEvent(const GUIMouseEvent& event)
  289. {
  290. if(event.getType() == GUIMouseEventType::MouseUp)
  291. {
  292. if(DragAndDropManager::instance().isDragInProgress())
  293. return false;
  294. const GUISceneTreeView::InteractableElement* element = findElementUnderCoord(event.getPosition());
  295. TreeElement* treeElement = nullptr;
  296. if(element != nullptr && element->isTreeElement())
  297. {
  298. treeElement = interactableToRealElement(*element);
  299. }
  300. if(treeElement != nullptr && event.getPosition().x >= treeElement->mElement->getBounds().x)
  301. {
  302. if(event.isCtrlDown())
  303. {
  304. selectElement(treeElement);
  305. }
  306. else if(event.isShiftDown())
  307. {
  308. if(isSelectionActive())
  309. {
  310. TreeElement* selectionRoot = mSelectedElements[0].element;
  311. unselectAll();
  312. auto iterStartFind = std::find_if(mVisibleElements.begin(), mVisibleElements.end(),
  313. [&] (const InteractableElement& x) { return x.parent == selectionRoot->mParent; } );
  314. bool foundStart = false;
  315. bool foundEnd = false;
  316. for(; iterStartFind != mVisibleElements.end(); ++iterStartFind)
  317. {
  318. if(!iterStartFind->isTreeElement())
  319. continue;
  320. TreeElement* curElem = interactableToRealElement(*iterStartFind);
  321. if(curElem == selectionRoot)
  322. {
  323. foundStart = true;
  324. break;
  325. }
  326. }
  327. auto iterEndFind = std::find_if(mVisibleElements.begin(), mVisibleElements.end(),
  328. [&] (const InteractableElement& x) { return &x == element; } );
  329. if(iterEndFind != mVisibleElements.end())
  330. foundEnd = true;
  331. if(foundStart && foundEnd)
  332. {
  333. if(iterStartFind < iterEndFind)
  334. {
  335. for(;iterStartFind != (iterEndFind + 1); ++iterStartFind)
  336. {
  337. if(iterStartFind->isTreeElement())
  338. selectElement(interactableToRealElement(*iterStartFind));
  339. }
  340. }
  341. else if(iterEndFind < iterStartFind)
  342. {
  343. for(;iterEndFind != (iterStartFind + 1); ++iterEndFind)
  344. {
  345. if(iterEndFind->isTreeElement())
  346. selectElement(interactableToRealElement(*iterEndFind));
  347. }
  348. }
  349. else
  350. selectElement(treeElement);
  351. }
  352. if(!foundStart || !foundEnd)
  353. selectElement(treeElement);
  354. }
  355. else
  356. {
  357. selectElement(treeElement);
  358. }
  359. }
  360. else
  361. {
  362. unselectAll();
  363. selectElement(treeElement);
  364. }
  365. markContentAsDirty();
  366. return true;
  367. }
  368. }
  369. else if(event.getType() == GUIMouseEventType::MouseDragStart)
  370. {
  371. mDragStartPosition = event.getPosition();
  372. }
  373. else if(event.getType() == GUIMouseEventType::MouseDrag)
  374. {
  375. UINT32 dist = mDragStartPosition.manhattanDist(event.getPosition());
  376. if(!DragAndDropManager::instance().isDragInProgress())
  377. {
  378. if(dist > DRAG_MIN_DISTANCE)
  379. {
  380. const GUISceneTreeView::InteractableElement* element = findElementUnderCoord(mDragStartPosition);
  381. TreeElement* treeElement = nullptr;
  382. if(element != nullptr && element->isTreeElement())
  383. {
  384. // If element we are trying to drag isn't selected, select it
  385. TreeElement* treeElement = interactableToRealElement(*element);
  386. auto iterFind = std::find_if(mSelectedElements.begin(), mSelectedElements.end(),
  387. [&] (const SelectedElement& x) { return x.element == treeElement; });
  388. if(iterFind == mSelectedElements.end())
  389. {
  390. unselectAll();
  391. selectElement(interactableToRealElement(*element));
  392. }
  393. }
  394. DraggedSceneObjects* draggedSceneObjects = cm_new<DraggedSceneObjects>((UINT32)mSelectedElements.size());
  395. UINT32 cnt = 0;
  396. for(auto& selectedElement : mSelectedElements)
  397. {
  398. draggedSceneObjects->objects[cnt] = selectedElement.element->mSceneObject;
  399. cnt++;
  400. }
  401. DragAndDropManager::instance().startDrag(HTexture(), (UINT32)DragAndDropType::SceneObject, (void*)draggedSceneObjects,
  402. boost::bind(&GUISceneTreeView::dragAndDropEnded, this));
  403. mDragPosition = event.getPosition();
  404. mDragInProgress = true;
  405. markContentAsDirty();
  406. }
  407. }
  408. }
  409. else if(event.getType() == GUIMouseEventType::MouseDragAndDropDragged)
  410. {
  411. if(DragAndDropManager::instance().isDragInProgress() && DragAndDropManager::instance().getDragTypeId() == (UINT32)DragAndDropType::SceneObject)
  412. {
  413. mDragPosition = event.getPosition();
  414. mDragInProgress = true;
  415. markContentAsDirty();
  416. return true;
  417. }
  418. }
  419. else if(event.getType() == GUIMouseEventType::MouseDragAndDropDropped)
  420. {
  421. if(DragAndDropManager::instance().isDragInProgress() && DragAndDropManager::instance().getDragTypeId() == (UINT32)DragAndDropType::SceneObject)
  422. {
  423. DraggedSceneObjects* draggedSceneObjects = reinterpret_cast<DraggedSceneObjects*>(DragAndDropManager::instance().getDragData());
  424. const GUISceneTreeView::InteractableElement* element = findElementUnderCoord(event.getPosition());
  425. TreeElement* treeElement = nullptr;
  426. if(element != nullptr)
  427. {
  428. if(element->isTreeElement())
  429. treeElement = interactableToRealElement(*element);
  430. else
  431. treeElement = element->parent;
  432. }
  433. if(treeElement != nullptr)
  434. {
  435. Vector<HSceneObject>::type sceneObjects;
  436. HSceneObject newParent = treeElement->mSceneObject;
  437. for(UINT32 i = 0; i < draggedSceneObjects->numObjects; i++)
  438. {
  439. if(draggedSceneObjects->objects[i] != newParent)
  440. sceneObjects.push_back(draggedSceneObjects->objects[i]);
  441. }
  442. CmdReparentSO::execute(sceneObjects, newParent);
  443. }
  444. unselectAll();
  445. return true;
  446. }
  447. }
  448. else if(event.getType() == GUIMouseEventType::MouseOut)
  449. {
  450. mDragInProgress = false;
  451. markContentAsDirty();
  452. }
  453. return false;
  454. }
  455. bool GUISceneTreeView::commandEvent(const GUICommandEvent& ev)
  456. {
  457. if(ev.getType() == GUICommandEventType::Rename)
  458. {
  459. if(isSelectionActive() && mEditElement == nullptr)
  460. {
  461. unselectAll();
  462. enableEdit(mSelectedElements[0].element);
  463. }
  464. return true;
  465. }
  466. return false;
  467. }
  468. void GUISceneTreeView::dragAndDropEnded()
  469. {
  470. mDragInProgress = false;
  471. markContentAsDirty();
  472. DraggedSceneObjects* draggedSceneObjects = reinterpret_cast<DraggedSceneObjects*>(DragAndDropManager::instance().getDragData());
  473. cm_delete(draggedSceneObjects);
  474. }
  475. bool GUISceneTreeView::isSelectionActive() const
  476. {
  477. return mIsElementSelected && mSelectedElements.size() > 0;
  478. }
  479. void GUISceneTreeView::selectElement(TreeElement* element)
  480. {
  481. auto iterFind = std::find_if(mSelectedElements.begin(), mSelectedElements.end(),
  482. [&] (const SelectedElement& x) { return x.element == element; });
  483. if(iterFind == mSelectedElements.end())
  484. {
  485. GUITexture* background = GUITexture::create(_getParentWidget(), mSelectionBackgroundStyle);
  486. _registerChildElement(background);
  487. element->mIsSelected = true;
  488. mSelectedElements.push_back(SelectedElement(element, background));
  489. mIsElementSelected = true;
  490. }
  491. }
  492. void GUISceneTreeView::unselectElement(TreeElement* element)
  493. {
  494. auto iterFind = std::find_if(mSelectedElements.begin(), mSelectedElements.end(),
  495. [&] (const SelectedElement& x) { return x.element == element; });
  496. if(iterFind != mSelectedElements.end())
  497. {
  498. iterFind->element->mIsSelected = false;
  499. GUIElement::destroy(iterFind->background);
  500. mSelectedElements.erase(iterFind);
  501. markContentAsDirty();
  502. }
  503. mIsElementSelected = mSelectedElements.size() > 0;
  504. }
  505. void GUISceneTreeView::unselectAll()
  506. {
  507. for(auto& selectedElem : mSelectedElements)
  508. {
  509. selectedElem.element->mIsSelected = false;
  510. GUIElement::destroy(selectedElem.background);
  511. }
  512. mSelectedElements.clear();
  513. mIsElementSelected = false;
  514. markContentAsDirty();
  515. }
  516. void GUISceneTreeView::elementToggled(TreeElement* element, bool toggled)
  517. {
  518. element->mIsExpanded = toggled;
  519. }
  520. void GUISceneTreeView::onEditAccepted()
  521. {
  522. disableEdit(true);
  523. }
  524. void GUISceneTreeView::onEditCanceled()
  525. {
  526. if(mEditElement != nullptr)
  527. disableEdit(false);
  528. }
  529. void GUISceneTreeView::enableEdit(TreeElement* element)
  530. {
  531. assert(mEditElement == nullptr);
  532. mEditElement = element;
  533. mNameEditBox->enableRecursively();
  534. mNameEditBox->setFocus(true);
  535. if(element->mElement != nullptr)
  536. element->mElement->disableRecursively();
  537. }
  538. void GUISceneTreeView::disableEdit(bool applyChanges)
  539. {
  540. assert(mEditElement != nullptr);
  541. if(mEditElement->mElement != nullptr)
  542. mEditElement->mElement->enableRecursively();
  543. if(applyChanges)
  544. {
  545. String newName = toString(mNameEditBox->getText());
  546. CmdEditPlainFieldGO<String>::execute(mEditElement->mSceneObject, "mName", newName);
  547. }
  548. mNameEditBox->disableRecursively();
  549. mEditElement = nullptr;
  550. }
  551. Vector2I GUISceneTreeView::_getOptimalSize() const
  552. {
  553. struct UpdateTreeElement
  554. {
  555. UpdateTreeElement(const TreeElement* element, UINT32 indent)
  556. :element(element), indent(indent)
  557. { }
  558. const TreeElement* element;
  559. UINT32 indent;
  560. };
  561. Vector2I optimalSize;
  562. if(_getLayoutOptions().fixedWidth && _getLayoutOptions().fixedHeight)
  563. {
  564. optimalSize.x = _getLayoutOptions().width;
  565. optimalSize.y = _getLayoutOptions().height;
  566. }
  567. else
  568. {
  569. Stack<UpdateTreeElement>::type todo;
  570. todo.push(UpdateTreeElement(&mRootElement, 0));
  571. while(!todo.empty())
  572. {
  573. UpdateTreeElement currentUpdateElement = todo.top();
  574. const TreeElement* current = currentUpdateElement.element;
  575. todo.pop();
  576. INT32 yOffset = 0;
  577. if(current->mElement != nullptr)
  578. {
  579. Vector2I curOptimalSize = current->mElement->_getOptimalSize();
  580. optimalSize.x = std::max(optimalSize.x,
  581. (INT32)(INITIAL_INDENT_OFFSET + curOptimalSize.x + currentUpdateElement.indent * INDENT_SIZE));
  582. yOffset = curOptimalSize.y + ELEMENT_EXTRA_SPACING;
  583. }
  584. optimalSize.y += yOffset;
  585. for(auto& child : current->mChildren)
  586. {
  587. if(!child->mIsVisible)
  588. continue;
  589. todo.push(UpdateTreeElement(child, currentUpdateElement.indent + 1));
  590. }
  591. }
  592. if(_getLayoutOptions().fixedWidth)
  593. optimalSize.x = _getLayoutOptions().width;
  594. else
  595. {
  596. if(_getLayoutOptions().minWidth > 0)
  597. optimalSize.x = std::max((INT32)_getLayoutOptions().minWidth, optimalSize.x);
  598. if(_getLayoutOptions().maxWidth > 0)
  599. optimalSize.x = std::min((INT32)_getLayoutOptions().maxWidth, optimalSize.x);
  600. }
  601. if(_getLayoutOptions().fixedHeight)
  602. optimalSize.y = _getLayoutOptions().height;
  603. else
  604. {
  605. if(_getLayoutOptions().minHeight > 0)
  606. optimalSize.y = std::max((INT32)_getLayoutOptions().minHeight, optimalSize.y);
  607. if(_getLayoutOptions().maxHeight > 0)
  608. optimalSize.y = std::min((INT32)_getLayoutOptions().maxHeight, optimalSize.y);
  609. }
  610. }
  611. return optimalSize;
  612. }
  613. void GUISceneTreeView::updateClippedBounds()
  614. {
  615. Vector2I offset = _getOffset();
  616. mClippedBounds = RectI(offset.x, offset.y, _getWidth(), _getHeight());
  617. RectI localClipRect(mClipRect.x + mOffset.x, mClipRect.y + mOffset.y, mClipRect.width, mClipRect.height);
  618. mClippedBounds.clip(localClipRect);
  619. }
  620. void GUISceneTreeView::_updateLayoutInternal(INT32 x, INT32 y, UINT32 width, UINT32 height,
  621. RectI clipRect, UINT8 widgetDepth, UINT16 areaDepth)
  622. {
  623. struct UpdateTreeElement
  624. {
  625. UpdateTreeElement(TreeElement* element, UINT32 indent)
  626. :element(element), indent(indent)
  627. { }
  628. TreeElement* element;
  629. UINT32 indent;
  630. };
  631. mVisibleElements.clear();
  632. Stack<UpdateTreeElement>::type todo;
  633. todo.push(UpdateTreeElement(&mRootElement, 0));
  634. // NOTE - Instead of iterating through all elements, try to find those within the clip rect
  635. // and only iterate through those. Others should somehow be marked in-active (similar to GUIElement::isDisabled()?)
  636. Vector<TreeElement*>::type tempOrderedElements;
  637. Vector2I offset(x, y);
  638. while(!todo.empty())
  639. {
  640. UpdateTreeElement currentUpdateElement = todo.top();
  641. TreeElement* current = currentUpdateElement.element;
  642. UINT32 indent = currentUpdateElement.indent;
  643. todo.pop();
  644. INT32 btnHeight = 0;
  645. INT32 yOffset = 0;
  646. if(current->mElement != nullptr)
  647. {
  648. Vector2I elementSize = current->mElement->_getOptimalSize();
  649. btnHeight = elementSize.y;
  650. mVisibleElements.push_back(InteractableElement(current->mParent, current->mSortedIdx * 2 + 0, RectI(x, offset.y, width, ELEMENT_EXTRA_SPACING)));
  651. mVisibleElements.push_back(InteractableElement(current->mParent, current->mSortedIdx * 2 + 1, RectI(x, offset.y + ELEMENT_EXTRA_SPACING, width, btnHeight)));
  652. offset.x = x + INITIAL_INDENT_OFFSET + indent * INDENT_SIZE;
  653. offset.y += ELEMENT_EXTRA_SPACING;
  654. current->mElement->_setOffset(offset);
  655. current->mElement->_setWidth(elementSize.x);
  656. current->mElement->_setHeight(elementSize.y);
  657. current->mElement->_setAreaDepth(areaDepth);
  658. current->mElement->_setWidgetDepth(widgetDepth);
  659. RectI elemClipRect(clipRect.x - offset.x, clipRect.y - offset.y, clipRect.width, clipRect.height);
  660. current->mElement->_setClipRect(elemClipRect);
  661. yOffset = btnHeight;
  662. }
  663. if(current->mFoldoutBtn != nullptr)
  664. {
  665. Vector2I elementSize = current->mFoldoutBtn->_getOptimalSize();
  666. offset.x -= std::min((INT32)INITIAL_INDENT_OFFSET, elementSize.x);
  667. Vector2I myOffset = offset;
  668. myOffset.y -= 2; // TODO: Arbitrary offset, I should adjust it based on font baseline so that the button is nicely centered on text
  669. if(elementSize.y > btnHeight)
  670. {
  671. UINT32 diff = elementSize.y - btnHeight;
  672. float half = diff * 0.5f;
  673. myOffset.y -= Math::floorToInt(half);
  674. }
  675. current->mFoldoutBtn->_setOffset(myOffset);
  676. current->mFoldoutBtn->_setWidth(elementSize.x);
  677. current->mFoldoutBtn->_setHeight(elementSize.y);
  678. current->mFoldoutBtn->_setAreaDepth(areaDepth);
  679. current->mFoldoutBtn->_setWidgetDepth(widgetDepth);
  680. RectI elemClipRect(clipRect.x - myOffset.x, clipRect.y - myOffset.y, clipRect.width, clipRect.height);
  681. current->mFoldoutBtn->_setClipRect(elemClipRect);
  682. }
  683. offset.y += yOffset;
  684. tempOrderedElements.resize(current->mChildren.size(), nullptr);
  685. for(auto& child : current->mChildren)
  686. {
  687. tempOrderedElements[child->mSortedIdx] = child;
  688. }
  689. for(auto iter = tempOrderedElements.rbegin(); iter != tempOrderedElements.rend(); ++iter)
  690. {
  691. TreeElement* child = *iter;
  692. if(!child->mIsVisible)
  693. continue;
  694. todo.push(UpdateTreeElement(child, indent + 1));
  695. }
  696. }
  697. UINT32 remainingHeight = (UINT32)std::max(0, (INT32)height - (offset.y - y));
  698. if(remainingHeight > 0)
  699. mVisibleElements.push_back(InteractableElement(&mRootElement, (UINT32)mRootElement.mChildren.size() * 2, RectI(x, offset.y, width, remainingHeight)));
  700. for(auto selectedElem : mSelectedElements)
  701. {
  702. GUILabel* targetElement = selectedElem.element->mElement;
  703. Vector2I offset = targetElement->_getOffset();
  704. offset.x = x;
  705. selectedElem.background->_setOffset(offset);
  706. selectedElem.background->_setWidth(width);
  707. selectedElem.background->_setHeight(targetElement->_getHeight());
  708. selectedElem.background->_setAreaDepth(areaDepth + 1);
  709. selectedElem.background->_setWidgetDepth(widgetDepth);
  710. RectI elemClipRect(clipRect.x - offset.x, clipRect.y - offset.y, clipRect.width, clipRect.height);
  711. selectedElem.background->_setClipRect(elemClipRect);
  712. }
  713. if(mEditElement != nullptr)
  714. {
  715. GUILabel* targetElement = mEditElement->mElement;
  716. Vector2I offset = targetElement->_getOffset();
  717. UINT32 remainingWidth = (UINT32)std::max(0, (((INT32)width) - (offset.x - x)));
  718. mNameEditBox->_setOffset(offset);
  719. mNameEditBox->_setWidth(remainingWidth);
  720. mNameEditBox->_setHeight(targetElement->_getHeight());
  721. mNameEditBox->_setAreaDepth(areaDepth);
  722. mNameEditBox->_setWidgetDepth(widgetDepth);
  723. RectI elemClipRect(clipRect.x - offset.x, clipRect.y - offset.y, clipRect.width, clipRect.height);
  724. mNameEditBox->_setClipRect(elemClipRect);
  725. }
  726. if(mDragInProgress)
  727. {
  728. const InteractableElement* interactableElement = findElementUnderCoord(mDragPosition);
  729. if(interactableElement == nullptr)
  730. {
  731. if(!mDragHighlight->_isDisabled())
  732. mDragHighlight->disableRecursively();
  733. if(!mDragSepHighlight->_isDisabled())
  734. mDragSepHighlight->disableRecursively();
  735. }
  736. else
  737. {
  738. if(interactableElement->isTreeElement())
  739. {
  740. if(!mDragSepHighlight->_isDisabled())
  741. mDragSepHighlight->disableRecursively();
  742. if(mDragHighlight->_isDisabled())
  743. mDragHighlight->enableRecursively();
  744. Vector2I offset(interactableElement->bounds.x, interactableElement->bounds.y);
  745. mDragHighlight->_setOffset(offset);
  746. mDragHighlight->_setWidth(interactableElement->bounds.width);
  747. mDragHighlight->_setHeight(interactableElement->bounds.height);
  748. mDragHighlight->_setAreaDepth(areaDepth + 1);
  749. mDragHighlight->_setWidgetDepth(widgetDepth);
  750. RectI elemClipRect(clipRect.x - offset.x, clipRect.y - offset.y, clipRect.width, clipRect.height);
  751. mDragHighlight->_setClipRect(elemClipRect);
  752. }
  753. else
  754. {
  755. if(!mDragHighlight->_isDisabled())
  756. mDragHighlight->disableRecursively();
  757. if(mDragSepHighlight->_isDisabled())
  758. mDragSepHighlight->enableRecursively();
  759. Vector2I offset(interactableElement->bounds.x, interactableElement->bounds.y);
  760. mDragSepHighlight->_setOffset(offset);
  761. mDragSepHighlight->_setWidth(interactableElement->bounds.width);
  762. mDragSepHighlight->_setHeight(interactableElement->bounds.height);
  763. mDragSepHighlight->_setAreaDepth(areaDepth + 1);
  764. mDragSepHighlight->_setWidgetDepth(widgetDepth);
  765. RectI elemClipRect(clipRect.x - offset.x, clipRect.y - offset.y, clipRect.width, clipRect.height);
  766. mDragSepHighlight->_setClipRect(elemClipRect);
  767. }
  768. }
  769. }
  770. else
  771. {
  772. if(!mDragHighlight->_isDisabled())
  773. mDragHighlight->disableRecursively();
  774. if(!mDragSepHighlight->_isDisabled())
  775. mDragSepHighlight->disableRecursively();
  776. }
  777. }
  778. const GUISceneTreeView::InteractableElement* GUISceneTreeView::findElementUnderCoord(const CM::Vector2I& coord) const
  779. {
  780. for(auto& element : mVisibleElements)
  781. {
  782. if(element.bounds.contains(coord))
  783. {
  784. return &element;
  785. }
  786. }
  787. return nullptr;
  788. }
  789. GUISceneTreeView::TreeElement* GUISceneTreeView::interactableToRealElement(const GUISceneTreeView::InteractableElement& element)
  790. {
  791. if(!element.isTreeElement())
  792. return nullptr;
  793. UINT32 sortedIdx = (element.index - 1) / 2;
  794. auto findIter = std::find_if(element.parent->mChildren.begin(), element.parent->mChildren.end(),
  795. [&](const TreeElement* x) { return x->mSortedIdx == sortedIdx; });
  796. if(findIter != element.parent->mChildren.end())
  797. return *findIter;
  798. return nullptr;
  799. }
  800. const String& GUISceneTreeView::getGUITypeName()
  801. {
  802. static String typeName = "SceneTreeView";
  803. return typeName;
  804. }
  805. }