Răsfoiți Sursa

Lots more work on ProjectLibrary, plus work on ResourceTreeView

Marko Pintera 12 ani în urmă
părinte
comite
236f028298

+ 2 - 0
CamelotClient/CamelotClient.vcxproj

@@ -271,6 +271,7 @@
     <ClInclude Include="Include\BsEditorWindowManager.h" />
     <ClInclude Include="Include\BsEditorWindowManager.h" />
     <ClInclude Include="Include\BsGUIDockSlider.h" />
     <ClInclude Include="Include\BsGUIDockSlider.h" />
     <ClInclude Include="Include\BsGUIMenuBar.h" />
     <ClInclude Include="Include\BsGUIMenuBar.h" />
+    <ClInclude Include="Include\BsGUIResourceTreeView.h" />
     <ClInclude Include="Include\BsGUISceneTreeView.h" />
     <ClInclude Include="Include\BsGUISceneTreeView.h" />
     <ClInclude Include="Include\BsGUITabbedTitleBar.h" />
     <ClInclude Include="Include\BsGUITabbedTitleBar.h" />
     <ClInclude Include="Include\BsGUITabButton.h" />
     <ClInclude Include="Include\BsGUITabButton.h" />
@@ -305,6 +306,7 @@
     <ClCompile Include="Source\BsEditorWindowManager.cpp" />
     <ClCompile Include="Source\BsEditorWindowManager.cpp" />
     <ClCompile Include="Source\BsGUIDockSlider.cpp" />
     <ClCompile Include="Source\BsGUIDockSlider.cpp" />
     <ClCompile Include="Source\BsGUIMenuBar.cpp" />
     <ClCompile Include="Source\BsGUIMenuBar.cpp" />
+    <ClCompile Include="Source\BsGUIResourceTreeView.cpp" />
     <ClCompile Include="Source\BsGUISceneTreeView.cpp" />
     <ClCompile Include="Source\BsGUISceneTreeView.cpp" />
     <ClCompile Include="Source\BsGUITabbedTitleBar.cpp" />
     <ClCompile Include="Source\BsGUITabbedTitleBar.cpp" />
     <ClCompile Include="Source\BsGUITabButton.cpp" />
     <ClCompile Include="Source\BsGUITabButton.cpp" />

+ 6 - 0
CamelotClient/CamelotClient.vcxproj.filters

@@ -135,6 +135,9 @@
     <ClInclude Include="Include\BsResourceMetaRTTI.h">
     <ClInclude Include="Include\BsResourceMetaRTTI.h">
       <Filter>Header Files\Editor</Filter>
       <Filter>Header Files\Editor</Filter>
     </ClInclude>
     </ClInclude>
+    <ClInclude Include="Include\BsGUIResourceTreeView.h">
+      <Filter>Header Files\Editor</Filter>
+    </ClInclude>
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <ClCompile Include="stdafx.cpp">
     <ClCompile Include="stdafx.cpp">
@@ -230,5 +233,8 @@
     <ClCompile Include="Source\BsResourceMeta.cpp">
     <ClCompile Include="Source\BsResourceMeta.cpp">
       <Filter>Source Files\Editor</Filter>
       <Filter>Source Files\Editor</Filter>
     </ClCompile>
     </ClCompile>
+    <ClCompile Include="Source\BsGUIResourceTreeView.cpp">
+      <Filter>Source Files\Editor</Filter>
+    </ClCompile>
   </ItemGroup>
   </ItemGroup>
 </Project>
 </Project>

+ 2 - 1
CamelotClient/Include/BsEditorPrerequisites.h

@@ -29,7 +29,8 @@ namespace BansheeEditor
 	enum class DragAndDropType
 	enum class DragAndDropType
 	{
 	{
 		EditorWidget = 10000,
 		EditorWidget = 10000,
-		SceneObject = 10001
+		SceneObject = 10001,
+		Resources = 10002
 	};
 	};
 
 
 	enum TypeID_BansheeEditor
 	enum TypeID_BansheeEditor

+ 81 - 0
CamelotClient/Include/BsGUIResourceTreeView.h

@@ -0,0 +1,81 @@
+#pragma once
+
+#include "BsEditorPrerequisites.h"
+#include "BsGUITreeView.h"
+#include "BsVirtualInput.h"
+#include "CmPath.h"
+#include <boost/signal.hpp>
+
+namespace BansheeEditor
+{
+	class GUIResourceTreeView : public GUITreeView
+	{
+		struct ResourceTreeElement : public GUITreeView::TreeElement
+		{
+			CM::WPath mFullPath;
+		};
+
+		struct DraggedResources
+		{
+			CM::Vector<CM::String>::type resourceUUIDs;
+		};
+
+		struct InternalDraggedResources
+		{
+			InternalDraggedResources(CM::UINT32 numObjects);
+			~InternalDraggedResources();
+
+			CM::UINT32 numObjects;
+			CM::WPath* resourcePaths;
+		};
+
+	public:
+		static const CM::String& getGUITypeName();
+
+		static GUIResourceTreeView* create(BS::GUIWidget& parent,
+			BS::GUIElementStyle* backgroundStyle = nullptr, BS::GUIElementStyle* elementBtnStyle = nullptr, 
+			BS::GUIElementStyle* foldoutBtnStyle = nullptr, BS::GUIElementStyle* selectionBackgroundStyle = nullptr,
+			BS::GUIElementStyle* editBoxStyle = nullptr, BS::GUIElementStyle* dragHighlightStyle = nullptr, 
+			BS::GUIElementStyle* dragSepHighlightStyle = nullptr);
+
+		static GUIResourceTreeView* create(BS::GUIWidget& parent, const BS::GUIOptions& options, 
+			BS::GUIElementStyle* backgroundStyle = nullptr, BS::GUIElementStyle* elementBtnStyle = nullptr, 
+			BS::GUIElementStyle* foldoutBtnStyle = nullptr, BS::GUIElementStyle* selectionBackgroundStyle = nullptr,
+			BS::GUIElementStyle* editBoxStyle = nullptr, BS::GUIElementStyle* dragHighlightStyle = nullptr, 
+			BS::GUIElementStyle* dragSepHighlightStyle = nullptr);
+
+
+	protected:
+		virtual ~GUIResourceTreeView();
+
+	protected:
+		InternalDraggedResources* mDraggedResources;
+		ResourceTreeElement mRootElement;
+
+		GUIResourceTreeView(BS::GUIWidget& parent, BS::GUIElementStyle* backgroundStyle, BS::GUIElementStyle* elementBtnStyle, 
+			BS::GUIElementStyle* foldoutBtnStyle, BS::GUIElementStyle* selectionBackgroundStyle, BS::GUIElementStyle* editBoxStyle, 
+			BS::GUIElementStyle* dragHighlightStyle, BS::GUIElementStyle* dragSepHighlightStyle, const BS::GUILayoutOptions& layoutOptions);
+
+		virtual TreeElement& getRootElement() { return mRootElement; }
+		virtual const TreeElement& getRootElementConst() const { return mRootElement; }
+		virtual void updateTreeElementHierarchy();
+		virtual void renameTreeElement(TreeElement* element, const CM::WString& name);
+		virtual bool acceptDragAndDrop() const;
+		virtual void dragAndDropStart();
+		virtual void dragAndDropEnded(TreeElement* overTreeElement);
+		virtual void dragAndDropFinalize();
+
+		ResourceTreeElement* addTreeElement(ResourceTreeElement* parent, const CM::WPath& fullPath);
+		void deleteTreeElement(ResourceTreeElement* element);
+		void sortTreeElement(ResourceTreeElement* element);
+
+		ResourceTreeElement* findTreeElement(const CM::WPath& fullPath);
+
+		void entryAdded(const CM::WPath& path);
+		void entryRemoved(const CM::WPath& path);
+
+		void quicksortTreeElements(CM::Vector<TreeElement*>::type& elements, CM::INT32 first, CM::INT32 last);
+
+		CM::WPath findUniquePath(const CM::WPath& path);
+	};
+}

+ 7 - 0
CamelotClient/Include/BsGUISceneTreeView.h

@@ -11,7 +11,12 @@ namespace BansheeEditor
 	{
 	{
 		struct SceneTreeElement : public GUITreeView::TreeElement
 		struct SceneTreeElement : public GUITreeView::TreeElement
 		{
 		{
+			SceneTreeElement()
+				:mId(0)
+			{ }
+
 			CM::HSceneObject mSceneObject;
 			CM::HSceneObject mSceneObject;
+			CM::UINT32 mId;
 		};
 		};
 
 
 		struct DraggedSceneObjects
 		struct DraggedSceneObjects
@@ -59,5 +64,7 @@ namespace BansheeEditor
 		virtual void dragAndDropStart();
 		virtual void dragAndDropStart();
 		virtual void dragAndDropEnded(TreeElement* overTreeElement);
 		virtual void dragAndDropEnded(TreeElement* overTreeElement);
 		virtual void dragAndDropFinalize();
 		virtual void dragAndDropFinalize();
+
+		void deleteTreeElement(TreeElement* element);
 	};
 	};
 }
 }

+ 0 - 3
CamelotClient/Include/BsGUITreeView.h

@@ -31,7 +31,6 @@ namespace BansheeEditor
 			BS::GUILabel* mElement;
 			BS::GUILabel* mElement;
 
 
 			CM::String mName;
 			CM::String mName;
-			CM::UINT32 mId;
 
 
 			CM::UINT32 mSortedIdx;
 			CM::UINT32 mSortedIdx;
 			bool mIsExpanded;
 			bool mIsExpanded;
@@ -153,8 +152,6 @@ namespace BansheeEditor
 		void unselectElement(TreeElement* element);
 		void unselectElement(TreeElement* element);
 		void unselectAll();
 		void unselectAll();
 
 
-		void deleteTreeElement(TreeElement* element);
-
 		void closeTemporarilyExpandedElements();
 		void closeTemporarilyExpandedElements();
 		void temporarilyExpandElement(const GUITreeView::InteractableElement* mouseOverElement);
 		void temporarilyExpandElement(const GUITreeView::InteractableElement* mouseOverElement);
 		void scrollToElement(TreeElement* element, bool center);
 		void scrollToElement(TreeElement* element, bool center);

+ 48 - 6
CamelotClient/Include/BsProjectLibrary.h

