Просмотр исходного кода

A lot more work on ProjectLibrary and ResourceTreeView
Started testing and bugfixing

Marko Pintera 12 лет назад
Родитель
Сommit
3bd69f2da6

+ 1 - 0
CamelotClient/Include/BsEditorPrerequisites.h

@@ -20,6 +20,7 @@ namespace BansheeEditor
 	class GUIMenuBar;
 	class GUIDockSlider;
 	class GUISceneTreeView;
+	class GUIResourceTreeView;
 	class GUITreeViewEditBox;
 	class EditorCommand;
 	class ResourceMeta;

+ 6 - 8
CamelotClient/Include/BsGUIResourceTreeView.h

@@ -2,7 +2,6 @@
 
 #include "BsEditorPrerequisites.h"
 #include "BsGUITreeView.h"
-#include "BsVirtualInput.h"
 #include "CmPath.h"
 #include <boost/signal.hpp>
 
@@ -12,7 +11,8 @@ namespace BansheeEditor
 	{
 		struct ResourceTreeElement : public GUITreeView::TreeElement
 		{
-			CM::WPath mFullPath;
+			CM::WString mFullPath;
+			CM::WString mElementName;
 		};
 
 		struct DraggedResources
@@ -26,7 +26,7 @@ namespace BansheeEditor
 			~InternalDraggedResources();
 
 			CM::UINT32 numObjects;
-			CM::WPath* resourcePaths;
+			CM::WString* resourcePaths;
 		};
 
 	public:
@@ -76,11 +76,11 @@ namespace BansheeEditor
 		virtual void dragAndDropEnded(TreeElement* overTreeElement);
 		virtual void dragAndDropFinalize();
 
-		ResourceTreeElement* addTreeElement(ResourceTreeElement* parent, const CM::WPath& fullPath);
+		ResourceTreeElement* addTreeElement(ResourceTreeElement* parent, const CM::WString& fullPath);
 		void deleteTreeElement(ResourceTreeElement* element);
 		void sortTreeElement(ResourceTreeElement* element);
 
-		ResourceTreeElement* findTreeElement(const CM::WPath& fullPath);
+		ResourceTreeElement* findTreeElement(const CM::WString& fullPath);
 
 		void entryAdded(const CM::WPath& path);
 		void entryRemoved(const CM::WPath& path);
@@ -92,9 +92,7 @@ namespace BansheeEditor
 		void dropTargetDragLeave();
 		void dropTargetDragDropped(CM::INT32 x, CM::INT32 y);
 
-		void quicksortTreeElements(CM::Vector<TreeElement*>::type& elements, CM::INT32 first, CM::INT32 last);
-
-		CM::WPath findUniquePath(const CM::WPath& path);
+		CM::WString findUniquePath(const CM::WString& path);
 
 		void _changeParentWidget(BS::GUIWidget* widget);
 	};

+ 0 - 1
CamelotClient/Include/BsGUISceneTreeView.h

@@ -2,7 +2,6 @@
 
 #include "BsEditorPrerequisites.h"
 #include "BsGUITreeView.h"
-#include "BsVirtualInput.h"
 #include <boost/signal.hpp>
 
 namespace BansheeEditor

+ 0 - 1
CamelotClient/Include/BsMainEditorWindow.h

@@ -21,7 +21,6 @@ namespace BansheeEditor
 		GUIMenuBar* mMenuBar;
 		DockManager* mDockManager;
 
-		void itemDropped(CM::OSDropTarget& dropTarget, CM::INT32 x, CM::INT32 y);
 		virtual void resized();
 
 		void updateAreas();

+ 7 - 0
CamelotClient/Include/BsProjectLibrary.h

@@ -21,20 +21,27 @@ namespace BansheeEditor
 
 		struct LibraryEntry
 		{
+			LibraryEntry(const CM::WPath& path, const CM::WString& name, DirectoryEntry* parent, LibraryEntryType type);
+
 			LibraryEntryType type;
 			CM::WPath path;
+			CM::WString elementName;
 
 			DirectoryEntry* parent;
 		};
 
 		struct ResourceEntry : public LibraryEntry
 		{
+			ResourceEntry(const CM::WPath& path, const CM::WString& name, DirectoryEntry* parent);
+
 			ResourceMetaPtr meta;
 			std::time_t lastUpdateTime;
 		};
 
 		struct DirectoryEntry : public LibraryEntry
 		{
+			DirectoryEntry(const CM::WPath& path, const CM::WString& name, DirectoryEntry* parent);
+
 			CM::Vector<LibraryEntry*>::type mChildren;
 		};
 

+ 1 - 0
CamelotClient/Include/CmTestTextSprite.h

@@ -20,6 +20,7 @@ namespace BansheeEditor
 		BS::GUILabel* mLabel;
 		BS::GUIListBox* mListBox;
 		GUISceneTreeView* mSceneTreeView;
+		GUIResourceTreeView* mResourceTreeView;
 		CM::HString labelString;
 
 		CM::HSceneObject mDbgMainA;

+ 5 - 0
CamelotClient/Source/BsEditorApplication.cpp

@@ -24,6 +24,7 @@
 #include "BsDbgTestGameObjectRef.h"
 #include "BsVirtualInput.h"
 #include "CmWin32FolderMonitor.h"
