#include "BsGUISceneTreeView.h" #include "BsSceneObject.h" #include "BsSceneManager.h" #include "BsGUISkin.h" #include "BsCmdRecordSO.h" #include "BsCmdReparentSO.h" #include "BsCmdDeleteSO.h" #include "BsCmdCloneSO.h" #include "BsCmdCreateSO.h" #include "BsCmdInstantiateSO.h" #include "BsDragAndDropManager.h" #include "BsSelection.h" #include "BsGUIResourceTreeView.h" #include "BsProjectLibrary.h" #include "BsProjectResourceMeta.h" #include "BsPrefab.h" #include "BsResources.h" #include "BsGUIContextMenu.h" namespace BansheeEngine { const MessageId GUISceneTreeView::SELECTION_CHANGED_MSG = MessageId("SceneTreeView_SelectionChanged"); DraggedSceneObjects::DraggedSceneObjects(UINT32 numObjects) :numObjects(numObjects) { objects = bs_newN(numObjects); } DraggedSceneObjects::~DraggedSceneObjects() { bs_deleteN(objects, numObjects); objects = nullptr; } GUISceneTreeView::GUISceneTreeView(const String& backgroundStyle, const String& elementBtnStyle, const String& foldoutBtnStyle, const String& highlightBackgroundStyle, const String& selectionBackgroundStyle, const String& editBoxStyle, const String& dragHighlightStyle, const String& dragSepHighlightStyle, const GUIDimensions& dimensions) :GUITreeView(backgroundStyle, elementBtnStyle, foldoutBtnStyle, highlightBackgroundStyle, selectionBackgroundStyle, editBoxStyle, dragHighlightStyle, dragSepHighlightStyle, dimensions), mCutFlag(false) { SceneTreeViewLocator::_provide(this); GUIContextMenuPtr contextMenu = bs_shared_ptr_new(); contextMenu->addMenuItem(L"New", std::bind(&GUISceneTreeView::createNewSO, this), 50); contextMenu->addMenuItem(L"Rename", std::bind(&GUISceneTreeView::renameSelected, this), 49, ShortcutKey(ButtonModifier::None, BC_F2)); contextMenu->addMenuItem(L"Delete", std::bind(&GUISceneTreeView::deleteSelection, this), 48, ShortcutKey(ButtonModifier::None, BC_DELETE)); contextMenu->addSeparator(L"", 40); contextMenu->addMenuItem(L"Duplicate", std::bind(&GUISceneTreeView::duplicateSelection, this), 39, ShortcutKey(ButtonModifier::Ctrl, BC_D)); contextMenu->addMenuItem(L"Copy", std::bind(&GUISceneTreeView::copySelection, this), 38, ShortcutKey(ButtonModifier::Ctrl, BC_C)); contextMenu->addMenuItem(L"Cut", std::bind(&GUISceneTreeView::cutSelection, this), 37, ShortcutKey(ButtonModifier::Ctrl, BC_X)); contextMenu->addMenuItem(L"Paste", std::bind(&GUISceneTreeView::paste, this), 36, ShortcutKey(ButtonModifier::Ctrl, BC_V)); setContextMenu(contextMenu); } GUISceneTreeView::~GUISceneTreeView() { SceneTreeViewLocator::_provide(nullptr); } GUISceneTreeView* GUISceneTreeView::create(const String& backgroundStyle, const String& elementBtnStyle, const String& foldoutBtnStyle, const String& highlightBackgroundStyle, const String& selectionBackgroundStyle, const String& editBoxStyle, const String& dragHighlightStyle, const String& dragSepHighlightStyle) { return new (bs_alloc()) GUISceneTreeView(backgroundStyle, elementBtnStyle, foldoutBtnStyle, highlightBackgroundStyle, selectionBackgroundStyle, editBoxStyle, dragHighlightStyle, dragSepHighlightStyle, GUIDimensions::create()); } GUISceneTreeView* GUISceneTreeView::create(const GUIOptions& options, const String& backgroundStyle, const String& elementBtnStyle, const String& foldoutBtnStyle, const String& highlightBackgroundStyle, const String& selectionBackgroundStyle, const String& editBoxStyle, const String& dragHighlightStyle, const String& dragSepHighlightStyle) { return new (bs_alloc()) GUISceneTreeView(backgroundStyle, elementBtnStyle, foldoutBtnStyle, highlightBackgroundStyle, selectionBackgroundStyle, editBoxStyle, dragHighlightStyle, dragSepHighlightStyle, GUIDimensions::create(options)); } void GUISceneTreeView::updateTreeElement(SceneTreeElement* element) { HSceneObject currentSO = element->mSceneObject; // Check if SceneObject has changed in any way and update the tree element // Early exit case - Most commonly there will be no changes between active and cached data so // we first do a quick check in order to avoid expensive comparison later bool completeMatch = true; UINT32 visibleChildCount = 0; for (UINT32 i = 0; i < currentSO->getNumChildren(); i++) { if (i >= element->mChildren.size()) { completeMatch = false; break; } HSceneObject currentSOChild = currentSO->getChild(i); #if BS_DEBUG_MODE == 0 if (currentSOChild->hasFlag(SOF_Internal)) continue; #endif SceneTreeElement* currentChild = static_cast(element->mChildren[visibleChildCount]); visibleChildCount++; UINT64 curId = currentSOChild->getInstanceId(); if (curId != currentChild->mId) { completeMatch = false; break; } } completeMatch &= visibleChildCount == element->mChildren.size(); // Not a complete match, compare everything and insert/delete elements as needed bool needsUpdate = false; if(!completeMatch) { Vector newChildren; bool* tempToDelete = (bool*)bs_stack_alloc(sizeof(bool) * (UINT32)element->mChildren.size()); for(UINT32 i = 0; i < (UINT32)element->mChildren.size(); i++) tempToDelete[i] = true; for(UINT32 i = 0; i < currentSO->getNumChildren(); i++) { HSceneObject currentSOChild = currentSO->getChild(i); bool isInternal = currentSOChild->hasFlag(SOF_Internal); #if BS_DEBUG_MODE == 0 if (isInternal) continue; #endif UINT64 curId = currentSOChild->getInstanceId(); bool found = false; for(UINT32 j = 0; j < element->mChildren.size(); j++) { SceneTreeElement* currentChild = static_cast(element->mChildren[j]); if(curId == currentChild->mId) { tempToDelete[j] = false; currentChild->mSortedIdx = (UINT32)newChildren.size(); newChildren.push_back(currentChild); found = true; break; } } if(!found) { SceneTreeElement* newChild = bs_new(); newChild->mParent = element; newChild->mSceneObject = currentSOChild; newChild->mId = currentSOChild->getInstanceId(); newChild->mName = currentSOChild->getName(); newChild->mSortedIdx = (UINT32)newChildren.size(); newChild->mIsVisible = element->mIsVisible && element->mIsExpanded; newChild->mTint = isInternal ? Color::Red : Color::White; newChildren.push_back(newChild); updateElementGUI(newChild); } } for(UINT32 i = 0; i < element->mChildren.size(); i++) { if(!tempToDelete[i]) continue; deleteTreeElementInternal(element->mChildren[i]); } bs_stack_free(tempToDelete); element->mChildren = newChildren; needsUpdate = true; } // Check if name needs updating const String& name = element->mSceneObject->getName(); if(element->mName != name) { element->mName = name; needsUpdate = true; } if(needsUpdate) updateElementGUI(element); for(UINT32 i = 0; i < (UINT32)element->mChildren.size(); i++) { SceneTreeElement* sceneElement = static_cast(element->mChildren[i]); updateTreeElement(sceneElement); } // Calculate the sorted index of the elements based on their name bs_frame_mark(); FrameVector sortVector; for (auto& child : element->mChildren) sortVector.push_back(static_cast(child)); std::sort(sortVector.begin(), sortVector.end(), [&](const SceneTreeElement* lhs, const SceneTreeElement* rhs) { return StringUtil::compare(lhs->mName, rhs->mName, false) < 0; }); UINT32 idx = 0; for (auto& child : sortVector) { child->mSortedIdx = idx; idx++; } bs_frame_clear(); } void GUISceneTreeView::updateTreeElementHierarchy() { HSceneObject root = gCoreSceneManager().getRootNode(); mRootElement.mSceneObject = root; mRootElement.mId = root->getInstanceId(); mRootElement.mSortedIdx = 0; mRootElement.mIsExpanded = true; updateTreeElement(&mRootElement); } void GUISceneTreeView::renameTreeElement(GUITreeView::TreeElement* element, const WString& name) { SceneTreeElement* sceneTreeElement = static_cast(element); HSceneObject so = sceneTreeElement->mSceneObject; CmdRecordSO::execute(so, L"Renamed \"" + toWString(so->getName()) + L"\""); so->setName(toString(name)); } void GUISceneTreeView::deleteTreeElement(TreeElement* element) { SceneTreeElement* sceneTreeElement = static_cast(element); HSceneObject so = sceneTreeElement->mSceneObject; CmdDeleteSO::execute(so, L"Deleted \"" + toWString(so->getName()) + L"\""); } void GUISceneTreeView::deleteTreeElementInternal(GUITreeView::TreeElement* element) { closeTemporarilyExpandedElements(); // In case this element is one of them if (element->mIsHighlighted) clearPing(); if(element->mIsSelected) unselectElement(element); bs_delete(element); } bool GUISceneTreeView::acceptDragAndDrop() const { return DragAndDropManager::instance().isDragInProgress() && (DragAndDropManager::instance().getDragTypeId() == (UINT32)DragAndDropType::SceneObject || DragAndDropManager::instance().getDragTypeId() == (UINT32)DragAndDropType::Resources); } void GUISceneTreeView::dragAndDropStart() { DraggedSceneObjects* draggedSceneObjects = bs_new((UINT32)mSelectedElements.size()); UINT32 cnt = 0; for(auto& selectedElement : mSelectedElements) { SceneTreeElement* sceneTreeElement = static_cast(selectedElement.element); draggedSceneObjects->objects[cnt] = sceneTreeElement->mSceneObject; cnt++; } DragAndDropManager::instance().startDrag((UINT32)DragAndDropType::SceneObject, (void*)draggedSceneObjects, std::bind(&GUISceneTreeView::dragAndDropFinalize, this), false); } void GUISceneTreeView::dragAndDropEnded(TreeElement* overTreeElement) { UINT32 dragTypeId = DragAndDropManager::instance().getDragTypeId(); if (dragTypeId == (UINT32)DragAndDropType::SceneObject) { if (overTreeElement != nullptr) { DraggedSceneObjects* draggedSceneObjects = reinterpret_cast(DragAndDropManager::instance().getDragData()); Vector sceneObjects; SceneTreeElement* sceneTreeElement = static_cast(overTreeElement); HSceneObject newParent = sceneTreeElement->mSceneObject; for (UINT32 i = 0; i < draggedSceneObjects->numObjects; i++) { if (draggedSceneObjects->objects[i] != newParent) sceneObjects.push_back(draggedSceneObjects->objects[i]); } CmdReparentSO::execute(sceneObjects, newParent); } } else if (dragTypeId == (UINT32)DragAndDropType::Resources) { DraggedResources* draggedResources = reinterpret_cast(DragAndDropManager::instance().getDragData()); HSceneObject newParent; if (overTreeElement != nullptr) { SceneTreeElement* sceneTreeElement = static_cast(overTreeElement); newParent = sceneTreeElement->mSceneObject; } for (auto& path : draggedResources->resourcePaths) { ProjectLibrary::LibraryEntry* entry = gProjectLibrary().findEntry(path); if (entry != nullptr && entry->type == ProjectLibrary::LibraryEntryType::File) { ProjectLibrary::ResourceEntry* resEntry = static_cast(entry); if (resEntry->meta->getTypeID() == TID_Prefab) { HPrefab prefab = static_resource_cast(gResources().loadFromUUID(resEntry->meta->getUUID())); if (prefab != nullptr) { HSceneObject instance = CmdInstantiateSO::execute(prefab, L"Instantiated " + prefab->getName()); if (newParent != nullptr) instance->setParent(newParent); } } } } } } void GUISceneTreeView::dragAndDropFinalize() { mDragInProgress = false; _markLayoutAsDirty(); if (DragAndDropManager::instance().getDragTypeId() == (UINT32)DragAndDropType::SceneObject) { DraggedSceneObjects* draggedSceneObjects = reinterpret_cast(DragAndDropManager::instance().getDragData()); bs_delete(draggedSceneObjects); } } bool GUISceneTreeView::_acceptDragAndDrop(const Vector2I position, UINT32 typeId) const { return typeId == (UINT32)DragAndDropType::SceneObject || typeId == (UINT32)DragAndDropType::Resources; } void GUISceneTreeView::selectionChanged() { onSelectionChanged(); sendMessage(SELECTION_CHANGED_MSG); } Vector GUISceneTreeView::getSelection() const { Vector selectedSOs; for (auto& selectedElem : mSelectedElements) { SceneTreeElement* sceneTreeElement = static_cast(selectedElem.element); selectedSOs.push_back(sceneTreeElement->mSceneObject); } return selectedSOs; } void GUISceneTreeView::setSelection(const Vector& objects) { unselectAll(); SceneTreeElement& root = mRootElement; Stack todo; todo.push(&mRootElement); while (!todo.empty()) { SceneTreeElement* currentElem = todo.top(); todo.pop(); auto iterFind = std::find(objects.begin(), objects.end(), currentElem->mSceneObject); if (iterFind != objects.end()) { expandToElement(currentElem); selectElement(currentElem); } for (auto& child : currentElem->mChildren) { SceneTreeElement* sceneChild = static_cast(child); todo.push(sceneChild); } } } void GUISceneTreeView::ping(const HSceneObject& object) { SceneTreeElement& root = mRootElement; Stack todo; todo.push(&mRootElement); while (!todo.empty()) { SceneTreeElement* currentElem = todo.top(); todo.pop(); if (currentElem->mSceneObject == object) { GUITreeView::ping(currentElem); break; } for (auto& child : currentElem->mChildren) { SceneTreeElement* sceneChild = static_cast(child); todo.push(sceneChild); } } } GUISceneTreeView::SceneTreeElement* GUISceneTreeView::findTreeElement(const HSceneObject& so) { SceneTreeElement& root = mRootElement; Stack todo; todo.push(&mRootElement); while (!todo.empty()) { SceneTreeElement* currentElem = todo.top(); todo.pop(); if (so == currentElem->mSceneObject) return currentElem; for (auto& child : currentElem->mChildren) { SceneTreeElement* sceneChild = static_cast(child); todo.push(sceneChild); } } return nullptr; } void GUISceneTreeView::duplicateSelection() { Vector duplicateList; for (auto& selectedElem : mSelectedElements) { SceneTreeElement* sceneElement = static_cast(selectedElem.element); duplicateList.push_back(sceneElement->mSceneObject); } cleanDuplicates(duplicateList); if (duplicateList.size() == 0) return; WString message; if (duplicateList.size() == 1) message = L"Duplicated " + toWString(duplicateList[0]->getName()); else message = L"Duplicated " + toWString(duplicateList.size()) + L" elements"; CmdCloneSO::execute(duplicateList, message); } void GUISceneTreeView::copySelection() { clearCopyList(); for (auto& selectedElem : mSelectedElements) { SceneTreeElement* sceneElement = static_cast(selectedElem.element); mCopyList.push_back(sceneElement->mSceneObject); } mCutFlag = false; } void GUISceneTreeView::cutSelection() { clearCopyList(); for (auto& selectedElem : mSelectedElements) { SceneTreeElement* sceneElement = static_cast(selectedElem.element); mCopyList.push_back(sceneElement->mSceneObject); sceneElement->mIsGrayedOut = true; updateElementGUI(sceneElement); } mCutFlag = true; _markLayoutAsDirty(); } void GUISceneTreeView::paste() { cleanDuplicates(mCopyList); if (mCopyList.size() == 0) return; HSceneObject parent = mRootElement.mSceneObject; if (mSelectedElements.size() > 0) { SceneTreeElement* sceneElement = static_cast(mSelectedElements[0].element); parent = sceneElement->mSceneObject; } if (mCutFlag) { WString message; if (mCopyList.size() == 1) message = L"Moved " + toWString(mCopyList[0]->getName()); else message = L"Moved " + toWString(mCopyList.size()) + L" elements"; CmdReparentSO::execute(mCopyList, parent, message); clearCopyList(); } else { WString message; if (mCopyList.size() == 1) message = L"Copied " + toWString(mCopyList[0]->getName()); else message = L"Copied " + toWString(mCopyList.size()) + L" elements"; Vector clones = CmdCloneSO::execute(mCopyList, message); for (auto& clone : clones) clone->setParent(parent); } } void GUISceneTreeView::clearCopyList() { for (auto& so : mCopyList) { if (so.isDestroyed()) continue; TreeElement* treeElem = findTreeElement(so); if (treeElem != nullptr) { treeElem->mIsGrayedOut = false; updateElementGUI(treeElem); } } mCopyList.clear(); _markLayoutAsDirty(); } void GUISceneTreeView::createNewSO() { HSceneObject newSO = CmdCreateSO::execute("New", 0, L"Created a new SceneObject"); if (mSelectedElements.size() > 0) { SceneTreeElement* sceneElement = static_cast(mSelectedElements[0].element); newSO->setParent(sceneElement->mSceneObject); } updateTreeElementHierarchy(); TreeElement* newTreeElement = findTreeElement(newSO); expandToElement(newTreeElement); setSelection({ newSO }); renameSelected(); } void GUISceneTreeView::cleanDuplicates(Vector& objects) { auto isChildOf = [&](const HSceneObject& parent, const HSceneObject& child) { HSceneObject elem = child; while (elem != nullptr && elem != parent) elem = elem->getParent(); return elem == parent; }; Vector cleanList; for (UINT32 i = 0; i < (UINT32)objects.size(); i++) { bool foundParent = false; for (UINT32 j = 0; j < (UINT32)objects.size(); j++) { if (i != j && isChildOf(objects[j], objects[i])) { foundParent = true; break; } } if (!foundParent) cleanList.push_back(objects[i]); } objects = cleanList; } const String& GUISceneTreeView::getGUITypeName() { static String typeName = "SceneTreeView"; return typeName; } }