@@ -8,29 +8,71 @@ namespace BansheeEditor
 {
 {
 	class ProjectLibrary : public CM::Module<ProjectLibrary>
 	class ProjectLibrary : public CM::Module<ProjectLibrary>
 	{
 	{
+	public:
 		struct LibraryEntry;
 		struct LibraryEntry;
 		struct ResourceEntry;
 		struct ResourceEntry;
 		struct DirectoryEntry;
 		struct DirectoryEntry;
 
 
+		enum class LibraryEntryType
+		{
+			File,
+			Directory
+		};
+
+		struct LibraryEntry
+		{
+			LibraryEntryType type;
+			CM::WPath path;
+
+			DirectoryEntry* parent;
+		};
+
+		struct ResourceEntry : public LibraryEntry
+		{
+			ResourceMetaPtr meta;
+			std::time_t lastUpdateTime;
+		};
+
+		struct DirectoryEntry : public LibraryEntry
+		{
+			CM::Vector<LibraryEntry*>::type mChildren;
+		};
+
 	public:
 	public:
 		ProjectLibrary();
 		ProjectLibrary();
 		~ProjectLibrary();
 		~ProjectLibrary();
 
 
-		void checkForModifications(const CM::WString& folder);
+		void update();
+		void checkForModifications(const CM::WString& fullPath);
+
+		const LibraryEntry* getRootEntry() const { return mRootEntry; }
+		LibraryEntry* findEntry(const CM::WPath& fullPath) const;
+
+		void moveEntry(const CM::WPath& oldPath, const CM::WPath& newPath);
+		void deleteEntry(const CM::WPath& path);
 
 
+		boost::signal<void(const CM::WPath&)> onEntryRemoved;
+		boost::signal<void(const CM::WPath&)> onEntryAdded;
 	private:
 	private:
 		static const CM::WString INTERNAL_RESOURCES_DIR;
 		static const CM::WString INTERNAL_RESOURCES_DIR;
 
 
 		DirectoryEntry* mRootEntry;
 		DirectoryEntry* mRootEntry;
+		CM::FolderMonitor* mMonitor;
+
+		ResourceEntry* addResourceInternal(DirectoryEntry* parent, const CM::WPath& filePath);
+		DirectoryEntry* addDirectoryInternal(DirectoryEntry* parent, const CM::WPath& dirPath);
+
+		void deleteResourceInternal(ResourceEntry* resource);
+		void deleteDirectoryInternal(DirectoryEntry* directory);
 
 
-		void addResource(DirectoryEntry* parent, const CM::WPath& filePath);
-		void reimportResource(ResourceEntry* resource);
+		void reimportResourceInternal(ResourceEntry* resource);
 
 
-		void deleteResource(ResourceEntry* resource);
-		void deleteDirectory(DirectoryEntry* directory);
+		void createInternalParentHierarchy(const CM::WPath& fullPath, DirectoryEntry** newHierarchyRoot, DirectoryEntry** newHierarchyLeaf);
 
 
 		bool isUpToDate(ResourceEntry* resource) const;
 		bool isUpToDate(ResourceEntry* resource) const;
+		CM::WPath getMetaPath(const CM::WPath& path) const;
+		bool isMeta(const CM::WPath& fullPath) const;
 
 
-		DirectoryEntry* findDirectoryEntry(const CM::WString& folder) const;
+		void onMonitorFileModified(const CM::WString& path);
 	};
 	};
 }
 }

+ 336 - 0
CamelotClient/Source/BsGUIResourceTreeView.cpp

@@ -0,0 +1,336 @@
+#include "BsGUIResourceTreeView.h"
+#include "BsGUISkin.h"
+#include "BsProjectLibrary.h"
+#include "BsDragAndDropManager.h"
+#include "CmResources.h"
+#include "CmResourceManifest.h"
+#include "BsProjectLibrary.h"
+#include "CmFileSystem.h"
+
+using namespace CamelotFramework;
+using namespace BansheeEngine;
+
+namespace BansheeEditor
+{
+	GUIResourceTreeView::InternalDraggedResources::InternalDraggedResources(UINT32 numObjects)
+		:numObjects(numObjects)
+	{
+		resourcePaths = cm_newN<WPath>(numObjects);
+	}
+
+	GUIResourceTreeView::InternalDraggedResources::~InternalDraggedResources()
+	{
+		cm_deleteN(resourcePaths, numObjects);
+		resourcePaths = nullptr;
+	}
+
+	GUIResourceTreeView::GUIResourceTreeView(GUIWidget& parent, GUIElementStyle* backgroundStyle, GUIElementStyle* elementBtnStyle, 
+		GUIElementStyle* foldoutBtnStyle, GUIElementStyle* selectionBackgroundStyle, GUIElementStyle* editBoxStyle, 
+		BS::GUIElementStyle* dragHighlightStyle, BS::GUIElementStyle* dragSepHighlightStyle, const GUILayoutOptions& layoutOptions)
+		:GUITreeView(parent, backgroundStyle, elementBtnStyle, foldoutBtnStyle, selectionBackgroundStyle, editBoxStyle, dragHighlightStyle,
+		dragSepHighlightStyle, layoutOptions), mDraggedResources(nullptr)
+	{
+		struct StackElem
+		{
+			StackElem(const ProjectLibrary::LibraryEntry* entry, ResourceTreeElement* treeElem)
+				:entry(entry), treeElem(treeElem)
+			{ }
+
+			const ProjectLibrary::LibraryEntry* entry;
+			ResourceTreeElement* treeElem;
+		};
+
+		ProjectLibrary::instance().onEntryAdded.connect(boost::bind(&GUIResourceTreeView::entryAdded, this, _1));
+		ProjectLibrary::instance().onEntryRemoved.connect(boost::bind(&GUIResourceTreeView::entryRemoved, this, _1));
+
+		const ProjectLibrary::LibraryEntry* rootEntry = ProjectLibrary::instance().getRootEntry();
+
+		mRootElement.mIsExpanded = true;
+
+		Stack<StackElem>::type todo;
+		todo.push(StackElem(rootEntry, &mRootElement));
+
+		while(!todo.empty())
+		{
+			StackElem curElem = todo.top();
+			todo.top();
+
+			const ProjectLibrary::DirectoryEntry* dirEntry = static_cast<const ProjectLibrary::DirectoryEntry*>(curElem.entry);
+
+			for(auto& child : dirEntry->mChildren)
+			{
+				ResourceTreeElement* newChild = addTreeElement(curElem.treeElem, curElem.entry->path);
+
+				if(child->type == ProjectLibrary::LibraryEntryType::Directory)
+					todo.push(StackElem(child, newChild));
+			}
+
+			sortTreeElement(curElem.treeElem);
+		}
+	}
+
+	GUIResourceTreeView::~GUIResourceTreeView()
+	{
+
+	}
+
+	GUIResourceTreeView* GUIResourceTreeView::create(GUIWidget& parent, GUIElementStyle* backgroundStyle, GUIElementStyle* elementBtnStyle, 
+		GUIElementStyle* foldoutBtnStyle, GUIElementStyle* selectionBackgroundStyle, GUIElementStyle* editBoxStyle, GUIElementStyle* dragHighlightStyle, 
+		GUIElementStyle* dragSepHighlightStyle)
+	{
+		return new (cm_alloc<GUIResourceTreeView, PoolAlloc>()) GUIResourceTreeView(parent, backgroundStyle, elementBtnStyle, foldoutBtnStyle, 
+			selectionBackgroundStyle, editBoxStyle, dragHighlightStyle, dragSepHighlightStyle, GUILayoutOptions::create(&GUISkin::DefaultStyle));
+	}
+
+	GUIResourceTreeView* GUIResourceTreeView::create(GUIWidget& parent, const GUIOptions& options, GUIElementStyle* backgroundStyle,
+		GUIElementStyle* elementBtnStyle, GUIElementStyle* foldoutBtnStyle, GUIElementStyle* selectionBackgroundStyle, 
+		GUIElementStyle* editBoxStyle, GUIElementStyle* dragHighlightStyle, GUIElementStyle* dragSepHighlightStyle)
+	{
+		return new (cm_alloc<GUIResourceTreeView, PoolAlloc>()) GUIResourceTreeView(parent, backgroundStyle, elementBtnStyle, 
+			foldoutBtnStyle, selectionBackgroundStyle, editBoxStyle, dragHighlightStyle, dragSepHighlightStyle, GUILayoutOptions::create(options, &GUISkin::DefaultStyle));
+	}
+
+	void GUIResourceTreeView::updateTreeElementHierarchy()
+	{
+		// Do nothing, updates are handled via callbacks
+	}
+
+	void GUIResourceTreeView::renameTreeElement(GUITreeView::TreeElement* element, const CM::WString& name)
+	{
+		ResourceTreeElement* resourceTreeElement = static_cast<ResourceTreeElement*>(element);
+		
+		WPath oldPath = resourceTreeElement->mFullPath;
+		WPath newPath = PathUtil::combine(PathUtil::parentPath(oldPath), toPath(name));
+
+		ProjectLibrary::instance().moveEntry(oldPath, findUniquePath(newPath));
+	}
+
+	GUIResourceTreeView::ResourceTreeElement* GUIResourceTreeView::addTreeElement(ResourceTreeElement* parent, const CM::WPath& fullPath)
+	{
+		ResourceTreeElement* newChild = cm_new<ResourceTreeElement>();
+		newChild->mParent = parent;
+		newChild->mName = toString(PathUtil::getFilename(fullPath));
+		newChild->mFullPath = fullPath;
+		newChild->mSortedIdx = (UINT32)parent->mChildren.size();
+		newChild->mIsDirty = true;
+
+		parent->mChildren.push_back(newChild);
+
+		return newChild;
+	}
+
+	void GUIResourceTreeView::deleteTreeElement(ResourceTreeElement* element)
+	{
+		closeTemporarilyExpandedElements(); // In case this element is one of them
+
+		if(element->mIsSelected)
+			unselectElement(element);
+
+		Stack<ResourceTreeElement*>::type todo;
+		todo.push(element);
+
+		while(!todo.empty())
+		{
+			ResourceTreeElement* cur = todo.top();
+			todo.pop();
+
+			for(auto& child : cur->mChildren)
+				todo.push(static_cast<ResourceTreeElement*>(child));
+		}
+
+		if(element->mParent != nullptr)
+		{
+			auto iterFind = std::find(element->mParent->mChildren.begin(), element->mParent->mChildren.end(), element);
+			if(iterFind != element->mParent->mChildren.end())
+				element->mParent->mChildren.erase(iterFind);
+		}
+
+		cm_delete(element);
+	}
+
+	void GUIResourceTreeView::sortTreeElement(ResourceTreeElement* element)
+	{
+		if(element->mChildren.size() > 0)
+			quicksortTreeElements(element->mChildren, 0, (INT32)element->mChildren.size() - 1);
+	}
+
+	void GUIResourceTreeView::quicksortTreeElements(CM::Vector<TreeElement*>::type& elements, INT32 first, INT32 last)
+	{
+		if(first < last)
+		{
+			int pivot = first + std::rand() % (last - first);
+
+			std::swap(elements[pivot]->mSortedIdx, elements[last]->mSortedIdx);
+			pivot = last;
+
+			int i = first;
+			for(int j = first; j < last; j++)
+			{
+				INT32 stringCompare = elements[j]->mName.compare(elements[pivot]->mName);
+				if(stringCompare < 0)
+				{
+					std::swap(elements[j]->mSortedIdx, elements[i]->mSortedIdx);
+					i++;
+				}
+			}
+
+			std::swap(elements[last]->mSortedIdx, elements[pivot]->mSortedIdx);
+
+			quicksortTreeElements(elements, first, i - 1);
+			quicksortTreeElements(elements, i + 1, last);
+		}
+	}
+
+	GUIResourceTreeView::ResourceTreeElement* GUIResourceTreeView::findTreeElement(const CM::WPath& fullPath)
+	{
+		auto pathIter = fullPath.begin();
+		auto rootIter = mRootElement.mFullPath.begin();
+
+		while(*pathIter == *rootIter)
+		{
+			++pathIter;
+			++rootIter;
+		}
+
+		if(pathIter == fullPath.begin()) // Supplied path not part of the root path
+			return nullptr;
+
+		--pathIter;
+
+		Stack<ResourceTreeElement*>::type todo;
+		todo.push(&mRootElement);
+
+		while(!todo.empty())
+		{
+			ResourceTreeElement* current = todo.top();
+			todo.pop();
+
+			if(*pathIter == *(--(current->mFullPath.end())))
+			{
+				for(auto& child : current->mChildren)
+					todo.push(static_cast<ResourceTreeElement*>(child));
+
+				if(pathIter == fullPath.end())
+					return current;
+
+				++pathIter;
+			}
+		}
+
+		return nullptr;
+	}
+
+	void GUIResourceTreeView::entryAdded(const WPath& path)
+	{
+		WPath parentPath = PathUtil::parentPath(path);
+
+		ResourceTreeElement* parentElement = findTreeElement(parentPath);
+		assert(parentElement != nullptr);
+
+		addTreeElement(parentElement, path);
+		sortTreeElement(parentElement);
+
+		markContentAsDirty();
+	}
+
+	void GUIResourceTreeView::entryRemoved(const WPath& path)
+	{
+		ResourceTreeElement* treeElement = findTreeElement(path);
+		
+		if(treeElement != nullptr)
+			deleteTreeElement(treeElement);
+	}
+
+	CM::WPath GUIResourceTreeView::findUniquePath(const CM::WPath& path)
+	{
+		if(FileSystem::exists(path))
+		{
+			WPath noExtensionPath = path;
+			WPath extension = PathUtil::getExtension(path);
+			PathUtil::replaceExtension(noExtensionPath, L"");
+
+			WPath newPath;
+			UINT32 cnt = 1;
+			do 
+			{
+				newPath = PathUtil::combine(PathUtil::combine(noExtensionPath, toPath(L" " + toWString(cnt))), extension);
+				cnt++;
+			} while (FileSystem::exists(newPath));
+		}
+		else
+			return path;
+	}
+
+	bool GUIResourceTreeView::acceptDragAndDrop() const
+	{
+		return DragAndDropManager::instance().isDragInProgress() && DragAndDropManager::instance().getDragTypeId() == (UINT32)DragAndDropType::Resources;
+	}
+
+	void GUIResourceTreeView::dragAndDropStart()
+	{
+		assert(mDraggedResources == nullptr);
+
+		DraggedResources* draggedResources = cm_new<DraggedResources>();
+		InternalDraggedResources* internalDraggedResources = cm_new<InternalDraggedResources>((UINT32)mSelectedElements.size());
+
+		ResourceManifestPtr resourceManifest = gResources().getResourceManifest();
+
+		UINT32 cnt = 0;
+		UINT32 numValidResources = 0;
+		for(auto& selectedElement : mSelectedElements)
+		{
+			ResourceTreeElement* resourceTreeElement = static_cast<ResourceTreeElement*>(selectedElement.element);
+			internalDraggedResources->resourcePaths[cnt] = resourceTreeElement->mFullPath; 
+			cnt++;
+
+			if(resourceManifest->filePathExists(internalDraggedResources->resourcePaths[cnt]))
+				draggedResources->resourceUUIDs.push_back(resourceManifest->filePathToUUID(internalDraggedResources->resourcePaths[cnt]));
+		}
+
+		mDraggedResources = internalDraggedResources;
+
+		DragAndDropManager::instance().startDrag(HTexture(), (UINT32)DragAndDropType::Resources, (void*)draggedResources, 
+			boost::bind(&GUIResourceTreeView::dragAndDropFinalize, this));
+	}
+
+	void GUIResourceTreeView::dragAndDropEnded(TreeElement* overTreeElement)
+	{
+		if(overTreeElement != nullptr && mDraggedResources != nullptr)
+		{
+			ResourceTreeElement* resourceTreeElement = static_cast<ResourceTreeElement*>(overTreeElement);
+
+			WPath destDir = resourceTreeElement->mFullPath;
+			if(FileSystem::isFile(destDir))
+				destDir = PathUtil::parentPath(destDir);
+
+			for(UINT32 i = 0; i < mDraggedResources->numObjects; i++)
+			{
+				WPath filename = PathUtil::getFilename(mDraggedResources->resourcePaths[i]);
+
+				WPath newPath = PathUtil::combine(destDir, filename);
+				ProjectLibrary::instance().moveEntry(mDraggedResources->resourcePaths[i], findUniquePath(newPath));
+			}
+		}
+	}
+
+	void GUIResourceTreeView::dragAndDropFinalize()
+	{
+		mDragInProgress = false;
+		markContentAsDirty();
+
+		DraggedResources* draggedResources = reinterpret_cast<DraggedResources*>(DragAndDropManager::instance().getDragData());
+		cm_delete(draggedResources);
+
+		if(mDraggedResources != nullptr)
+		{
+			cm_delete(mDraggedResources);
+			mDraggedResources = nullptr;
+		}
+	}
+
+	const String& GUIResourceTreeView::getGUITypeName()
+	{
+		static String typeName = "ResourceTreeView";
+		return typeName;
+	}
+}