+#include "BsProjectLibrary.h"
 
 using namespace CamelotFramework;
 using namespace BansheeEngine;
@@ -55,6 +56,8 @@ namespace BansheeEditor
 			inputConfig->registerButton("Paste", BC_V, VButtonModifier::Ctrl);
 		}
 
+		ProjectLibrary::startUp(cm_new<ProjectLibrary>());
+
 		//gApplication().loadPlugin("SBansheeEditor"); // Managed part of the editor
 
 		/************************************************************************/
@@ -294,6 +297,7 @@ namespace BansheeEditor
 		/* 							END DEBUG CODE                      		*/
 		/************************************************************************/
 
+		ProjectLibrary::shutDown();
 		EditorGUI::shutDown();
 		gBansheeApp().shutDown();
 
@@ -313,6 +317,7 @@ namespace BansheeEditor
 
 	void EditorApplication::update()
 	{
+		ProjectLibrary::instance().update();
 		EditorWindowManager::instance().update();	
 	}
 

+ 51 - 55
CamelotClient/Source/BsGUIResourceTreeView.cpp

@@ -19,7 +19,7 @@ namespace BansheeEditor
 	GUIResourceTreeView::InternalDraggedResources::InternalDraggedResources(UINT32 numObjects)
 		:numObjects(numObjects)
 	{
-		resourcePaths = cm_newN<WPath>(numObjects);
+		resourcePaths = cm_newN<WString>(numObjects);
 	}
 
 	GUIResourceTreeView::InternalDraggedResources::~InternalDraggedResources()
@@ -49,6 +49,8 @@ namespace BansheeEditor
 
 		const ProjectLibrary::LibraryEntry* rootEntry = ProjectLibrary::instance().getRootEntry();
 
+		mRootElement.mFullPath = toWString(rootEntry->path);
+		mRootElement.mElementName = PathUtil::getFilename(mRootElement.mFullPath);
 		expandElement(&mRootElement);
 
 		Stack<StackElem>::type todo;
@@ -57,13 +59,13 @@ namespace BansheeEditor
 		while(!todo.empty())
 		{
 			StackElem curElem = todo.top();
-			todo.top();
+			todo.pop();
 
 			const ProjectLibrary::DirectoryEntry* dirEntry = static_cast<const ProjectLibrary::DirectoryEntry*>(curElem.entry);
 
 			for(auto& child : dirEntry->mChildren)
 			{
-				ResourceTreeElement* newChild = addTreeElement(curElem.treeElem, curElem.entry->path);
+				ResourceTreeElement* newChild = addTreeElement(curElem.treeElem, toWString(child->path));
 
 				if(child->type == ProjectLibrary::LibraryEntryType::Directory)
 					todo.push(StackElem(child, newChild));
@@ -121,13 +123,13 @@ namespace BansheeEditor
 	{
 		ResourceTreeElement* resourceTreeElement = static_cast<ResourceTreeElement*>(element);
 		
-		WPath oldPath = resourceTreeElement->mFullPath;
-		WPath newPath = PathUtil::combine(PathUtil::parentPath(oldPath), toPath(name));
+		WString oldPath = resourceTreeElement->mFullPath;
+		WString newPath = PathUtil::combine(PathUtil::parentPath(oldPath), name);
 
-		ProjectLibrary::instance().moveEntry(oldPath, findUniquePath(newPath));
+		ProjectLibrary::instance().moveEntry(toPath(oldPath), toPath(findUniquePath(newPath)));
 	}
 
-	GUIResourceTreeView::ResourceTreeElement* GUIResourceTreeView::addTreeElement(ResourceTreeElement* parent, const CM::WPath& fullPath)
+	GUIResourceTreeView::ResourceTreeElement* GUIResourceTreeView::addTreeElement(ResourceTreeElement* parent, const CM::WString& fullPath)
 	{
 		ResourceTreeElement* newChild = cm_new<ResourceTreeElement>();
 		newChild->mParent = parent;
@@ -135,6 +137,7 @@ namespace BansheeEditor
 		newChild->mFullPath = fullPath;
 		newChild->mSortedIdx = (UINT32)parent->mChildren.size();
 		newChild->mIsVisible = parent->mIsVisible && parent->mIsExpanded;
+		newChild->mElementName = PathUtil::getFilename(fullPath);
 
 		parent->mChildren.push_back(newChild);
 
@@ -168,6 +171,9 @@ namespace BansheeEditor
 			auto iterFind = std::find(element->mParent->mChildren.begin(), element->mParent->mChildren.end(), element);
 			if(iterFind != element->mParent->mChildren.end())
 				element->mParent->mChildren.erase(iterFind);
+
+			sortTreeElement(static_cast<ResourceTreeElement*>(element->mParent));
+			updateElementGUI(element->mParent);
 		}
 
 		cm_delete(element);
@@ -175,49 +181,36 @@ namespace BansheeEditor
 
 	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)
+		auto cmp = [&] (const TreeElement* a, const TreeElement* b)
 		{
-			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++;
-				}
-			}
+			return a->mName.compare(b->mName) < 0;
+		};
 
-			std::swap(elements[last]->mSortedIdx, elements[pivot]->mSortedIdx);
+		std::sort(element->mChildren.begin(), element->mChildren.end(), cmp);
 
