瀏覽代碼

Scene tree view features (untested)
- cut/copy/paste/duplicate
- context menu
- create child

Marko Pintera 10 年之前
父節點
當前提交
e8ef106115

+ 3 - 0
BansheeCore/Include/BsSceneObject.h

@@ -51,6 +51,9 @@ namespace BansheeEngine
 		/**
 		 * @brief	Creates a new SceneObject with the specified name. Object will be placed in the top
 		 *			of the scene hierarchy.
+		 *
+		 * @param	name	Name of the scene object.
+		 * @param	flags	Optional flags that control object behavior. See SceneObjectFlags.
 		 */
 		static HSceneObject create(const String& name, UINT32 flags = 0);
 

+ 4 - 0
BansheeEditor/BansheeEditor.vcxproj

@@ -267,6 +267,8 @@
   <ItemGroup>
     <ClInclude Include="Include\BsBuildDataRTTI.h" />
     <ClInclude Include="Include\BsBuildManager.h" />
+    <ClInclude Include="Include\BsCmdCloneSO.h" />
+    <ClInclude Include="Include\BsCmdCreateSO.h" />
     <ClInclude Include="Include\BsCmdDeleteSO.h" />
     <ClInclude Include="Include\BsCmdInputFieldValueChange.h" />
     <ClInclude Include="Include\BsCmdRecordSO.h" />
@@ -346,6 +348,8 @@
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="Source\BsBuildManager.cpp" />
+    <ClCompile Include="Source\BsCmdCloneSO.cpp" />
+    <ClCompile Include="Source\BsCmdCreateSO.cpp" />
     <ClCompile Include="Source\BsCmdRecordSO.cpp" />
     <ClCompile Include="Source\BsCmdReparentSO.cpp" />
     <ClCompile Include="Source\BsCmdUtility.cpp" />

+ 12 - 0
BansheeEditor/BansheeEditor.vcxproj.filters

@@ -264,6 +264,12 @@
     <ClInclude Include="Include\BsCmdDeleteSO.h">
       <Filter>Header Files\Commands</Filter>
     </ClInclude>
+    <ClInclude Include="Include\BsCmdCreateSO.h">
+      <Filter>Header Files\Commands</Filter>
+    </ClInclude>
+    <ClInclude Include="Include\BsCmdCloneSO.h">
+      <Filter>Header Files\Commands</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="Source\BsEditorCommand.cpp">
@@ -476,5 +482,11 @@
     <ClCompile Include="Source\BsCmdDeleteSO.cpp">
       <Filter>Source Files\Commands</Filter>
     </ClCompile>
+    <ClCompile Include="Source\BsCmdCreateSO.cpp">
+      <Filter>Source Files\Commands</Filter>
+    </ClCompile>
+    <ClCompile Include="Source\BsCmdCloneSO.cpp">
+      <Filter>Source Files\Commands</Filter>
+    </ClCompile>
   </ItemGroup>
 </Project>

+ 59 - 0
BansheeEditor/Include/BsCmdCloneSO.h

@@ -0,0 +1,59 @@
+#pragma once
+
+#include "BsEditorPrerequisites.h"
+#include "BsEditorCommand.h"
+#include "BsUndoRedo.h"
+#include "BsCmdUtility.h"
+
+namespace BansheeEngine
+{
+	/**
+	 * @brief	A command used for undo/redo purposes. Clones scene object(s)
+	 *			and removes them as an undo operation.
+	 */
+	class CmdCloneSO : public EditorCommand
+	{
+	public:
+		~CmdCloneSO();
+
+		/**
+		 * @brief	Creates a new scene object by cloning an existing object.
+		 *			Automatically registers the command with undo/redo system.
+		 *
+		 * @param	sceneObject		Scene object to clone.
+		 * @param	description		Optional description of what exactly the command does.
+		 *
+		 * @return	Cloned object.
+		 */
+		static HSceneObject execute(const HSceneObject& sceneObject, const WString& description = StringUtil::WBLANK);
+
+		/**
+		 * @brief	Creates new scene object(s) by cloning existing objects.
+		 *			Automatically registers the command with undo/redo system.
+		 *
+		 * @param	sceneObjects	 Scene object(s) to clone.
+		 * @param	description		Optional description of what exactly the command does.
+		 *
+		 * @return	Cloned objects.
+		 */
+		static Vector<HSceneObject> execute(const Vector<HSceneObject>& sceneObjects, const WString& description = StringUtil::WBLANK);
+
+		/**
+		 * @copydoc	EditorCommand::commit
+		 */
+		void commit() override;
+
+		/**
+		 * @copydoc	EditorCommand::revert
+		 */
+		void revert() override;
+
+	private:
+		friend class UndoRedo;
+
+		CmdCloneSO(const WString& description, const Vector<HSceneObject>& originals);
+
+		Vector<HSceneObject> mOriginals;
+		Vector<HSceneObject> mClones;
+	};
+}