+ 14 - 2
CamelotClient/Source/BsGUISceneTreeView.cpp

@@ -68,8 +68,10 @@ namespace BansheeEditor
 			{
 			{
 				for(UINT32 i = 0; i < currentSO->getNumChildren(); i++)
 				for(UINT32 i = 0; i < currentSO->getNumChildren(); i++)
 				{
 				{
+					SceneTreeElement* currentChild = static_cast<SceneTreeElement*>(element->mChildren[i]);
+
 					UINT32 curId = currentSO->getChild(i)->getId();
 					UINT32 curId = currentSO->getChild(i)->getId();
-					if(curId != element->mChildren[i]->mId)
+					if(curId != currentChild->mId)
 					{
 					{
 						completeMatch = false;
 						completeMatch = false;
 						break;
 						break;
@@ -93,7 +95,7 @@ namespace BansheeEditor
 					bool found = false;
 					bool found = false;
 					for(UINT32 j = 0; j < element->mChildren.size(); j++)
 					for(UINT32 j = 0; j < element->mChildren.size(); j++)
 					{
 					{
-						TreeElement* currentChild = element->mChildren[j];
+						SceneTreeElement* currentChild = static_cast<SceneTreeElement*>(element->mChildren[j]);
 
 
 						if(curId == currentChild->mId)
 						if(curId == currentChild->mId)
 						{
 						{
@@ -199,6 +201,16 @@ namespace BansheeEditor
 		CmdEditPlainFieldGO<String>::execute(sceneTreeElement->mSceneObject, "mName", toString(name));
 		CmdEditPlainFieldGO<String>::execute(sceneTreeElement->mSceneObject, "mName", toString(name));
 	}
 	}
 
 
+	void GUISceneTreeView::deleteTreeElement(GUITreeView::TreeElement* element)
+	{
+		closeTemporarilyExpandedElements(); // In case this element is one of them
+
+		if(element->mIsSelected)
+			unselectElement(element);
+
+		cm_delete(element);
+	}
+
 	bool GUISceneTreeView::acceptDragAndDrop() const
 	bool GUISceneTreeView::acceptDragAndDrop() const
 	{
 	{
 		return DragAndDropManager::instance().isDragInProgress() && DragAndDropManager::instance().getDragTypeId() == (UINT32)DragAndDropType::SceneObject;
 		return DragAndDropManager::instance().isDragInProgress() && DragAndDropManager::instance().getDragTypeId() == (UINT32)DragAndDropType::SceneObject;

+ 1 - 11
CamelotClient/Source/BsGUITreeView.cpp

@@ -33,7 +33,7 @@ namespace BansheeEditor
 
 
 	GUITreeView::TreeElement::TreeElement()
 	GUITreeView::TreeElement::TreeElement()
 		:mParent(nullptr), mFoldoutBtn(nullptr), mElement(nullptr), mIsSelected(false),
 		:mParent(nullptr), mFoldoutBtn(nullptr), mElement(nullptr), mIsSelected(false),
-		mId(0), mIsExpanded(false), mSortedIdx(0), mIsDirty(false), mIsVisible(true)
+		mIsExpanded(false), mSortedIdx(0), mIsDirty(false), mIsVisible(true)
 	{ }
 	{ }
 
 
 	GUITreeView::TreeElement::~TreeElement()
 	GUITreeView::TreeElement::~TreeElement()
@@ -602,16 +602,6 @@ namespace BansheeEditor
 			element->mElement->disableRecursively();
 			element->mElement->disableRecursively();
 	}
 	}
 
 
-	void GUITreeView::deleteTreeElement(TreeElement* element)
-	{
-		closeTemporarilyExpandedElements(); // In case this element is one of them
-
-		if(element->mIsSelected)
-			unselectElement(element);
-
-		cm_delete(element);
-	}
-
 	void GUITreeView::disableEdit(bool applyChanges)
 	void GUITreeView::disableEdit(bool applyChanges)
 	{
 	{
 		assert(mEditElement != nullptr);
 		assert(mEditElement != nullptr);

+ 404 - 173
CamelotClient/Source/BsProjectLibrary.cpp

@@ -11,6 +11,8 @@
 #include "CmImporter.h"
 #include "CmImporter.h"
 #include "CmImportOptions.h"
 #include "CmImportOptions.h"
 #include "CmFileSerializer.h"
 #include "CmFileSerializer.h"
+#include "CmFolderMonitor.h"
+#include "CmDebug.h"
 
 
 using namespace CamelotFramework;
 using namespace CamelotFramework;
 using namespace BansheeEngine;
 using namespace BansheeEngine;
@@ -19,46 +21,37 @@ namespace BansheeEditor
 {
 {
 	const WString ProjectLibrary::INTERNAL_RESOURCES_DIR = L"Internal/Resources";
 	const WString ProjectLibrary::INTERNAL_RESOURCES_DIR = L"Internal/Resources";
 
 
-	enum class LibraryEntryType
-	{
-		File,
-		Directory
-	};
-
-	struct ProjectLibrary::LibraryEntry
-	{
-		LibraryEntryType type;
-		WPath path;
-
-		DirectoryEntry* parent;
-	};
-
-	struct ProjectLibrary::ResourceEntry : public ProjectLibrary::LibraryEntry
-	{
-		ResourceMetaPtr meta;
-		std::time_t lastUpdateTime;
-	};
-
-	struct ProjectLibrary::DirectoryEntry : public ProjectLibrary::LibraryEntry
-	{
-		Vector<LibraryEntry*>::type mChildren;
-	};
-
 	ProjectLibrary::ProjectLibrary()
 	ProjectLibrary::ProjectLibrary()
 		:mRootEntry(nullptr)
 		:mRootEntry(nullptr)
 	{
 	{
+		mMonitor = cm_new<FolderMonitor>();
+
+		FolderChange folderChanges = (FolderChange)((UINT32)FolderChange::FileName | (UINT32)FolderChange::DirName | 
+				(UINT32)FolderChange::Creation | (UINT32)FolderChange::LastWrite);
+		mMonitor->startMonitor(EditorApplication::instance().getResourcesFolderPath(), true, folderChanges);
 
 
+		mMonitor->onAdded.connect(boost::bind(&ProjectLibrary::onMonitorFileModified, this, _1));
+		mMonitor->onRemoved.connect(boost::bind(&ProjectLibrary::onMonitorFileModified, this, _1));
+		mMonitor->onModified.connect(boost::bind(&ProjectLibrary::onMonitorFileModified, this, _1));
 	}
 	}
 
 
 	ProjectLibrary::~ProjectLibrary()
 	ProjectLibrary::~ProjectLibrary()
 	{
 	{
+		mMonitor->stopMonitorAll();
+		cm_delete(mMonitor);
+
 		if(mRootEntry != nullptr)
 		if(mRootEntry != nullptr)
-			deleteDirectory(mRootEntry);
+			deleteDirectoryInternal(mRootEntry);
 	}
 	}
 
 
-	void ProjectLibrary::checkForModifications(const CM::WString& folder)
+	void ProjectLibrary::update()
 	{
 	{
-		if(!PathUtil::includes(folder, EditorApplication::instance().getResourcesFolderPath()))
+		mMonitor->update();
+	}
+
+	void ProjectLibrary::checkForModifications(const CM::WString& fullPath)
+	{
+		if(!PathUtil::includes(fullPath, EditorApplication::instance().getResourcesFolderPath()))
 			return; // Folder not part of our resources path, so no modifications
 			return; // Folder not part of our resources path, so no modifications
 
 
 		if(mRootEntry == nullptr)
 		if(mRootEntry == nullptr)
@@ -67,120 +60,165 @@ namespace BansheeEditor
 			mRootEntry->path = toPath(EditorApplication::instance().getResourcesFolderPath());
 			mRootEntry->path = toPath(EditorApplication::instance().getResourcesFolderPath());
 		}
 		}
 
 
-		DirectoryEntry* directoryEntry = findDirectoryEntry(folder);
-		if(directoryEntry == nullptr)
+		WPath pathToSearch = toPath(fullPath);
+		LibraryEntry* entry = findEntry(pathToSearch);
+		if(entry == nullptr) // File could be new, try to find parent directory entry
 		{
 		{
-			CM_EXCEPT(InternalErrorException, "Attempting to check for modifications on a folder that is not yet part " \
-				"of the project library. Maybe check for modifications on the parent folder first? Folder: \"" + toString(folder) + "\"");
-		}
+			WPath parentDirPath = PathUtil::parentPath(pathToSearch);
+			entry = findEntry(parentDirPath);
+
+			// Cannot find parent directory. Create the needed hierarchy.
+			DirectoryEntry* entryParent = nullptr;
+			DirectoryEntry* newHierarchyParent = nullptr;
+			if(entry == nullptr) 
+				createInternalParentHierarchy(pathToSearch, &newHierarchyParent, &entryParent);
+			else
+				entryParent = static_cast<DirectoryEntry*>(entry);
 
 
-		Stack<DirectoryEntry*>::type todo;
-		todo.push(directoryEntry);
+			if(FileSystem::isFile(pathToSearch))
+			{
+				addResourceInternal(entryParent, pathToSearch);
+			}
+			else if(FileSystem::isDirectory(pathToSearch))
+			{
+				addDirectoryInternal(entryParent, pathToSearch);
 
 
-		Vector<WPath>::type childFiles;
-		Vector<WPath>::type childDirectories;
-		Vector<bool>::type existingEntries;
-		Vector<LibraryEntry*>::type toDelete;
+				if(newHierarchyParent == nullptr)
+					checkForModifications(toWString(pathToSearch));
+			}
 
 
-		while(!todo.empty())
+			if(newHierarchyParent != nullptr)
+				checkForModifications(toWString(newHierarchyParent->path));
+		}
+		else if(entry->type == LibraryEntryType::File)
 		{
 		{
-			DirectoryEntry* currentDir = todo.top();
-			todo.pop();
+			if(FileSystem::isFile(entry->path))
+			{
+				reimportResourceInternal(static_cast<ResourceEntry*>(entry));
+			}
+			else
+			{
+				deleteResourceInternal(static_cast<ResourceEntry*>(entry));
+			}
+		}
+		else if(entry->type == LibraryEntryType::Directory) // Check folder and all subfolders for modifications
+		{
+			Stack<DirectoryEntry*>::type todo;
+			todo.push(static_cast<DirectoryEntry*>(entry));
+
+			Vector<WPath>::type childFiles;
+			Vector<WPath>::type childDirectories;
+			Vector<bool>::type existingEntries;
+			Vector<LibraryEntry*>::type toDelete;
+
+			while(!todo.empty())
+			{
+				DirectoryEntry* currentDir = todo.top();
+				todo.pop();
 
 
-			existingEntries.clear();
-			existingEntries.resize(currentDir->mChildren.size());
-			for(UINT32 i = 0; i < (UINT32)currentDir->mChildren.size(); i++)
-				existingEntries[i] = false;
+				existingEntries.clear();
+				existingEntries.resize(currentDir->mChildren.size());
+				for(UINT32 i = 0; i < (UINT32)currentDir->mChildren.size(); i++)
+					existingEntries[i] = false;
 
 
-			childFiles.clear();
-			childDirectories.clear();
+				childFiles.clear();
+				childDirectories.clear();
 
 
-			FileSystem::getChildren(currentDir->path, childFiles, childDirectories);
+				FileSystem::getChildren(currentDir->path, childFiles, childDirectories);
 			
 			
-			for(auto& filePath : childFiles)
-			{
-				ResourceEntry* existingEntry = nullptr;
-				UINT32 idx = 0;
-				for(auto& child : currentDir->mChildren)
+				for(auto& filePath : childFiles)
 				{
 				{
-					if(child->type == LibraryEntryType::File && child->path == filePath)
+					if(isMeta(filePath))
 					{
 					{
-						existingEntries[idx] = true;
-						existingEntry = static_cast<ResourceEntry*>(child);
-						break;
-					}
+						WPath sourceFilePath = filePath;
+						PathUtil::replaceExtension(sourceFilePath, L"");
 
 
-					idx++;
-				}
+						if(FileSystem::isFile(sourceFilePath))
+						{
+							LOGWRN("Found a .meta file without a corresponding resource. Deleting.");
 
 
-				if(existingEntry != nullptr)
-				{
-					reimportResource(existingEntry);
-				}
-				else
-				{
-					addResource(currentDir, filePath);
+							FileSystem::remove(filePath);
+						}
+					}
+					else
+					{
+						ResourceEntry* existingEntry = nullptr;
+						UINT32 idx = 0;
+						for(auto& child : currentDir->mChildren)
+						{
+							if(child->type == LibraryEntryType::File && child->path == filePath)
+							{
+								existingEntries[idx] = true;
+								existingEntry = static_cast<ResourceEntry*>(child);
+								break;
+							}
+
+							idx++;
+						}
+
+						if(existingEntry != nullptr)
+						{
+							reimportResourceInternal(existingEntry);
+						}
+						else
+						{
+							addResourceInternal(currentDir, filePath);
+						}
+					}
 				}
 				}
-			}
 
 
-			for(auto& dirPath : childDirectories)
-			{
-				DirectoryEntry* existingEntry = nullptr;
-				UINT32 idx = 0;
-				for(auto& child : currentDir->mChildren)
+				for(auto& dirPath : childDirectories)
 				{
 				{
-					if(child->type == LibraryEntryType::Directory && child->path == dirPath)
+					DirectoryEntry* existingEntry = nullptr;
+					UINT32 idx = 0;
+					for(auto& child : currentDir->mChildren)
 					{
 					{
-						existingEntries[idx] = true;
-						existingEntry = static_cast<DirectoryEntry*>(child);
-						break;
+						if(child->type == LibraryEntryType::Directory && child->path == dirPath)
+						{
+							existingEntries[idx] = true;
+							existingEntry = static_cast<DirectoryEntry*>(child);
+							break;
+						}
+
+						idx++;
 					}
 					}
 
 
-					idx++;
+					if(existingEntry == nullptr)
+						addDirectoryInternal(currentDir, dirPath);
 				}
 				}
 
 
-				if(existingEntry == nullptr)
 				{
 				{
-					DirectoryEntry* newEntry = cm_new<DirectoryEntry>();
-					newEntry->path = dirPath;
-					newEntry->type = LibraryEntryType::Directory;
-					newEntry->parent = currentDir;
-
-					currentDir->mChildren.push_back(newEntry);
-				}
-			}
+					UINT32 idx = 0;
+					toDelete.clear();
+					for(auto& child : currentDir->mChildren)
+					{
+						if(existingEntries[idx])
+							continue;
 
 
-			{
-				UINT32 idx = 0;
-				toDelete.clear();
-				for(auto& child : currentDir->mChildren)
-				{
-					if(existingEntries[idx])
-						continue;
+						toDelete.push_back(child);
 
 
-					toDelete.push_back(child);
+						idx++;
+					}
 
 
-					idx++;
+					for(auto& child : toDelete)
+					{
+						if(child->type == LibraryEntryType::Directory)
+							deleteDirectoryInternal(static_cast<DirectoryEntry*>(child));
+						else if(child->type == LibraryEntryType::File)
+							deleteResourceInternal(static_cast<ResourceEntry*>(child));
+					}
 				}
 				}
 
 
-				for(auto& child : toDelete)
+				for(auto& child : currentDir->mChildren)
 				{
 				{
 					if(child->type == LibraryEntryType::Directory)
 					if(child->type == LibraryEntryType::Directory)
-						deleteDirectory(static_cast<DirectoryEntry*>(child));
-					else if(child->type == LibraryEntryType::File)
-						deleteResource(static_cast<ResourceEntry*>(child));
+						todo.push(static_cast<DirectoryEntry*>(child));
 				}
 				}
 			}
 			}
-
-			for(auto& child : currentDir->mChildren)
-			{
-				if(child->type == LibraryEntryType::Directory)
-					todo.push(static_cast<DirectoryEntry*>(child));
-			}
 		}
 		}
 	}
 	}
 
 
-	void ProjectLibrary::addResource(DirectoryEntry* parent, const CM::WPath& filePath)
+	ProjectLibrary::ResourceEntry* ProjectLibrary::addResourceInternal(DirectoryEntry* parent, const CM::WPath& filePath)
 	{
 	{
 		ResourceEntry* newResource = cm_new<ResourceEntry>();
 		ResourceEntry* newResource = cm_new<ResourceEntry>();
 		newResource->type = LibraryEntryType::File;
 		newResource->type = LibraryEntryType::File;
@@ -189,10 +227,83 @@ namespace BansheeEditor
 
 
 		parent->mChildren.push_back(newResource);
 		parent->mChildren.push_back(newResource);
 
 
-		reimportResource(newResource);
+		reimportResourceInternal(newResource);
+
+		if(!onEntryAdded.empty())
+			onEntryAdded(filePath);
+
+		return newResource;
 	}
 	}
 
 
-	void ProjectLibrary::reimportResource(ResourceEntry* resource)
+	ProjectLibrary::DirectoryEntry* ProjectLibrary::addDirectoryInternal(DirectoryEntry* parent, const CM::WPath& dirPath)
+	{
+		DirectoryEntry* newEntry = cm_new<DirectoryEntry>();
+		newEntry->type = LibraryEntryType::Directory;
+		newEntry->path = dirPath;
+		newEntry->parent = parent;
+
+		parent->mChildren.push_back(newEntry);
+
+		if(!onEntryAdded.empty())
+			onEntryAdded(dirPath);
+
+		return newEntry;
+	}
+
+	void ProjectLibrary::deleteResourceInternal(ResourceEntry* resource)
+	{
+		ResourceManifestPtr manifest = gResources().getResourceManifest();
+
+		if(resource->meta != nullptr)
+		{
+			if(manifest->uuidExists(resource->meta->getUUID()))
+			{
+				const WPath& path = manifest->uuidToFilePath(resource->meta->getUUID());
+				if(FileSystem::isFile(path))
+					FileSystem::remove(path);
+
+				manifest->unregisterResource(resource->meta->getUUID());
+			}
+		}
+
+		DirectoryEntry* parent = resource->parent;
+		auto findIter = std::find_if(parent->mChildren.begin(), parent->mChildren.end(), 
+			[&] (const LibraryEntry* entry) { return entry == resource; });
+
+		parent->mChildren.erase(findIter);
+
+		if(!onEntryRemoved.empty())
+			onEntryRemoved(resource->path);
+
+		cm_delete(resource);
+	}
+
+	void ProjectLibrary::deleteDirectoryInternal(DirectoryEntry* directory)
+	{
+		if(directory == mRootEntry)
+			mRootEntry = nullptr;
+
+		for(auto& child : directory->mChildren)
+		{
+			if(child->type == LibraryEntryType::Directory)
+				deleteDirectoryInternal(static_cast<DirectoryEntry*>(child));
+			else
+				deleteResourceInternal(static_cast<ResourceEntry*>(child));
+		}
+
+		DirectoryEntry* parent = directory->parent;
+		auto findIter = std::find_if(parent->mChildren.begin(), parent->mChildren.end(), 
+			[&] (const LibraryEntry* entry) { return entry == directory; });
+
+		parent->mChildren.erase(findIter);
+
+		if(!onEntryRemoved.empty())
+			onEntryRemoved(directory->path);
+
+		cm_delete(directory);
+	}
+
+	void ProjectLibrary::reimportResourceInternal(ResourceEntry* resource)
 	{
 	{
 		WString ext = toWString(PathUtil::getExtension(resource->path));
 		WString ext = toWString(PathUtil::getExtension(resource->path));
 		WPath metaPath = resource->path;
 		WPath metaPath = resource->path;
@@ -220,7 +331,7 @@ namespace BansheeEditor
 		if(!isUpToDate(resource))
 		if(!isUpToDate(resource))
 		{
 		{
 			ImportOptionsPtr importOptions = nullptr;
 			ImportOptionsPtr importOptions = nullptr;
-			
+
 			if(resource->meta != nullptr)
 			if(resource->meta != nullptr)
 				importOptions = resource->meta->getImportOptions();
 				importOptions = resource->meta->getImportOptions();
 			else
 			else
@@ -232,7 +343,7 @@ namespace BansheeEditor
 			{
 			{
 				resource->meta = ResourceMeta::create(importedResource.getUUID(), importOptions);
 				resource->meta = ResourceMeta::create(importedResource.getUUID(), importOptions);
 				FileSerializer fs;
 				FileSerializer fs;
-				fs.encode(resource->meta.get(), toWString(metaPath)); // TODO - Make it accept WPath
+				fs.encode(resource->meta.get(), toWString(metaPath));
 			}
 			}
 
 
 			WPath internalResourcesPath = toPath(EditorApplication::instance().getActiveProjectPath()) / toPath(INTERNAL_RESOURCES_DIR);
 			WPath internalResourcesPath = toPath(EditorApplication::instance().getActiveProjectPath()) / toPath(INTERNAL_RESOURCES_DIR);
@@ -242,104 +353,224 @@ namespace BansheeEditor
 			internalResourcesPath /= toPath(toWString(importedResource.getUUID()) + L".asset");
 			internalResourcesPath /= toPath(toWString(importedResource.getUUID()) + L".asset");
 
 
 			gResources().save(importedResource, toWString(internalResourcesPath), true);
 			gResources().save(importedResource, toWString(internalResourcesPath), true);
+
+			ResourceManifestPtr manifest = gResources().getResourceManifest();
+			manifest->registerResource(importedResource.getUUID(), internalResourcesPath);
+
 			resource->lastUpdateTime = std::time(nullptr);
 			resource->lastUpdateTime = std::time(nullptr);
 		}
 		}
 	}
 	}
 
 
-	void ProjectLibrary::deleteResource(ResourceEntry* resource)
+	bool ProjectLibrary::isUpToDate(ResourceEntry* resource) const
 	{
 	{
+		if(resource->meta == nullptr)
+			return false;
+
 		ResourceManifestPtr manifest = gResources().getResourceManifest();
 		ResourceManifestPtr manifest = gResources().getResourceManifest();
 
 
-		if(resource->meta != nullptr)
-		{
-			if(manifest->uuidExists(resource->meta->getUUID()))
-			{
-				const WString& path = manifest->uuidToFilePath(resource->meta->getUUID());
-				if(FileSystem::isFile(path))
-					FileSystem::remove(path);
-			}
+		if(!manifest->uuidExists(resource->meta->getUUID()))
+			return false;
 
 
-			WString ext = toWString(PathUtil::getExtension(resource->path));
-			WPath metaPath = resource->path;
-			PathUtil::replaceExtension(metaPath, ext + L".meta");
+		const WPath& path = manifest->uuidToFilePath(resource->meta->getUUID());
+		if(!FileSystem::isFile(path))
+			return false;
 
 
-			if(FileSystem::isFile(metaPath))
-				FileSystem::remove(metaPath);
+		std::time_t lastModifiedTime = FileSystem::getLastModifiedTime(path);
+		return lastModifiedTime <= resource->lastUpdateTime;
+	}
+
+	ProjectLibrary::LibraryEntry* ProjectLibrary::findEntry(const CM::WPath& fullPath) const
+	{
+		auto pathIter = fullPath.begin();
+		auto rootIter = mRootEntry->path.begin();
+
+		while(*pathIter == *rootIter)
+		{
+			++pathIter;
+			++rootIter;
 		}
 		}
 
 
-		DirectoryEntry* parent = resource->parent;
-		auto findIter = std::find_if(parent->mChildren.begin(), parent->mChildren.end(), 
-			[&] (const LibraryEntry* entry) { return entry == resource; });
+		if(pathIter == fullPath.begin()) // Not a single entry matches Resources path
+			return nullptr;
 
 
-		parent->mChildren.erase(findIter);
+		--pathIter;
 
 
-		cm_delete(resource);
+		Stack<LibraryEntry*>::type todo;
+		todo.push(mRootEntry);
+
+		while(!todo.empty())
+		{
+			LibraryEntry* current = todo.top();
+			todo.pop();
+
+			if(*pathIter == *(--(current->path.end())))
+			{
+				if(current->type == LibraryEntryType::Directory)
+				{
+					DirectoryEntry* dirEntry = static_cast<DirectoryEntry*>(current);
+					for(auto& child : dirEntry->mChildren)
+						todo.push(child);
+				}
+
+				if(pathIter == fullPath.end())
+					return current;
+
+				++pathIter;
+			}
+		}
+
+		return nullptr;
 	}
 	}
 
 
-	void ProjectLibrary::deleteDirectory(DirectoryEntry* directory)
+	void ProjectLibrary::moveEntry(const CM::WPath& oldPath, const CM::WPath& newPath)
 	{
 	{
-		if(directory == mRootEntry)
-			mRootEntry = nullptr;
+		if(FileSystem::isFile(oldPath) || FileSystem::isDirectory(oldPath))
+			FileSystem::move(oldPath, newPath);
 
 
-		for(auto& child : directory->mChildren)
+		WPath oldMetaPath = getMetaPath(oldPath);
+		WPath newMetaPath = getMetaPath(newPath);
+
+		LibraryEntry* oldEntry = findEntry(oldPath);
+		if(oldEntry != nullptr) // Moving from the Resources folder
 		{
 		{
-			if(child->type == LibraryEntryType::Directory)
-				deleteDirectory(static_cast<DirectoryEntry*>(child));
-			else
-				deleteResource(static_cast<ResourceEntry*>(child));
-		}
+			// Moved outside of Resources, delete entry & meta file
+			if(!PathUtil::includes(toWString(newPath), EditorApplication::instance().getResourcesFolderPath()))
+			{
+				if(oldEntry->type == LibraryEntryType::File)
+				{
+					deleteResourceInternal(static_cast<ResourceEntry*>(oldEntry));
 
 
-		DirectoryEntry* parent = directory->parent;
-		auto findIter = std::find_if(parent->mChildren.begin(), parent->mChildren.end(), 
-			[&] (const LibraryEntry* entry) { return entry == directory; });
+					if(FileSystem::isFile(oldMetaPath))
+						FileSystem::remove(oldMetaPath);
+				}
+				else if(oldEntry->type == LibraryEntryType::Directory)
+					deleteDirectoryInternal(static_cast<DirectoryEntry*>(oldEntry));
+			}
+			else // Just moving internally
+			{
+				if(FileSystem::isFile(oldMetaPath))
+					FileSystem::move(oldMetaPath, newMetaPath);
 
 
-		parent->mChildren.erase(findIter);
+				DirectoryEntry* parent = oldEntry->parent;
+				auto findIter = std::find(parent->mChildren.begin(), parent->mChildren.end(), oldEntry);
+				if(findIter != parent->mChildren.end())
+					parent->mChildren.erase(findIter);
 
 
-		cm_delete(directory);
-	}
+				WPath parentPath = PathUtil::parentPath(newPath);
 
 
-	bool ProjectLibrary::isUpToDate(ResourceEntry* resource) const
-	{
-		if(resource->meta == nullptr)
-			return false;
+				DirectoryEntry* newEntryParent = nullptr;
+				LibraryEntry* newEntryParentLib = findEntry(parentPath);
+				if(newEntryParentLib != nullptr)
+				{
+					assert(newEntryParentLib->type == LibraryEntryType::Directory);
+					newEntryParent = static_cast<DirectoryEntry*>(newEntryParentLib);
+				}
 
 
-		ResourceManifestPtr manifest = gResources().getResourceManifest();
+				DirectoryEntry* newHierarchyParent = nullptr;
+				if(newEntryParent == nullptr) // New path parent doesn't exist, so we need to create the hierarchy
+					createInternalParentHierarchy(newPath, &newHierarchyParent, &newEntryParent);
 
 
-		if(!manifest->uuidExists(resource->meta->getUUID()))
-			return false;
+				newEntryParent->mChildren.push_back(oldEntry);
+				oldEntry->parent = newEntryParent;
+				oldEntry->path = newPath;
 
 
-		const WString& path = manifest->uuidToFilePath(resource->meta->getUUID());
-		if(!FileSystem::isFile(path))
-			return false;
+				if(!onEntryRemoved.empty())
+					onEntryRemoved(oldPath);
 
 
-		std::time_t lastModifiedTime = FileSystem::getLastModifiedTime(path);
-		return lastModifiedTime <= resource->lastUpdateTime;
+				if(!onEntryAdded.empty())
+					onEntryAdded(newPath);
+
+				if(newHierarchyParent != nullptr)
+					checkForModifications(toWString(newHierarchyParent->path));
+			}
+		}
+		else // Moving from outside of the Resources folder (likely adding a new resource)
+		{
+			checkForModifications(toWString(newPath));
+		}
 	}
 	}
 
 
-	ProjectLibrary::DirectoryEntry* ProjectLibrary::findDirectoryEntry(const CM::WString& folder) const
+	void ProjectLibrary::deleteEntry(const CM::WPath& path)
 	{
 	{
-		WPath pathToSearch = toPath(folder);
+		if(FileSystem::isFile(path))
+			FileSystem::remove(path);
 
 
-		Stack<DirectoryEntry*>::type todo;
-		todo.push(mRootEntry);
+		LibraryEntry* entry = findEntry(path);
+		if(entry != nullptr)
+		{
+			if(entry->type == LibraryEntryType::File)
+			{
+				deleteResourceInternal(static_cast<ResourceEntry*>(entry));
 
 
-		while(!todo.empty())
+				WPath metaPath = getMetaPath(path);
+				if(FileSystem::isFile(metaPath))
+					FileSystem::remove(metaPath);
+			}
+			else if(entry->type == LibraryEntryType::Directory)
+				deleteDirectoryInternal(static_cast<DirectoryEntry*>(entry));
+		}
+	}
+
+	void ProjectLibrary::createInternalParentHierarchy(const CM::WPath& fullPath, DirectoryEntry** newHierarchyRoot, DirectoryEntry** newHierarchyLeaf)
+	{
+		WPath parentPath = fullPath;
+
+		DirectoryEntry* newEntryParent = nullptr;
+		Stack<WPath>::type parentPaths;
+		do 
 		{
 		{
-			DirectoryEntry* currentDir = todo.top();
-			todo.pop();
+			WPath newParentPath = PathUtil::parentPath(parentPath);
+
+			if(newParentPath == parentPath)
+				break;
 
 
-			for(auto& child : currentDir->mChildren)
+			LibraryEntry* newEntryParentLib = findEntry(newParentPath);
+			if(newEntryParentLib != nullptr)
 			{
 			{
-				if(child->type == LibraryEntryType::Directory)
-				{
-					if(pathToSearch == child->path)
-						return static_cast<DirectoryEntry*>(child);
+				assert(newEntryParentLib->type == LibraryEntryType::Directory);
+				newEntryParent = static_cast<DirectoryEntry*>(newEntryParentLib);
 
 
-					todo.push(static_cast<DirectoryEntry*>(child));
-				}
+				break;
 			}
 			}
+
+			parentPaths.push(newParentPath);
+			parentPath = newParentPath;
+
+		} while (true);
+
+		assert(newEntryParent != nullptr); // Must exist
+
+		if(newHierarchyRoot != nullptr)
+			*newHierarchyRoot = newEntryParent;
+
+		while(!parentPaths.empty())
+		{
+			WPath curPath = parentPaths.top();
+			parentPaths.pop();
+
+			newEntryParent = addDirectoryInternal(newEntryParent, curPath);
 		}
 		}
 
 
-		return nullptr;
+		if(newHierarchyLeaf != nullptr)
+			*newHierarchyLeaf = newEntryParent;
+	}
+
+	WPath ProjectLibrary::getMetaPath(const CM::WPath& path) const
+	{
+		WString ext = toWString(PathUtil::getExtension(path));
+		WPath metaPath = path;
+		PathUtil::replaceExtension(metaPath, ext + L".meta");
+
+		return metaPath;
+	}
+
+	bool ProjectLibrary::isMeta(const WPath& fullPath) const
+	{
+		return PathUtil::getExtension(fullPath) == toPath(L".meta");
+	}
+
+	void ProjectLibrary::onMonitorFileModified(const WString& path)
+	{
+		checkForModifications(path);
 	}
 	}
 }
 }

+ 1 - 0
CamelotCore/CamelotCore.vcxproj

@@ -280,6 +280,7 @@
     <ClInclude Include="Include\CmDeferredCallManager.h" />
     <ClInclude Include="Include\CmDeferredCallManager.h" />
     <ClInclude Include="Include\CmDrawOps.h" />
     <ClInclude Include="Include\CmDrawOps.h" />
     <ClInclude Include="Include\CmEventQuery.h" />
     <ClInclude Include="Include\CmEventQuery.h" />
+    <ClInclude Include="Include\CmFolderMonitor.h" />
     <ClInclude Include="Include\CmGameObjectHandle.h" />
     <ClInclude Include="Include\CmGameObjectHandle.h" />
     <ClInclude Include="Include\CmGameObject.h" />
     <ClInclude Include="Include\CmGameObject.h" />
     <ClInclude Include="Include\CmGameObjectHandleRTTI.h" />
     <ClInclude Include="Include\CmGameObjectHandleRTTI.h" />

+ 3 - 0
CamelotCore/CamelotCore.vcxproj.filters

@@ -540,6 +540,9 @@
     <ClInclude Include="Include\CmWin32FolderMonitor.h">
     <ClInclude Include="Include\CmWin32FolderMonitor.h">
       <Filter>Header Files\Platform</Filter>
       <Filter>Header Files\Platform</Filter>
     </ClInclude>
     </ClInclude>
+    <ClInclude Include="Include\CmFolderMonitor.h">
+      <Filter>Header Files\Platform</Filter>
+    </ClInclude>
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <ClCompile Include="Source\CmApplication.cpp">
     <ClCompile Include="Source\CmApplication.cpp">

+ 7 - 0
CamelotCore/Include/CmFolderMonitor.h

@@ -0,0 +1,7 @@
+#pragma once
+
+#include "CmPrerequisites.h"
+
+#if CM_PLATFORM == CM_PLATFORM_WIN32
+#include "CmWin32FolderMonitor.h"
+#endif

+ 1 - 0
CamelotCore/Include/CmPrerequisites.h

@@ -132,6 +132,7 @@ namespace CamelotFramework
 	class EventQuery;
 	class EventQuery;
 	class TimerQuery;
 	class TimerQuery;
 	class FrameAlloc;
 	class FrameAlloc;
+	class FolderMonitor;
 	// Asset import
 	// Asset import
 	class SpecificImporter;
 	class SpecificImporter;
 	class Importer;
 	class Importer;

+ 7 - 6
CamelotCore/Include/CmResourceManifest.h

@@ -2,6 +2,7 @@
 
 
 #include "CmPrerequisites.h"
 #include "CmPrerequisites.h"
 #include "CmIReflectable.h"
 #include "CmIReflectable.h"
+#include "CmPath.h"
 
 
 namespace CamelotFramework
 namespace CamelotFramework
 {
 {
@@ -16,18 +17,18 @@ namespace CamelotFramework
 	class CM_EXPORT ResourceManifest : public IReflectable
 	class CM_EXPORT ResourceManifest : public IReflectable
 	{
 	{
 	public:
 	public:
-		void registerResource(const String& uuid, const WString& filePath);
+		void registerResource(const String& uuid, const WPath& filePath);
 		void unregisterResource(const String& uuid);
 		void unregisterResource(const String& uuid);
 
 
-		const WString& uuidToFilePath(const String& uuid) const;
-		const String& filePathToUUID(const WString& filePath) const;
+		const WPath& uuidToFilePath(const String& uuid) const;
+		const String& filePathToUUID(const WPath& filePath) const;
 
 
 		bool uuidExists(const String& uuid) const;
 		bool uuidExists(const String& uuid) const;
-		bool filePathExists(const WString& filePath) const;
+		bool filePathExists(const WPath& filePath) const;
 
 
 	private:
 	private:
-		Map<String, WString>::type mUUIDToFilePath;
-		Map<WString, String>::type mFilePathToUUID;
+		Map<String, WPath>::type mUUIDToFilePath;
+		Map<WPath, String>::type mFilePathToUUID;
 
 
 		/************************************************************************/
 		/************************************************************************/
 		/* 								RTTI		                     		*/
 		/* 								RTTI		                     		*/

+ 2 - 2
CamelotCore/Include/CmResourceManifestRTTI.h

@@ -52,12 +52,12 @@ namespace CamelotFramework
 	class CM_EXPORT ResourceManifestRTTI : public RTTIType<ResourceManifest, IReflectable, ResourceManifestRTTI>
 	class CM_EXPORT ResourceManifestRTTI : public RTTIType<ResourceManifest, IReflectable, ResourceManifestRTTI>
 	{
 	{
 	private:
 	private:
-		Map<String, WString>::type& getUUIDMap(ResourceManifest* obj) 
+		Map<String, WPath>::type& getUUIDMap(ResourceManifest* obj) 
 		{ 
 		{ 
 			return obj->mUUIDToFilePath;
 			return obj->mUUIDToFilePath;
 		}
 		}
 
 
-		void setUUIDMap(ResourceManifest* obj, Map<String, WString>::type& val) 
+		void setUUIDMap(ResourceManifest* obj, Map<String, WPath>::type& val) 
 		{ 
 		{ 
 			obj->mUUIDToFilePath = val; 
 			obj->mUUIDToFilePath = val; 
 
 

+ 2 - 1
CamelotCore/Include/CmResources.h

@@ -3,6 +3,7 @@
 #include "CmPrerequisites.h"
 #include "CmPrerequisites.h"
 #include "CmModule.h"
 #include "CmModule.h"
 #include "CmWorkQueue.h"
 #include "CmWorkQueue.h"
+#include "CmPath.h"
 
 
 namespace CamelotFramework
 namespace CamelotFramework
 {
 {
@@ -156,7 +157,7 @@ namespace CamelotFramework
 		HResource loadInternal(const WString& filePath, bool synchronous); 
 		HResource loadInternal(const WString& filePath, bool synchronous); 
 		ResourcePtr loadFromDiskAndDeserialize(const WString& filePath);
 		ResourcePtr loadFromDiskAndDeserialize(const WString& filePath);
 
 
-		const WString& getPathFromUUID(const String& uuid) const;
+		const WPath& getPathFromUUID(const String& uuid) const;
 		const String& getUUIDFromPath(const WString& path) const;
 		const String& getUUIDFromPath(const WString& path) const;
 
 
 		void notifyResourceLoadingFinished(HResource& handle);
 		void notifyResourceLoadingFinished(HResource& handle);

+ 3 - 3
CamelotCore/Include/CmWin32FolderMonitor.h

@@ -16,14 +16,14 @@ namespace CamelotFramework
 		Security = 0x0080
 		Security = 0x0080
 	};
 	};
 
 
-	class CM_EXPORT Win32FolderMonitor
+	class CM_EXPORT FolderMonitor
 	{
 	{
 		struct Pimpl;
 		struct Pimpl;
 		class FileNotifyInfo;
 		class FileNotifyInfo;
 		struct FolderWatchInfo;
 		struct FolderWatchInfo;
 	public:
 	public:
-		Win32FolderMonitor();
-		~Win32FolderMonitor();
+		FolderMonitor();
+		~FolderMonitor();
 
 
 		void startMonitor(const WString& folderPath, bool subdirectories, FolderChange changeFilter);
 		void startMonitor(const WString& folderPath, bool subdirectories, FolderChange changeFilter);
 		void stopMonitor(const WString& folderPath);
 		void stopMonitor(const WString& folderPath);

+ 5 - 5
CamelotCore/Source/CmResourceManifest.cpp

@@ -3,7 +3,7 @@
 
 
 namespace CamelotFramework
 namespace CamelotFramework
 {
 {
-	void ResourceManifest::registerResource(const String& uuid, const WString& filePath)
+	void ResourceManifest::registerResource(const String& uuid, const WPath& filePath)
 	{
 	{
 		auto iterFind = mUUIDToFilePath.find(uuid);
 		auto iterFind = mUUIDToFilePath.find(uuid);
 
 
@@ -35,17 +35,17 @@ namespace CamelotFramework
 		}
 		}
 	}
 	}
 
 
-	const WString& ResourceManifest::uuidToFilePath(const String& uuid) const
+	const WPath& ResourceManifest::uuidToFilePath(const String& uuid) const
 	{
 	{
 		auto iterFind = mUUIDToFilePath.find(uuid);
 		auto iterFind = mUUIDToFilePath.find(uuid);
 
 
 		if(iterFind != mUUIDToFilePath.end())
 		if(iterFind != mUUIDToFilePath.end())
 			return iterFind->second;
 			return iterFind->second;
 		else
 		else
-			return StringUtil::WBLANK;
+			return PathUtil::BLANK;
 	}
 	}
 
 
-	const String& ResourceManifest::filePathToUUID(const WString& filePath) const
+	const String& ResourceManifest::filePathToUUID(const WPath& filePath) const
 	{
 	{
 		auto iterFind = mFilePathToUUID.find(filePath);
 		auto iterFind = mFilePathToUUID.find(filePath);
 
 
@@ -62,7 +62,7 @@ namespace CamelotFramework
 		return iterFind != mUUIDToFilePath.end();
 		return iterFind != mUUIDToFilePath.end();
 	}
 	}
 
 
-	bool ResourceManifest::filePathExists(const WString& filePath) const
+	bool ResourceManifest::filePathExists(const WPath& filePath) const
 	{
 	{
 		auto iterFind = mFilePathToUUID.find(filePath);
 		auto iterFind = mFilePathToUUID.find(filePath);
 
 

+ 6 - 9
CamelotCore/Source/CmResources.cpp

@@ -114,7 +114,7 @@ namespace CamelotFramework
 			return HResource();
 			return HResource();
 		}
 		}
 
 
-		WString filePath = getPathFromUUID(uuid);
+		WString filePath = toWString(getPathFromUUID(uuid));
 		return load(filePath);
 		return load(filePath);
 	}
 	}
 
 
@@ -126,18 +126,15 @@ namespace CamelotFramework
 			return HResource();
 			return HResource();
 		}
 		}
 
 
-		WString filePath = getPathFromUUID(uuid);
+		WString filePath = toWString(getPathFromUUID(uuid));
 		return loadAsync(filePath);
 		return loadAsync(filePath);
 	}
 	}
 
 
 	HResource Resources::loadInternal(const WString& filePath, bool synchronous)
 	HResource Resources::loadInternal(const WString& filePath, bool synchronous)
 	{
 	{
 		String uuid;
 		String uuid;
-		if(!mResourceManifest->filePathExists(filePath))
-		{
+		if(!mResourceManifest->filePathExists(toPath(filePath)))
 			uuid = UUIDGenerator::generateRandom();
 			uuid = UUIDGenerator::generateRandom();
-			mResourceManifest->registerResource(uuid, filePath);
-		}
 		else
 		else
 			uuid = getUUIDFromPath(filePath);
 			uuid = getUUIDFromPath(filePath);
 
 
@@ -263,20 +260,20 @@ namespace CamelotFramework
 				CM_EXCEPT(InvalidParametersException, "Another file exists at the specified location.");
 				CM_EXCEPT(InvalidParametersException, "Another file exists at the specified location.");
 		}
 		}
 
 
-		mResourceManifest->registerResource(resource.getUUID(), filePath);
+		mResourceManifest->registerResource(resource.getUUID(), toPath(filePath));
 
 
 		FileSerializer fs;
 		FileSerializer fs;
 		fs.encode(resource.get(), filePath);
 		fs.encode(resource.get(), filePath);
 	}
 	}
 
 
-	const WString& Resources::getPathFromUUID(const String& uuid) const
+	const WPath& Resources::getPathFromUUID(const String& uuid) const
 	{
 	{
 		return mResourceManifest->uuidToFilePath(uuid);
 		return mResourceManifest->uuidToFilePath(uuid);
 	}
 	}
 
 
 	const String& Resources::getUUIDFromPath(const WString& path) const
 	const String& Resources::getUUIDFromPath(const WString& path) const
 	{
 	{
-		return mResourceManifest->filePathToUUID(path);
+		return mResourceManifest->filePathToUUID(toPath(path));
 	}
 	}
 
 
 	void Resources::notifyResourceLoadingFinished(HResource& handle)
 	void Resources::notifyResourceLoadingFinished(HResource& handle)

+ 22 - 22
CamelotCore/Source/CmWin32FolderMonitor.cpp

@@ -18,15 +18,15 @@ namespace CamelotFramework
 	class WorkerFunc
 	class WorkerFunc
 	{
 	{
 	public:
 	public:
-		WorkerFunc(Win32FolderMonitor* owner);
+		WorkerFunc(FolderMonitor* owner);
 
 
 		void operator()();
 		void operator()();
 
 
 	private:
 	private:
-		Win32FolderMonitor* mOwner;
+		FolderMonitor* mOwner;
 	};
 	};
 
 
-	struct Win32FolderMonitor::FolderWatchInfo
+	struct FolderMonitor::FolderWatchInfo
 	{
 	{
 		FolderWatchInfo(const WString& folderToMonitor, HANDLE dirHandle, bool monitorSubdirectories, DWORD monitorFlags);
 		FolderWatchInfo(const WString& folderToMonitor, HANDLE dirHandle, bool monitorSubdirectories, DWORD monitorFlags);
 		~FolderWatchInfo();
 		~FolderWatchInfo();
@@ -52,7 +52,7 @@ namespace CamelotFramework
 		CM_THREAD_SYNCHRONISER(mStartStopEvent)
 		CM_THREAD_SYNCHRONISER(mStartStopEvent)
 	};
 	};
 
 
-	Win32FolderMonitor::FolderWatchInfo::FolderWatchInfo(const WString& folderToMonitor, HANDLE dirHandle, bool monitorSubdirectories, DWORD monitorFlags)
+	FolderMonitor::FolderWatchInfo::FolderWatchInfo(const WString& folderToMonitor, HANDLE dirHandle, bool monitorSubdirectories, DWORD monitorFlags)
 		:mFolderToMonitor(folderToMonitor), mDirHandle(dirHandle), mState(MonitorState::Inactive), mBufferSize(0),
 		:mFolderToMonitor(folderToMonitor), mDirHandle(dirHandle), mState(MonitorState::Inactive), mBufferSize(0),
 		mMonitorSubdirectories(monitorSubdirectories), mMonitorFlags(monitorFlags), mReadError(0)
 		mMonitorSubdirectories(monitorSubdirectories), mMonitorFlags(monitorFlags), mReadError(0)
 	{
 	{
@@ -62,14 +62,14 @@ namespace CamelotFramework
 		memset(&mOverlapped, 0, sizeof(mOverlapped));
 		memset(&mOverlapped, 0, sizeof(mOverlapped));
 	}
 	}
 
 
-	Win32FolderMonitor::FolderWatchInfo::~FolderWatchInfo()
+	FolderMonitor::FolderWatchInfo::~FolderWatchInfo()
 	{
 	{
 		assert(mState == MonitorState::Inactive);
 		assert(mState == MonitorState::Inactive);
 
 
 		stopMonitor(0);
 		stopMonitor(0);
 	}
 	}
 
 
-	void Win32FolderMonitor::FolderWatchInfo::startMonitor(HANDLE compPortHandle)
+	void FolderMonitor::FolderWatchInfo::startMonitor(HANDLE compPortHandle)
 	{
 	{
 		if(mState != MonitorState::Inactive)
 		if(mState != MonitorState::Inactive)
 			return; // Already monitoring
 			return; // Already monitoring
@@ -96,7 +96,7 @@ namespace CamelotFramework
 		}
 		}
 	}
 	}
 
 
-	void Win32FolderMonitor::FolderWatchInfo::stopMonitor(HANDLE compPortHandle)
+	void FolderMonitor::FolderWatchInfo::stopMonitor(HANDLE compPortHandle)
 	{
 	{
 		if(mState != MonitorState::Inactive)
 		if(mState != MonitorState::Inactive)
 		{
 		{
@@ -116,7 +116,7 @@ namespace CamelotFramework
 		}
 		}
 	}
 	}
 
 
-	class Win32FolderMonitor::FileNotifyInfo
+	class FolderMonitor::FileNotifyInfo
 	{
 	{
 	public:
 	public:
 		FileNotifyInfo(UINT8* notifyBuffer, DWORD bufferSize)
 		FileNotifyInfo(UINT8* notifyBuffer, DWORD bufferSize)
@@ -138,7 +138,7 @@ namespace CamelotFramework
 		PFILE_NOTIFY_INFORMATION mCurrentRecord;
 		PFILE_NOTIFY_INFORMATION mCurrentRecord;
 	};
 	};
 
 
-	bool Win32FolderMonitor::FileNotifyInfo::getNext()
+	bool FolderMonitor::FileNotifyInfo::getNext()
 	{
 	{
 		if(mCurrentRecord && mCurrentRecord->NextEntryOffset != 0)
 		if(mCurrentRecord && mCurrentRecord->NextEntryOffset != 0)
 		{
 		{
@@ -159,7 +159,7 @@ namespace CamelotFramework
 		return false;
 		return false;
 	}
 	}
 
 
-	DWORD Win32FolderMonitor::FileNotifyInfo::getAction() const
+	DWORD FolderMonitor::FileNotifyInfo::getAction() const
 	{ 
 	{ 
 		assert(mCurrentRecord != nullptr);
 		assert(mCurrentRecord != nullptr);
 
 
@@ -169,7 +169,7 @@ namespace CamelotFramework
 		return 0;
 		return 0;
 	}
 	}
 
 
-	WString Win32FolderMonitor::FileNotifyInfo::getFileName() const
+	WString FolderMonitor::FileNotifyInfo::getFileName() const
 	{
 	{
 		if(mCurrentRecord)
 		if(mCurrentRecord)
 		{
 		{
@@ -184,7 +184,7 @@ namespace CamelotFramework
 		return WString();
 		return WString();
 	}		
 	}		
 
 
-	WString Win32FolderMonitor::FileNotifyInfo::getFileNameWithPath(const WString& rootPath) const
+	WString FolderMonitor::FileNotifyInfo::getFileNameWithPath(const WString& rootPath) const
 	{
 	{
 		WStringStream wss;
 		WStringStream wss;
 		wss<<rootPath;
 		wss<<rootPath;
@@ -288,7 +288,7 @@ namespace CamelotFramework
 		FileActionType type;
 		FileActionType type;
 	};
 	};
 
 
-	struct Win32FolderMonitor::Pimpl
+	struct FolderMonitor::Pimpl
 	{
 	{
 		Vector<FolderWatchInfo*>::type mFoldersToWatch;
 		Vector<FolderWatchInfo*>::type mFoldersToWatch;
 		HANDLE mCompPortHandle;
 		HANDLE mCompPortHandle;
@@ -300,14 +300,14 @@ namespace CamelotFramework
 		CM_THREAD_TYPE* mWorkerThread;
 		CM_THREAD_TYPE* mWorkerThread;
 	};
 	};
 
 
-	Win32FolderMonitor::Win32FolderMonitor()
+	FolderMonitor::FolderMonitor()
 	{
 	{
 		mPimpl = cm_new<Pimpl>();
 		mPimpl = cm_new<Pimpl>();
 		mPimpl->mWorkerThread = nullptr;
 		mPimpl->mWorkerThread = nullptr;
 		mPimpl->mCompPortHandle = nullptr;
 		mPimpl->mCompPortHandle = nullptr;
 	}
 	}
 
 
-	Win32FolderMonitor::~Win32FolderMonitor()
+	FolderMonitor::~FolderMonitor()
 	{
 	{
 		stopMonitorAll();
 		stopMonitorAll();
 
 
@@ -323,7 +323,7 @@ namespace CamelotFramework
 		cm_delete(mPimpl);
 		cm_delete(mPimpl);
 	}
 	}
 
 
-	void Win32FolderMonitor::startMonitor(const WString& folderPath, bool subdirectories, FolderChange changeFilter)
+	void FolderMonitor::startMonitor(const WString& folderPath, bool subdirectories, FolderChange changeFilter)
 	{
 	{
 		if(!FileSystem::isDirectory(folderPath))
 		if(!FileSystem::isDirectory(folderPath))
 		{
 		{
@@ -380,7 +380,7 @@ namespace CamelotFramework
 
 
 		if(mPimpl->mWorkerThread == nullptr)
 		if(mPimpl->mWorkerThread == nullptr)
 		{
 		{
-			CM_THREAD_CREATE(t, (boost::bind(&Win32FolderMonitor::workerThreadMain, this)));
+			CM_THREAD_CREATE(t, (boost::bind(&FolderMonitor::workerThreadMain, this)));
 			mPimpl->mWorkerThread = t;
 			mPimpl->mWorkerThread = t;
 
 
 			if(mPimpl->mWorkerThread == nullptr)
 			if(mPimpl->mWorkerThread == nullptr)
@@ -412,7 +412,7 @@ namespace CamelotFramework
 		}
 		}
 	}
 	}
 
 
-	void Win32FolderMonitor::stopMonitor(const WString& folderPath)
+	void FolderMonitor::stopMonitor(const WString& folderPath)
 	{
 	{
 		WString folderPathForComparison = folderPath;
 		WString folderPathForComparison = folderPath;
 		StringUtil::trim(folderPathForComparison, L"\\", false, true);
 		StringUtil::trim(folderPathForComparison, L"\\", false, true);
@@ -435,7 +435,7 @@ namespace CamelotFramework
 			stopMonitorAll();
 			stopMonitorAll();
 	}
 	}
 
 
-	void Win32FolderMonitor::stopMonitorAll()
+	void FolderMonitor::stopMonitorAll()
 	{
 	{
 		for(auto& watchInfo : mPimpl->mFoldersToWatch)
 		for(auto& watchInfo : mPimpl->mFoldersToWatch)
 		{
 		{
@@ -461,7 +461,7 @@ namespace CamelotFramework
 		}
 		}
 	}
 	}
 
 
-	void Win32FolderMonitor::workerThreadMain()
+	void FolderMonitor::workerThreadMain()
 	{
 	{
 		FolderWatchInfo* watchInfo = nullptr;
 		FolderWatchInfo* watchInfo = nullptr;
 
 
@@ -562,7 +562,7 @@ namespace CamelotFramework
 		} while (watchInfo != nullptr);
 		} while (watchInfo != nullptr);
 	}
 	}
 
 
-	void Win32FolderMonitor::handleNotifications(FileNotifyInfo& notifyInfo, FolderWatchInfo& watchInfo)
+	void FolderMonitor::handleNotifications(FileNotifyInfo& notifyInfo, FolderWatchInfo& watchInfo)
 	{
 	{
 		Vector<FileAction*>::type mActions;
 		Vector<FileAction*>::type mActions;
 
 
@@ -613,7 +613,7 @@ namespace CamelotFramework
 		}
 		}
 	}
 	}
 
 
-	void Win32FolderMonitor::update()
+	void FolderMonitor::update()
 	{
 	{
 		{
 		{
 			CM_LOCK_MUTEX(mPimpl->mFileActionsMutex);
 			CM_LOCK_MUTEX(mPimpl->mFileActionsMutex);

+ 7 - 0
CamelotUtility/Include/CmFileSystem.h

@@ -16,9 +16,14 @@ namespace CamelotFramework
 		static void remove(const WString& fullPath, bool recursively = true);
 		static void remove(const WString& fullPath, bool recursively = true);
 		static void remove(const WPath& fullPath, bool recursively = true);
 		static void remove(const WPath& fullPath, bool recursively = true);
 
 
+		static void move(const WPath& oldPath, const WPath& newPath, bool overwriteExisting = true);
+
 		static void createDir(const WString& fullPath);
 		static void createDir(const WString& fullPath);
 		static void createDir(const WPath& fullPath);
 		static void createDir(const WPath& fullPath);
 
 
+		static bool exists(const WString& fullPath);
+		static bool exists(const WPath& fullPath);
+
 		static bool isFile(const WString& fullPath);
 		static bool isFile(const WString& fullPath);
 		static bool isFile(const WPath& fullPath);
 		static bool isFile(const WPath& fullPath);
 		
 		
@@ -26,7 +31,9 @@ namespace CamelotFramework
 		static bool isDirectory(const WPath& fullPath);
 		static bool isDirectory(const WPath& fullPath);
 
 
 		static void getChildren(const WPath& dirPath, Vector<WPath>::type& files, Vector<WPath>::type& directories);
 		static void getChildren(const WPath& dirPath, Vector<WPath>::type& files, Vector<WPath>::type& directories);
+
 		static std::time_t getLastModifiedTime(const WString& fullPath);
 		static std::time_t getLastModifiedTime(const WString& fullPath);
+		static std::time_t getLastModifiedTime(const WPath& fullPath);
 
 
 		static WString getWorkingDirectoryPath();
 		static WString getWorkingDirectoryPath();
 		static WString getParentDirectory(const WString& path);
 		static WString getParentDirectory(const WString& path);

+ 47 - 0
CamelotUtility/Include/CmPath.h

@@ -20,6 +20,8 @@ namespace CamelotFramework
 	class CM_UTILITY_EXPORT PathUtil
 	class CM_UTILITY_EXPORT PathUtil
 	{
 	{
 	public:
 	public:
+		static const WPath BLANK;
+
 		static WString getExtension(const WString& path)
 		static WString getExtension(const WString& path)
 		{
 		{
 			boost::filesystem3::wpath ext = boost::filesystem3::extension(boost::filesystem3::wpath(path.c_str()));
 			boost::filesystem3::wpath ext = boost::filesystem3::extension(boost::filesystem3::wpath(path.c_str()));
@@ -41,6 +43,16 @@ namespace CamelotFramework
 			path.replace_extension(newExtension.c_str());
 			path.replace_extension(newExtension.c_str());
 		}
 		}
 
 
+		static WString parentPath(const WString& path)
+		{
+			return WPath(path.c_str()).parent_path().c_str();
+		}
+
+		static WPath parentPath(const WPath& path)
+		{
+			return path.parent_path();
+		}
+
 		/**
 		/**
 		 * @brief	Returns true if path child is included in path parent.
 		 * @brief	Returns true if path child is included in path parent.
 		 * 			Both paths must be canonical.
 		 * 			Both paths must be canonical.
@@ -70,6 +82,16 @@ namespace CamelotFramework
 				return base + L'/' + name;
 				return base + L'/' + name;
 		}
 		}
 
 
+		static WPath combine(const WPath& base, const WPath& name)
+		{
+			return base / name;
+		}
+
+		static WPath getFilename(const WPath& path)
+		{
+			return path.filename();
+		}
+
 		/**
 		/**
 		 * @brief	Method for standardizing paths - use forward slashes only, end with slash.
 		 * @brief	Method for standardizing paths - use forward slashes only, end with slash.
 		 */
 		 */
@@ -135,4 +157,29 @@ namespace CamelotFramework
 			splitBaseFilename(fullName, outBasename, outExtention);
 			splitBaseFilename(fullName, outBasename, outExtention);
 		}
 		}
 	};
 	};
+
+	template<> struct RTTIPlainType<WPath>
+	{	
+		enum { id = TID_WPath }; enum { hasDynamicSize = 1 };
+
+		static void toMemory(const WPath& data, char* memory)
+		{ 
+			rttiWriteElem(toWString(data), memory);
+		}
+
+		static UINT32 fromMemory(WPath& data, char* memory)
+		{ 
+			WString strData;
+
+			RTTIPlainType<WString>::fromMemory(strData, memory);
+			data = toPath(strData);
+
+			return rttiGetElemSize(strData);
+		}
+
+		static UINT32 getDynamicSize(const WPath& data)	
+		{ 
+			return rttiGetElemSize(toWString(data));
+		}	
+	}; 
 }
 }

+ 2 - 1
CamelotUtility/Include/CmTypes.h

@@ -23,6 +23,7 @@ namespace CamelotFramework
 	enum TypeID_Utility
 	enum TypeID_Utility
 	{
 	{
 		TID_Abstract = 50, // Special type ID used for Abstract classes. Only type ID that may be used by more than one class.
 		TID_Abstract = 50, // Special type ID used for Abstract classes. Only type ID that may be used by more than one class.
-		TID_WString = 51
+		TID_WString = 51,
+		TID_WPath = 52
 	};
 	};
 }
 }

+ 33 - 3
CamelotUtility/Source/CmFileSystem.cpp

@@ -90,9 +90,34 @@ namespace CamelotFramework
 			boost::filesystem3::remove(fullPath);
 			boost::filesystem3::remove(fullPath);
 	}
 	}
 
 
+	void FileSystem::move(const WPath& oldPath, const WPath& newPath, bool overwriteExisting)
+	{
+		if(FileSystem::isFile(newPath) || FileSystem::isDirectory(newPath))
+		{
+			if(overwriteExisting)
+				FileSystem::remove(newPath, true);
+			else
+			{
+				CM_EXCEPT(InvalidStateException, "Move operation failed because another file already exists at the new path: \"" + toString(newPath) + "\"");
+			}
+		}
+
+		boost::filesystem3::rename(oldPath, newPath);
+	}
+
+	bool FileSystem::exists(const WString& fullPath)
+	{
+		return boost::filesystem3::exists(fullPath.c_str());
+	}
+
+	bool FileSystem::exists(const WPath& fullPath)
+	{
+		return boost::filesystem3::exists(fullPath);
+	}
+
 	bool FileSystem::isFile(const WString& fullPath)
 	bool FileSystem::isFile(const WString& fullPath)
 	{
 	{
-		if(exists(fullPath.c_str()) && !is_directory(fullPath.c_str()))
+		if(boost::filesystem3::exists(fullPath.c_str()) && !is_directory(fullPath.c_str()))
 			return true;
 			return true;
 
 
 		return false;
 		return false;
@@ -108,7 +133,7 @@ namespace CamelotFramework
 
 
 	bool FileSystem::isDirectory(const WString& fullPath)
 	bool FileSystem::isDirectory(const WString& fullPath)
 	{
 	{
-		if(exists(fullPath.c_str()) && is_directory(fullPath.c_str()))
+		if(boost::filesystem3::exists(fullPath.c_str()) && is_directory(fullPath.c_str()))
 			return true;
 			return true;
 
 
 		return false;
 		return false;
@@ -116,7 +141,7 @@ namespace CamelotFramework
 
 
 	bool FileSystem::isDirectory(const WPath& fullPath)
 	bool FileSystem::isDirectory(const WPath& fullPath)
 	{
 	{
-		if(exists(fullPath) && is_directory(fullPath))
+		if(boost::filesystem3::exists(fullPath) && is_directory(fullPath))
 			return true;
 			return true;
 
 
 		return false;
 		return false;
@@ -155,6 +180,11 @@ namespace CamelotFramework
 		return last_write_time(fullPath.c_str());
 		return last_write_time(fullPath.c_str());
 	}
 	}
 
 
+	std::time_t FileSystem::getLastModifiedTime(const WPath& fullPath)
+	{
+		return last_write_time(fullPath);
+	}
+
 	WString FileSystem::getWorkingDirectoryPath()
 	WString FileSystem::getWorkingDirectoryPath()
 	{
 	{
 		return current_path().wstring().c_str();
 		return current_path().wstring().c_str();

+ 2 - 0
CamelotUtility/Source/CmPath.cpp

@@ -2,6 +2,8 @@
 
 
 namespace CamelotFramework
 namespace CamelotFramework
 {
 {
+	const WPath PathUtil::BLANK = WPath();
+
 	Path toPath(const String& p) { return p.c_str(); }
 	Path toPath(const String& p) { return p.c_str(); }
 	WPath toPath(const WString& p) { return p.c_str(); }
 	WPath toPath(const WString& p) { return p.c_str(); }
 
 

+ 18 - 26
ProjectLibrary.txt

@@ -1,17 +1,23 @@
 
 
  ProjectLibrary
  ProjectLibrary
   - Save/Load - saves/loads the hierarchy of ResourceEntries
   - Save/Load - saves/loads the hierarchy of ResourceEntries
-  - import(filePath)
-    - Check if we have an importer that supports the resource with the specified extension
-	- If we are okay then import the resource, otherwise do nothing and return false
-	  - Check if importer failed and report error if it did, also return false
-  - move(oldLocation, newLocation)
-    - Moves the resource from one location to another, first performs the move on the disk, then updates the hierarchy
-	 - Potentially suspends file monitoring since we don't need to detect these changes?
-  - remove(location, subfolders)
-    - Removes the resource from the file-system, and the rest is handled by the auto-update system
-  - monitoring
-    - Sets up a FileMonitor at the Resources folder, and calls add/move/remove/import whenever changes are reported
+
+--------------------
+
+Imported resources should get queued. Since I want to display a progress bar with % of resources imported.
+ - Plus importers should work on separate threads (Will likely make Resources worker threads global and re-use them for importer)
+
+-------------------
+
+TOMORROW:
+mIsVisible seems to get set in updateTreeElement? Which means expanding/closing wont work without it
+ - I should probably move those so they are changed whenever mIsExpanded is toggled
+ - I might want to move stuff from TreeView::update in general to an event-based system. Only in case of Scene those events would be generated internally.
+
+Later: Projectlibrary add (tracking drag and drop using Win32DragAndDropManager)
+-------------------
+
+LOW PRIORITY
 
 
 ResourceManifest contains absolute file locations. What happens when the project moves?
 ResourceManifest contains absolute file locations. What happens when the project moves?
  - When saving ResourceManifest make sure to process all paths and make them relative.
  - When saving ResourceManifest make sure to process all paths and make them relative.
@@ -22,18 +28,4 @@ What happens when importer fails importing a file?
     and clean up properly on error.
     and clean up properly on error.
 	- In fact they should use exceptions but those should be translated to error codes when returned to Importer
 	- In fact they should use exceptions but those should be translated to error codes when returned to Importer
 	  - OR just write directly to debug log?
 	  - OR just write directly to debug log?
-	   - Probably log and error code, since I will need to inform the outside world that import failed somehow
-
---------------------
-
-IMPLEMENTATION: First ensure scan and import works. Then work out how to deal with FileMonitor. And finally how to add/remove/move resources from outside.
-
-Don't forget meta files and a way to store import options.
-
-I'll likely need to update ResourceEntry in ProjectLibrary
- - Replace UUID field with ResourceMeta class
-    - Contains UUID and ImportOptions
-	- Created whenever resource is imported
-	- Deleted when resource is deleted
-
-Make ProjectLibrary also check for .meta files that have no corresponding file and delete them (With a warning logged)
+	   - Probably log and error code, since I will need to inform the outside world that import failed somehow