Ver Fonte

Added ScriptCode inspector
Hooked up missing CLR hooks for ScriptCode
Fixed text parsing so it properly handles all types of newlines, and tab characters

BearishSun há 10 anos atrás
pai
commit
7a6945b210

+ 2 - 3
BansheeCore/Include/BsTextData.h

@@ -148,11 +148,10 @@ namespace BansheeEngine
 			 *			without actually adding it.
 			 *			without actually adding it.
 			 *
 			 *
 			 * @param	desc		Character description from the font.
 			 * @param	desc		Character description from the font.
-			 * @param	space		True if the character is a space.
 			 *
 			 *
 			 * @returns	Width of the line in pixels with the character appended to it.
 			 * @returns	Width of the line in pixels with the character appended to it.
 			 */
 			 */
-			UINT32 calcWidthWithChar(const CHAR_DESC& desc, bool space);
+			UINT32 calcWidthWithChar(const CHAR_DESC& desc);
 
 
 			/**
 			/**
 			 * @brief	Fills the vertex/uv/index buffers for the specified page, with all the character data
 			 * @brief	Fills the vertex/uv/index buffers for the specified page, with all the character data
@@ -199,7 +198,7 @@ namespace BansheeEngine
 			/**
 			/**
 			 * @brief	Appends a space to the line.
 			 * @brief	Appends a space to the line.
 			 */
 			 */
