Browse Source

Added a way to create new resources in Library Window

BearishSun 10 years ago
parent
commit
77fd1ec83a

+ 8 - 0
BansheeCore/Include/BsPlatform.h

@@ -420,6 +420,14 @@ namespace BansheeEngine
 		static bool openBrowseDialog(FileDialogType type, const Path& defaultPath, const WString& filterList,
 			Vector<Path>& paths);
 
+		/**
+		 * @brief	Opens the provided file or folder using the default application for that file type, as specified
+		 * 			by the operating system.
+		 *
+		 * @param	path	Absolute path to the file or folder to open.
+		 */
+		static void open(const Path& path);
+
 		/**
 		 * @brief	Message pump. Processes OS messages and returns when it's free.
 		 *

+ 1 - 1
BansheeCore/Include/BsStringTableRTTI.h

@@ -6,7 +6,7 @@
 
 namespace BansheeEngine
 {
-	class BS_CORE_EXPORT StringTableRTTI : public RTTIType<StringTable, IReflectable, StringTableRTTI>
+	class BS_CORE_EXPORT StringTableRTTI : public RTTIType<StringTable, Resource, StringTableRTTI>
 	{
 	private:
 		Language& getActiveLanguage(StringTable* obj) { return obj->mActiveLanguage; }

+ 6 - 1
BansheeCore/Source/Win32/BsWin32Platform.cpp

@@ -428,10 +428,15 @@ namespace BansheeEngine
 		BS_PVT_DELETE(OSDropTarget, &target);
 	}
 
+	void Platform::open(const Path& path)
+	{
+		ShellExecute(nullptr, "open", path.toString().c_str(), nullptr, nullptr, SW_SHOWNORMAL);
+	}
+
 	void Platform::_messagePump()
 	{
 		MSG  msg;
-		while(PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE))
+		while (PeekMessage(&msg, nullptr, 0U, 0U, PM_REMOVE))
 		{
 			TranslateMessage(&msg);
 			DispatchMessage(&msg);

+ 13 - 0
BansheeEditor/Include/BsBuiltinEditorResources.h

@@ -121,6 +121,16 @@ namespace BansheeEngine
 		 */
 		HSpriteTexture getLogMessageIcon(LogMessageIcon icon) const;
 
+		/**
+		 * @brief	Returns text contained in the default "empty" shader.
+		 */
+		WString getEmptyShaderCode() const;
+
+		/**
+		 * @brief	Returns text contained in the default "empty" C# script.
+		 */
+		WString getEmptyCSScriptCode() const;
+
 		static const String ObjectFieldStyleName;
 		static const String ObjectFieldLabelStyleName;
 		static const String ObjectFieldDropBtnStyleName;
@@ -404,5 +414,8 @@ namespace BansheeEngine
 		static const WString ShaderGizmoPickingFile;
 		static const WString ShaderGizmoPickingAlphaFile;
 		static const WString ShaderSelectionFile;
+
+		static const WString EmptyShaderCodeFile;
+		static const WString EmptyCSScriptCodeFile;
 	};
 }

+ 33 - 0
BansheeEditor/Source/BsBuiltinEditorResources.cpp

@@ -42,6 +42,7 @@
 #include "BsRTTIType.h"
 #include "BsFileSystem.h"
 #include "BsResourceManifest.h"
+#include "BsDataStream.h"
 
 namespace BansheeEngine
 {
@@ -285,6 +286,12 @@ namespace BansheeEngine
 	const WString BuiltinEditorResources::ShaderGizmoPickingAlphaFile = L"GizmoPickingAlpha.bsl";
 	const WString BuiltinEditorResources::ShaderSelectionFile = L"Selection.bsl";
 
+	/************************************************************************/
+	/* 								OTHER							  		*/
+	/************************************************************************/
+	const WString BuiltinEditorResources::EmptyShaderCodeFile = L"EmptyShaderText.txt";
+	const WString BuiltinEditorResources::EmptyCSScriptCodeFile = L"EmptyCSScriptText.txt";
+
 	BuiltinEditorResources::BuiltinEditorResources()
 	{
 		Path absoluteDataPath = FileSystem::getWorkingDirectoryPath();
@@ -1818,4 +1825,30 @@ namespace BansheeEngine
 
 		return HSpriteTexture();
 	}
+
+	WString BuiltinEditorResources::getEmptyShaderCode() const
+	{
+		Path filePath = FileSystem::getWorkingDirectoryPath();
+		filePath.append(BuiltinDataFolder);
+		filePath.append(EmptyShaderCodeFile);
+
+		DataStreamPtr fileStream = FileSystem::openFile(filePath);
+		if (fileStream != nullptr)
+			return fileStream->getAsWString();
+
+		return StringUtil::WBLANK;
+	}
+
+	WString BuiltinEditorResources::getEmptyCSScriptCode() const
+	{
+		Path filePath = FileSystem::getWorkingDirectoryPath();
+		filePath.append(BuiltinDataFolder);
+		filePath.append(EmptyCSScriptCodeFile);
+
+		DataStreamPtr fileStream = FileSystem::openFile(filePath);
+		if (fileStream != nullptr)
+			return fileStream->getAsWString();
+
+		return StringUtil::WBLANK;
+	}
 }

+ 1 - 1
BansheeEditor/Source/BsGUISceneTreeView.cpp

@@ -598,7 +598,7 @@ namespace BansheeEngine
 			HSceneObject elem = child;
 
 			while (elem != nullptr && elem != parent)
-				elem = child->getParent();
+				elem = elem->getParent();
 
 			return elem == parent;
 		};

+ 37 - 31
BansheeEditor/Source/BsProjectLibrary.cpp

@@ -95,45 +95,48 @@ namespace BansheeEngine
 
 		Path pathToSearch = fullPath;
 		LibraryEntry* entry = findEntry(pathToSearch);