-			quicksortTreeElements(elements, first, i - 1);
-			quicksortTreeElements(elements, i + 1, last);
+		UINT32 idx = 0;
+		for(auto& child : element->mChildren)
+		{
+			child->mSortedIdx = idx;
+			idx++;
 		}
 	}
 
-	GUIResourceTreeView::ResourceTreeElement* GUIResourceTreeView::findTreeElement(const CM::WPath& fullPath)
+	GUIResourceTreeView::ResourceTreeElement* GUIResourceTreeView::findTreeElement(const CM::WString& fullPath)
 	{
-		auto pathIter = fullPath.begin();
-		auto rootIter = mRootElement.mFullPath.begin();
+		Vector<WString>::type pathElems = PathUtil::split(fullPath);
+		Vector<WString>::type rootElems = PathUtil::split(mRootElement.mFullPath);
+
+		auto pathIter = pathElems.begin();
+		auto rootIter = rootElems.begin();
 
-		while(*pathIter == *rootIter)
+		while(pathIter != pathElems.end() && rootIter != rootElems.end() && PathUtil::comparePathElements(*pathIter, *rootIter))
 		{
 			++pathIter;
 			++rootIter;
 		}
 
-		if(pathIter == fullPath.begin()) // Supplied path not part of the root path
+		if(pathIter == pathElems.begin()) // Supplied path not part of the root path
 			return nullptr;
 
 		--pathIter;
@@ -230,15 +223,18 @@ namespace BansheeEditor
 			ResourceTreeElement* current = todo.top();
 			todo.pop();
 
-			if(*pathIter == *(--(current->mFullPath.end())))
+			if(PathUtil::comparePathElements(*pathIter, current->mElementName))
 			{
-				for(auto& child : current->mChildren)
-					todo.push(static_cast<ResourceTreeElement*>(child));
+				++pathIter;
 
-				if(pathIter == fullPath.end())
+				if(pathIter == pathElems.end())
 					return current;
 
-				++pathIter;
+				while(!todo.empty())
+					todo.pop();
+
+				for(auto& child : current->mChildren)
+					todo.push(static_cast<ResourceTreeElement*>(child));
 			}
 		}
 
@@ -249,10 +245,10 @@ namespace BansheeEditor
 	{
 		WPath parentPath = PathUtil::parentPath(path);
 
-		ResourceTreeElement* parentElement = findTreeElement(parentPath);
+		ResourceTreeElement* parentElement = findTreeElement(toWString(parentPath));
 		assert(parentElement != nullptr);
 
-		addTreeElement(parentElement, path);
+		addTreeElement(parentElement, toWString(path));
 		sortTreeElement(parentElement);
 
 		markContentAsDirty();
@@ -260,7 +256,7 @@ namespace BansheeEditor
 
 	void GUIResourceTreeView::entryRemoved(const WPath& path)
 	{
-		ResourceTreeElement* treeElement = findTreeElement(path);
+		ResourceTreeElement* treeElement = findTreeElement(toWString(path));
 		
 		if(treeElement != nullptr)
 			deleteTreeElement(treeElement);
@@ -344,7 +340,7 @@ namespace BansheeEditor
 
 			mDraggedResources = cm_new<InternalDraggedResources>((UINT32)fileList.size());
 			for(UINT32 i = 0; i < (UINT32)fileList.size(); i++)
-				mDraggedResources->resourcePaths[i] = toPath(fileList[i]);
+				mDraggedResources->resourcePaths[i] = fileList[i];
 
 			dragAndDropEnded(treeElement);
 
@@ -359,19 +355,19 @@ namespace BansheeEditor
 		markContentAsDirty();
 	}
 
-	CM::WPath GUIResourceTreeView::findUniquePath(const CM::WPath& path)
+	CM::WString GUIResourceTreeView::findUniquePath(const CM::WString& path)
 	{
 		if(FileSystem::exists(path))
 		{
-			WPath noExtensionPath = path;
-			WPath extension = PathUtil::getExtension(path);
+			WString noExtensionPath = path;
+			WString extension = PathUtil::getExtension(path);
 			PathUtil::replaceExtension(noExtensionPath, L"");
 
-			WPath newPath;
+			WString newPath;
 			UINT32 cnt = 1;
 			do 
 			{
-				newPath = PathUtil::combine(PathUtil::combine(noExtensionPath, toPath(L" " + toWString(cnt))), extension);
+				newPath = PathUtil::combine(PathUtil::combine(noExtensionPath, L" " + toWString(cnt)), extension);
 				cnt++;
 			} while (FileSystem::exists(newPath));
 
@@ -418,16 +414,16 @@ namespace BansheeEditor
 		{
 			ResourceTreeElement* resourceTreeElement = static_cast<ResourceTreeElement*>(overTreeElement);
 
-			WPath destDir = resourceTreeElement->mFullPath;
+			WString 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]);
+				WString filename = PathUtil::getFilename(mDraggedResources->resourcePaths[i]);
 
-				WPath newPath = PathUtil::combine(destDir, filename);
-				ProjectLibrary::instance().moveEntry(mDraggedResources->resourcePaths[i], findUniquePath(newPath));
+				WString newPath = PathUtil::combine(destDir, filename);
+				ProjectLibrary::instance().moveEntry(toPath(mDraggedResources->resourcePaths[i]), toPath(findUniquePath(newPath)));
 			}
 		}
 	}