+ 51 - 0
BansheeEditor/Include/BsCmdCreateSO.h

@@ -0,0 +1,51 @@
+#pragma once
+
+#include "BsEditorPrerequisites.h"
+#include "BsEditorCommand.h"
+#include "BsUndoRedo.h"
+#include "BsCmdUtility.h"
+
+namespace BansheeEngine
+{
+	/**
+	 * @brief	A command used for undo/redo purposes. Creates a scene object 
+	 *			and removes it as an undo operation.
+	 */
+	class CmdCreateSO : public EditorCommand
+	{
+	public:
+		~CmdCreateSO();
+
+		/**
+		 * @brief	Creates a new scene object.
+		 *			Automatically registers the command with undo/redo system.
+		 *
+		 * @param	name		Name of the scene object.
+		 * @param	flags		Optional creation flags for the scene object.
+		 * @param	description	Optional description of what exactly the command does.
+		 *
+		 * @return	Newly created scene object.
+		 */
+		static HSceneObject execute(const String& name, UINT32 flags, const WString& description = StringUtil::WBLANK);
+
+		/**
+		 * @copydoc	EditorCommand::commit
+		 */
+		void commit() override;
+
+		/**
+		 * @copydoc	EditorCommand::revert
+		 */
+		void revert() override;
+
+	private:
+		friend class UndoRedo;
+
+		CmdCreateSO(const WString& description, const String& name, UINT32 flags);
+
+		String mName;
+		UINT32 mFlags;
+
+		HSceneObject mSceneObject;
+	};
+}

+ 12 - 1
BansheeEditor/Include/BsCmdReparentSO.h

@@ -15,7 +15,7 @@ namespace BansheeEngine
 	{
 	public:
 		/**
-		 * @brief	Creates and executes the command on the provided scene object.
+		 * @brief	Creates and executes the command on the provided scene object(s).
 		 *			Automatically registers the command with undo/redo system.
 		 *
 		 * @param	sceneObjects	Object(s) to change the parent for.
@@ -25,6 +25,17 @@ namespace BansheeEngine
 		static void execute(const Vector<HSceneObject>& sceneObjects, const HSceneObject& newParent, 
 			const WString& description = StringUtil::WBLANK);
 
+		/**
+		 * @brief	Creates and executes the command on the provided scene object.
+		 *			Automatically registers the command with undo/redo system.
+		 *
+		 * @param	sceneObject		Object to change the parent for.
+		 * @param	newParent		New parent for the provided objects.
+		 * @param	description		Optional description of what exactly the command does.
+		 */
+		static void execute(HSceneObject& sceneObject, const HSceneObject& newParent, 
+			const WString& description = StringUtil::WBLANK);
+
 		/**
 		 * @copydoc	EditorCommand::commit
 		 */

+ 46 - 0
BansheeEditor/Include/BsGUISceneTreeView.h

@@ -179,6 +179,52 @@ namespace BansheeEngine
 		 *			actually deleting the referenced SceneObject.
 		 */
 		void deleteTreeElementInternal(TreeElement* element);
+
+		/**
+		 * @brief	Attempts to find a tree element referencing the specified
+		 *			scene object.
+		 */
+		SceneTreeElement* findTreeElement(const HSceneObject& so);
+
+		/**
+		 * @copydoc	GUITreeView::duplicateSelection
+		 */
+		void duplicateSelection() override;
+
+		/**
+		 * @copydoc	GUITreeView::copySelection
+		 */
+		void copySelection() override;
+		
+		/**
+		 * @copydoc	GUITreeView::cutSelection
+		 */
+		void cutSelection() override;
+
+		/**
+		 * @copydoc	GUITreeView::paste
+		 */
+		void paste() override;
+
+		/**
+		 * @brief	Creates a new scene object as a child of the currently selected object (if any).
+		 */
+		void createNewSO();
+
+		/**
+		 * @brief	Removes all elements from the list used for copy/cut operations.
+		 */
+		void clearCopyList();
+
+		/**
+		 * @brief	Cleans duplicate objects from the provided scene object list.
+		 *			This involves removing child elements if their parents are
+		 *			already part of the list.
+		 */
+		static void cleanDuplicates(Vector<HSceneObject>& objects);
+
+		Vector<HSceneObject> mCopyList;
+		bool mCutFlag;
 	};
 
 	typedef ServiceLocator<GUISceneTreeView> SceneTreeViewLocator;