-		if(entry == nullptr) // File could be new, try to find parent directory entry
+		if (entry == nullptr) // File could be new, try to find parent directory entry
 		{
-			if (isMeta(pathToSearch))
+			if (FileSystem::exists(pathToSearch))
 			{
-				Path sourceFilePath = pathToSearch;
-				sourceFilePath.setExtension(L"");
-
-				if (!FileSystem::isFile(sourceFilePath))
+				if (isMeta(pathToSearch))
 				{
-					LOGWRN("Found a .meta file without a corresponding resource. Deleting.");
+					Path sourceFilePath = pathToSearch;
+					sourceFilePath.setExtension(L"");
 
-					FileSystem::remove(pathToSearch);
-				}
-			}
-			else
-			{
-				Path parentDirPath = pathToSearch.getParent();
-				entry = findEntry(parentDirPath);
+					if (!FileSystem::isFile(sourceFilePath))
+					{
+						LOGWRN("Found a .meta file without a corresponding resource. Deleting.");
 
-				// Cannot find parent directory. Create the needed hierarchy.
-				DirectoryEntry* entryParent = nullptr;
-				DirectoryEntry* newHierarchyParent = nullptr;
-				if (entry == nullptr)
-					createInternalParentHierarchy(pathToSearch, &newHierarchyParent, &entryParent);
+						FileSystem::remove(pathToSearch);
+					}
+				}
 				else
-					entryParent = static_cast<DirectoryEntry*>(entry);
-
-				if (FileSystem::isFile(pathToSearch))
 				{
-					if (import)
-						addResourceInternal(entryParent, pathToSearch);
+					Path parentDirPath = pathToSearch.getParent();
+					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);
 
-					dirtyResources.push_back(pathToSearch);
-				}
-				else if (FileSystem::isDirectory(pathToSearch))
-				{
-					addDirectoryInternal(entryParent, pathToSearch);
+					if (FileSystem::isFile(pathToSearch))
+					{
+						if (import)
+							addResourceInternal(entryParent, pathToSearch);
+
+						dirtyResources.push_back(pathToSearch);
+					}
+					else if (FileSystem::isDirectory(pathToSearch))
+					{
+						addDirectoryInternal(entryParent, pathToSearch);
 
-					checkForModifications(pathToSearch);
+						checkForModifications(pathToSearch);
+					}
 				}
 			}
 		}
@@ -616,7 +619,10 @@ namespace BansheeEngine
 
 		LibraryEntry* existingEntry = findEntry(assetPath);
 		if (existingEntry != nullptr)
-			BS_EXCEPT(InvalidParametersException, "Resource already exists at the specified path: " + assetPath.toString());
+		{
+			LOGWRN("Resource already exists at the specified path : " + assetPath.toString() + ". Unable to save.");
+			return;
+		}
 
 		Resources::instance().save(resource, assetPath.getAbsolute(getResourcesFolder()), false);
 		checkForModifications(assetPath);

+ 1 - 1
BansheeEngine/Source/BsGUIMenu.cpp

@@ -114,7 +114,7 @@ namespace BansheeEngine
 				else
 				{
 					existingItem = bs_alloc<GUIMenuItem>();
-					existingItem = new (existingItem)GUIMenuItem(curSubMenu, pathElem, nullptr, 0, mNextIdx++, ShortcutKey::NONE);
+					existingItem = new (existingItem)GUIMenuItem(curSubMenu, pathElem, nullptr, priority, mNextIdx++, ShortcutKey::NONE);
 				}
 
 				curSubMenu->addChild(existingItem);

+ 14 - 14
BansheeUtility/Include/BsDataStream.h

@@ -171,37 +171,37 @@ namespace BansheeEngine
         /** 
 		 * @copydoc DataStream::read
          */
-		size_t read(void* buf, size_t count);
+		size_t read(void* buf, size_t count) override;
 
         /** 
 		 * @copydoc DataStream::write
          */
-		size_t write(const void* buf, size_t count);
+		size_t write(const void* buf, size_t count) override;
 
         /** 
 		 * @copydoc DataStream::skip
          */
-		void skip(size_t count);
+		void skip(size_t count) override;
 	
         /** 
 		 * @copydoc DataStream::seek
          */
-	    void seek( size_t pos );
+		void seek(size_t pos) override;
 		
         /** 
 		 * @copydoc DataStream::tell
          */
-	    size_t tell(void) const;
+		size_t tell(void) const override;
 
         /** 
 		 * @copydoc DataStream::eof
          */
-	    bool eof(void) const;
+		bool eof(void) const override;
 
         /** 
 		 * @copydoc DataStream::close
          */
-        void close(void);
+		void close(void) override;
 
 	protected:
 		UINT8* mData;
@@ -256,37 +256,37 @@ namespace BansheeEngine
         /** 
 		 * @copydoc DataStream::read
          */
-		size_t read(void* buf, size_t count);
+		size_t read(void* buf, size_t count) override;
 
         /** 
 		 * @copydoc DataStream::write
          */
-		size_t write(const void* buf, size_t count);
+		size_t write(const void* buf, size_t count) override;
 
         /** 
 		 * @copydoc DataStream::skip
          */
-		void skip(size_t count);
+		void skip(size_t count) override;
 	
         /** 
 		 * @copydoc DataStream::seek
          */
-	    void seek(size_t pos);
+		void seek(size_t pos) override;
 
         /** 
 		 * @copydoc DataStream::tell
          */
-		size_t tell() const;
+		size_t tell() const override;
 
         /** 
 		 * @copydoc DataStream::eof
          */
-	    bool eof() const;
+		bool eof() const override;
 
         /** 
 		 * @copydoc DataStream::close
          */
-        void close();
+		void close() override;
 
 	protected:
 		std::shared_ptr<std::istream> mpInStream;

+ 1 - 1
BansheeUtility/Source/BsDataStream.cpp

@@ -131,7 +131,7 @@ namespace BansheeEngine
 				const std::codecvt_mode convMode = (std::codecvt_mode)(0);
 				typedef std::codecvt_utf8<char16_t, 1114111, convMode> utf8utf16;
 
-				// Bug?: Regardless me of not providing the std::little_endian flag it seems that is how the data is read
+				// Bug?: Regardless of not providing the std::little_endian flag it seems that is how the data is read
 				// so I manually flip it
 				UINT32 numChars = (UINT32)(string.size() - 2) / 2;
 				for (UINT32 i = 0; i < numChars; i++)

+ 16 - 3
BansheeUtility/Source/Win32/BsFileSystem.cpp

@@ -1,6 +1,7 @@
 #include "BsFileSystem.h"
 #include "BsException.h"
 #include "BsDataStream.h"
+#include "BsDebug.h"
 #include <windows.h>
 
 namespace BansheeEngine