+ 0 - 10
CamelotClient/Source/BsMainEditorWindow.cpp

@@ -76,11 +76,6 @@ namespace BansheeEditor
 		AABox dbgBox(Vector3(-300, -200, 1000), Vector3(300, 300, 1500));
 		//DrawHelper3D::instance().drawAABox(sceneCamera, dbgBox, Color::Green, 250.0f);
 
-		OSDropTarget& dropTarget = Platform::createDropTarget(mRenderWindow.get(), 100, 100, 400, 400);
-		dropTarget.onDrop.connect(boost::bind(&MainEditorWindow::itemDropped, this, boost::ref(dropTarget), _1, _2));
-
-		Platform::destroyDropTarget(dropTarget);
-
 		ProfilerOverlay::startUp(cm_new<ProfilerOverlay>(sceneCamera->getViewport()));
 		ProfilerOverlay::instance().show();
 	}
@@ -93,11 +88,6 @@ namespace BansheeEditor
 		cm_delete(mMenuBar);
 	}
 
-	void MainEditorWindow::itemDropped(OSDropTarget& dropTarget, INT32 x, INT32 y)
-	{
-		int a = 5;
-	}
-
 	void MainEditorWindow::resized()
 	{
 		EditorWindowBase::resized();

+ 129 - 105
CamelotClient/Source/BsProjectLibrary.cpp

@@ -19,7 +19,19 @@ using namespace BansheeEngine;
 
 namespace BansheeEditor
 {
-	const WString ProjectLibrary::INTERNAL_RESOURCES_DIR = L"Internal/Resources";
+	const WString ProjectLibrary::INTERNAL_RESOURCES_DIR = L"Internal\\Resources";
+
+	ProjectLibrary::LibraryEntry::LibraryEntry(const CM::WPath& path, const CM::WString& name, DirectoryEntry* parent, LibraryEntryType type)
+		:path(path), parent(parent), type(type), elementName(name)
+	{ }
+
+	ProjectLibrary::ResourceEntry::ResourceEntry(const CM::WPath& path, const CM::WString& name, DirectoryEntry* parent)
+		:LibraryEntry(path, name, parent, LibraryEntryType::File), lastUpdateTime(0)
+	{ }
+
+	ProjectLibrary::DirectoryEntry::DirectoryEntry(const CM::WPath& path, const CM::WString& name, DirectoryEntry* parent)
+		:LibraryEntry(path, name, parent, LibraryEntryType::Directory)
+	{ }
 
 	ProjectLibrary::ProjectLibrary()
 		:mRootEntry(nullptr)
@@ -33,10 +45,15 @@ namespace BansheeEditor
 		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));
+
+		// TODO - Load
+		checkForModifications(EditorApplication::instance().getResourcesFolderPath());
 	}
 
 	ProjectLibrary::~ProjectLibrary()
 	{
+		// TODO - Save
+
 		mMonitor->stopMonitorAll();
 		cm_delete(mMonitor);
 
@@ -56,8 +73,8 @@ namespace BansheeEditor
 
 		if(mRootEntry == nullptr)
 		{
-			mRootEntry = cm_new<DirectoryEntry>();
-			mRootEntry->path = toPath(EditorApplication::instance().getResourcesFolderPath());
+			WPath resPath = toPath(EditorApplication::instance().getResourcesFolderPath());
+			mRootEntry = cm_new<DirectoryEntry>(resPath, toWString(PathUtil::getFilename(resPath)), nullptr);
 		}
 
 		WPath pathToSearch = toPath(fullPath);
@@ -103,128 +120,127 @@ namespace BansheeEditor
 		}
 		else if(entry->type == LibraryEntryType::Directory) // Check folder and all subfolders for modifications
 		{
-			Stack<DirectoryEntry*>::type todo;
-			todo.push(static_cast<DirectoryEntry*>(entry));
+			if(!FileSystem::isDirectory(entry->path))
+			{
+				deleteDirectoryInternal(static_cast<DirectoryEntry*>(entry));
+			}
+			else
+			{
+				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;
+				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();
+				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)
-				{
-					if(isMeta(filePath))
+					for(auto& filePath : childFiles)
 					{
-						WPath sourceFilePath = filePath;
-						PathUtil::replaceExtension(sourceFilePath, L"");
+						if(isMeta(filePath))
+						{
+							WPath sourceFilePath = filePath;
+							PathUtil::replaceExtension(sourceFilePath, L"");
+
+							if(FileSystem::isFile(sourceFilePath))
+							{
+								LOGWRN("Found a .meta file without a corresponding resource. Deleting.");
 
-						if(FileSystem::isFile(sourceFilePath))
+								FileSystem::remove(filePath);
+							}
+						}
+						else
 						{
-							LOGWRN("Found a .meta file without a corresponding resource. Deleting.");
+							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++;
+							}
 
-							FileSystem::remove(filePath);
+							if(existingEntry != nullptr)
+							{
+								reimportResourceInternal(existingEntry);
+							}
+							else
+							{
+								addResourceInternal(currentDir, filePath);
+							}
 						}
 					}
-					else
+
+					for(auto& dirPath : childDirectories)
 					{
-						ResourceEntry* existingEntry = nullptr;
+						DirectoryEntry* existingEntry = nullptr;
 						UINT32 idx = 0;
 						for(auto& child : currentDir->mChildren)
 						{
-							if(child->type == LibraryEntryType::File && child->path == filePath)
+							if(child->type == LibraryEntryType::Directory && child->path == dirPath)
 							{
 								existingEntries[idx] = true;
-								existingEntry = static_cast<ResourceEntry*>(child);
+								existingEntry = static_cast<DirectoryEntry*>(child);
 								break;
 							}
 
 							idx++;
 						}
 
-						if(existingEntry != nullptr)
-						{
-							reimportResourceInternal(existingEntry);
-						}
-						else
-						{
-							addResourceInternal(currentDir, filePath);
-						}
+						if(existingEntry == nullptr)
+							addDirectoryInternal(currentDir, dirPath);
 					}
-				}
 