-			void addSpace();
+			void addSpace(UINT32 spaceWidth);
 
 
 			/**
 			/**
 			 * @brief	Adds a new word to the line.
 			 * @brief	Adds a new word to the line.

+ 47 - 30
BansheeCore/Source/BsTextData.cpp

@@ -6,6 +6,7 @@
 namespace BansheeEngine
 namespace BansheeEngine
 {
 {
 	const int SPACE_CHAR = 32;
 	const int SPACE_CHAR = 32;
+	const int TAB_CHAR = 9;
 
 
 	void TextDataBase::TextWord::init(bool spacer)
 	void TextDataBase::TextWord::init(bool spacer)
 	{
 	{
@@ -103,7 +104,7 @@ namespace BansheeEngine
 		mHeight = std::max(mHeight, lastWord.getHeight());
 		mHeight = std::max(mHeight, lastWord.getHeight());
 	}
 	}
 
 
-	void TextDataBase::TextLine::addSpace()
+	void TextDataBase::TextLine::addSpace(UINT32 spaceWidth)
 	{
 	{
 		if(mIsEmpty)
 		if(mIsEmpty)
 		{
 		{
@@ -114,9 +115,9 @@ namespace BansheeEngine
 			mWordsEnd = MemBuffer->allocWord(true); // Each space is counted as its own word, to make certain operations easier
 			mWordsEnd = MemBuffer->allocWord(true); // Each space is counted as its own word, to make certain operations easier
 
 
 		TextWord& lastWord = MemBuffer->WordBuffer[mWordsEnd];
 		TextWord& lastWord = MemBuffer->WordBuffer[mWordsEnd];
-		lastWord.addSpace(mTextData->getSpaceWidth());
+		lastWord.addSpace(spaceWidth);
 
 
-		mWidth += mTextData->getSpaceWidth();
+		mWidth += spaceWidth;
 	}
 	}
 
 
 	// Assumes wordIdx is an index right after last word in the list (if any). All words need to be sequential.
 	// Assumes wordIdx is an index right after last word in the list (if any). All words need to be sequential.
@@ -154,27 +155,22 @@ namespace BansheeEngine
 		return lastWord;
 		return lastWord;
 	}
 	}
 
 
-	UINT32 TextDataBase::TextLine::calcWidthWithChar(const CHAR_DESC& desc, bool space)
+	UINT32 TextDataBase::TextLine::calcWidthWithChar(const CHAR_DESC& desc)
 	{
 	{
 		UINT32 charWidth = 0;
 		UINT32 charWidth = 0;
 
 
-		if (space)
-			charWidth = mTextData->getSpaceWidth();
-		else
+		UINT32 word = mWordsEnd;
+		if (!mIsEmpty)
 		{
 		{
-			UINT32 word = mWordsEnd;
-			if (!mIsEmpty)
-			{
-				TextWord& lastWord = MemBuffer->WordBuffer[mWordsEnd];
-				if (lastWord.isSpacer())
-					charWidth = TextWord::calcCharWidth(nullptr, desc);
-				else
-					charWidth = lastWord.calcWidthWithChar(desc) - lastWord.getWidth();
-			}
-			else
-			{
+			TextWord& lastWord = MemBuffer->WordBuffer[mWordsEnd];
+			if (lastWord.isSpacer())
 				charWidth = TextWord::calcCharWidth(nullptr, desc);
 				charWidth = TextWord::calcCharWidth(nullptr, desc);
-			}
+			else
+				charWidth = lastWord.calcWidthWithChar(desc) - lastWord.getWidth();
+		}
+		else
+		{
+			charWidth = TextWord::calcCharWidth(nullptr, desc);
 		}
 		}
 
 
 		return mWidth + charWidth;
 		return mWidth + charWidth;
@@ -211,9 +207,9 @@ namespace BansheeEngine
 					UINT32 curIndex = offset * 6;
 					UINT32 curIndex = offset * 6;
 
 
 					vertices[curVert + 0] = Vector2((float)curX, (float)curY);
 					vertices[curVert + 0] = Vector2((float)curX, (float)curY);
-					vertices[curVert + 1] = Vector2((float)(curX + mTextData->getSpaceWidth()), (float)curY);
+					vertices[curVert + 1] = Vector2((float)(curX + word.getWidth()), (float)curY);
 					vertices[curVert + 2] = Vector2((float)curX, (float)curY + (float)mTextData->getLineHeight());
 					vertices[curVert + 2] = Vector2((float)curX, (float)curY + (float)mTextData->getLineHeight());
-					vertices[curVert + 3] = Vector2((float)(curX + mTextData->getSpaceWidth()), (float)curY + (float)mTextData->getLineHeight());
+					vertices[curVert + 3] = Vector2((float)(curX + word.getWidth()), (float)curY + (float)mTextData->getLineHeight());
 
 
 					if(uvs != nullptr)
 					if(uvs != nullptr)
 					{
 					{
@@ -241,7 +237,7 @@ namespace BansheeEngine
 						BS_EXCEPT(InternalErrorException, "Out of buffer bounds. Buffer size: " + toString(size));
 						BS_EXCEPT(InternalErrorException, "Out of buffer bounds. Buffer size: " + toString(size));
 				}
 				}
 
 
-				penX += mTextData->getSpaceWidth();
+				penX += word.getWidth();
 			}
 			}
 			else
 			else
 			{
 			{
@@ -388,7 +384,7 @@ namespace BansheeEngine
 
 
 			TextLine* curLine = &MemBuffer->LineBuffer[curLineIdx];
 			TextLine* curLine = &MemBuffer->LineBuffer[curLineIdx];
 
 
-			if(text[charIdx] == '\n')
+			if(text[charIdx] == '\n' || text[charIdx] == '\r')
 			{
 			{
 				curLine->finalize(true);
 				curLine->finalize(true);
 
 
@@ -398,14 +394,30 @@ namespace BansheeEngine
 				curHeight += mFontData->fontDesc.lineHeight;
 				curHeight += mFontData->fontDesc.lineHeight;
 
 
 				charIdx++;
 				charIdx++;
+
+				// Check for \r\n
+				if (charIdx < text.size())
+				{
+					if (text[charIdx] == '\n')
+						charIdx++;
+				}
+
 				continue;
 				continue;
 			}
 			}
 
 
 			if (widthIsLimited && wordWrap)
 			if (widthIsLimited && wordWrap)
 			{
 			{
-				if (curLine->calcWidthWithChar(charDesc, charId == SPACE_CHAR) > width && !curLine->isEmpty())
+				UINT32 widthWithChar = 0;
+				if (charIdx == SPACE_CHAR)
+					widthWithChar = curLine->getWidth() + getSpaceWidth();
+				else if (charIdx == TAB_CHAR)
+					widthWithChar = curLine->getWidth() + getSpaceWidth() * 4;
+				else
+					widthWithChar = curLine->calcWidthWithChar(charDesc);
+
+				if (widthWithChar > width && !curLine->isEmpty())
 				{
 				{
-					bool atWordBoundary = charId == SPACE_CHAR || curLine->isAtWordBoundary();
+					bool atWordBoundary = charId == SPACE_CHAR || charId == TAB_CHAR || curLine->isAtWordBoundary();
 
 
 					if (!atWordBoundary) // Need to break word into multiple pieces, or move it to next line
 					if (!atWordBoundary) // Need to break word into multiple pieces, or move it to next line
 					{
 					{
@@ -464,16 +476,21 @@ namespace BansheeEngine
 				}
 				}
 			}
 			}
 
 
-			if(charId != SPACE_CHAR)
+			if(charId == SPACE_CHAR)
 			{
 			{
-				curLine->add(charIdx, charDesc);
-				MemBuffer->addCharToPage(charDesc.page, *mFontData);
+				curLine->addSpace(getSpaceWidth());
+				MemBuffer->addCharToPage(0, *mFontData);
 			}
 			}
-			else
+			else if (charId == TAB_CHAR)
 			{
 			{
-				curLine->addSpace();
+				curLine->addSpace(getSpaceWidth() * 4);
 				MemBuffer->addCharToPage(0, *mFontData);
 				MemBuffer->addCharToPage(0, *mFontData);
 			}
 			}
+			else
+			{
+				curLine->add(charIdx, charDesc);
+				MemBuffer->addCharToPage(charDesc.page, *mFontData);
+			}
 
 
 			charIdx++;
 			charIdx++;
 		}
 		}

+ 12 - 2
BansheeEngine/Include/BsScriptCode.h

@@ -12,15 +12,25 @@ namespace BansheeEngine
 	{
 	{
 	public:
 	public:
 		/**
 		/**
-		 * @brief	Returns the source code contained in the resource.
+		 * @brief	Gets the source code contained in the resource.
 		 */
 		 */
 		const WString& getString() const { return mString; }
 		const WString& getString() const { return mString; }
 
 
 		/**
 		/**
-		 * @brief	Modifies the source code contained in the resource.
+		 * @brief	Sets the source code contained in the resource.
 		 */
 		 */
 		void setString(const WString& data) { mString = data; }
 		void setString(const WString& data) { mString = data; }
 
 