@@ -246,6 +247,15 @@ namespace BansheeEngine
 
 	DataStreamPtr FileSystem::openFile(const Path& fullPath, bool readOnly)
 	{
+		WString pathWString = fullPath.toWString();
+		const wchar_t* pathString = pathWString.c_str();
+
+		if (!win32_pathExists(pathString) || !win32_isFile(pathString))
+		{
+			LOGWRN("Attempting to open a file that doesn't exist: " + fullPath.toString());
+			return nullptr;
+		}
+
 		UINT64 fileSize = getFileSize(fullPath);
 
 		// Always open in binary mode
@@ -259,19 +269,22 @@ namespace BansheeEngine
 		{
 			mode |= std::ios::out;
 			rwStream = bs_shared_ptr_new<std::fstream>();
-			rwStream->open(fullPath.toWString().c_str(), mode);
+			rwStream->open(pathString, mode);
 			baseStream = rwStream;
 		}
 		else
 		{
 			roStream = bs_shared_ptr_new<std::ifstream>();
-			roStream->open(fullPath.toWString().c_str(), mode);
+			roStream->open(pathString, mode);
 			baseStream = roStream;
 		}
 
 		// Should check ensure open succeeded, in case fail for some reason.
 		if (baseStream->fail())
-			BS_EXCEPT(FileNotFoundException, "Cannot open file: " + fullPath.toString());
+		{
+			LOGWRN("Cannot open file: " + fullPath.toString());
+			return nullptr;
+		}
 
 		/// Construct return stream, tell it to delete on destroy
 		FileDataStream* stream = 0;

+ 12 - 0
MBansheeEditor/EditorApplication.cs

@@ -245,6 +245,15 @@ namespace BansheeEditor
             Internal_LoadProject(path); // Triggers OnProjectLoaded when done
         }
 
+        /// <summary>
+        /// Opens a file or a folder in the default external application.
+        /// </summary>
+        /// <param name="path">Absolute path to the file or folder to open.</param>
+        public static void OpenExternally(string path)
+        {
+            Internal_OpenExternally(path);
+        }
+
         private static void OnProjectLoaded()
         {
             if (!IsProjectLoaded)
@@ -489,5 +498,8 @@ namespace BansheeEditor
 
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern void Internal_CreateProject(string path);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern void Internal_OpenExternally(string path);
     }
 }

+ 16 - 0
MBansheeEditor/EditorBuiltin.cs

@@ -19,6 +19,16 @@ namespace BansheeEditor
 
         public static SpriteTexture XBtnIcon { get { return Internal_GetXBtnIcon(); } }
 
+        /// <summary>
+        /// Returns text contained in the default "empty" shader.
+        /// </summary>
+        public static string EmptyShaderCode { get { return Internal_GetEmptyShaderCode(); } }
+
+        /// <summary>
+        /// Returns text contained in the default "empty" C# script.
+        /// </summary>
+        public static string EmptyCSScriptCode { get { return Internal_GetEmptyCSScriptCode(); } }
+
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern SpriteTexture Internal_GetFolderIcon();
 
@@ -54,5 +64,11 @@ namespace BansheeEditor
 
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern SpriteTexture Internal_GetXBtnIcon();
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern string Internal_GetEmptyShaderCode();
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern string Internal_GetEmptyCSScriptCode();
     }
 }

+ 163 - 0
MBansheeEditor/LibraryMenu.cs