+ 36 - 0
BansheeEditor/Include/BsGUITreeView.h

@@ -55,6 +55,7 @@ namespace BansheeEngine
 			bool mIsSelected;
 			bool mIsHighlighted;
 			bool mIsVisible;
+			bool mIsGrayedOut;
 
 			bool isParentRec(TreeElement* element) const;
 		};
@@ -254,6 +255,16 @@ namespace BansheeEngine
 		 */
 		void unselectAll();
 
+		/**
+		 * @brief	Starts rename operation on the currently selected element.
+		 */
+		void renameSelected();
+
+		/**
+		 * @brief	Deletes all currently selected elements.
+		 */
+		void deleteSelection();
+
 		/**
 		 * @brief	Expands all parents of the provided TreeElement making it interactable.
 		 */
@@ -269,6 +280,26 @@ namespace BansheeEngine
 		 */
 		void collapseElement(TreeElement* element);
 
+		/**
+		 * @brief	Duplicates the currently selected entries.
+		 */
+		virtual void duplicateSelection() { }
+
+		/**
+		 * @brief	Marks the current selection for copying.
+		 */
+		virtual void copySelection() { }
+		
+		/**
+		 * @brief	Marks the current selection for cutting.
+		 */
+		virtual void cutSelection() { }
+
+		/**
+		 * @brief	Pastes a set of entries previously marked for cut or copy.
+		 */
+		virtual void paste() { }
+
 		/**
 		 * @brief	Rebuilds the needed GUI elements for the provided TreeElement.
 		 */
@@ -359,6 +390,10 @@ namespace BansheeEngine
 
 		static VirtualButton mRenameVB;
 		static VirtualButton mDeleteVB;
+		static VirtualButton mDuplicateVB;
+		static VirtualButton mCutVB;
+		static VirtualButton mCopyVB;
+		static VirtualButton mPasteVB;
 
 		static const UINT32 ELEMENT_EXTRA_SPACING;
 		static const UINT32 INDENT_SIZE;
@@ -367,5 +402,6 @@ namespace BansheeEngine
 		static const float AUTO_EXPAND_DELAY_SEC;
 		static const float SCROLL_AREA_HEIGHT_PCT;
 		static const UINT32 SCROLL_SPEED_PX_PER_SEC;
+		static const Color GRAYED_OUT_COLOR;
 	};
 }

+ 61 - 0
BansheeEditor/Source/BsCmdCloneSO.cpp

@@ -0,0 +1,61 @@
+#include "BsCmdCloneSO.h"
+#include "BsSceneObject.h"
+
+namespace BansheeEngine
+{
+	CmdCloneSO::CmdCloneSO(const WString& description, const Vector<HSceneObject>& originals)
+		:EditorCommand(description), mOriginals(originals)
+	{
+
+	}
+
+	CmdCloneSO::~CmdCloneSO()
+	{
+
+	}
+
+	HSceneObject CmdCloneSO::execute(const HSceneObject& sceneObject, const WString& description)
+	{
+		// Register command and commit it
+		CmdCloneSO* command = new (bs_alloc<CmdCloneSO>()) CmdCloneSO(description, { sceneObject });
+		UndoRedo::instance().registerCommand(command);
+		command->commit();
+
+		if (command->mClones.size() > 0)
+			return command->mClones[0];
+
+		return HSceneObject();
+	}
+
+	Vector<HSceneObject> CmdCloneSO::execute(const Vector<HSceneObject>& sceneObjects, const WString& description)
+	{
+		// Register command and commit it
+		CmdCloneSO* command = new (bs_alloc<CmdCloneSO>()) CmdCloneSO(description, sceneObjects);
+		UndoRedo::instance().registerCommand(command);
+		command->commit();
+
+		return command->mClones;
+	}
+
+	void CmdCloneSO::commit()
+	{
+		mClones.clear();
+
+		for (auto& original : mOriginals)
+		{
+			if (!original.isDestroyed())
+				mClones.push_back(original->clone());
+		}
+	}
+
+	void CmdCloneSO::revert()
+	{
+		for (auto& clone : mClones)
+		{
+			if (!clone.isDestroyed())
+				clone->destroy(true);
+		}
+
+		mClones.clear();
+	}
+}