+		/**
+		 * @brief	Gets a value that determines should the script code be compiled with editor assemblies.
+		 */
+		bool getIsEditorScript() const { return mEditorScript; }
+
+		/**
+		 * @brief	Sets a value that determines should the script code be compiled with editor assemblies.
+		 */
+		void setIsEditorScript(bool editorScript) { mEditorScript = editorScript; }
+
 		/**
 		/**
 		 * @brief	Creates a new script code resource with the specified source code.
 		 * @brief	Creates a new script code resource with the specified source code.
 		 */
 		 */

+ 10 - 0
MBansheeEditor/EditorApplication.cs

@@ -345,6 +345,16 @@ namespace BansheeEditor
             Internal_OpenExternally(path);
             Internal_OpenExternally(path);
         }
         }
 
 
+        /// <summary>
+        /// Marks a resource as dirty so that it may be saved the next time the project is saved. Optionally you may also
+        /// call <see cref="ProjectLibrary.Save"/> to save it immediately.
+        /// </summary>
+        /// <param name="resource">Resource to mark as dirty</param>
+        public static void SetDirty(Resource resource)
+        {
+            // TODO - Not implemented
+        }
+
         /// <summary>
         /// <summary>
         /// Triggered when <see cref="LoadProject"/> method completes.
         /// Triggered when <see cref="LoadProject"/> method completes.
         /// </summary>
         /// </summary>