@@ -0,0 +1,163 @@
+using System.IO;
+using BansheeEngine;
+
+namespace BansheeEditor
+{
+    /// <summary>
+    /// Handles main menu and context menu items and callbacks for project library window.
+    /// </summary>
+    internal static class LibraryMenu
+    {
+        /// <summary>
+        /// Creates the context menu used by project library window. New context menu must be created when a new instance
+        /// of the project library window is created.
+        /// </summary>
+        /// <param name="win">Instance of the project library window.</param>
+        /// <returns>Context menu bound to the specified instance of the project library window.</returns>
+        internal static ContextMenu CreateContextMenu(LibraryWindow win)
+        {
+            ContextMenu entryContextMenu = new ContextMenu();
+            entryContextMenu.AddItem("Create", null);
+            entryContextMenu.AddItem("Create/Material", CreateEmptyMaterial);
+            entryContextMenu.AddItem("Create/Shader", CreateEmptyShader);
+            entryContextMenu.AddItem("Create/C# script", CreateEmptyCSScript);
+            entryContextMenu.AddItem("Create/Sprite texture", CreateEmptySpriteTexture);
+            entryContextMenu.AddItem("Create/GUI skin", CreateEmptyGUISkin);
+            entryContextMenu.AddItem("Create/String table", CreateEmptyStringTable);
+            entryContextMenu.AddSeparator("");
+            entryContextMenu.AddItem("Rename", win.RenameSelection, new ShortcutKey(ButtonModifier.None, ButtonCode.F2));
+            entryContextMenu.AddSeparator("");
+            entryContextMenu.AddItem("Cut", win.CutSelection, new ShortcutKey(ButtonModifier.Ctrl, ButtonCode.X));
+            entryContextMenu.AddItem("Copy", win.CopySelection, new ShortcutKey(ButtonModifier.Ctrl, ButtonCode.C));
+            entryContextMenu.AddItem("Duplicate", win.DuplicateSelection, new ShortcutKey(ButtonModifier.Ctrl, ButtonCode.D));
+            entryContextMenu.AddItem("Paste", win.PasteToSelection, new ShortcutKey(ButtonModifier.Ctrl, ButtonCode.V));
+            entryContextMenu.AddSeparator("");
+            entryContextMenu.AddItem("Delete", win.DeleteSelection, new ShortcutKey(ButtonModifier.None, ButtonCode.Delete));
+            entryContextMenu.AddSeparator("");
+            entryContextMenu.AddItem("Open externally", OpenExternally);
+            entryContextMenu.AddItem("Explore location", ExploreLocation);
+
+            entryContextMenu.SetLocalizedName("Rename", new LocEdString("Rename"));
+            entryContextMenu.SetLocalizedName("Cut", new LocEdString("Cut"));
+            entryContextMenu.SetLocalizedName("Copy", new LocEdString("Copy"));
+            entryContextMenu.SetLocalizedName("Duplicate", new LocEdString("Duplicate"));
+            entryContextMenu.SetLocalizedName("Paste", new LocEdString("Paste"));
+            entryContextMenu.SetLocalizedName("Delete", new LocEdString("Delete"));
+
+            return entryContextMenu;
+        }
+
+        /// <summary>
+        /// Queries if a library window is displayed.
+        /// </summary>
+        /// <returns>True if a library window is active, false if not.</returns>
+        internal static bool IsLibraryWindowActive()
+        {
+            return EditorWindow.GetWindow<LibraryWindow>() != null;
+        }
+
+        /// <summary>
+        /// Creates a new material with the default shader in the currently selected project library folder.
+        /// </summary>
+        [MenuItem("Resources/Create/Material", 50, false, "IsLibraryWindowActive")]
+        internal static void CreateEmptyMaterial()
+        {
+            LibraryWindow win = EditorWindow.GetWindow<LibraryWindow>();
+            if(win == null)
+                return;
+
+            LibraryUtility.CreateEmptyMaterial(win.SelectedFolder);
+        }
+
+        /// <summary>
+        /// Creates a new shader containing a rough code outline in the currently selected project library folder.
+        /// </summary>
+        [MenuItem("Resources/Create/Shader", 49, false, "IsLibraryWindowActive")]
+        internal static void CreateEmptyShader()
+        {
+            LibraryWindow win = EditorWindow.GetWindow<LibraryWindow>();
+            if (win == null)
+                return;
+
+            LibraryUtility.CreateEmptyShader(win.SelectedFolder);
+        }
+
+        /// <summary>
+        /// Creates a new C# script containing a rough code outline in the currently selected project library folder.
+        /// </summary>
+        [MenuItem("Resources/Create/C# script", 48, false, "IsLibraryWindowActive")]
+        internal static void CreateEmptyCSScript()
+        {
+            LibraryWindow win = EditorWindow.GetWindow<LibraryWindow>();
+            if (win == null)
+                return;
+
+            LibraryUtility.CreateEmptyCSScript(win.SelectedFolder);
+        }
+
+        /// <summary>
+        /// Creates a new empty sprite texture in the currently selected project library folder.
+        /// </summary>
+        [MenuItem("Resources/Create/Sprite texture", 47, false, "IsLibraryWindowActive")]
+        internal static void CreateEmptySpriteTexture()
+        {
+            LibraryWindow win = EditorWindow.GetWindow<LibraryWindow>();
+            if (win == null)
+                return;
+
+            LibraryUtility.CreateEmptySpriteTexture(win.SelectedFolder);
+        }
+
+        /// <summary>
+        /// Creates a new empty GUI skin in the currently selected project library folder.
+        /// </summary>
+        [MenuItem("Resources/Create/GUI skin", 46, false, "IsLibraryWindowActive")]
+        internal static void CreateEmptyGUISkin()
+        {
+            LibraryWindow win = EditorWindow.GetWindow<LibraryWindow>();
+            if (win == null)
+                return;
+
+            LibraryUtility.CreateEmptyGUISkin(win.SelectedFolder);
+        }
+
+        /// <summary>
+        /// Creates a new empty string table in the currently selected project library folder.
+        /// </summary>
+        [MenuItem("Resources/Create/String table", 45, false, "IsLibraryWindowActive")]
+        internal static void CreateEmptyStringTable()
+        {
+            LibraryWindow win = EditorWindow.GetWindow<LibraryWindow>();
+            if (win == null)
+                return;
+
+            LibraryUtility.CreateEmptyStringTable(win.SelectedFolder);
+        }
+
+        /// <summary>
+        /// Opens the currently selected project library file or folder in the default external application.
+        /// </summary>
+        [MenuItem("Resources/Open externally", 40, true, "IsLibraryWindowActive")]
+        internal static void OpenExternally()
+        {
+            LibraryWindow win = EditorWindow.GetWindow<LibraryWindow>();
+            if (win == null)
+                return;
+
+            EditorApplication.OpenExternally(Path.Combine(ProjectLibrary.ResourceFolder, win.SelectedEntry));
+        }
+
+        /// <summary>
+        /// Explores the current project library folder in the external file system explorer.
+        /// </summary>
+        [MenuItem("Resources/Explore location", 39, false, "IsLibraryWindowActive")]
+        internal static void ExploreLocation()
+        {
+            LibraryWindow win = EditorWindow.GetWindow<LibraryWindow>();
+            if (win == null)
+                return;
+
+            EditorApplication.OpenExternally(Path.Combine(ProjectLibrary.ResourceFolder, win.CurrentFolder));
+        }
+    }
+}

+ 117 - 0
MBansheeEditor/LibraryUtility.cs