+ 41 - 0
BansheeEditor/Source/BsCmdCreateSO.cpp

@@ -0,0 +1,41 @@
+#include "BsCmdCreateSO.h"
+#include "BsSceneObject.h"
+
+namespace BansheeEngine
+{
+	CmdCreateSO::CmdCreateSO(const WString& description, const String& name, UINT32 flags)
+		:EditorCommand(description), mName(name), mFlags(flags)
+	{
+
+	}
+
+	CmdCreateSO::~CmdCreateSO()
+	{
+
+	}
+
+	HSceneObject CmdCreateSO::execute(const String& name, UINT32 flags, const WString& description)
+	{
+		// Register command and commit it
+		CmdCreateSO* command = new (bs_alloc<CmdCreateSO>()) CmdCreateSO(description, name, flags);
+		UndoRedo::instance().registerCommand(command);
+		command->commit();
+
+		return command->mSceneObject;
+	}
+
+	void CmdCreateSO::commit()
+	{
+		mSceneObject = SceneObject::create(mName, mFlags);
+	}
+
+	void CmdCreateSO::revert()
+	{
+		if (mSceneObject == nullptr)
+			return;
+
+		if (!mSceneObject.isDestroyed())
+			mSceneObject->destroy(true);
+		mSceneObject = nullptr;
+	}
+}

+ 8 - 0
BansheeEditor/Source/BsCmdReparentSO.cpp

@@ -21,6 +21,14 @@ namespace BansheeEngine
 		command->commit();
 	}
 
+	void CmdReparentSO::execute(HSceneObject& sceneObject, const HSceneObject& newParent, const WString& description)
+	{
+		// Register command and commit it
+		CmdReparentSO* command = new (bs_alloc<CmdReparentSO>()) CmdReparentSO(description, { sceneObject }, newParent);
+		UndoRedo::instance().registerCommand(command);
+		command->commit();
+	}
+
 	void CmdReparentSO::commit()
 	{
 		if(mNewParent.isDestroyed())

+ 1 - 0
BansheeEditor/Source/BsEditorApplication.cpp

@@ -111,6 +111,7 @@ namespace BansheeEngine
 			inputConfig->registerButton("Copy", BC_C, ButtonModifier::Ctrl);
 			inputConfig->registerButton("Cut", BC_X, ButtonModifier::Ctrl);
 			inputConfig->registerButton("Paste", BC_V, ButtonModifier::Ctrl);
+			inputConfig->registerButton("Duplicate", BC_D, ButtonModifier::Ctrl);
 			inputConfig->registerButton("Delete", BC_DELETE);
 		}
 

+ 199 - 1
BansheeEditor/Source/BsGUISceneTreeView.cpp

@@ -5,6 +5,8 @@
 #include "BsCmdRecordSO.h"
 #include "BsCmdReparentSO.h"
 #include "BsCmdDeleteSO.h"
+#include "BsCmdCloneSO.h"
+#include "BsCmdCreateSO.h"
 #include "BsDragAndDropManager.h"
 #include "BsSelection.h"
 #include "BsGUIResourceTreeView.h"
@@ -12,6 +14,7 @@
 #include "BsProjectResourceMeta.h"
 #include "BsPrefab.h"
 #include "BsResources.h"
+#include "BsGUIContextMenu.h"
 
 namespace BansheeEngine
 {
@@ -33,9 +36,22 @@ namespace BansheeEngine
 		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)
+		dragSepHighlightStyle, dimensions), mCutFlag(false)
 	{
 		SceneTreeViewLocator::_provide(this);
+
+		GUIContextMenuPtr contextMenu = bs_shared_ptr<GUIContextMenu>();
+
+		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()
@@ -417,6 +433,188 @@ namespace BansheeEngine
 		}
 	}
 