+ 10 - 0
MBansheeEditor/Inspectors/MaterialInspector.cs

@@ -173,6 +173,7 @@ namespace BansheeEditor
             guiElem.OnChanged += (x) =>
             guiElem.OnChanged += (x) =>
             {
             {
                 material.SetFloat(shaderParam.name, x);
                 material.SetFloat(shaderParam.name, x);
+                EditorApplication.SetDirty(material);
             };
             };
 
 
             layout.AddElement(guiElem);
             layout.AddElement(guiElem);
@@ -219,6 +220,7 @@ namespace BansheeEditor
             guiElem.OnChanged += (x) =>
             guiElem.OnChanged += (x) =>
             {
             {
                 material.SetVector2(shaderParam.name, x);
                 material.SetVector2(shaderParam.name, x);
+                EditorApplication.SetDirty(material);
             };
             };
 
 
             layout.AddElement(guiElem);
             layout.AddElement(guiElem);
@@ -265,6 +267,7 @@ namespace BansheeEditor
             guiElem.OnChanged += (x) =>
             guiElem.OnChanged += (x) =>
             {
             {
                 material.SetVector3(shaderParam.name, x);
                 material.SetVector3(shaderParam.name, x);
+                EditorApplication.SetDirty(material);
             };
             };
 
 
             layout.AddElement(guiElem);
             layout.AddElement(guiElem);
@@ -311,6 +314,7 @@ namespace BansheeEditor
             guiElem.OnChanged += (x) =>
             guiElem.OnChanged += (x) =>
             {
             {
                 material.SetVector4(shaderParam.name, x);
                 material.SetVector4(shaderParam.name, x);
+                EditorApplication.SetDirty(material);
             };
             };
 
 
             layout.AddElement(guiElem);
             layout.AddElement(guiElem);
@@ -383,6 +387,7 @@ namespace BansheeEditor
                         Matrix3 value = material.GetMatrix3(shaderParam.name);
                         Matrix3 value = material.GetMatrix3(shaderParam.name);
                         value[hoistedRow, hoistedCol] = x;
                         value[hoistedRow, hoistedCol] = x;
                         material.SetMatrix3(shaderParam.name, value);
                         material.SetMatrix3(shaderParam.name, value);
+                        EditorApplication.SetDirty(material);
                     };
                     };
                 }
                 }
             }
             }
@@ -471,6 +476,7 @@ namespace BansheeEditor
                         Matrix4 value = material.GetMatrix4(shaderParam.name);
                         Matrix4 value = material.GetMatrix4(shaderParam.name);
                         value[hoistedRow, hoistedCol] = x;
                         value[hoistedRow, hoistedCol] = x;
                         material.SetMatrix4(shaderParam.name, value);
                         material.SetMatrix4(shaderParam.name, value);
+                        EditorApplication.SetDirty(material);
                     };
                     };
                 }
                 }
             }
             }
@@ -533,6 +539,7 @@ namespace BansheeEditor
             guiElem.OnChanged += (x) =>
             guiElem.OnChanged += (x) =>
             {
             {
                 material.SetColor(shaderParam.name, x);
                 material.SetColor(shaderParam.name, x);
+                EditorApplication.SetDirty(material);
             };
             };
 
 
             layout.AddElement(guiElem);
             layout.AddElement(guiElem);