@@ -0,0 +1,117 @@
+using System;
+using System.IO;
+using BansheeEngine;
+
+namespace BansheeEditor
+{
+    /// <summary>
+    /// Contains various helper methods for dealing with the project library.
+    /// </summary>
+    public static class LibraryUtility
+    {
+        /// <summary>
+        /// Creates a new material with the default shader in the specified folder.
+        /// </summary>
+        /// <param name="folder">Folder relative to project library to create the material in.</param>
+        public static void CreateEmptyMaterial(string folder)
+        {
+            string path = Path.Combine(folder, "New Material.asset");
+            path = GetUniquePath(path);
+
+            Material material = new Material(Builtin.DiffuseShader);
+            ProjectLibrary.Create(material, path);
+        }
+
+        /// <summary>
+        /// Creates a new empty sprite texture in the specified folder.
+        /// </summary>
+        /// <param name="folder">Folder relative to project library to create the sprite texture in.</param>
+        public static void CreateEmptySpriteTexture(string folder)
+        {
+            string path = Path.Combine(folder, "New Sprite Texture.asset");
+            path = GetUniquePath(path);
+
+            SpriteTexture spriteTexture = new SpriteTexture(null);
+            ProjectLibrary.Create(spriteTexture, path);
+        }
+
+        /// <summary>
+        /// Creates a new empty string table in the specified folder.
+        /// </summary>
+        /// <param name="folder">Folder relative to project library to create the string table in.</param>
+        public static void CreateEmptyStringTable(string folder)
+        {
+            string path = Path.Combine(folder, "New String Table.asset");
+            path = GetUniquePath(path);
+
+            StringTable stringTable = new StringTable();
+            ProjectLibrary.Create(stringTable, path);
+        }
+
+        /// <summary>
+        /// Creates a new empty GUI skin in the specified folder.
+        /// </summary>
+        /// <param name="folder">Folder relative to project library to create the GUI skin in.</param>
+        public static void CreateEmptyGUISkin(string folder)
+        {
+            string path = Path.Combine(folder, "New GUI Skin.asset");
+            path = GetUniquePath(path);
+
+            GUISkin guiSkin = new GUISkin();
+            ProjectLibrary.Create(guiSkin, path);
+        }
+
+        /// <summary>
+        /// Creates a new shader containing a rough code outline in the specified folder.
+        /// </summary>
+        /// <param name="folder">Folder relative to project library to create the shader in.</param>
+        public static void CreateEmptyShader(string folder)
+        {
+            string path = Path.Combine(folder, "New Shader.bsl");
+            path = Path.Combine(ProjectLibrary.ResourceFolder, path);
+            path = GetUniquePath(path);
+
+            File.WriteAllText(path, EditorBuiltin.EmptyShaderCode);
+            ProjectLibrary.Refresh(path);
+        }
+
+        /// <summary>
+        /// Creates a new C# script containing a rough code outline in the specified folder.
+        /// </summary>
+        /// <param name="folder">Folder relative to project library to create the C# script in.</param>
+        public static void CreateEmptyCSScript(string folder)
+        {
+            string path = Path.Combine(folder, "New Script.cs");
+            path = Path.Combine(ProjectLibrary.ResourceFolder, path);
+            path = GetUniquePath(path);
+
+            File.WriteAllText(path, EditorBuiltin.EmptyCSScriptCode);
+            ProjectLibrary.Refresh(path);
+        }
+
+        /// <summary>
+        /// Checks if a file or folder at the specified path exists in the library, and if it does generates a new unique 
+        /// name for the file or folder.
+        /// </summary>
+        /// <param name="path">Path to the file or folder to generate a unique name.</param>
+        /// <returns>New path with the unique name. This will be unchanged from input path if the input path doesn't
+        ///          already exist.</returns>
+        public static string GetUniquePath(string path)
+        {
+            string extension = Path.GetExtension(path);
+            string pathNoExtension = path;
+            if (!String.IsNullOrEmpty(extension))
+                pathNoExtension = path.Remove(path.Length - extension.Length);
+
+            int idx = 0;
+            string destination = path;
+            while (ProjectLibrary.Exists(destination))
+            {
+                destination = pathNoExtension + "_" + idx + extension;
+                idx++;
+            }
+
+            return destination;
+        }
+    }
+}

+ 79 - 72
MBansheeEditor/LibraryWindow.cs

@@ -75,6 +75,57 @@ namespace BansheeEditor
             set { viewType = value; Refresh(); }
         }
 
+        /// <summary>
+        /// Returns a file or folder currently selected in the library window. If nothing is selected, returns the active
+        /// folder. Returned path is relative to project library resources folder.
+        /// </summary>
+        public string SelectedEntry
+        {
+            get
+            {
+                if (selectionPaths.Count == 1)
+                {
+                    LibraryEntry entry = ProjectLibrary.GetEntry(selectionPaths[0]);
+                    if (entry != null)
+                        return entry.Path;
+                }
+
+                return currentDirectory;
+            }
+        }
+
+        /// <summary>
+        /// Returns a folder currently selected in the library window. If no folder is selected, returns the active
+        /// folder. Returned path is relative to project library resources folder.
+        /// </summary>
+        public string SelectedFolder
+        {
+            get
+            {
+                DirectoryEntry selectedDirectory = null;
+                if (selectionPaths.Count == 1)
+                {
+                    LibraryEntry entry = ProjectLibrary.GetEntry(selectionPaths[0]);
+                    if (entry != null && entry.Type == LibraryEntryType.Directory)
+                        selectedDirectory = (DirectoryEntry) entry;
+                }
+
+                if (selectedDirectory != null)
+                    return selectedDirectory.Path;
+                
+                return currentDirectory;
+            }
+        }
+
+        /// <summary>
+        /// Returns the path to the folder currently displayed in the library window. Returned path is relative to project 
+        /// library resources folder.
+        /// </summary>
+        public string CurrentFolder
+        {
+            get { return currentDirectory; }
+        }
+
         [MenuItem("Windows/Library", ButtonModifier.CtrlAlt, ButtonCode.L)]
         private static void OpenLibraryWindow()
         {
@@ -120,22 +171,7 @@ namespace BansheeEditor
             contentLayout.AddElement(contentScrollArea);
             contentLayout.AddFlexibleSpace();
 
-            entryContextMenu = new ContextMenu();
-            entryContextMenu.AddItem("Rename", RenameSelection, new ShortcutKey(ButtonModifier.None, ButtonCode.F2));
-            entryContextMenu.AddSeparator("");
-            entryContextMenu.AddItem("Cut", CutSelection, new ShortcutKey(ButtonModifier.Ctrl, ButtonCode.X));
-            entryContextMenu.AddItem("Copy", CopySelection, new ShortcutKey(ButtonModifier.Ctrl, ButtonCode.C));
-            entryContextMenu.AddItem("Duplicate", DuplicateSelection, new ShortcutKey(ButtonModifier.Ctrl, ButtonCode.D));
-            entryContextMenu.AddItem("Paste", PasteToSelection, new ShortcutKey(ButtonModifier.Ctrl, ButtonCode.V));
-            entryContextMenu.AddSeparator("");
-            entryContextMenu.AddItem("Delete", DeleteSelection, new ShortcutKey(ButtonModifier.None, ButtonCode.Delete));
-
-            entryContextMenu.SetLocalizedName("Rename", new LocEdString("Rename"));
-            entryContextMenu.SetLocalizedName("Cut", new LocEdString("Cut"));
-            entryContextMenu.SetLocalizedName("Copy", new LocEdString("Copy"));
-            entryContextMenu.SetLocalizedName("Duplicate", new LocEdString("Duplicate"));
-            entryContextMenu.SetLocalizedName("Paste", new LocEdString("Paste"));
-            entryContextMenu.SetLocalizedName("Delete", new LocEdString("Delete"));
+            entryContextMenu = LibraryMenu.CreateContextMenu(this);
 
             Reset();
 
@@ -306,16 +342,16 @@ namespace BansheeEditor
                     if (Directory.Exists(path))
                     {
                         if (doCopy)
-                            DirectoryEx.Copy(path, GetUniquePath(destination));
+                            DirectoryEx.Copy(path, LibraryUtility.GetUniquePath(destination));
                         else
-                            DirectoryEx.Move(path, GetUniquePath(destination));
+                            DirectoryEx.Move(path, LibraryUtility.GetUniquePath(destination));
                     }
                     else if (File.Exists(path))
                     {
                         if (doCopy)
-                            FileEx.Copy(path, GetUniquePath(destination));
+                            FileEx.Copy(path, LibraryUtility.GetUniquePath(destination));
                         else
-                            FileEx.Move(path, GetUniquePath(destination));
+                            FileEx.Move(path, LibraryUtility.GetUniquePath(destination));
                     }
 
                     ProjectLibrary.Refresh();
@@ -350,7 +386,7 @@ namespace BansheeEditor
 
                     Prefab newPrefab = new Prefab(so);
 
-                    string destination = GetUniquePath(Path.Combine(destinationFolder, so.Name + ".prefab"));
+                    string destination = LibraryUtility.GetUniquePath(Path.Combine(destinationFolder, so.Name + ".prefab"));
                     ProjectLibrary.Create(newPrefab, destination);
 
                     ProjectLibrary.Refresh();
@@ -631,9 +667,9 @@ namespace BansheeEditor
             foreach (var source in sourcePaths)
             {
                 if (Directory.Exists(source))
-                    DirectoryEx.Copy(source, GetUniquePath(source));
+                    DirectoryEx.Copy(source, LibraryUtility.GetUniquePath(source));
                 else if (File.Exists(source))
-                    FileEx.Copy(source, GetUniquePath(source));
+                    FileEx.Copy(source, LibraryUtility.GetUniquePath(source));
 
                 ProjectLibrary.Refresh();
             }
@@ -648,9 +684,9 @@ namespace BansheeEditor
                     string destination = Path.Combine(destinationFolder, PathEx.GetTail(copyPaths[i]));
 
                     if (Directory.Exists(copyPaths[i]))
-                        DirectoryEx.Copy(copyPaths[i], GetUniquePath(destination));
+                        DirectoryEx.Copy(copyPaths[i], LibraryUtility.GetUniquePath(destination));
                     else if (File.Exists(copyPaths[i]))
-                        FileEx.Copy(copyPaths[i], GetUniquePath(destination));
+                        FileEx.Copy(copyPaths[i], LibraryUtility.GetUniquePath(destination));
                 }
 
                 ProjectLibrary.Refresh();