-				for(auto& dirPath : childDirectories)
-				{
-					DirectoryEntry* existingEntry = nullptr;
-					UINT32 idx = 0;
-					for(auto& child : currentDir->mChildren)
 					{
-						if(child->type == LibraryEntryType::Directory && child->path == dirPath)
+						for(UINT32 i = 0; i < (UINT32)existingEntries.size(); i++)
 						{
-							existingEntries[idx] = true;
-							existingEntry = static_cast<DirectoryEntry*>(child);
-							break;
+							if(existingEntries[i])
+								continue;
+
+							toDelete.push_back(currentDir->mChildren[i]);
 						}
 
-						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));
+						}
 					}
 
-					if(existingEntry == nullptr)
-						addDirectoryInternal(currentDir, dirPath);
-				}
-
-				{
-					UINT32 idx = 0;
-					toDelete.clear();
 					for(auto& child : currentDir->mChildren)
-					{
-						if(existingEntries[idx])
-							continue;
-
-						toDelete.push_back(child);
-
-						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));
+							todo.push(static_cast<DirectoryEntry*>(child));
 					}
 				}
-
-				for(auto& child : currentDir->mChildren)
-				{
-					if(child->type == LibraryEntryType::Directory)
-						todo.push(static_cast<DirectoryEntry*>(child));
-				}
 			}
 		}
 	}
 
 	ProjectLibrary::ResourceEntry* ProjectLibrary::addResourceInternal(DirectoryEntry* parent, const CM::WPath& filePath)
 	{
-		ResourceEntry* newResource = cm_new<ResourceEntry>();
-		newResource->type = LibraryEntryType::File;
-		newResource->path = filePath;
-		newResource->parent = parent;
-
+		ResourceEntry* newResource = cm_new<ResourceEntry>(filePath, toWString(PathUtil::getFilename(filePath)), parent);
 		parent->mChildren.push_back(newResource);
 
 		reimportResourceInternal(newResource);
@@ -237,11 +253,7 @@ namespace BansheeEditor
 
 	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;
-
+		DirectoryEntry* newEntry = cm_new<DirectoryEntry>(dirPath, toWString(PathUtil::getFilename(dirPath)), parent);
 		parent->mChildren.push_back(newEntry);
 
 		if(!onEntryAdded.empty())
@@ -258,7 +270,7 @@ namespace BansheeEditor
 		{
 			if(manifest->uuidExists(resource->meta->getUUID()))
 			{
-				const WPath& path = manifest->uuidToFilePath(resource->meta->getUUID());
+				const WString& path = manifest->uuidToFilePath(resource->meta->getUUID());
 				if(FileSystem::isFile(path))
 					FileSystem::remove(path);
 
@@ -355,7 +367,7 @@ namespace BansheeEditor
 			gResources().save(importedResource, toWString(internalResourcesPath), true);
 
 			ResourceManifestPtr manifest = gResources().getResourceManifest();
-			manifest->registerResource(importedResource.getUUID(), internalResourcesPath);
+			manifest->registerResource(importedResource.getUUID(), toWString(internalResourcesPath));
 
 			resource->lastUpdateTime = std::time(nullptr);
 		}
@@ -371,7 +383,7 @@ namespace BansheeEditor
 		if(!manifest->uuidExists(resource->meta->getUUID()))
 			return false;
 
-		const WPath& path = manifest->uuidToFilePath(resource->meta->getUUID());
+		const WString& path = manifest->uuidToFilePath(resource->meta->getUUID());
 		if(!FileSystem::isFile(path))
 			return false;
 
@@ -381,20 +393,22 @@ namespace BansheeEditor
 
 	ProjectLibrary::LibraryEntry* ProjectLibrary::findEntry(const CM::WPath& fullPath) const
 	{
-		auto pathIter = fullPath.begin();
-		auto rootIter = mRootEntry->path.begin();
+		Vector<WString>::type pathElems = PathUtil::split(toWString(fullPath));
+		Vector<WString>::type rootElems = PathUtil::split(toWString(mRootEntry->path));
+
+		auto pathIter = pathElems.begin();
+		auto rootIter = rootElems.begin();
 
-		while(*pathIter == *rootIter)
+		while(pathIter != pathElems.end() && rootIter != rootElems.end() && PathUtil::comparePathElements(*pathIter, *rootIter))
 		{
 			++pathIter;
 			++rootIter;
 		}
 
-		if(pathIter == fullPath.begin()) // Not a single entry matches Resources path
+		if(pathIter == pathElems.begin()) // Not a single entry matches Resources path
 			return nullptr;
 
 		--pathIter;
-
 		Stack<LibraryEntry*>::type todo;
 		todo.push(mRootEntry);
 
@@ -403,19 +417,22 @@ namespace BansheeEditor
 			LibraryEntry* current = todo.top();
 			todo.pop();
 
-			if(*pathIter == *(--(current->path.end())))
+			if(PathUtil::comparePathElements(*pathIter, current->elementName))
 			{
+				++pathIter;
+
+				if(pathIter == pathElems.end())
+					return current;
+
+				while(!todo.empty())
+					todo.pop();
+
 				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;
 			}
 		}
 
@@ -473,6 +490,7 @@ namespace BansheeEditor
 				newEntryParent->mChildren.push_back(oldEntry);
 				oldEntry->parent = newEntryParent;
 				oldEntry->path = newPath;
+				oldEntry->elementName = toWString(PathUtil::getFilename(newPath));
 
 				if(!onEntryRemoved.empty())
 					onEntryRemoved(oldPath);
@@ -557,9 +575,7 @@ namespace BansheeEditor
 
 	WPath ProjectLibrary::getMetaPath(const CM::WPath& path) const
 	{
-		WString ext = toWString(PathUtil::getExtension(path));
-		WPath metaPath = path;
-		PathUtil::replaceExtension(metaPath, ext + L".meta");
+		WPath metaPath = toPath(toWString(path) + L".meta");
 
 		return metaPath;
 	}
@@ -571,6 +587,14 @@ namespace BansheeEditor
 
 	void ProjectLibrary::onMonitorFileModified(const WString& path)
 	{
-		checkForModifications(path);
+		if(!isMeta(toPath(path)))
+			checkForModifications(path);
+		else
+		{
+			WPath resourcePath = toPath(path);
+			PathUtil::replaceExtension(resourcePath, L"");
+
+			checkForModifications(toWString(resourcePath));
+		}
 	}
 }

+ 19 - 16
CamelotClient/Source/CmTestTextSprite.cpp

@@ -25,6 +25,7 @@
 #include "BsGUIContent.h"
 #include "CmStringTable.h"
 #include "BsGUISceneTreeView.h"
+#include "BsGUIResourceTreeView.h"
 #include "BsGUIScrollArea.h"
 
 using namespace CamelotFramework;
@@ -49,7 +50,7 @@ namespace BansheeEditor
 		GUIArea* area = GUIArea::createStretchedXY(*this, 0, 0, 0, 0);
 
 		mSceneTreeView = GUISceneTreeView::create(*this, GUIOptions(GUIOption::flexibleWidth(), GUIOption::flexibleHeight()));
-
+		mResourceTreeView = GUIResourceTreeView::create(*this, GUIOptions(GUIOption::flexibleWidth(), GUIOption::flexibleHeight()));
 		GUILayout& sceneTreeViewLayout = area->getLayout().addLayoutY();
 		
 		//sceneTreeViewLayout.addElement(mSceneTreeView);
@@ -85,26 +86,27 @@ namespace BansheeEditor
 		sceneTreeViewLayout.addFlexibleSpace();
 
 		scrollArea->getLayout().addElement(mSceneTreeView);
+		area->getLayout().addElement(mResourceTreeView);
 
-		GUIButton* button = GUIButton::create(*this, HString(L"dbgBtn"));
-		button->onClick.connect(boost::bind(&TestTextSprite::dbgBtn, this));
-		area->getLayout().addElement(button);
+		//GUIButton* button = GUIButton::create(*this, HString(L"dbgBtn"));
+		//button->onClick.connect(boost::bind(&TestTextSprite::dbgBtn, this));
+		//area->getLayout().addElement(button);
 
-		button = GUIButton::create(*this, HString(L"Add GameObjects"));
-		button->onClick.connect(boost::bind(&TestTextSprite::dbgAdd, this));
-		area->getLayout().addElement(button);
+		//button = GUIButton::create(*this, HString(L"Add GameObjects"));
+		//button->onClick.connect(boost::bind(&TestTextSprite::dbgAdd, this));
+		//area->getLayout().addElement(button);
 
-		button = GUIButton::create(*this, HString(L"Rename GameObject"));
-		button->onClick.connect(boost::bind(&TestTextSprite::dbgRename, this));
-		area->getLayout().addElement(button);
+		//button = GUIButton::create(*this, HString(L"Rename GameObject"));
+		//button->onClick.connect(boost::bind(&TestTextSprite::dbgRename, this));
+		//area->getLayout().addElement(button);
 
-		button = GUIButton::create(*this, HString(L"Remove child GameObjects"));
-		button->onClick.connect(boost::bind(&TestTextSprite::dbgRemoveChildren, this));
-		area->getLayout().addElement(button);
+		//button = GUIButton::create(*this, HString(L"Remove child GameObjects"));
+		//button->onClick.connect(boost::bind(&TestTextSprite::dbgRemoveChildren, this));
+		//area->getLayout().addElement(button);
 
-		button = GUIButton::create(*this, HString(L"Remove parent GameObjects"));
-		button->onClick.connect(boost::bind(&TestTextSprite::dbgRemoveParents, this));
-		area->getLayout().addElement(button);
+		//button = GUIButton::create(*this, HString(L"Remove parent GameObjects"));
+		//button->onClick.connect(boost::bind(&TestTextSprite::dbgRemoveParents, this));
+		//area->getLayout().addElement(button);
 
 		area->getLayout().addFlexibleSpace();
 
@@ -116,6 +118,7 @@ namespace BansheeEditor
 	{
 		labelString.setParameter(0, toWString(Input::instance().getCursorPosition().x));
 		mSceneTreeView->update();
+		mResourceTreeView->update();
 		//labelString.setParameter(1, toWString(Input::instance().getCursorPosition().y));
 	}
 

+ 6 - 7
CamelotCore/Include/CmResourceManifest.h

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

+ 2 - 2
CamelotCore/Include/CmResourceManifestRTTI.h

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

+ 1 - 2
CamelotCore/Include/CmResources.h

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

+ 1 - 0
CamelotCore/Source/CmPlatform.cpp

@@ -43,6 +43,7 @@ namespace CamelotFramework
 	{
 		_clear();
 
+		mDropType = OSDropType::FileList;
 		mFileList = cm_new<Vector<WString>::type>();
 		*mFileList = fileList;
 	}

+ 5 - 5
CamelotCore/Source/CmResourceManifest.cpp

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

+ 6 - 6
CamelotCore/Source/CmResources.cpp

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

+ 70 - 53
CamelotUtility/Include/CmPath.h

@@ -43,6 +43,11 @@ namespace CamelotFramework
 			path.replace_extension(newExtension.c_str());
 		}
 