@@ -583,18 +590,21 @@ namespace BansheeEditor
                     guiElem.OnChanged += (x) =>
                     guiElem.OnChanged += (x) =>
                     {
                     {
                         material.SetTexture2D(shaderParam.name, x as Texture2D);
                         material.SetTexture2D(shaderParam.name, x as Texture2D);
+                        EditorApplication.SetDirty(material);
                     };
                     };
                     break;
                     break;
                 case ShaderParameterType.Texture3D:
                 case ShaderParameterType.Texture3D:
                     guiElem.OnChanged += (x) =>
                     guiElem.OnChanged += (x) =>
                     {
                     {
                         material.SetTexture3D(shaderParam.name, x as Texture3D);
                         material.SetTexture3D(shaderParam.name, x as Texture3D);
+                        EditorApplication.SetDirty(material);
                     };
                     };
                     break;
                     break;
                 case ShaderParameterType.TextureCube:
                 case ShaderParameterType.TextureCube:
                     guiElem.OnChanged += (x) =>
                     guiElem.OnChanged += (x) =>
                     {
                     {
                         material.SetTextureCube(shaderParam.name, x as TextureCube);
                         material.SetTextureCube(shaderParam.name, x as TextureCube);
+                        EditorApplication.SetDirty(material);
                     };
                     };
                     break;
                     break;
             }
             }

+ 74 - 0
MBansheeEditor/Inspectors/ScriptCodeInspector.cs

@@ -0,0 +1,74 @@
+using System.Collections.Generic;
+using BansheeEngine;
+
+namespace BansheeEditor
+{
+    /// <summary>
+    /// Renders an inspector for the <see cref="ScriptCode"/> resource.
+    /// </summary>
+    [CustomInspector(typeof (ScriptCode))]
+    internal class ScriptCodeInspector : Inspector
+    {
+        private const int MAX_SHOWN_CHARACTERS = 3000;
+
+        private GUILabel textLabel = new GUILabel("", EditorStyles.MultiLineLabel, GUIOption.FixedHeight(500));
+        private GUITexture textBg = new GUITexture(null, EditorStyles.ScrollAreaBg);
+        private GUIToggleField isEditorField = new GUIToggleField(new LocEdString("Is editor script"));
+        private bool isInitialized;
+
+        private string shownText = "";
+
+        /// <inheritdoc/>
+        internal override bool Refresh()
+        {
+            ScriptCode scriptCode = referencedObject as ScriptCode;
+            if (scriptCode == null)
+                return false;
+
+            if (!isInitialized)
+            {
+                isEditorField.OnChanged += x =>
+                {
+                    scriptCode.EditorScript = x;
+                    EditorApplication.SetDirty(scriptCode);
+                };
+
+                GUIPanel textPanel = layout.AddPanel();
+                GUILayout textLayoutY = textPanel.AddLayoutY();
+                textLayoutY.AddSpace(5);
+                GUILayout textLayoutX = textLayoutY.AddLayoutX();
+                textLayoutX.AddSpace(5);
+                textLayoutX.AddElement(textLabel);
+                textLayoutX.AddSpace(5);
+                textLayoutY.AddSpace(5);
+
+                GUIPanel textBgPanel = textPanel.AddPanel(1);
+                textBgPanel.AddElement(textBg);
+
+                layout.AddElement(isEditorField);
+
+                isInitialized = true;
+            }
+
+            bool anythingModified = false;
+
+            if (scriptCode.EditorScript != isEditorField.Value)
+            {
+                isEditorField.Value = scriptCode.EditorScript;
+                anythingModified = true;
+            }
+
+            string newText = scriptCode.Text;
+            string newShownText = scriptCode.Text.Substring(0, MathEx.Min(newText.Length, MAX_SHOWN_CHARACTERS));
+
+            if (newShownText != shownText)
+            {
+                textLabel.SetContent(newShownText);
+                shownText = newShownText;
+                anythingModified = true;
+            }
+
+            return anythingModified;
+        }
+    }
+}

+ 1 - 0
MBansheeEditor/MBansheeEditor.csproj