@@ -662,9 +698,9 @@ namespace BansheeEditor
                     string destination = Path.Combine(destinationFolder, PathEx.GetTail(cutPaths[i]));
 
                     if (Directory.Exists(cutPaths[i]))
-                        DirectoryEx.Move(cutPaths[i], GetUniquePath(destination));
+                        DirectoryEx.Move(cutPaths[i], LibraryUtility.GetUniquePath(destination));
                     else if (File.Exists(cutPaths[i]))
-                        FileEx.Move(cutPaths[i], GetUniquePath(destination));
+                        FileEx.Move(cutPaths[i], LibraryUtility.GetUniquePath(destination));
                 }
 
                 cutPaths.Clear();
@@ -672,24 +708,6 @@ namespace BansheeEditor
             }
         }
 
-        private string GetUniquePath(string path)
-        {
-            string extension = Path.GetExtension(path);
-            string pathNoExtension = path;
-            if (!string.IsNullOrEmpty(extension))
-                pathNoExtension = path.Remove(path.Length - extension.Length);
-
-            int idx = 0;
-            string destination = path;
-            while (ProjectLibrary.Exists(destination))
-            {
-                destination = pathNoExtension + "_" + idx;
-                idx++;
-            }
-
-            return destination + extension;
-        }
-
         private void OnEditorUpdate()
         {
             bool isRenameInProgress = inProgressRenameElement != null;
@@ -1250,41 +1268,30 @@ namespace BansheeEditor
             }
         }
 
-        private void CutSelection()
+        internal void CutSelection()
         {
             if (selectionPaths.Count > 0)
                 Cut(selectionPaths);
         }
 
-        private void CopySelection()
+        internal void CopySelection()
         {
             if (selectionPaths.Count > 0)
                 Copy(selectionPaths);
         }
 
-        private void DuplicateSelection()
+        internal void DuplicateSelection()
         {
             if (selectionPaths.Count > 0)
                 Duplicate(selectionPaths);
         }
 
-        private void PasteToSelection()
+        internal void PasteToSelection()
         {
-            DirectoryEntry selectedDirectory = null;
-            if (selectionPaths.Count == 1)
-            {
-                LibraryEntry entry = ProjectLibrary.GetEntry(selectionPaths[0]);
-                if (entry != null && entry.Type == LibraryEntryType.Directory)
-                    selectedDirectory = (DirectoryEntry) entry;
-            }
-
-            if(selectedDirectory != null)
-                Paste(selectedDirectory.Path);
-            else
-                Paste(currentDirectory);
+            Paste(SelectedFolder);
         }
 
-        private void RenameSelection()
+        internal void RenameSelection()
         {
             if (selectionPaths.Count == 0)
                 return;
@@ -1303,16 +1310,7 @@ namespace BansheeEditor
             }
         }
 
-        private void StopRename()
-        {
-            if (inProgressRenameElement != null)
-            {
-                inProgressRenameElement.StopRename();
-                inProgressRenameElement = null;
-            }
-        }
-
-        private void DeleteSelection()
+        internal void DeleteSelection()
         {
             if (selectionPaths.Count == 0)
                 return;
@@ -1334,6 +1332,15 @@ namespace BansheeEditor
                 });
         }
 
