BsGUIResourceTreeView.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  1. //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
  2. //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
  3. #include "GUI/BsGUIResourceTreeView.h"
  4. #include "Library/BsProjectLibrary.h"
  5. #include "GUI/BsDragAndDropManager.h"
  6. #include "FileSystem/BsFileSystem.h"
  7. #include "GUI/BsGUIWidget.h"
  8. #include "RenderAPI/BsViewport.h"
  9. #include "RenderAPI/BsRenderWindow.h"
  10. #include "Platform/BsDropTarget.h"
  11. #include "String/BsUnicode.h"
  12. using namespace std::placeholders;
  13. namespace bs
  14. {
  15. const MessageId GUIResourceTreeView::SELECTION_CHANGED_MSG = MessageId("ResourceTreeView_SelectionChanged");
  16. GUIResourceTreeView::InternalDraggedResources::InternalDraggedResources(UINT32 numObjects)
  17. :numObjects(numObjects)
  18. {
  19. resourcePaths = bs_newN<Path>(numObjects);
  20. }
  21. GUIResourceTreeView::InternalDraggedResources::~InternalDraggedResources()
  22. {
  23. bs_deleteN(resourcePaths, numObjects);
  24. resourcePaths = nullptr;
  25. }
  26. GUIResourceTreeView::GUIResourceTreeView(const String& backgroundStyle, const String& elementBtnStyle, const String& foldoutBtnStyle,
  27. const String& highlightBackgroundStyle, const String& selectionBackgroundStyle, const String& editBoxStyle,
  28. const String& dragHighlightStyle, const String& dragSepHighlightStyle, const GUIDimensions& dimensions)
  29. :GUITreeView(backgroundStyle, elementBtnStyle, foldoutBtnStyle, highlightBackgroundStyle,
  30. selectionBackgroundStyle, editBoxStyle, dragHighlightStyle, dragSepHighlightStyle, dimensions),
  31. mDraggedResources(nullptr), mCurrentWindow(nullptr), mDropTarget(nullptr), mDropTargetDragActive(false)
  32. {
  33. ResourceTreeViewLocator::_provide(this);
  34. gProjectLibrary().onEntryAdded.connect(std::bind(&GUIResourceTreeView::entryAdded, this, _1));
  35. gProjectLibrary().onEntryRemoved.connect(std::bind(&GUIResourceTreeView::entryRemoved, this, _1));
  36. const ProjectLibrary::LibraryEntry* rootEntry = gProjectLibrary().getRootEntry().get();
  37. mRootElement.mFullPath = rootEntry->path;
  38. mRootElement.mElementName = mRootElement.mFullPath.getTail();
  39. expandElement(&mRootElement);
  40. updateFromProjectLibraryEntry(&mRootElement, rootEntry);
  41. }
  42. GUIResourceTreeView::~GUIResourceTreeView()
  43. {
  44. clearDropTarget();
  45. for(auto& child : mRootElement.mChildren)
  46. deleteTreeElement((ResourceTreeElement*)&child);
  47. mRootElement.mChildren.clear();
  48. ResourceTreeViewLocator::_provide(nullptr);
  49. }
  50. GUIResourceTreeView* GUIResourceTreeView::create(const String& backgroundStyle, const String& elementBtnStyle,
  51. const String& foldoutBtnStyle, const String& highlightBackgroundStyle, const String& selectionBackgroundStyle,
  52. const String& editBoxStyle, const String& dragHighlightStyle, const String& dragSepHighlightStyle)
  53. {
  54. return new (bs_alloc<GUIResourceTreeView>()) GUIResourceTreeView(backgroundStyle, elementBtnStyle, foldoutBtnStyle,
  55. highlightBackgroundStyle, selectionBackgroundStyle, editBoxStyle, dragHighlightStyle, dragSepHighlightStyle, GUIDimensions::create());
  56. }
  57. GUIResourceTreeView* GUIResourceTreeView::create(const GUIOptions& options, const String& backgroundStyle,
  58. const String& elementBtnStyle, const String& foldoutBtnStyle, const String& highlightBackgroundStyle,
  59. const String& selectionBackgroundStyle, const String& editBoxStyle, const String& dragHighlightStyle,
  60. const String& dragSepHighlightStyle)
  61. {
  62. return new (bs_alloc<GUIResourceTreeView>()) GUIResourceTreeView(backgroundStyle, elementBtnStyle, foldoutBtnStyle,
  63. highlightBackgroundStyle, selectionBackgroundStyle, editBoxStyle, dragHighlightStyle, dragSepHighlightStyle, GUIDimensions::create(options));
  64. }
  65. void GUIResourceTreeView::_updateLayoutInternal(const GUILayoutData& data)
  66. {
  67. GUITreeView::_updateLayoutInternal(data);
  68. if(mDropTarget != nullptr)
  69. mDropTarget->setArea(data.area);
  70. }
  71. void GUIResourceTreeView::updateTreeElementHierarchy()
  72. {
  73. // Do nothing, updates are handled via callbacks
  74. }
  75. void GUIResourceTreeView::renameTreeElement(GUITreeView::TreeElement* element, const String& name)
  76. {
  77. ResourceTreeElement* resourceTreeElement = static_cast<ResourceTreeElement*>(element);
  78. Path oldPath = resourceTreeElement->mFullPath;
  79. Path newPath = oldPath.getParent();
  80. newPath.append(name);
  81. gProjectLibrary().moveEntry(oldPath, findUniquePath(newPath));
  82. }
  83. void GUIResourceTreeView::deleteTreeElement(TreeElement* element)
  84. {
  85. ResourceTreeElement* resourceTreeElement = static_cast<ResourceTreeElement*>(element);
  86. gProjectLibrary().deleteEntry(resourceTreeElement->mFullPath);
  87. }
  88. void GUIResourceTreeView::updateFromProjectLibraryEntry(ResourceTreeElement* treeElement, const ProjectLibrary::LibraryEntry* libraryEntry)
  89. {
  90. struct StackElem
  91. {
  92. StackElem(const ProjectLibrary::LibraryEntry* entry, ResourceTreeElement* treeElem)
  93. :entry(entry), treeElem(treeElem)
  94. { }
  95. const ProjectLibrary::LibraryEntry* entry;
  96. ResourceTreeElement* treeElem;
  97. };
  98. if(libraryEntry->type == ProjectLibrary::LibraryEntryType::Directory)
  99. {
  100. Stack<StackElem> todo;
  101. todo.push(StackElem(libraryEntry, treeElement));
  102. while(!todo.empty())
  103. {
  104. StackElem curElem = todo.top();
  105. todo.pop();
  106. const ProjectLibrary::DirectoryEntry* dirEntry = static_cast<const ProjectLibrary::DirectoryEntry*>(curElem.entry);
  107. for(auto& child : dirEntry->mChildren)
  108. {
  109. ResourceTreeElement* newChild = addTreeElement(curElem.treeElem, child->path);
  110. if(child->type == ProjectLibrary::LibraryEntryType::Directory)
  111. todo.push(StackElem(child.get(), newChild));
  112. }
  113. sortTreeElement(curElem.treeElem);
  114. }
  115. }
  116. }
  117. GUIResourceTreeView::ResourceTreeElement* GUIResourceTreeView::addTreeElement(ResourceTreeElement* parent, const Path& fullPath)
  118. {
  119. ResourceTreeElement* newChild = bs_new<ResourceTreeElement>();
  120. newChild->mParent = parent;
  121. newChild->mName = fullPath.getTail();
  122. newChild->mFullPath = fullPath;
  123. newChild->mSortedIdx = (UINT32)parent->mChildren.size();
  124. newChild->mIsVisible = parent->mIsVisible && parent->mIsExpanded;
  125. newChild->mElementName = fullPath.getTail();
  126. parent->mChildren.push_back(newChild);
  127. updateElementGUI(parent);
  128. updateElementGUI(newChild);
  129. return newChild;
  130. }
  131. void GUIResourceTreeView::deleteTreeElement(ResourceTreeElement* element)
  132. {
  133. closeTemporarilyExpandedElements(); // In case this element is one of them
  134. for(auto& child : element->mChildren)
  135. deleteTreeElement((ResourceTreeElement*)child);
  136. element->mChildren.clear();
  137. if (element->mIsHighlighted)
  138. clearPing();
  139. if(element->mIsSelected)
  140. unselectElement(element);
  141. if(element->mParent != nullptr)
  142. {
  143. auto iterFind = std::find(element->mParent->mChildren.begin(), element->mParent->mChildren.end(), element);
  144. if(iterFind != element->mParent->mChildren.end())
  145. element->mParent->mChildren.erase(iterFind);
  146. sortTreeElement(static_cast<ResourceTreeElement*>(element->mParent));
  147. updateElementGUI(element->mParent);
  148. }
  149. if(&mRootElement != element)
  150. bs_delete(element);
  151. }
  152. void GUIResourceTreeView::sortTreeElement(ResourceTreeElement* element)
  153. {
  154. auto cmp = [&] (const TreeElement* a, const TreeElement* b)
  155. {
  156. return a->mName.compare(b->mName) < 0;
  157. };
  158. std::sort(element->mChildren.begin(), element->mChildren.end(), cmp);
  159. UINT32 idx = 0;
  160. for(auto& child : element->mChildren)
  161. {
  162. child->mSortedIdx = idx;
  163. idx++;
  164. }
  165. }
  166. GUIResourceTreeView::ResourceTreeElement* GUIResourceTreeView::findTreeElement(const Path& fullPath)
  167. {
  168. if (!mRootElement.mFullPath.includes(fullPath))
  169. return nullptr;
  170. Path relPath = fullPath.getRelative(mRootElement.mFullPath);
  171. UINT32 numElems = relPath.getNumDirectories() + (relPath.isFile() ? 1 : 0);
  172. UINT32 idx = 0;
  173. ResourceTreeElement* current = &mRootElement;
  174. while (current != nullptr)
  175. {
  176. if (idx == numElems)
  177. return current;
  178. String curElem;
  179. if (relPath.isFile() && idx == (numElems - 1))
  180. curElem = relPath.getFilename();
  181. else
  182. curElem = relPath[idx];
  183. bool foundChild = false;
  184. for (auto& child : current->mChildren)
  185. {
  186. ResourceTreeElement* resourceChild = static_cast<ResourceTreeElement*>(child);
  187. if (Path::comparePathElem(curElem, resourceChild->mElementName))
  188. {
  189. idx++;
  190. current = resourceChild;
  191. foundChild = true;
  192. break;
  193. }
  194. }
  195. if (!foundChild)
  196. current = nullptr;
  197. }
  198. return nullptr;
  199. }
  200. void GUIResourceTreeView::entryAdded(const Path& path)
  201. {
  202. Path parentPath = path.getParent();
  203. ResourceTreeElement* parentElement = findTreeElement(parentPath);
  204. assert(parentElement != nullptr);
  205. ResourceTreeElement* newElement = addTreeElement(parentElement, path);
  206. sortTreeElement(parentElement);
  207. ProjectLibrary::LibraryEntry* libEntry = gProjectLibrary().findEntry(path).get();
  208. assert(libEntry != nullptr);
  209. updateFromProjectLibraryEntry(newElement, libEntry);
  210. _markLayoutAsDirty();
  211. }
  212. void GUIResourceTreeView::entryRemoved(const Path& path)
  213. {
  214. ResourceTreeElement* treeElement = findTreeElement(path);
  215. if(treeElement != nullptr)
  216. deleteTreeElement(treeElement);
  217. }
  218. void GUIResourceTreeView::setDropTarget(RenderWindow* parentWindow, INT32 x, INT32 y, UINT32 width, UINT32 height)
  219. {
  220. if(mDropTarget != nullptr)
  221. {
  222. mDropTarget = nullptr;
  223. mDropTargetEnterConn.disconnect();
  224. mDropTargetLeaveConn.disconnect();
  225. mDropTargetMoveConn.disconnect();
  226. mDropTargetDroppedConn.disconnect();
  227. }
  228. if(parentWindow != nullptr)
  229. {
  230. mCurrentWindow = parentWindow;
  231. mDropTarget = DropTarget::create(mCurrentWindow, mLayoutData.area);
  232. mDropTargetEnterConn = mDropTarget->onEnter.connect(std::bind(&GUIResourceTreeView::dropTargetDragMove, this, _1, _2));
  233. mDropTargetMoveConn = mDropTarget->onDragOver.connect(std::bind(&GUIResourceTreeView::dropTargetDragMove, this, _1, _2));
  234. mDropTargetLeaveConn = mDropTarget->onLeave.connect(std::bind(&GUIResourceTreeView::dropTargetDragLeave, this));
  235. mDropTargetDroppedConn = mDropTarget->onDrop.connect(std::bind(&GUIResourceTreeView::dropTargetDragDropped, this, _1, _2));
  236. }
  237. else
  238. mDropTarget = nullptr;
  239. }
  240. void GUIResourceTreeView::clearDropTarget()
  241. {
  242. setDropTarget(nullptr, 0, 0, 0, 0);
  243. }
  244. void GUIResourceTreeView::dropTargetDragMove(INT32 x, INT32 y)
  245. {
  246. mDragPosition = Vector2I(x, y);
  247. mDragInProgress = true;
  248. mDropTargetDragActive = true;
  249. _markLayoutAsDirty();
  250. if(mBottomScrollBounds.contains(mDragPosition))
  251. {
  252. if(mScrollState != ScrollState::Down)
  253. mScrollState = ScrollState::TransitioningDown;
  254. }
  255. else if(mTopScrollBounds.contains(mDragPosition))
  256. {
  257. if(mScrollState != ScrollState::Up)
  258. mScrollState = ScrollState::TransitioningUp;
  259. }
  260. else
  261. mScrollState = ScrollState::None;
  262. }
  263. void GUIResourceTreeView::dropTargetDragLeave()
  264. {
  265. mDragInProgress = false;
  266. mDropTargetDragActive = false;
  267. _markLayoutAsDirty();
  268. }
  269. void GUIResourceTreeView::dropTargetDragDropped(INT32 x, INT32 y)
  270. {
  271. const GUITreeView::InteractableElement* element = findElementUnderCoord(Vector2I(x, y));
  272. TreeElement* treeElement = nullptr;
  273. if(element != nullptr)
  274. {
  275. if(element->isTreeElement())
  276. treeElement = element->getTreeElement();
  277. else
  278. treeElement = element->parent;
  279. }
  280. if(mDropTarget->getDropType() == DropTargetType::FileList)
  281. {
  282. Vector<Path> fileList = mDropTarget->getFileList();
  283. mDraggedResources = bs_new<InternalDraggedResources>((UINT32)fileList.size());
  284. for(UINT32 i = 0; i < (UINT32)fileList.size(); i++)
  285. mDraggedResources->resourcePaths[i] = fileList[i];
  286. dragAndDropEnded(treeElement);
  287. bs_delete(mDraggedResources);
  288. mDraggedResources = nullptr;
  289. unselectAll();
  290. }
  291. mDragInProgress = false;
  292. mDropTargetDragActive = false;
  293. _markLayoutAsDirty();
  294. }
  295. Path GUIResourceTreeView::findUniquePath(const Path& path)
  296. {
  297. if(FileSystem::exists(path))
  298. {
  299. Path newPath = path;
  300. String filename = path.getFilename(false);
  301. UINT32 cnt = 1;
  302. do
  303. {
  304. newPath.setBasename(filename + toString(cnt));
  305. cnt++;
  306. } while (FileSystem::exists(newPath));
  307. return newPath;
  308. }
  309. else
  310. return path;
  311. }
  312. bool GUIResourceTreeView::acceptDragAndDrop() const
  313. {
  314. return mDropTargetDragActive || (DragAndDropManager::instance().isDragInProgress() &&
  315. DragAndDropManager::instance().getDragTypeId() == (UINT32)DragAndDropType::Resources);
  316. }
  317. void GUIResourceTreeView::dragAndDropStart(const Vector<TreeElement*>& elements)
  318. {
  319. assert(mDraggedResources == nullptr);
  320. DraggedResources* draggedResources = bs_new<DraggedResources>();
  321. InternalDraggedResources* internalDraggedResources = bs_new<InternalDraggedResources>((UINT32)mSelectedElements.size());
  322. UINT32 cnt = 0;
  323. for(auto& entry : elements)
  324. {
  325. ResourceTreeElement* resourceTreeElement = static_cast<ResourceTreeElement*>(entry);
  326. internalDraggedResources->resourcePaths[cnt] = resourceTreeElement->mFullPath;
  327. draggedResources->resourcePaths.push_back(internalDraggedResources->resourcePaths[cnt]);
  328. cnt++;
  329. }
  330. mDraggedResources = internalDraggedResources;
  331. DragAndDropManager::instance().startDrag((UINT32)DragAndDropType::Resources, (void*)draggedResources,
  332. std::bind(&GUIResourceTreeView::dragAndDropFinalize, this), true);
  333. }
  334. void GUIResourceTreeView::dragAndDropEnded(TreeElement* overTreeElement)
  335. {
  336. if(overTreeElement != nullptr && mDraggedResources != nullptr)
  337. {
  338. ResourceTreeElement* resourceTreeElement = static_cast<ResourceTreeElement*>(overTreeElement);
  339. Path destDir = resourceTreeElement->mFullPath;
  340. if(FileSystem::isFile(destDir))
  341. destDir = destDir.getParent();
  342. for(UINT32 i = 0; i < mDraggedResources->numObjects; i++)
  343. {
  344. String filename = mDraggedResources->resourcePaths[i].getFilename();
  345. Path currentParent = mDraggedResources->resourcePaths[i].getParent();
  346. if(currentParent != destDir)
  347. {
  348. Path newPath = destDir;
  349. newPath.append(filename);
  350. gProjectLibrary().moveEntry(mDraggedResources->resourcePaths[i], findUniquePath(newPath));
  351. }
  352. }
  353. }
  354. }
  355. void GUIResourceTreeView::dragAndDropFinalize()
  356. {
  357. mDragInProgress = false;
  358. _markLayoutAsDirty();
  359. DraggedResources* draggedResources = reinterpret_cast<DraggedResources*>(DragAndDropManager::instance().getDragData());
  360. bs_delete(draggedResources);
  361. if(mDraggedResources != nullptr)
  362. {
  363. bs_delete(mDraggedResources);
  364. mDraggedResources = nullptr;
  365. }
  366. }
  367. void GUIResourceTreeView::_changeParentWidget(GUIWidget* widget)
  368. {
  369. GUITreeView::_changeParentWidget(widget);
  370. if (widget != nullptr && widget->getTarget()->getTarget()->getProperties().isWindow)
  371. {
  372. RenderWindow* parentWindow = static_cast<RenderWindow*>(widget->getTarget()->getTarget().get());
  373. setDropTarget(parentWindow, mLayoutData.area.x, mLayoutData.area.y, mLayoutData.area.width, mLayoutData.area.height);
  374. }
  375. else
  376. clearDropTarget();
  377. }
  378. bool GUIResourceTreeView::_acceptDragAndDrop(const Vector2I position, UINT32 typeId) const
  379. {
  380. return typeId == (UINT32)DragAndDropType::Resources && !_isDisabled();
  381. }
  382. void GUIResourceTreeView::selectionChanged()
  383. {
  384. onSelectionChanged();
  385. sendMessage(SELECTION_CHANGED_MSG);
  386. }
  387. Vector<Path> GUIResourceTreeView::getSelection() const
  388. {
  389. Vector<Path> selectedPaths;
  390. for (auto& selectedElem : mSelectedElements)
  391. {
  392. ResourceTreeElement* resTreeElement = static_cast<ResourceTreeElement*>(selectedElem.element);
  393. selectedPaths.push_back(resTreeElement->mFullPath);
  394. }
  395. return selectedPaths;
  396. }
  397. void GUIResourceTreeView::setSelection(const Vector<Path>& paths)
  398. {
  399. unselectAll();
  400. Stack<ResourceTreeElement*> todo;
  401. todo.push(&mRootElement);
  402. while (!todo.empty())
  403. {
  404. ResourceTreeElement* currentElem = todo.top();
  405. todo.pop();
  406. auto iterFind = std::find(paths.begin(), paths.end(), currentElem->mFullPath);
  407. if (iterFind != paths.end())
  408. {
  409. expandToElement(currentElem);
  410. selectElement(currentElem);
  411. }
  412. for (auto& child : currentElem->mChildren)
  413. {
  414. ResourceTreeElement* sceneChild = static_cast<ResourceTreeElement*>(child);
  415. todo.push(sceneChild);
  416. }
  417. }
  418. }
  419. const String& GUIResourceTreeView::getGUITypeName()
  420. {
  421. static String typeName = "ResourceTreeView";
  422. return typeName;
  423. }
  424. }