@@ -58,6 +58,7 @@
     <Compile Include="Inspectors\LightInspector.cs" />
     <Compile Include="Inspectors\LightInspector.cs" />
     <Compile Include="Inspectors\MaterialInspector.cs" />
     <Compile Include="Inspectors\MaterialInspector.cs" />
     <Compile Include="Inspectors\MeshInspector.cs" />
     <Compile Include="Inspectors\MeshInspector.cs" />
+    <Compile Include="Inspectors\ScriptCodeInspector.cs" />
     <Compile Include="Inspectors\Texture2DInspector.cs" />
     <Compile Include="Inspectors\Texture2DInspector.cs" />
     <Compile Include="Inspector\InspectorUtility.cs" />
     <Compile Include="Inspector\InspectorUtility.cs" />
     <Compile Include="Library\LibraryGUIContent.cs" />
     <Compile Include="Library\LibraryGUIContent.cs" />

+ 2 - 0
SBansheeEngine/Include/BsScriptScriptCode.h

@@ -54,6 +54,8 @@ namespace BansheeEngine
 		static void internal_createInstance(MonoObject* instance, MonoString* text);
 		static void internal_createInstance(MonoObject* instance, MonoString* text);
 		static MonoString* internal_getText(ScriptScriptCode* thisPtr);
 		static MonoString* internal_getText(ScriptScriptCode* thisPtr);
 		static void internal_setText(ScriptScriptCode* thisPtr, MonoString* text);
 		static void internal_setText(ScriptScriptCode* thisPtr, MonoString* text);
+		static bool internal_isEditorScript(ScriptScriptCode* thisPtr);
+		static void internal_setEditorScript(ScriptScriptCode* thisPtr, bool value);
 		static MonoArray* internal_getTypes(ScriptScriptCode* thisPtr);
 		static MonoArray* internal_getTypes(ScriptScriptCode* thisPtr);
 	};
 	};
 }
 }

+ 24 - 0
SBansheeEngine/Source/BsScriptScriptCode.cpp

@@ -24,6 +24,8 @@ namespace BansheeEngine
 		metaData.scriptClass->addInternalCall("Internal_CreateInstance", &ScriptScriptCode::internal_createInstance);
 		metaData.scriptClass->addInternalCall("Internal_CreateInstance", &ScriptScriptCode::internal_createInstance);
 		metaData.scriptClass->addInternalCall("Internal_GetText", &ScriptScriptCode::internal_getText);
 		metaData.scriptClass->addInternalCall("Internal_GetText", &ScriptScriptCode::internal_getText);
 		metaData.scriptClass->addInternalCall("Internal_SetText", &ScriptScriptCode::internal_setText);
 		metaData.scriptClass->addInternalCall("Internal_SetText", &ScriptScriptCode::internal_setText);
+		metaData.scriptClass->addInternalCall("Internal_IsEditorScript", &ScriptScriptCode::internal_isEditorScript);
+		metaData.scriptClass->addInternalCall("Internal_SetEditorScript", &ScriptScriptCode::internal_setEditorScript);
 		metaData.scriptClass->addInternalCall("Internal_GetTypes", &ScriptScriptCode::internal_getTypes);
 		metaData.scriptClass->addInternalCall("Internal_GetTypes", &ScriptScriptCode::internal_getTypes);
 	}
 	}
 
 
@@ -39,6 +41,8 @@ namespace BansheeEngine
 	MonoString* ScriptScriptCode::internal_getText(ScriptScriptCode* thisPtr)
 	MonoString* ScriptScriptCode::internal_getText(ScriptScriptCode* thisPtr)
 	{
 	{
 		HScriptCode scriptCode = thisPtr->mScriptCode;
 		HScriptCode scriptCode = thisPtr->mScriptCode;
+		if (!scriptCode.isLoaded())
+			MonoUtil::wstringToMono(MonoManager::instance().getDomain(), L"");
 
 
 		return MonoUtil::wstringToMono(MonoManager::instance().getDomain(), scriptCode->getString());
 		return MonoUtil::wstringToMono(MonoManager::instance().getDomain(), scriptCode->getString());
 	}
 	}