+        private void StopRename()
+        {
+            if (inProgressRenameElement != null)
+            {
+                inProgressRenameElement.StopRename();
+                inProgressRenameElement = null;
+            }
+        }
+
         private void OnSearchChanged(string newValue)
         {
             searchQuery = newValue;

+ 2 - 0
MBansheeEditor/MBansheeEditor.csproj

@@ -53,6 +53,8 @@
     <Compile Include="GUI\TextureField.cs" />
     <Compile Include="HierarchyWindow.cs" />
     <Compile Include="Inspector\InspectorUtility.cs" />
+    <Compile Include="LibraryMenu.cs" />
+    <Compile Include="LibraryUtility.cs" />
     <Compile Include="LocEdString.cs" />
     <Compile Include="PrefabUtility.cs" />
     <Compile Include="LibraryDropTarget.cs" />

+ 6 - 2
MBansheeEditor/MenuItem.cs

@@ -6,24 +6,28 @@ namespace BansheeEditor
     [AttributeUsage(AttributeTargets.Method)]
     public sealed class MenuItem : Attribute
     {
-        public MenuItem(string path, ButtonModifier shortcutModifier, ButtonCode shortcutKey, int priority = 0, bool separator = false)
+        public MenuItem(string path, ButtonModifier shortcutModifier, ButtonCode shortcutKey, int priority = 0, 
+            bool separator = false, string contextCallback = null)
         {
             this.path = path;
             this.shortcut = new ShortcutKey(shortcutModifier, shortcutKey);
             this.priority = priority;
             this.separator = separator;
+            this.contextCallback = contextCallback;
         }
 
-        public MenuItem(string path, int priority = 0, bool separator = false)
+        public MenuItem(string path, int priority = 0, bool separator = false, string contextCallback = null)
         {
             this.path = path;
             this.priority = priority;
             this.separator = separator;
+            this.contextCallback = contextCallback;
         }
 
         private string path;
         private ShortcutKey shortcut;
         private int priority;
         private bool separator;
+        private string contextCallback;
     }
 }

+ 1 - 0
SBansheeEditor/Include/BsScriptEditorApplication.h

@@ -44,6 +44,7 @@ namespace BansheeEngine
 		static void internal_LoadProject(MonoString* path);
 		static void internal_UnloadProject();
 		static void internal_CreateProject(MonoString* path);
+		static void internal_OpenExternally(MonoString* path);
 
 		typedef void(__stdcall *OnProjectLoadedThunkDef)(MonoException**);
 

+ 3 - 0
SBansheeEditor/Include/BsScriptEditorBuiltin.h

@@ -31,5 +31,8 @@ namespace BansheeEngine
 		static MonoObject* internal_getSpriteTextureIcon();
 		static MonoObject* internal_getPrefabIcon();
 		static MonoObject* internal_getXBtnIcon();
+
+		static MonoString* internal_GetEmptyShaderCode();
+		static MonoString* internal_GetEmptyCSScriptCode();
 	};
 }

+ 9 - 0
SBansheeEditor/Source/BsScriptEditorApplication.cpp

@@ -9,6 +9,7 @@
 #include "BsPrefab.h"
 #include "BsPrefabUtility.h"
 #include "BsSceneManager.h"
+#include "BsPlatform.h"
 #include "BsResources.h"
 #include "BsScriptEditorWindow.h"
 #include "BsEditorWindowBase.h"
@@ -47,6 +48,7 @@ namespace BansheeEngine
 		metaData.scriptClass->addInternalCall("Internal_LoadProject", &ScriptEditorApplication::internal_LoadProject);
 		metaData.scriptClass->addInternalCall("Internal_UnloadProject", &ScriptEditorApplication::internal_UnloadProject);
 		metaData.scriptClass->addInternalCall("Internal_CreateProject", &ScriptEditorApplication::internal_CreateProject);
+		metaData.scriptClass->addInternalCall("Internal_OpenExternally", &ScriptEditorApplication::internal_OpenExternally);
 
 		onProjectLoadedThunk = (OnProjectLoadedThunkDef)metaData.scriptClass->getMethod("OnProjectLoaded")->getThunk();
 	}
@@ -192,4 +194,11 @@ namespace BansheeEngine
 
 		gEditorApplication().createProject(nativePath);
 	}
+
+	void ScriptEditorApplication::internal_OpenExternally(MonoString* path)
+	{
+		Path nativePath = MonoUtil::monoToWString(path);
+
+		Platform::open(nativePath);
+	}
 }

+ 16 - 0
SBansheeEditor/Source/BsScriptEditorBuiltin.cpp

@@ -27,6 +27,8 @@ namespace BansheeEngine
 		metaData.scriptClass->addInternalCall("Internal_GetSpriteTextureIcon", &ScriptEditorBuiltin::internal_getSpriteTextureIcon);
 		metaData.scriptClass->addInternalCall("Internal_GetPrefabIcon", &ScriptEditorBuiltin::internal_getPrefabIcon);
 		metaData.scriptClass->addInternalCall("Internal_GetXBtnIcon", &ScriptEditorBuiltin::internal_getXBtnIcon);
+		metaData.scriptClass->addInternalCall("Internal_GetEmptyShaderCode", &ScriptEditorBuiltin::internal_GetEmptyShaderCode);
+		metaData.scriptClass->addInternalCall("Internal_GetEmptyCSScriptCode", &ScriptEditorBuiltin::internal_GetEmptyCSScriptCode);
 	}
 
 	MonoObject* ScriptEditorBuiltin::internal_getFolderIcon()
@@ -112,4 +114,18 @@ namespace BansheeEngine
 
 		return ScriptSpriteTexture::toManaged(tex);
 	}
+
+	MonoString* ScriptEditorBuiltin::internal_GetEmptyShaderCode()
+	{
+		WString code = BuiltinEditorResources::instance().getEmptyShaderCode();
+
+		return MonoUtil::wstringToMono(MonoManager::instance().getDomain(), code);
+	}
+
+	MonoString* ScriptEditorBuiltin::internal_GetEmptyCSScriptCode()
+	{
+		WString code = BuiltinEditorResources::instance().getEmptyCSScriptCode();
+
+		return MonoUtil::wstringToMono(MonoManager::instance().getDomain(), code);
+	}
 }

+ 2 - 2
SBansheeEngine/Include/BsScriptBuiltin.h

@@ -19,7 +19,7 @@ namespace BansheeEngine
 		/************************************************************************/
 		/* 								CLR HOOKS						   		*/
 		/************************************************************************/
-		static MonoObject* internal_getWhiteTexture();
-		static MonoObject* internal_getDiffuseShader();
+		static MonoObject* internal_GetWhiteTexture();
+		static MonoObject* internal_GetDiffuseShader();
 	};
 }

+ 4 - 4
SBansheeEngine/Source/BsScriptBuiltin.cpp