+		static void replaceExtension(WString& path, const WString& newExtension)
+		{
+			path = toWString(toPath(path).replace_extension(newExtension.c_str()));
+		}
+
 		static WString parentPath(const WString& path)
 		{
 			return WPath(path.c_str()).parent_path().c_str();
@@ -59,21 +64,29 @@ namespace CamelotFramework
 		 */
 		static bool includes(const WString& child, const WString& parent)
 		{
-			boost::filesystem3::wpath childPath = child.c_str();
-			boost::filesystem3::wpath parentPath = parent.c_str();
+			Vector<WString>::type childPathElems = split(child);
+			Vector<WString>::type parentPathElems = split(parent);
 
-			auto iterChild = childPath.begin();
-			auto iterParent = parentPath.begin();
+			auto iterChild = childPathElems.begin();
+			auto iterParent = parentPathElems.begin();
 
-			for(; iterChild != childPath.end(); ++iterChild, ++iterParent)
+			for(; iterParent != parentPathElems.end(); ++iterChild, ++iterParent)
 			{
-				if(*iterChild != *iterParent)
+				if(!comparePathElements(*iterChild, *iterParent))
 					return false;
 			}
 
 			return true;
 		}
 
+		static Vector<WString>::type split(const WString& path)
+		{
+			Vector<WString>::type splitPath;
+
+			WString standardizedPath = standardisePath(path);
+			return StringUtil::split(standardizedPath, L"/");
+		}
+
 		static WString combine(const WString& base, const WString& name)
 		{
 			if (base.empty())
@@ -87,74 +100,78 @@ namespace CamelotFramework
 			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	Compares two path elements for equality. 
+		 * 			
+		 * @note	Should not be used for comparing entire paths. First you need to split your
+		 * 			path into sub-elements using some other method and then send those sub-elements to
+		 * 			this method. 
 		 */
-		static WString standardisePath(const WString& inPath)
+		static bool comparePathElements(const WString& left, const WString& right)
 		{
-			WString path = inPath;
+			if(left.size() != right.size())
+				return false;
 
-			std::replace(path.begin(), path.end(), L'\\', L'/');
-			if(path[path.length() - 1] != L'/')
-				path += L'/';
+			for(UINT32 i = 0; i < (UINT32)left.size(); i++)
+			{
+#if CM_PLATFORM == CM_PLATFORM_WIN32 // Compare case insensitive
+				if(tolower(left[i]) != tolower(right[i]))
+					return false;
+#else
+				assert(false); // Implement case sensitive or insensitive comparison, depending on platform
+#endif
+			}
 
-			return path;
+			return true;
 		}
 
 		/**
-		 * @brief	Method for splitting a fully qualified filename into the base name and path.
+		 * @see		comparePathElements(const WString&, const WString&)
 		 */
-		static void splitFilename(const WString& qualifiedName, WString& outBasename, WString& outPath)
+		static bool comparePathElements(const WPath& left, const WPath& right)
 		{
-			WString path = qualifiedName;
-			// Replace \ with / first
-			std::replace( path.begin(), path.end(), L'\\', L'/' );
-			// split based on final /
-			size_t i = path.find_last_of(L'/');
+			if(left.native().size() != right.native().size())
+				return false;
 
-			if (i == String::npos)
-			{
-				outPath.clear();
-				outBasename = qualifiedName;
-			}
-			else
+			for(UINT32 i = 0; i < (UINT32)left.native().size(); i++)
 			{
-				outBasename = path.substr(i+1, path.size() - i - 1);
-				outPath = path.substr(0, i+1);
+#if CM_PLATFORM == CM_PLATFORM_WIN32 // Compare case insensitive
+				if(tolower(left.native()[i]) != tolower(right.native()[i]))
+					return false;
+#else
+				assert(false); // Implement case sensitive or insensitive comparison, depending on platform
+#endif
 			}
+
+			return true;
 		}
 
-		/**
-		 * @brief	Method for splitting a filename into the base name and extension.
-		 */
-		static void splitBaseFilename(const WString& fullName, WString& outBasename, WString& outExtension)
+		static WString getFilename(const WString& path)
 		{
-			size_t i = fullName.find_last_of(L".");
-			if (i == CamelotFramework::WString::npos)
-			{
-				outExtension.clear();
-				outBasename = fullName;
-			}
-			else
-			{
-				outExtension = fullName.substr(i+1);
-				outBasename = fullName.substr(0, i);
-			}
+			return toWString(toPath(path).filename());
+		}
+
+		static WPath getFilename(const WPath& path)
+		{
+			return path.filename();
 		}
 
 		/**
-		 * @brief	Method for splitting a fully qualified filename into the base name, extension and path.
+		 * @brief	Method for standardizing paths - use forward slashes only, end with slash.
 		 */
-		static void splitFullFilename(const WString& qualifiedName, WString& outBasename, WString& outExtention, WString& outPath)
+		static WString standardisePath(const WString& inPath)
 		{
-			WString fullName;
-			splitFilename(qualifiedName, fullName, outPath);
-			splitBaseFilename(fullName, outBasename, outExtention);
+			WString path = inPath;
+
+			std::replace(path.begin(), path.end(), L'\\', L'/');
+
+			if(path.length() > 0)
+			{
+				if(path[path.length() - 1] != L'/')
+					path += L'/';
+			}
+
+			return path;
 		}
 	};
 

+ 24 - 2
CamelotUtility/Source/CmFileSystem.cpp

@@ -149,12 +149,34 @@ namespace CamelotFramework
 
 	void FileSystem::createDir(const WString& fullPath)
 	{
-		create_directory(fullPath.c_str());
+		createDir(WPath(fullPath.c_str()));
 	}
 
 	void FileSystem::createDir(const WPath& fullPath)
 	{
-		create_directory(fullPath);
+		if(fullPath.empty())
+			return;
+
+		WPath parentPath = fullPath;
+		auto pathEnd = fullPath.end();
+
+		while(!exists(parentPath))
+		{
+			if(pathEnd == fullPath.begin())
+				break;
+
+			parentPath = parentPath.parent_path();
+			pathEnd--;
+		}
+
+		for(; pathEnd != fullPath.end(); ++pathEnd)
+		{
+			create_directory(parentPath);
+
+			parentPath /= *pathEnd;
+		}
+
+		create_directory(parentPath);
 	}
 
 	void FileSystem::getChildren(const WPath& dirPath, Vector<WPath>::type& files, Vector<WPath>::type& directories)

+ 25 - 23
ProjectLibrary.txt

@@ -1,37 +1,39 @@
 
-Today:
- - Fix TreeView issues with expand
-
-Tomorrow:
- - Attempt to add ResourceTreeView to scene (and initialize ProjectLibrary)
- - Project library save/load
- - Figure out how to deal with import queue
- 
- 
- ProjectLibrary
-  - Save/Load - saves/loads the hierarchy of ResourceEntries
+TODO
+ - ResourceManifest filePathToUUID wont work as intended as file-path comparison is case sensitive plus it wont deal with slashes properly
+ - Remove WPath. In general, but specifically from CmPath.h, CmFileSystem.h and Projectlibrary
+ - ResourceTreeView - Element sorting doesn't work
+ - ProjectLibrary Save/Load - saves/loads the hierarchy of ResourceEntries
+    - ResourceManifest needs to store relative locations when I save it, so that user can move Project folder safely
+ - Resource import queue (ability to display progress bar for resources that are importing)
+ - Implement Delete command for ResourceTreeView
 
 --------------------
 
-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)
+Test:
+ - OSDropTarget
+    - Drag and drop one file from Explorer into TreeView
+    - Drag and drop multiple files from Explorer into TreeView
+    - Drag and drop entire folders from Explorer into TreeView
+    - Dragging folders or files from within Resources to another sub-folder in Resources
 
--------------------
+ - File monitoring
+    - Add a file and ensure it is imported
+    - Delete a file and ensure it has been cleaned up
+    - Add a whole folder hierarchy with files and see if it is constructed properly
+    - Delete a folder hierarcha and ensure it has been cleaned up
+
+ - Start-up
+     - Load up ProjectLibrary with a complex hierarchy already existing, with multiple files to import
 
-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.
+ - Save/Load (After it is implemented)
+     - Ensure that leaving application and then reloading will still recognize previously saved assets and will not reimport them
+       - Also ensure that UUIDs will be properly read from .meta files
 
-Later: Projectlibrary add (tracking drag and drop using Win32DragAndDropManager)
 -------------------
 
 LOW PRIORITY
 
-ResourceManifest contains absolute file locations. What happens when the project moves?
- - When saving ResourceManifest make sure to process all paths and make them relative.
- - Then reverse that process on load
-
 What happens when importer fails importing a file?
  - This can be one of the lasts steps, but I will likely need to go through all importers manually and set them up so they return proper error codes
     and clean up properly on error.