@@ -46,9 +50,29 @@ namespace BansheeEngine
 	void ScriptScriptCode::internal_setText(ScriptScriptCode* thisPtr, MonoString* text)
 	void ScriptScriptCode::internal_setText(ScriptScriptCode* thisPtr, MonoString* text)
 	{
 	{
 		HScriptCode scriptCode = thisPtr->mScriptCode;
 		HScriptCode scriptCode = thisPtr->mScriptCode;
+		if (!scriptCode.isLoaded())
+			return;
 
 
 		scriptCode->setString(MonoUtil::monoToWString(text));
 		scriptCode->setString(MonoUtil::monoToWString(text));
 	}
 	}
+
+	bool ScriptScriptCode::internal_isEditorScript(ScriptScriptCode* thisPtr)
+	{
+		HScriptCode scriptCode = thisPtr->mScriptCode;
+		if (!scriptCode.isLoaded())
+			return false;
+
+		return scriptCode->getIsEditorScript();
+	}
+
+	void ScriptScriptCode::internal_setEditorScript(ScriptScriptCode* thisPtr, bool value)
+	{
+		HScriptCode scriptCode = thisPtr->mScriptCode;
+		if (!scriptCode.isLoaded())
+			return;
+
+		scriptCode->setIsEditorScript(value);
+	}
 	
 	
 	MonoArray* ScriptScriptCode::internal_getTypes(ScriptScriptCode* thisPtr)
 	MonoArray* ScriptScriptCode::internal_getTypes(ScriptScriptCode* thisPtr)
 	{
 	{

+ 1 - 4
TODO.txt

@@ -59,8 +59,6 @@ Ribek use:
  - Component inspector for Renderable
  - Component inspector for Renderable
  - Resource inspectors for: Font, Shader, Script Code, Plain Text, Sprite Texture, GUISkin, StringTable, Prefab (just something basic for now)
  - Resource inspectors for: Font, Shader, Script Code, Plain Text, Sprite Texture, GUISkin, StringTable, Prefab (just something basic for now)
  - Test release mode
  - Test release mode
- - Add temporary icon textures too all icon buttons currently containing only text so that Ribek can modify them
-  - Also add dummy icons to toolbar (Open Project, Save Scene, Undo, Redo, Basic shapes, Camera, Renderable, Lights, Play, Pause, Step)
 
 
 Other polish:
 Other polish:
  - Add menu items:
  - Add menu items:
@@ -75,7 +73,7 @@ Other polish:
 Stage 2 polish:
 Stage 2 polish:
  - Prefabs
  - Prefabs
  - Game window
  - Game window
- - Game play/pause/step (+ save/restore objects on play/pause switch) (+ toolbar and menu play/pause/step entries)
+ - Game play/pause/step (+ save/restore objects on play/pause switch)
  - Resource hotswap
  - Resource hotswap
  - C# script compiling in editor
  - C# script compiling in editor
  - VS integration
  - VS integration
@@ -98,7 +96,6 @@ Optional:
   - There should be a CmdRecordSO equivalent for resources (probably)
   - There should be a CmdRecordSO equivalent for resources (probably)
   - Add commands for breaking or reverting a scene object 
   - Add commands for breaking or reverting a scene object 
   - Test & finalize undo/redo system
   - Test & finalize undo/redo system
-  - Add Undo/Redo menu and toolbar entries to "Edit" menu
  - Add "focus on object" key (F) - animate it: rotate camera towards then speed towards while zooming in (+ menu entry)
  - Add "focus on object" key (F) - animate it: rotate camera towards then speed towards while zooming in (+ menu entry)
  - Ortographic camera views (+ gizmo in scene view corner that shows camera orientation)
  - Ortographic camera views (+ gizmo in scene view corner that shows camera orientation)
  - Drag to select in scene view
  - Drag to select in scene view