@@ -16,11 +16,11 @@ namespace BansheeEngine
 
 	void ScriptBuiltin::initRuntimeData()
 	{
-		metaData.scriptClass->addInternalCall("Internal_GetWhiteTexture", &ScriptBuiltin::internal_getWhiteTexture);
-		metaData.scriptClass->addInternalCall("Internal_GetDiffuseShader", &ScriptBuiltin::internal_getDiffuseShader);
+		metaData.scriptClass->addInternalCall("Internal_GetWhiteTexture", &ScriptBuiltin::internal_GetWhiteTexture);
+		metaData.scriptClass->addInternalCall("Internal_GetDiffuseShader", &ScriptBuiltin::internal_GetDiffuseShader);
 	}
 
-	MonoObject* ScriptBuiltin::internal_getWhiteTexture()
+	MonoObject* ScriptBuiltin::internal_GetWhiteTexture()
 	{
 		HSpriteTexture whiteTexture = BuiltinResources::instance().getWhiteSpriteTexture();
 
@@ -30,7 +30,7 @@ namespace BansheeEngine
 		return scriptSpriteTex->getManagedInstance();
 	}
 
-	MonoObject* ScriptBuiltin::internal_getDiffuseShader()
+	MonoObject* ScriptBuiltin::internal_GetDiffuseShader()
 	{
 		HShader diffuseShader = BuiltinResources::instance().getDiffuseShader();
 

+ 1 - 1
SBansheeEngine/Source/BsScriptStringTable.cpp

@@ -15,7 +15,7 @@ namespace BansheeEngine
 
 	void ScriptStringTable::initRuntimeData()
 	{
-		metaData.scriptClass->addInternalCall("internal_CreateInstance", &ScriptStringTable::internal_CreateInstance);
+		metaData.scriptClass->addInternalCall("Internal_CreateInstance", &ScriptStringTable::internal_CreateInstance);
 
 		metaData.scriptClass->addInternalCall("Internal_GetNumStrings", &ScriptStringTable::internal_GetNumStrings);
 		metaData.scriptClass->addInternalCall("Internal_GetIdentifiers", &ScriptStringTable::internal_GetIdentifiers);

+ 12 - 11
TODO.txt

@@ -53,17 +53,17 @@ Code quality improvements:
 Polish
 
 Ribek use:
- - Renderables don't render - seems to be caused by core sync
- - Opening complex array entries causes inspector to get screwed up
  - Hook up color picker to guicolor field
  - Camera, Renderable, Material, Texture inspector
  - Test release mode
  - Ability to create assets in Project view (At least Material)
  - (Optionally, needed for GUI editing) GUISkin resource inspector & a way to inspect and save the default editor skin
+ - (Optionally) StringTable inspector
 
 Other polish:
- - C# inspectors for Point/Spot/Directional light
- - C# interface for Font
+ - C# inspectors for Point/Spot/Directional light, Sprite texture, Shader, Script code, Plain text
+ - Prefab inspector (render something similar to SceneObject inspector?)
+ - C# interface for Font and SpriteTexture
  - Import option inspectors for Texture, Mesh, Font
  - Add menu items:
   - Edit: Undo/Redo, Cut/Copy/Paste/Duplicate/Delete(need to make sure it works in Hierarchy, with shortcuts), Frame Selected, Preferences, Play/Pause/Step, View/Move/rotate/scale
@@ -71,10 +71,12 @@ Other polish:
   - Game Object (also add to context): Create(Empty, Empty Child, Camera, Renderable, Point/Spot/Directional Light), Apply prefab, Break prefab, Revert prefab
    - Possibly create helper objects: Cube, Sphere, Plane, Quad, Capsule, Cylinder
   - Help - About, API Reference (link to site)
+ - Add temporary icon textures too all icon buttons currently containing only text so that Ribek can modify them
  - When I expand inspector elements and them come back to that object it should remember the previous state
    - Add a chaching mechanism to inspector (likely based on instance ID & property names)
    - This has to work not only when I come back to the object, but whenever inspector rebuilds (e.g. after removing element from array)
    - Consider saving this information with the serialized object
+ - Need a proper way to detect when the scene was modified (and display it somewhere)
 
 Stage 2 polish:
  - Prefabs
@@ -107,7 +109,7 @@ Optional:
  - Ortographic camera views (+ gizmo in scene view corner that shows camera orientation)
  - Drag to select in scene view
  - MenuBar - will likely need a way to mark elements as disabled when not appropriate (e.g. no "frame selected unless scene is focused")
-   - Likely use a user-provided callback to trigger when populating the menus
+   - Likely use a user-provided callback to trigger when populating the menus (I already added a callback to MenuItem, just need to implement it)
  - Need to list all script components in the Components menu
 
 Finalizing:
@@ -137,10 +139,6 @@ There is a memory corruption happening. Haven't determined where exactly but it'
 to do with the opening of ColorPicker window. One time I got a heap read after delete error caused by GUIManager
 attempting to allocate a new transient mesh, and another time I got a hang when inserting a script object into a std::set.
 
-Got a crash on shutdown that was caused by locking a mutex in an Event destructor. Event was Platform::onMouseCaptureChanged. 
-Issue happened when I closed the app via the X button (if that's relevant). It doesn't seem to happen always.
- - This is likely due to some other error. When VS finds an exception it triggers a dialog box which triggers the msg loop in-engine and causes another exception.
-
 /*********************************************************************/
 /************************ LESS IMPORTANT *****************************/
 /*********************************************************************/
@@ -161,6 +159,9 @@ Mono cannot marshal structures? Taken from their documentation:
  Internal calls do not provide support for marshalling structures. This means that any API calls that take a structure 
  (excluding the system types like int32, int64, etc) must be passed as a pointer, in C# this means passing the value as a "ref" or "out" parameter.
 
+Sometimes exceptions cause a crash in Event, although this is due to an exception triggering a dialog box which triggers
+the message loop and causes another exception. Make sure to look for the original exception.
+
 ----------------------------------------------------------------------
 MenuItem
 
@@ -185,7 +186,7 @@ Script compilation
 	 assemblies for release. Also hook up console to compiler output?
 
 ----------------------------------------------------------------------
-Project window
+Library window
 
  - Might need to improve search (need to test). Do multiple search keywords work properly?
  - Consider delaying search until user stops pressing keys (so not to have thousands of search results in the initial stages)
@@ -209,4 +210,4 @@ Scene View
 Test:
  - Custom handles from C#
  - Handle snapping
- - Multi-select Move/Rotate/scale
+ - Multi-select Move/Rotate/scale