+	GUISceneTreeView::SceneTreeElement* GUISceneTreeView::findTreeElement(const HSceneObject& so)
+	{
+		SceneTreeElement& root = mRootElement;
+
+		Stack<SceneTreeElement*> 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<SceneTreeElement*>(child);
+				todo.push(sceneChild);
+			}
+		}
+
+		return nullptr;
+	}
+
+	void GUISceneTreeView::duplicateSelection()
+	{
+		Vector<HSceneObject> duplicateList;
+		for (auto& selectedElem : mSelectedElements)
+		{
+			SceneTreeElement* sceneElement = static_cast<SceneTreeElement*>(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<SceneTreeElement*>(selectedElem.element);
+			mCopyList.push_back(sceneElement->mSceneObject);
+		}
+
+		mCutFlag = false;
+	}
+
+	void GUISceneTreeView::cutSelection()
+	{
+		clearCopyList();
+
+		for (auto& selectedElem : mSelectedElements)
+		{
+			SceneTreeElement* sceneElement = static_cast<SceneTreeElement*>(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<SceneTreeElement*>(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";
+
+			CmdCloneSO::execute(mCopyList, message);
+		}
+	}
+
+	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<SceneTreeElement*>(mSelectedElements[0].element);
+			newSO->setParent(sceneElement->mSceneObject);
+		}
+
+		updateTreeElementHierarchy();
+		setSelection({ newSO });
+		renameSelected();
+	}
+
+	void GUISceneTreeView::cleanDuplicates(Vector<HSceneObject>& objects)
+	{
+		auto isChildOf = [&](const HSceneObject& parent, const HSceneObject& child)
+		{
+			HSceneObject elem = child;
+
+			while (elem != nullptr && elem != parent)
+				elem = child->getParent();
+
+			return elem == parent;
+		};
+
+		Vector<HSceneObject> 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";

+ 100 - 44
BansheeEditor/Source/BsGUITreeView.cpp

@@ -26,13 +26,18 @@ namespace BansheeEngine
 	const float GUITreeView::AUTO_EXPAND_DELAY_SEC = 0.5f;
 	const float GUITreeView::SCROLL_AREA_HEIGHT_PCT = 0.1f;
 	const UINT32 GUITreeView::SCROLL_SPEED_PX_PER_SEC = 100;
+	const Color GUITreeView::GRAYED_OUT_COLOR = Color(1.0f, 1.0f, 1.0f, 0.5f);
 
 	VirtualButton GUITreeView::mRenameVB = VirtualButton("Rename");
 	VirtualButton GUITreeView::mDeleteVB = VirtualButton("Delete");
+	VirtualButton GUITreeView::mDuplicateVB = VirtualButton("Duplicate");
+	VirtualButton GUITreeView::mCutVB = VirtualButton("Cut");
+	VirtualButton GUITreeView::mCopyVB = VirtualButton("Copy");
+	VirtualButton GUITreeView::mPasteVB = VirtualButton("Paste");
 
 	GUITreeView::TreeElement::TreeElement()
 		:mParent(nullptr), mFoldoutBtn(nullptr), mElement(nullptr), mIsSelected(false),
-		mIsExpanded(false), mSortedIdx(0), mIsVisible(true), mIsHighlighted(false)
+		mIsExpanded(false), mSortedIdx(0), mIsVisible(true), mIsHighlighted(false), mIsGrayedOut(false)
 	{ }
 
 	GUITreeView::TreeElement::~TreeElement()
@@ -285,8 +290,24 @@ namespace BansheeEngine
 					}
 					else
 					{
+						bool doRename = false;
+						if (isSelectionActive())
+						{
+							for (auto& selectedElem : mSelectedElements)
+							{
+								if (selectedElem.element == treeElement)
+								{
+									doRename = true;
+									break;
+								}
+							}
+						}
+
 						unselectAll();
 						selectElement(treeElement);
+
+						if (doRename)
+							renameSelected();
 					}
 
 					_markLayoutAsDirty();
@@ -455,54 +476,33 @@ namespace BansheeEngine
 	{
 		if(ev.getButton() == mRenameVB)
 		{
-			if(isSelectionActive() && mEditElement == nullptr)
-			{
-				clearPing();
-				enableEdit(mSelectedElements[0].element);
-				unselectAll();
-			}
+			renameSelected();
 
 			return true;
 		}
 		else if(ev.getButton() == mDeleteVB)
 		{
-			if(isSelectionActive())
-			{
-				auto isChildOf = [&] (const TreeElement* parent, const TreeElement* child)
-				{
-					const TreeElement* elem = child;
-
-					while(elem != nullptr && elem != parent)
-						elem = child->mParent;
-
-					return elem == parent;
-				};
-
-				// Ensure we don't unnecessarily try to delete children if their
-				// parent is getting deleted anyway
-				Vector<TreeElement*> elementsToDelete;
-				for(UINT32 i = 0; i < (UINT32)mSelectedElements.size(); i++)
-				{
-					bool hasDeletedParent = false;
-					for(UINT32 j = i + 1; j < (UINT32)mSelectedElements.size(); j++)
-					{
-						if(isChildOf(mSelectedElements[j].element, mSelectedElements[i].element))
-						{
-							hasDeletedParent = true;
-							break;
-						}
-					}
-
-					if(!hasDeletedParent)
-						elementsToDelete.push_back(mSelectedElements[i].element);
-				}
-
-				clearPing();
-				unselectAll();
-
-				for(auto& elem : elementsToDelete)
-					deleteTreeElement(elem);
-			}
+			deleteSelection();
+		}
+		else if (ev.getButton() == mDuplicateVB)
+		{
+			duplicateSelection();
+			return true;
+		}
+		else if (ev.getButton() == mCutVB)
+		{
+			cutSelection();
+			return true;
+		}
+		else if (ev.getButton() == mCopyVB)
+		{
+			copySelection();
+			return true;
+		}
+		else if (ev.getButton() == mPasteVB)
+		{
+			paste();
+			return true;
 		}
 
 		return false;
@@ -574,6 +574,60 @@ namespace BansheeEngine
 		selectionChanged();
 	}
 
+	void GUITreeView::renameSelected()
+	{
+		if (isSelectionActive() && mEditElement == nullptr)
+		{
+			clearPing();
+			enableEdit(mSelectedElements[0].element);
+			unselectAll();
+		}
+	}
+
+	void GUITreeView::deleteSelection()
+	{
+		if (isSelectionActive())
+		{
+			auto isChildOf = [&](const TreeElement* parent, const TreeElement* child)
+			{
+				const TreeElement* elem = child;
+
+				while (elem != nullptr && elem != parent)
+					elem = child->mParent;
+
+				return elem == parent;
+			};
+
+			// Ensure we don't unnecessarily try to delete children if their
+			// parent is getting deleted anyway
+			Vector<TreeElement*> elementsToDelete;
+			for (UINT32 i = 0; i < (UINT32)mSelectedElements.size(); i++)
+			{
+				bool hasDeletedParent = false;
+				for (UINT32 j = 0; j < (UINT32)mSelectedElements.size(); j++)
+				{
+					if (i == j)
+						continue;
+
+					if (isChildOf(mSelectedElements[j].element, mSelectedElements[i].element))
+					{
+						hasDeletedParent = true;
+						break;
+					}
+				}
+
+				if (!hasDeletedParent)
+					elementsToDelete.push_back(mSelectedElements[i].element);
+			}
+
+			clearPing();
+			unselectAll();
+
+			for (auto& elem : elementsToDelete)
+				deleteTreeElement(elem);
+		}
+	}
+
 	void GUITreeView::ping(TreeElement* element)
 	{
 		clearPing();
@@ -713,6 +767,8 @@ namespace BansheeEngine
 				_registerChildElement(element->mElement);
 			}
 
+			element->mElement->setTint(element->mIsGrayedOut ? GRAYED_OUT_COLOR : Color::White);
+
 			if(element->mChildren.size() > 0)
 			{
 				if(element->mFoldoutBtn == nullptr)

+ 6 - 3
TODO.txt

@@ -55,9 +55,12 @@ Code quality improvements:
 Polish
 
 SceneTreeView
- - Add cut/copy/duplicate/paste functionality (+ appropriate context menu)
- - Clicking on an already selected element should start rename
- - Also add context with "New SceneObject" & "New SceneObject (Child)"
+ - Test: 
+    - Cut/Copy/Paste/duplicate
+    - Context menu
+	- Ignore shortcuts when out of focus
+    - Creating new objects
+	- Clicking on an already selected element should start rename
 
 Ribek use:
  - Camera, Renderable, Material, Texture inspector