Преглед изворни кода

Lots of fixes related to list/dictionary GUI refactor
Editing scene objects in inspector will now mark the scene as dirty and create an undo point

BearishSun пре 10 година
родитељ
комит
6f6648e223
47 измењених фајлова са 1136 додато и 774 уклоњено
  1. 3 0
      BansheeEditor/Include/BsEditorCommand.h
  2. 14 0
      BansheeEditor/Include/BsUndoRedo.h
  3. 56 3
      BansheeEditor/Source/BsUndoRedo.cpp
  4. 44 34
      MBansheeEditor/GUI/GUIDictionaryField.cs
  5. 54 92
      MBansheeEditor/GUI/GUIListField.cs
  6. 6 2
      MBansheeEditor/Inspector/GenericInspector.cs
  7. 55 78
      MBansheeEditor/Inspector/InspectableArray.cs
  8. 11 15
      MBansheeEditor/Inspector/InspectableBool.cs
  9. 10 16
      MBansheeEditor/Inspector/InspectableColor.cs
  10. 110 99
      MBansheeEditor/Inspector/InspectableDictionary.cs
  11. 5 35
      MBansheeEditor/Inspector/InspectableField.cs
  12. 21 19
      MBansheeEditor/Inspector/InspectableFloat.cs
  13. 11 15
      MBansheeEditor/Inspector/InspectableGameObjectRef.cs
  14. 21 19
      MBansheeEditor/Inspector/InspectableInt.cs
  15. 53 76
      MBansheeEditor/Inspector/InspectableList.cs
  16. 159 105
      MBansheeEditor/Inspector/InspectableObject.cs
  17. 10 14
      MBansheeEditor/Inspector/InspectableResourceRef.cs
  18. 22 20
      MBansheeEditor/Inspector/InspectableString.cs
  19. 21 19
      MBansheeEditor/Inspector/InspectableVector2.cs
  20. 21 19
      MBansheeEditor/Inspector/InspectableVector3.cs
  21. 21 19
      MBansheeEditor/Inspector/InspectableVector4.cs
  22. 2 1
      MBansheeEditor/Inspector/Inspector.cs
  23. 4 4
      MBansheeEditor/Inspector/InspectorUtility.cs
  24. 66 11
      MBansheeEditor/Inspector/InspectorWindow.cs
  25. 116 16
      MBansheeEditor/Inspectors/CameraInspector.cs
  26. 3 3
      MBansheeEditor/Inspectors/FontInspector.cs
  27. 8 6
      MBansheeEditor/Inspectors/GUISkinInspector.cs
  28. 53 8
      MBansheeEditor/Inspectors/LightInspector.cs
  29. 4 2
      MBansheeEditor/Inspectors/MaterialInspector.cs
  30. 3 1
      MBansheeEditor/Inspectors/MeshInspector.cs
  31. 4 2
      MBansheeEditor/Inspectors/PlainTextInspector.cs
  32. 2 2
      MBansheeEditor/Inspectors/PrefabInspector.cs
  33. 44 4
      MBansheeEditor/Inspectors/RenderableInspector.cs
  34. 4 2
      MBansheeEditor/Inspectors/ScriptCodeInspector.cs
  35. 2 1
      MBansheeEditor/Inspectors/ShaderInspector.cs
  36. 4 2
      MBansheeEditor/Inspectors/SpriteTextureInspector.cs
  37. 3 1
      MBansheeEditor/Inspectors/StringTableInspector.cs
  38. 3 1
      MBansheeEditor/Inspectors/Texture2DInspector.cs
  39. 23 0
      MBansheeEditor/UndoRedo.cs
  40. 3 0
      MBansheeEditor/UnitTestTypes.cs
  41. 7 0
      MBansheeEditor/UnitTests.cs
  42. 5 0
      MBansheeEngine/SerializableProperty.cs
  43. 2 0
      SBansheeEditor/Include/BsScriptUndoRedo.h
  44. 12 0
      SBansheeEditor/Source/BsScriptUndoRedo.cpp
  45. 6 8
      SBansheeEngine/Include/BsManagedSerializableDictionaryRTTI.h
  46. 2 0
      SBansheeEngine/Include/BsScriptSerializableProperty.h
  47. 23 0
      SBansheeEngine/Source/BsScriptSerializableProperty.cpp

+ 3 - 0
BansheeEditor/Include/BsEditorCommand.h

@@ -31,6 +31,9 @@ namespace BansheeEngine
 		static void destroy(EditorCommand* command);
 		static void destroy(EditorCommand* command);
 
 
 	private:
 	private:
+		friend class UndoRedo;
+
 		WString mDescription;
 		WString mDescription;
+		UINT32 mId;
 	};
 	};
 }
 }

+ 14 - 0
BansheeEditor/Include/BsUndoRedo.h

@@ -60,6 +60,18 @@ namespace BansheeEngine
 		 */
 		 */
 		void registerCommand(EditorCommand* command);
 		void registerCommand(EditorCommand* command);
 
 
+		/**
+		 * @brief	Returns the unique identifier for the command on top of the undo stack.
+		 */
+		UINT32 getTopCommandId() const;
+
+		/**
+		 * @brief	Removes a command from the undo/redo list, without executing it.
+		 *
+		 * @param	id	Identifier of the command returned by ::getTopCommandIdx.
+		 */
+		void popCommand(UINT32 id);
+
 		/**
 		/**
 		 * @brief	Resets the undo/redo stacks.
 		 * @brief	Resets the undo/redo stacks.
 		 */
 		 */
@@ -97,6 +109,8 @@ namespace BansheeEngine
 		UINT32 mRedoStackPtr;
 		UINT32 mRedoStackPtr;
 		UINT32 mRedoNumElements;
 		UINT32 mRedoNumElements;
 
 
+		UINT32 mNextCommandId;
+
 		Stack<GroupData> mGroups;
 		Stack<GroupData> mGroups;
 	};
 	};
 }
 }

+ 56 - 3
BansheeEditor/Source/BsUndoRedo.cpp

@@ -6,9 +6,8 @@ namespace BansheeEngine
 	const UINT32 UndoRedo::MAX_STACK_ELEMENTS = 1000;
 	const UINT32 UndoRedo::MAX_STACK_ELEMENTS = 1000;
 
 
 	UndoRedo::UndoRedo()
 	UndoRedo::UndoRedo()
-		:mUndoStackPtr(0), mUndoNumElements(0),
-		mRedoStackPtr(0), mRedoNumElements(0),
-		mUndoStack(nullptr), mRedoStack(nullptr)
+		:mUndoStackPtr(0), mUndoNumElements(0), mRedoStackPtr(0), mRedoNumElements(0), mUndoStack(nullptr), 
+		mRedoStack(nullptr), mNextCommandId(0)
 	{
 	{
 		mUndoStack = bs_newN<EditorCommand*>(MAX_STACK_ELEMENTS);
 		mUndoStack = bs_newN<EditorCommand*>(MAX_STACK_ELEMENTS);
 		mRedoStack = bs_newN<EditorCommand*>(MAX_STACK_ELEMENTS);
 		mRedoStack = bs_newN<EditorCommand*>(MAX_STACK_ELEMENTS);
@@ -85,11 +84,65 @@ namespace BansheeEngine
 
 
 	void UndoRedo::registerCommand(EditorCommand* command)
 	void UndoRedo::registerCommand(EditorCommand* command)
 	{
 	{
+		command->mId = mNextCommandId++;
 		addToUndoStack(command);
 		addToUndoStack(command);
 
 
 		clearRedoStack();
 		clearRedoStack();
 	}
 	}
 
 
+	UINT32 UndoRedo::getTopCommandId() const
+	{
+		if (mUndoNumElements > 0)
+			return mUndoStack[mUndoStackPtr]->mId;
+
+		return 0;
+	}
+
+	void UndoRedo::popCommand(UINT32 id)
+	{
+		UINT32 undoPtr = mUndoStackPtr;
+		for (UINT32 i = 0; i < mUndoNumElements; i++)
+		{
+			if (mUndoStack[undoPtr]->mId == id)
+			{
+				for (UINT32 j = i; j < (mUndoNumElements - 1); j++)
+				{
+					UINT32 nextUndoPtr = (undoPtr + 1) % MAX_STACK_ELEMENTS;
+
+					std::swap(mUndoStack[undoPtr], mUndoStack[nextUndoPtr]);
+					undoPtr = nextUndoPtr;
+				}
+
+				mUndoStackPtr = (mUndoStackPtr - 1) % MAX_STACK_ELEMENTS;
+				mUndoNumElements--;
+				break;
+			}
+
+			undoPtr = (undoPtr + 1) % MAX_STACK_ELEMENTS;
+		}
+
+		UINT32 redoPtr = mRedoStackPtr;
+		for (UINT32 i = 0; i < mRedoNumElements; i++)
+		{
+			if (mRedoStack[redoPtr]->mId == id)
+			{
+				for (UINT32 j = i; j < (mRedoNumElements - 1); j++)
+				{
+					UINT32 nextRedoPtr = (redoPtr + 1) % MAX_STACK_ELEMENTS;
+
+					std::swap(mRedoStack[redoPtr], mRedoStack[nextRedoPtr]);
+					redoPtr = nextRedoPtr;
+				}
+
+				mRedoStackPtr = (mRedoStackPtr - 1) % MAX_STACK_ELEMENTS;
+				mRedoNumElements--;
+				break;
+			}
+
+			redoPtr = (redoPtr + 1) % MAX_STACK_ELEMENTS;
+		}
+	}
+
 	void UndoRedo::clear()
 	void UndoRedo::clear()
 	{
 	{
 		clearUndoStack();
 		clearUndoStack();

+ 44 - 34
MBansheeEditor/GUI/GUIDictionaryField.cs

@@ -18,6 +18,7 @@ namespace BansheeEditor
         protected GUILayoutY guiLayout;
         protected GUILayoutY guiLayout;
         protected GUILayoutX guiChildLayout;
         protected GUILayoutX guiChildLayout;
         protected GUILayoutX guiTitleLayout;
         protected GUILayoutX guiTitleLayout;
+        protected GUILayoutX guiInternalTitleLayout;
         protected GUILayoutY guiContentLayout;
         protected GUILayoutY guiContentLayout;
         protected bool isExpanded;
         protected bool isExpanded;
         protected int depth;
         protected int depth;
@@ -42,19 +43,31 @@ namespace BansheeEditor
         protected GUIDictionaryFieldBase(LocString title, GUILayout layout, int depth = 0)
         protected GUIDictionaryFieldBase(LocString title, GUILayout layout, int depth = 0)
         {
         {
             this.title = title;
             this.title = title;
-            this.guiLayout = layout.AddLayoutY();
             this.depth = depth;
             this.depth = depth;
+            guiLayout = layout.AddLayoutY();
+            guiTitleLayout = guiLayout.AddLayoutX();
         }
         }
 
 
         /// <summary>
         /// <summary>
         /// Completely rebuilds the dictionary GUI elements.
         /// Completely rebuilds the dictionary GUI elements.
         /// </summary>
         /// </summary>
-        protected void BuildGUI()
+        public void BuildGUI()
         {
         {
             editKey = CreateKey();
             editKey = CreateKey();
             editValue = CreateValue();
             editValue = CreateValue();
 
 
-            UpdateHeaderGUI(true);
+            UpdateHeaderGUI();
+
+            foreach (var KVP in rows)
+                KVP.Value.Destroy();
+
+            rows.Clear();
+
+            if (editRow != null)
+            {
+                editRow.Destroy();
+                editRow = null;
+            }
 
 
             if (!IsNull())
             if (!IsNull())
             {
             {
@@ -79,24 +92,25 @@ namespace BansheeEditor
         /// <summary>
         /// <summary>
         /// Rebuilds the GUI dictionary header if needed. 
         /// Rebuilds the GUI dictionary header if needed. 
         /// </summary>
         /// </summary>
-        /// <param name="forceRebuild">Forces the header to be rebuilt.</param>
-        protected void UpdateHeaderGUI(bool forceRebuild)
+        protected void UpdateHeaderGUI()
         {
         {
             Action BuildEmptyGUI = () =>
             Action BuildEmptyGUI = () =>
             {
             {
-                guiTitleLayout = guiLayout.AddLayoutX();
+                guiInternalTitleLayout = guiTitleLayout.InsertLayoutX(0);
 
 
-                guiTitleLayout.AddElement(new GUILabel(title));
-                guiTitleLayout.AddElement(new GUILabel("Empty", GUIOption.FixedWidth(100)));
+                guiInternalTitleLayout.AddElement(new GUILabel(title));
+                guiInternalTitleLayout.AddElement(new GUILabel("Empty", GUIOption.FixedWidth(100)));
 
 
                 GUIContent createIcon = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.Create));
                 GUIContent createIcon = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.Create));
                 GUIButton createBtn = new GUIButton(createIcon, GUIOption.FixedWidth(30));
                 GUIButton createBtn = new GUIButton(createIcon, GUIOption.FixedWidth(30));
                 createBtn.OnClick += OnCreateButtonClicked;
                 createBtn.OnClick += OnCreateButtonClicked;
-                guiTitleLayout.AddElement(createBtn);
+                guiInternalTitleLayout.AddElement(createBtn);
             };
             };
 
 
             Action BuildFilledGUI = () =>
             Action BuildFilledGUI = () =>
             {
             {
+                guiInternalTitleLayout = guiTitleLayout.InsertLayoutX(0);
+
                 GUIToggle guiFoldout = new GUIToggle(title, EditorStyles.Foldout);
                 GUIToggle guiFoldout = new GUIToggle(title, EditorStyles.Foldout);
                 guiFoldout.Value = isExpanded;
                 guiFoldout.Value = isExpanded;
                 guiFoldout.OnToggled += ToggleFoldout;
                 guiFoldout.OnToggled += ToggleFoldout;
@@ -109,10 +123,9 @@ namespace BansheeEditor
                 GUIButton guiAddBtn = new GUIButton(addIcon, GUIOption.FixedWidth(30));
                 GUIButton guiAddBtn = new GUIButton(addIcon, GUIOption.FixedWidth(30));
                 guiAddBtn.OnClick += OnAddButtonClicked;
                 guiAddBtn.OnClick += OnAddButtonClicked;
 
 
-                guiTitleLayout = guiLayout.AddLayoutX();
-                guiTitleLayout.AddElement(guiFoldout);
-                guiTitleLayout.AddElement(guiAddBtn);
-                guiTitleLayout.AddElement(guiClearBtn);
+                guiInternalTitleLayout.AddElement(guiFoldout);
+                guiInternalTitleLayout.AddElement(guiAddBtn);
+                guiInternalTitleLayout.AddElement(guiClearBtn);
 
 
                 guiChildLayout = guiLayout.AddLayoutX();
                 guiChildLayout = guiLayout.AddLayoutX();
                 guiChildLayout.AddSpace(IndentAmount);
                 guiChildLayout.AddSpace(IndentAmount);
@@ -139,17 +152,6 @@ namespace BansheeEditor
                 ToggleFoldout(isExpanded);
                 ToggleFoldout(isExpanded);
             };
             };
 
 
-            if (forceRebuild)
-            {
-                if (state != State.None)
-                    guiTitleLayout.Destroy();
-
-                if (state == State.Filled)
-                    guiChildLayout.Destroy();
-
-                state = State.None;
-            }
-
             if (state == State.None)
             if (state == State.None)
             {
             {
                 if (!IsNull())
                 if (!IsNull())
@@ -168,7 +170,7 @@ namespace BansheeEditor
             {
             {
                 if (!IsNull())
                 if (!IsNull())
                 {
                 {
-                    guiTitleLayout.Destroy();
+                    guiInternalTitleLayout.Destroy();
                     BuildFilledGUI();
                     BuildFilledGUI();
                     state = State.Filled;
                     state = State.Filled;
                 }
                 }
@@ -177,7 +179,7 @@ namespace BansheeEditor
             {
             {
                 if (IsNull())
                 if (IsNull())
                 {
                 {
-                    guiTitleLayout.Destroy();
+                    guiInternalTitleLayout.Destroy();
                     guiChildLayout.Destroy();
                     guiChildLayout.Destroy();
                     BuildEmptyGUI();
                     BuildEmptyGUI();
 
 
@@ -211,7 +213,7 @@ namespace BansheeEditor
         /// Refreshes contents of all dictionary rows and checks if anything was modified.
         /// Refreshes contents of all dictionary rows and checks if anything was modified.
         /// </summary>
         /// </summary>
         /// <returns>State representing was anything modified between two last calls to <see cref="Refresh"/>.</returns>
         /// <returns>State representing was anything modified between two last calls to <see cref="Refresh"/>.</returns>
-        public InspectableState Refresh()
+        public virtual InspectableState Refresh()
         {
         {
             InspectableState state = InspectableState.NotModified;
             InspectableState state = InspectableState.NotModified;
             for (int i = 0; i < rows.Count; i++)
             for (int i = 0; i < rows.Count; i++)
@@ -222,7 +224,7 @@ namespace BansheeEditor
 
 
             if (isModified)
             if (isModified)
             {
             {
-                state |= InspectableState.ModifiedConfirm;
+                state |= InspectableState.Modified;
                 isModified = false;
                 isModified = false;
             }
             }
 
 
@@ -437,7 +439,7 @@ namespace BansheeEditor
         protected void OnCreateButtonClicked()
         protected void OnCreateButtonClicked()
         {
         {
             CreateDictionary();
             CreateDictionary();
-            UpdateHeaderGUI(false);
+            UpdateHeaderGUI();
 
 
             editRow.Initialize(this, guiContentLayout, 0, depth + 1);
             editRow.Initialize(this, guiContentLayout, 0, depth + 1);
             editRow.Enabled = false;
             editRow.Enabled = false;
@@ -487,7 +489,7 @@ namespace BansheeEditor
         protected void OnClearButtonClicked()
         protected void OnClearButtonClicked()
         {
         {
             DeleteDictionary();
             DeleteDictionary();
-            UpdateHeaderGUI(false);
+            UpdateHeaderGUI();
             ClearRows();
             ClearRows();
 
 
             isModified = true;
             isModified = true;
@@ -526,6 +528,10 @@ namespace BansheeEditor
                         if (KVP.Value != newRowIdx)
                         if (KVP.Value != newRowIdx)
                         {
                         {
                             GUIDictionaryFieldRow temp = rows[KVP.Value];
                             GUIDictionaryFieldRow temp = rows[KVP.Value];
+
+                            temp.SetIndex(newRowIdx);
+                            rows[newRowIdx].SetIndex(KVP.Value);
+
                             rows[KVP.Value] = rows[newRowIdx];
                             rows[KVP.Value] = rows[newRowIdx];
                             rows[newRowIdx] = temp;
                             rows[newRowIdx] = temp;
                         }
                         }
@@ -734,6 +740,10 @@ namespace BansheeEditor
                         if (KVP.Value != newRowIdx)
                         if (KVP.Value != newRowIdx)
                         {
                         {
                             GUIDictionaryFieldRow temp = rows[KVP.Value];
                             GUIDictionaryFieldRow temp = rows[KVP.Value];
+
+                            temp.SetIndex(newRowIdx);
+                            rows[newRowIdx].SetIndex(KVP.Value);
+
                             rows[KVP.Value] = rows[newRowIdx];
                             rows[KVP.Value] = rows[newRowIdx];
                             rows[newRowIdx] = temp;
                             rows[newRowIdx] = temp;
                         }
                         }
@@ -1192,7 +1202,7 @@ namespace BansheeEditor
         internal protected virtual InspectableState Refresh()
         internal protected virtual InspectableState Refresh()
         {
         {
             InspectableState oldState = modifiedState;
             InspectableState oldState = modifiedState;
-            if (modifiedState.HasFlag(InspectableState.ModifiedConfirm))
+            if (modifiedState.HasFlag(InspectableState.Modified))
                 modifiedState = InspectableState.NotModified;
                 modifiedState = InspectableState.NotModified;
 
 
             return oldState;
             return oldState;
@@ -1203,7 +1213,7 @@ namespace BansheeEditor
         /// </summary>
         /// </summary>
         protected void MarkAsModified()
         protected void MarkAsModified()
         {
         {
-            modifiedState |= InspectableState.Modified;
+            modifiedState |= InspectableState.ModifyInProgress;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -1211,8 +1221,8 @@ namespace BansheeEditor
         /// </summary>
         /// </summary>
         protected void ConfirmModify()
         protected void ConfirmModify()
         {
         {
-            if (modifiedState.HasFlag(InspectableState.Modified))
-                modifiedState |= InspectableState.ModifiedConfirm;
+            if (modifiedState.HasFlag(InspectableState.ModifyInProgress))
+                modifiedState |= InspectableState.Modified;
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 54 - 92
MBansheeEditor/GUI/GUIListField.cs

@@ -17,6 +17,7 @@ namespace BansheeEditor
         protected GUIIntField guiSizeField;
         protected GUIIntField guiSizeField;
         protected GUILayoutX guiChildLayout;
         protected GUILayoutX guiChildLayout;
         protected GUILayoutX guiTitleLayout;
         protected GUILayoutX guiTitleLayout;
+        protected GUILayoutX guiInternalTitleLayout;
         protected GUILayoutY guiContentLayout;
         protected GUILayoutY guiContentLayout;
 
 
         protected bool isExpanded;
         protected bool isExpanded;
@@ -38,53 +39,73 @@ namespace BansheeEditor
         {
         {
             this.title = title;
             this.title = title;
             this.depth = depth;
             this.depth = depth;
-            this.guiLayout = layout.AddLayoutY();
+            guiLayout = layout.AddLayoutY();
+            guiTitleLayout = guiLayout.AddLayoutX();
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Builds the list GUI elements. Must be called at least once in order for the contents to be populated.
+        /// (Re)builds the list GUI elements. Must be called at least once in order for the contents to be populated.
         /// </summary>
         /// </summary>
-        protected void BuildGUI()
+        public void BuildGUI()
         {
         {
-            UpdateHeaderGUI(true);
+            UpdateHeaderGUI();
 
 
             if (!IsNull())
             if (!IsNull())
             {
             {
                 // Hidden dependency: Initialize must be called after all elements are 
                 // Hidden dependency: Initialize must be called after all elements are 
                 // in the dictionary so we do it in two steps
                 // in the dictionary so we do it in two steps
                 int numRows = GetNumRows();
                 int numRows = GetNumRows();
-                for (int i = 0; i < numRows; i++)
+                int oldNumRows = rows.Count;
+
+                for (int i = oldNumRows; i < numRows; i++)
                 {
                 {
                     GUIListFieldRow newRow = CreateRow();
                     GUIListFieldRow newRow = CreateRow();
                     rows.Add(newRow);
                     rows.Add(newRow);
                 }
                 }
 
 
-                for (int i = 0; i < numRows; i++)
+                for (int i = oldNumRows - 1; i >= numRows; i--)
+                {
+                    rows[i].Destroy();
+                    rows.RemoveAt(i);
+                }
+
+                for (int i = oldNumRows; i < numRows; i++)
                     rows[i].Initialize(this, guiContentLayout, i, depth + 1);
                     rows[i].Initialize(this, guiContentLayout, i, depth + 1);
+
+                for (int i = 0; i < rows.Count; i++)
+                    rows[i].SetIndex(i);
+            }
+            else
+            {
+                foreach (var row in rows)
+                    row.Destroy();
+
+                rows.Clear();
             }
             }
         }
         }
 
 
         /// <summary>
         /// <summary>
         /// Rebuilds the GUI list header if needed.
         /// Rebuilds the GUI list header if needed.
         /// </summary>
         /// </summary>
-        /// <param name="forceRebuild">Forces the header to be rebuilt.</param>
-        protected void UpdateHeaderGUI(bool forceRebuild)
+        protected void UpdateHeaderGUI()
         {
         {
             Action BuildEmptyGUI = () =>
             Action BuildEmptyGUI = () =>
             {
             {
-                guiTitleLayout = guiLayout.AddLayoutX();
+                guiInternalTitleLayout = guiTitleLayout.InsertLayoutX(0);
 
 
-                guiTitleLayout.AddElement(new GUILabel(title));
-                guiTitleLayout.AddElement(new GUILabel("Empty", GUIOption.FixedWidth(100)));
+                guiInternalTitleLayout.AddElement(new GUILabel(title));
+                guiInternalTitleLayout.AddElement(new GUILabel("Empty", GUIOption.FixedWidth(100)));
 
 
                 GUIContent createIcon = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.Create));
                 GUIContent createIcon = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.Create));
                 GUIButton createBtn = new GUIButton(createIcon, GUIOption.FixedWidth(30));
                 GUIButton createBtn = new GUIButton(createIcon, GUIOption.FixedWidth(30));
-                createBtn.OnClick += CreateList;
-                guiTitleLayout.AddElement(createBtn);
+                createBtn.OnClick += OnCreateButtonClicked;
+                guiInternalTitleLayout.AddElement(createBtn);
             };
             };
 
 
             Action BuildFilledGUI = () =>
             Action BuildFilledGUI = () =>
             {
             {
+                guiInternalTitleLayout = guiTitleLayout.InsertLayoutX(0);
+
                 GUIToggle guiFoldout = new GUIToggle(title, EditorStyles.Foldout);
                 GUIToggle guiFoldout = new GUIToggle(title, EditorStyles.Foldout);
                 guiFoldout.Value = isExpanded;
                 guiFoldout.Value = isExpanded;
                 guiFoldout.OnToggled += OnFoldoutToggled;
                 guiFoldout.OnToggled += OnFoldoutToggled;
@@ -99,11 +120,10 @@ namespace BansheeEditor
                 GUIButton guiClearBtn = new GUIButton(clearIcon, GUIOption.FixedWidth(30));
                 GUIButton guiClearBtn = new GUIButton(clearIcon, GUIOption.FixedWidth(30));
                 guiClearBtn.OnClick += OnClearButtonClicked;
                 guiClearBtn.OnClick += OnClearButtonClicked;
 
 
-                guiTitleLayout = guiLayout.AddLayoutX();
-                guiTitleLayout.AddElement(guiFoldout);
-                guiTitleLayout.AddElement(guiSizeField);
-                guiTitleLayout.AddElement(guiResizeBtn);
-                guiTitleLayout.AddElement(guiClearBtn);
+                guiInternalTitleLayout.AddElement(guiFoldout);
+                guiInternalTitleLayout.AddElement(guiSizeField);
+                guiInternalTitleLayout.AddElement(guiResizeBtn);
+                guiInternalTitleLayout.AddElement(guiClearBtn);
 
 
                 guiSizeField.Value = GetNumRows();
                 guiSizeField.Value = GetNumRows();
 
 
@@ -131,17 +151,6 @@ namespace BansheeEditor
                 backgroundPanel.AddElement(inspectorContentBg);
                 backgroundPanel.AddElement(inspectorContentBg);
             };
             };
 
 
-            if (forceRebuild)
-            {
-                if (state != State.None)
-                    guiTitleLayout.Destroy();
-
-                if (state == State.Filled)
-                    guiChildLayout.Destroy();
-
-                state = State.None;
-            }
-
             if (state == State.None)
             if (state == State.None)
             {
             {
                 if (!IsNull())
                 if (!IsNull())
@@ -160,7 +169,7 @@ namespace BansheeEditor
             {
             {
                 if (!IsNull())
                 if (!IsNull())
                 {
                 {
-                    guiTitleLayout.Destroy();
+                    guiInternalTitleLayout.Destroy();
                     BuildFilledGUI();
                     BuildFilledGUI();
                     state = State.Filled;
                     state = State.Filled;
                 }
                 }
@@ -169,7 +178,7 @@ namespace BansheeEditor
             {
             {
                 if (IsNull())
                 if (IsNull())
                 {
                 {
-                    guiTitleLayout.Destroy();
+                    guiInternalTitleLayout.Destroy();
                     guiChildLayout.Destroy();
                     guiChildLayout.Destroy();
                     BuildEmptyGUI();
                     BuildEmptyGUI();
 
 
@@ -178,26 +187,6 @@ namespace BansheeEditor
             }
             }
         }
         }
 
 
-        /// <summary>
-        /// Updates list entry indices for all existing list rows.
-        /// </summary>
-        private void UpdateRows()
-        {
-            for (int i = 0; i < rows.Count; i++)
-                rows[i].SetIndex(i);
-        }
-
-        /// <summary>
-        /// Destroys all rows and clears the row list.
-        /// </summary>
-        private void ClearRows()
-        {
-            foreach (var row in rows)
-                row.Destroy();
-
-            rows.Clear();
-        }
-
         /// <summary>
         /// <summary>
         /// Returns the layout that is used for positioning the elements in the title bar.
         /// Returns the layout that is used for positioning the elements in the title bar.
         /// </summary>
         /// </summary>
@@ -211,15 +200,16 @@ namespace BansheeEditor
         /// Refreshes contents of all list rows and checks if anything was modified.
         /// Refreshes contents of all list rows and checks if anything was modified.
         /// </summary>
         /// </summary>
         /// <returns>State representing was anything modified between two last calls to <see cref="Refresh"/>.</returns>
         /// <returns>State representing was anything modified between two last calls to <see cref="Refresh"/>.</returns>
-        public InspectableState Refresh()
+        public virtual InspectableState Refresh()
         {
         {
             InspectableState state = InspectableState.NotModified;
             InspectableState state = InspectableState.NotModified;
+
             for (int i = 0; i < rows.Count; i++)
             for (int i = 0; i < rows.Count; i++)
                 state |= rows[i].Refresh();
                 state |= rows[i].Refresh();
 
 
             if (isModified)
             if (isModified)
             {
             {
-                state |= InspectableState.ModifiedConfirm;
+                state |= InspectableState.Modified;
                 isModified = false;
                 isModified = false;
             }
             }
 
 
@@ -301,7 +291,7 @@ namespace BansheeEditor
         {
         {
             CreateList();
             CreateList();
 
 
-            UpdateHeaderGUI(false);
+            BuildGUI();
             isModified = true;
             isModified = true;
         }
         }
 
 
@@ -313,23 +303,7 @@ namespace BansheeEditor
         {
         {
             ResizeList();
             ResizeList();
 
 
-            int numRows = GetNumRows();
-            for (int i = numRows; i < rows.Count;)
-            {
-                rows[i].Destroy();
-                rows.RemoveAt(i);
-            }
-
-            // Hidden dependency: Initialize must be called after all elements are 
-            // in the dictionary so we do it in two steps
-            int oldNumRows = rows.Count;
-            for (int i = oldNumRows; i < numRows; i++)
-                rows.Add(CreateRow());
-
-            for (int i = oldNumRows; i < numRows; i++)
-                rows[i].Initialize(this, guiContentLayout, i, depth + 1);
-
-            UpdateRows();
+            BuildGUI();
             isModified = true;
             isModified = true;
         }
         }
 
 
@@ -340,8 +314,7 @@ namespace BansheeEditor
         {
         {
             ClearList();
             ClearList();
 
 
-            UpdateHeaderGUI(false);
-            ClearRows();
+            BuildGUI();
             isModified = true;
             isModified = true;
         }
         }
 
 
@@ -353,15 +326,8 @@ namespace BansheeEditor
         {
         {
             DeleteElement(index);
             DeleteElement(index);
 
 
-            if (rows.Count > 0)
-            {
-                rows[rows.Count - 1].Destroy();
-                rows.RemoveAt(rows.Count - 1);
-            }
-
             guiSizeField.Value = GetNumRows();
             guiSizeField.Value = GetNumRows();
-
-            UpdateRows();
+            BuildGUI();
             isModified = true;
             isModified = true;
         }
         }
 
 
@@ -374,12 +340,8 @@ namespace BansheeEditor
         {
         {
             CloneElement(index);
             CloneElement(index);
 
 
-            GUIListFieldRow row = CreateRow();
-            rows.Add(row);
-
             guiSizeField.Value = GetNumRows();
             guiSizeField.Value = GetNumRows();
-
-            UpdateRows();
+            BuildGUI();
             isModified = true;
             isModified = true;
         }
         }
 
 
@@ -392,7 +354,7 @@ namespace BansheeEditor
         {
         {
             MoveUpElement(index);
             MoveUpElement(index);
 
 
-            UpdateRows();
+            BuildGUI();
             isModified = true;
             isModified = true;
         }
         }
 
 
@@ -405,7 +367,7 @@ namespace BansheeEditor
         {
         {
             MoveDownElement(index);
             MoveDownElement(index);
 
 
-            UpdateRows();
+            BuildGUI();
             isModified = true;
             isModified = true;
         }
         }
 
 
@@ -980,7 +942,7 @@ namespace BansheeEditor
         internal protected virtual InspectableState Refresh()
         internal protected virtual InspectableState Refresh()
         {
         {
             InspectableState oldState = modifiedState;
             InspectableState oldState = modifiedState;
-            if (modifiedState.HasFlag(InspectableState.ModifiedConfirm))
+            if (modifiedState.HasFlag(InspectableState.Modified))
                 modifiedState = InspectableState.NotModified;
                 modifiedState = InspectableState.NotModified;
 
 
             return oldState;
             return oldState;
@@ -991,7 +953,7 @@ namespace BansheeEditor
         /// </summary>
         /// </summary>
         protected void MarkAsModified()
         protected void MarkAsModified()
         {
         {
-            modifiedState |= InspectableState.Modified;
+            modifiedState |= InspectableState.ModifyInProgress;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -999,8 +961,8 @@ namespace BansheeEditor
         /// </summary>
         /// </summary>
         protected void ConfirmModify()
         protected void ConfirmModify()
         {
         {
-            if (modifiedState.HasFlag(InspectableState.Modified))
-                modifiedState |= InspectableState.ModifiedConfirm;
+            if (modifiedState.HasFlag(InspectableState.ModifyInProgress))
+                modifiedState |= InspectableState.Modified;
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 6 - 2
MBansheeEditor/Inspector/GenericInspector.cs

@@ -38,14 +38,18 @@ namespace BansheeEditor
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        protected internal override void Refresh()
+        protected internal override InspectableState Refresh()
         {
         {
+            InspectableState state = InspectableState.NotModified;
+
             int currentIndex = 0;
             int currentIndex = 0;
             foreach (var field in inspectableFields)
             foreach (var field in inspectableFields)
             {
             {
-                field.Refresh(currentIndex);
+                state |= field.Refresh(currentIndex);
                 currentIndex += field.GetNumLayoutElements();
                 currentIndex += field.GetNumLayoutElements();
             }
             }
+
+            return state;
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>

+ 55 - 78
MBansheeEditor/Inspector/InspectableArray.cs

@@ -9,8 +9,6 @@ namespace BansheeEditor
     /// </summary>
     /// </summary>
     public class InspectableArray : InspectableField
     public class InspectableArray : InspectableField
     {
     {
-        private object propertyValue; // TODO - This will unnecessarily hold references to the object
-        private int numArrayElements;
         private InspectableArrayGUI arrayGUIField;
         private InspectableArrayGUI arrayGUIField;
 
 
         /// <summary>
         /// <summary>
@@ -34,70 +32,26 @@ namespace BansheeEditor
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public override bool IsModified()
+        public override InspectableState Refresh(int layoutIndex)
         {
         {
-            object newPropertyValue = property.GetValue<object>();
-            if (propertyValue == null)
-                return newPropertyValue != null;
-
-            if (newPropertyValue == null)
-                return propertyValue != null;
-                
-            SerializableArray array = property.GetArray();
-            if (array.GetLength() != numArrayElements)
-                return true;
-
-            return base.IsModified();
+            return arrayGUIField.Refresh();
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public override bool Refresh(int layoutIndex)
-        {
-            bool isModified = IsModified();
-
-            if (isModified)
-                Update(layoutIndex);
-
-            isModified |= arrayGUIField.Refresh().HasFlag(InspectableState.Modified);
-
-            return isModified;
-        }
-
-        /// <inheritdoc/>
-        public override bool ShouldRebuildOnModify()
-        {
-            return true;
-        }
-
-        /// <inheritdoc/>
-        protected internal override void BuildGUI(int layoutIndex)
+        protected internal override void Initialize(int layoutIndex)
         {
         {
             GUILayout arrayLayout = layout.AddLayoutY(layoutIndex);
             GUILayout arrayLayout = layout.AddLayoutY(layoutIndex);
 
 
             arrayGUIField = InspectableArrayGUI.Create(title, property, arrayLayout, depth);
             arrayGUIField = InspectableArrayGUI.Create(title, property, arrayLayout, depth);
         }
         }
 
 
-        /// <inheritdoc/>
-        protected internal override void Update(int layoutIndex)
-        {
-            propertyValue = property.GetValue<object>();
-            if (propertyValue != null)
-            {
-                SerializableArray array = property.GetArray();
-                numArrayElements = array.GetLength();
-            }
-            else
-                numArrayElements = 0;
-
-            layout.DestroyElements();
-            BuildGUI(layoutIndex);
-        }
-
         /// <summary>
         /// <summary>
         /// Handles creation of GUI elements for a GUI list field that displays a <see cref="SerializableArray"/> object.
         /// Handles creation of GUI elements for a GUI list field that displays a <see cref="SerializableArray"/> object.
         /// </summary>
         /// </summary>
         private class InspectableArrayGUI : GUIListFieldBase
         private class InspectableArrayGUI : GUIListFieldBase
         {
         {
+            private Array array;
+            private int numElements;
             private SerializableProperty property;
             private SerializableProperty property;
 
 
             /// <summary>
             /// <summary>
@@ -107,6 +61,10 @@ namespace BansheeEditor
                 : base(title, layout, depth)
                 : base(title, layout, depth)
             {
             {
                 this.property = property;
                 this.property = property;
+                array = property.GetValue<Array>();
+
+                if (array != null)
+                    numElements = array.Length;
             }
             }
 
 
             /// <summary>
             /// <summary>
@@ -122,10 +80,42 @@ namespace BansheeEditor
             {
             {
                 InspectableArrayGUI guiArray = new InspectableArrayGUI(title, property, layout, depth);
                 InspectableArrayGUI guiArray = new InspectableArrayGUI(title, property, layout, depth);
                 guiArray.BuildGUI();
                 guiArray.BuildGUI();
-
+                
                 return guiArray;
                 return guiArray;
             }
             }
 
 
+            /// <inheritdoc/>
+            public override InspectableState Refresh()
+            {
+                // Check if any modifications to the array were made outside the inspector
+                Array newArray = property.GetValue<Array>();
+                if (array == null && newArray != null)
+                {
+                    array = newArray;
+                    numElements = array.Length;
+                    BuildGUI();
+                }
+                else if (newArray == null && array != null)
+                {
+                    array = null;
+                    numElements = 0;
+                    BuildGUI();
+                }
+                else
+                {
+                    if (array != null)
+                    {
+                        if (numElements != array.Length)
+                        {
+                            numElements = array.Length;
+                            BuildGUI();
+                        }
+                    }
+                }
+
+                return base.Refresh();
+            }
+
             /// <inheritdoc/>
             /// <inheritdoc/>
             protected override GUIListFieldRow CreateRow()
             protected override GUIListFieldRow CreateRow()
             {
             {
@@ -135,16 +125,14 @@ namespace BansheeEditor
             /// <inheritdoc/>
             /// <inheritdoc/>
             protected override bool IsNull()
             protected override bool IsNull()
             {
             {
-                Array array = property.GetValue<Array>();
                 return array == null;
                 return array == null;
             }
             }
 
 
             /// <inheritdoc/>
             /// <inheritdoc/>
             protected override int GetNumRows()
             protected override int GetNumRows()
             {
             {
-                Array array = property.GetValue<Array>();
                 if (array != null)
                 if (array != null)
-                    return array.GetLength(0);
+                    return array.Length;
 
 
                 return 0;
                 return 0;
             }
             }
@@ -167,7 +155,9 @@ namespace BansheeEditor
             /// <inheritdoc/>
             /// <inheritdoc/>
             protected override void CreateList()
             protected override void CreateList()
             {
             {
-                property.SetValue(property.CreateArrayInstance(new int[1] { 0 }));
+                array = property.CreateArrayInstance(new int[1] { 0 });
+                property.SetValue(array);
+                numElements = 0;
             }
             }
 
 
             /// <inheritdoc/>
             /// <inheritdoc/>
@@ -176,27 +166,27 @@ namespace BansheeEditor
                 int size = guiSizeField.Value; // TODO - Support multi-rank arrays
                 int size = guiSizeField.Value; // TODO - Support multi-rank arrays
 
 
                 Array newArray = property.CreateArrayInstance(new int[] { size });
                 Array newArray = property.CreateArrayInstance(new int[] { size });
-                Array array = property.GetValue<Array>();
-
                 int maxSize = MathEx.Min(size, array.Length);
                 int maxSize = MathEx.Min(size, array.Length);
 
 
                 for (int i = 0; i < maxSize; i++)
                 for (int i = 0; i < maxSize; i++)
                     newArray.SetValue(array.GetValue(i), i);
                     newArray.SetValue(array.GetValue(i), i);
 
 
                 property.SetValue(newArray);
                 property.SetValue(newArray);
+                array = newArray;
+                numElements = size;
             }
             }
 
 
             /// <inheritdoc/>
             /// <inheritdoc/>
             protected override void ClearList()
             protected override void ClearList()
             {
             {
                 property.SetValue<object>(null);
                 property.SetValue<object>(null);
+                array = null;
+                numElements = 0;
             }
             }
 
 
             /// <inheritdoc/>
             /// <inheritdoc/>
             protected internal override void DeleteElement(int index)
             protected internal override void DeleteElement(int index)
             {
             {
-                Array array = property.GetValue<Array>();
-
                 int size = MathEx.Max(0, array.Length - 1);
                 int size = MathEx.Max(0, array.Length - 1);
                 Array newArray = property.CreateArrayInstance(new int[] { size });
                 Array newArray = property.CreateArrayInstance(new int[] { size });
 
 
@@ -211,6 +201,8 @@ namespace BansheeEditor
                 }
                 }
 
 
                 property.SetValue(newArray);
                 property.SetValue(newArray);
+                array = newArray;
+                numElements = array.Length;
             }
             }
 
 
             /// <inheritdoc/>
             /// <inheritdoc/>
@@ -237,13 +229,13 @@ namespace BansheeEditor
                 newArray.SetValue(clonedEntry, size - 1);
                 newArray.SetValue(clonedEntry, size - 1);
 
 
                 property.SetValue(newArray);
                 property.SetValue(newArray);
+                this.array = newArray;
+                numElements = newArray.Length;
             }
             }
 
 
             /// <inheritdoc/>
             /// <inheritdoc/>
             protected internal override void MoveUpElement(int index)
             protected internal override void MoveUpElement(int index)
             {
             {
-                Array array = property.GetValue<Array>();
-
                 if ((index - 1) >= 0)
                 if ((index - 1) >= 0)
                 {
                 {
                     object previousEntry = array.GetValue(index - 1);
                     object previousEntry = array.GetValue(index - 1);
@@ -256,8 +248,6 @@ namespace BansheeEditor
             /// <inheritdoc/>
             /// <inheritdoc/>
             protected internal override void MoveDownElement(int index)
             protected internal override void MoveDownElement(int index)
             {
             {
-                Array array = property.GetValue<Array>();
-
                 if ((index + 1) < array.Length)
                 if ((index + 1) < array.Length)
                 {
                 {
                     object nextEntry = array.GetValue(index + 1);
                     object nextEntry = array.GetValue(index + 1);
@@ -285,8 +275,6 @@ namespace BansheeEditor
                     field = CreateInspectable(SeqIndex + ".", 0, Depth + 1,
                     field = CreateInspectable(SeqIndex + ".", 0, Depth + 1,
                         new InspectableFieldLayout(layout), property);
                         new InspectableFieldLayout(layout), property);
                 }
                 }
-                else
-                    field.Refresh(0);
 
 
                 return field.GetTitleLayout();
                 return field.GetTitleLayout();
             }
             }
@@ -294,18 +282,7 @@ namespace BansheeEditor
             /// <inheritdoc/>
             /// <inheritdoc/>
             protected internal override InspectableState Refresh()
             protected internal override InspectableState Refresh()
             {
             {
-                //InspectableState state = field.Refresh(0);
-                InspectableState state = InspectableState.NotModified;
-                if (field.IsModified())
-                    state = InspectableState.Modified;
-
-                if(state.HasFlag(InspectableState.Modified))
-                {
-                    if (field.ShouldRebuildOnModify())
-                        BuildGUI();
-                }
-
-                return state;
+                return field.Refresh(0);
             }
             }
         }
         }
     }
     }

+ 11 - 15
MBansheeEditor/Inspector/InspectableBool.cs

@@ -8,8 +8,8 @@ namespace BansheeEditor
     /// </summary>
     /// </summary>
     public class InspectableBool : InspectableField
     public class InspectableBool : InspectableField
     {
     {
-        private bool propertyValue;
         private GUIToggleField guiField;
         private GUIToggleField guiField;
+        private InspectableState state;
 
 
         /// <summary>
         /// <summary>
         /// Creates a new inspectable boolean GUI for the specified property.
         /// Creates a new inspectable boolean GUI for the specified property.
@@ -26,7 +26,7 @@ namespace BansheeEditor
         }
         }
 
 
         /// <inheritoc/>
         /// <inheritoc/>
-        protected internal override void BuildGUI(int layoutIndex)
+        protected internal override void Initialize(int layoutIndex)
         {
         {
             if (property.Type == SerializableProperty.FieldType.Bool)
             if (property.Type == SerializableProperty.FieldType.Bool)
             {
             {
@@ -38,21 +38,16 @@ namespace BansheeEditor
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public override bool IsModified()
+        public override InspectableState Refresh(int layoutIndex)
         {
         {
-            bool newPropertyValue = property.GetValue<bool>();
-            if (propertyValue != newPropertyValue)
-                return true;
-                
-            return base.IsModified();
-        }
-
-        /// <inheritdoc/>
-        protected internal override void Update(int layoutIndex)
-        {
-            propertyValue = property.GetValue<bool>();
             if (guiField != null)
             if (guiField != null)
-                guiField.Value = propertyValue;
+                guiField.Value = property.GetValue<bool>();
+
+            InspectableState oldState = state;
+            if (state.HasFlag(InspectableState.Modified))
+                state = InspectableState.NotModified;
+
+            return oldState;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -62,6 +57,7 @@ namespace BansheeEditor
         private void OnFieldValueChanged(bool newValue)
         private void OnFieldValueChanged(bool newValue)
         {
         {
             property.SetValue(newValue);
             property.SetValue(newValue);
+            state = InspectableState.Modified;
         }
         }
     }
     }
 }
 }

+ 10 - 16
MBansheeEditor/Inspector/InspectableColor.cs

@@ -8,8 +8,8 @@ namespace BansheeEditor
     /// </summary>
     /// </summary>
     public class InspectableColor : InspectableField
     public class InspectableColor : InspectableField
     {
     {
-        private Color propertyValue;
         private GUIColorField guiField;
         private GUIColorField guiField;
+        private InspectableState state;
 
 
         /// <summary>
         /// <summary>
         /// Creates a new inspectable color GUI for the specified property.
         /// Creates a new inspectable color GUI for the specified property.
@@ -26,7 +26,7 @@ namespace BansheeEditor
         }
         }
 
 
         /// <inheritoc/>
         /// <inheritoc/>
-        protected internal override void BuildGUI(int layoutIndex)
+        protected internal override void Initialize(int layoutIndex)
         {
         {
             if (property.Type == SerializableProperty.FieldType.Color)
             if (property.Type == SerializableProperty.FieldType.Color)
             {
             {
@@ -38,23 +38,16 @@ namespace BansheeEditor
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public override bool IsModified()
+        public override InspectableState Refresh(int layoutIndex)
         {
         {
-            Color newPropertyValue = property.GetValue<Color>();
-            if (propertyValue != newPropertyValue)
-                return true;
-                
-            return base.IsModified();
-        }
+            if (guiField != null)
+                guiField.Value = property.GetValue<Color>();
 
 
-        /// <inheritdoc/>
-        protected internal override void Update(int layoutIndex)
-        {
-            // TODO - Skip update if it currently has input focus so user can modify the value in peace
+            InspectableState oldState = state;
+            if (state.HasFlag(InspectableState.Modified))
+                state = InspectableState.NotModified;
 
 
-            propertyValue = property.GetValue<Color>();
-            if (guiField != null)
-                guiField.Value = propertyValue;
+            return oldState;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -64,6 +57,7 @@ namespace BansheeEditor
         private void OnFieldValueChanged(Color newValue)
         private void OnFieldValueChanged(Color newValue)
         {
         {
             property.SetValue(newValue);
             property.SetValue(newValue);
+            state = InspectableState.Modified;
         }
         }
     }
     }
 }
 }

+ 110 - 99
MBansheeEditor/Inspector/InspectableDictionary.cs

@@ -11,8 +11,6 @@ namespace BansheeEditor
     /// </summary>
     /// </summary>
     public class InspectableDictionary : InspectableField
     public class InspectableDictionary : InspectableField
     {
     {
-        private object propertyValue; // TODO - This will unnecessarily hold references to the object
-        private int numElements;
         private InspectableDictionaryGUI dictionaryGUIField;
         private InspectableDictionaryGUI dictionaryGUIField;
 
 
         /// <summary>
         /// <summary>
@@ -36,65 +34,19 @@ namespace BansheeEditor
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public override bool IsModified()
+        public override InspectableState Refresh(int layoutIndex)
         {
         {
-            object newPropertyValue = property.GetValue<object>();
-            if (propertyValue == null)
-                return newPropertyValue != null;
-
-            if (newPropertyValue == null)
-                return propertyValue != null;
-
-            SerializableDictionary dictionary = property.GetDictionary();
-            if (dictionary.GetLength() != numElements)
-                return true;
-
-            return base.IsModified();
-        }
-
-        /// <inheritdoc/>
-        public override bool Refresh(int layoutIndex)
-        {
-            bool isModified = IsModified();
-
-            if (isModified)
-                Update(layoutIndex);
-
-            isModified |= dictionaryGUIField.Refresh().HasFlag(InspectableState.Modified);
-
-            return isModified;
+            return dictionaryGUIField.Refresh();
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public override bool ShouldRebuildOnModify()
-        {
-            return true;
-        }
-
-        /// <inheritdoc/>
-        protected internal override void BuildGUI(int layoutIndex)
+        protected internal override void Initialize(int layoutIndex)
         {
         {
             GUILayout dictionaryLayout = layout.AddLayoutY(layoutIndex);
             GUILayout dictionaryLayout = layout.AddLayoutY(layoutIndex);
 
 
             dictionaryGUIField = InspectableDictionaryGUI.Create(title, property, dictionaryLayout, depth);
             dictionaryGUIField = InspectableDictionaryGUI.Create(title, property, dictionaryLayout, depth);
         }
         }
 
 
-        /// <inheritdoc/>
-        protected internal override void Update(int layoutIndex)
-        {
-            propertyValue = property.GetValue<object>();
-            if (propertyValue != null)
-            {
-                SerializableDictionary dictionary = property.GetDictionary();
-                numElements = dictionary.GetLength();
-            }
-            else
-                numElements = 0;
-
-            layout.DestroyElements();
-            BuildGUI(layoutIndex);
-        }
-
         /// <summary>
         /// <summary>
         /// Creates GUI elements that allow viewing and manipulation of a <see cref="SerializableDictionary"/> referenced
         /// Creates GUI elements that allow viewing and manipulation of a <see cref="SerializableDictionary"/> referenced
         /// by a serializable property.
         /// by a serializable property.
@@ -102,7 +54,10 @@ namespace BansheeEditor
         public class InspectableDictionaryGUI : GUIDictionaryFieldBase
         public class InspectableDictionaryGUI : GUIDictionaryFieldBase
         {
         {
             private SerializableProperty property;
             private SerializableProperty property;
-            private List<object> orderedKeys = new List<object>();
+            private IDictionary dictionary;
+            private int numElements;
+
+            private List<SerializableProperty> orderedKeys = new List<SerializableProperty>();
 
 
             /// <summary>
             /// <summary>
             /// Constructs a new dictionary GUI.
             /// Constructs a new dictionary GUI.
@@ -115,10 +70,15 @@ namespace BansheeEditor
             ///                     depths divisible by two will use an alternate style.</param>
             ///                     depths divisible by two will use an alternate style.</param>
             protected InspectableDictionaryGUI(LocString title, SerializableProperty property, GUILayout layout, int depth = 0)
             protected InspectableDictionaryGUI(LocString title, SerializableProperty property, GUILayout layout, int depth = 0)
             : base(title, layout, depth)
             : base(title, layout, depth)
-        {
-            this.property = property;
-            UpdateKeys();
-        }
+            {
+                this.property = property;
+                dictionary = property.GetValue<IDictionary>();
+
+                if (dictionary != null)
+                    numElements = dictionary.Count;
+
+                UpdateKeys();
+            }
 
 
             /// <summary>
             /// <summary>
             /// Builds the inspectable dictionary GUI elements. Must be called at least once in order for the contents to 
             /// Builds the inspectable dictionary GUI elements. Must be called at least once in order for the contents to 
@@ -134,11 +94,45 @@ namespace BansheeEditor
                 int depth = 0)
                 int depth = 0)
             {
             {
                 InspectableDictionaryGUI guiDictionary = new InspectableDictionaryGUI(title, property, layout, depth);
                 InspectableDictionaryGUI guiDictionary = new InspectableDictionaryGUI(title, property, layout, depth);
-
                 guiDictionary.BuildGUI();
                 guiDictionary.BuildGUI();
+
                 return guiDictionary;
                 return guiDictionary;
             }
             }
 
 
+
+            /// <inheritdoc/>
+            public override InspectableState Refresh()
+            {
+                // Check if any modifications to the array were made outside the inspector
+                IDictionary newDict = property.GetValue<IDictionary>();
+                if (dictionary == null && newDict != null)
+                {
+                    dictionary = newDict;
+                    numElements = dictionary.Count;
+                    BuildGUI();
+                }
+                else if (newDict == null && dictionary != null)
+                {
+                    dictionary = null;
+                    numElements = 0;
+                    BuildGUI();
+                }
+                else
+                {
+                    if (dictionary != null)
+                    {
+                        if (numElements != dictionary.Count)
+                        {
+                            numElements = dictionary.Count;
+                            BuildGUI();
+                        }
+                    }
+                }
+
+                return base.Refresh();
+            }
+
+
             /// <summary>
             /// <summary>
             /// Updates the ordered set of keys used for mapping sequential indexes to keys. Should be called whenever a 
             /// Updates the ordered set of keys used for mapping sequential indexes to keys. Should be called whenever a 
             /// dictionary key changes.
             /// dictionary key changes.
@@ -147,11 +141,11 @@ namespace BansheeEditor
             {
             {
                 orderedKeys.Clear();
                 orderedKeys.Clear();
 
 
-                IDictionary dictionary = property.GetValue<IDictionary>();
-                if (dictionary != null)
+                SerializableDictionary dict = property.GetDictionary();
+                if (dict != null)
                 {
                 {
-                    foreach (var key in dictionary)
-                        orderedKeys.Add(key);
+                    foreach (var key in dictionary.Keys)
+                        orderedKeys.Add(dict.GetProperty(key).Key);
                 }
                 }
             }
             }
 
 
@@ -164,7 +158,6 @@ namespace BansheeEditor
             /// <inheritdoc/>
             /// <inheritdoc/>
             protected override int GetNumRows()
             protected override int GetNumRows()
             {
             {
-                IDictionary dictionary = property.GetValue<IDictionary>();
                 if (dictionary != null)
                 if (dictionary != null)
                     return dictionary.Count;
                     return dictionary.Count;
 
 
@@ -174,7 +167,6 @@ namespace BansheeEditor
             /// <inheritdoc/>
             /// <inheritdoc/>
             protected override bool IsNull()
             protected override bool IsNull()
             {
             {
-                IDictionary dictionary = property.GetValue<IDictionary>();
                 return dictionary == null;
                 return dictionary == null;
             }
             }
 
 
@@ -187,8 +179,10 @@ namespace BansheeEditor
             /// <inheritdoc/>
             /// <inheritdoc/>
             protected internal override object GetValue(object key)
             protected internal override object GetValue(object key)
             {
             {
+                SerializableProperty keyProperty = (SerializableProperty)key;
+
                 SerializableDictionary dictionary = property.GetDictionary();
                 SerializableDictionary dictionary = property.GetDictionary();
-                return dictionary.GetProperty(key);
+                return dictionary.GetProperty(keyProperty.GetValue<object>()).Value;
             }
             }
 
 
             /// <inheritdoc/>
             /// <inheritdoc/>
@@ -201,16 +195,20 @@ namespace BansheeEditor
             /// <inheritdoc/>
             /// <inheritdoc/>
             protected internal override bool Contains(object key)
             protected internal override bool Contains(object key)
             {
             {
-                IDictionary dictionary = property.GetValue<IDictionary>();
-                return dictionary.Contains(key); ;
+                SerializableProperty keyProperty = (SerializableProperty)key;
+                return dictionary.Contains(keyProperty.GetValue<object>()); ;
             }
             }
 
 
             /// <inheritdoc/>
             /// <inheritdoc/>
             protected internal override void EditEntry(object oldKey, object newKey, object value)
             protected internal override void EditEntry(object oldKey, object newKey, object value)
             {
             {
-                IDictionary dictionary = property.GetValue<IDictionary>();
-                dictionary.Remove(oldKey);
-                dictionary.Add(newKey, value);
+                SerializableProperty oldKeyProperty = (SerializableProperty)oldKey;
+                SerializableProperty newKeyProperty = (SerializableProperty)newKey;
+                SerializableProperty valueProperty = (SerializableProperty)value;
+
+                dictionary.Remove(oldKeyProperty.GetValue<object>());
+                dictionary.Add(newKeyProperty.GetValue<object>(), valueProperty.GetValue<object>());
+                numElements = dictionary.Count;
 
 
                 UpdateKeys();
                 UpdateKeys();
             }
             }
@@ -218,8 +216,11 @@ namespace BansheeEditor
             /// <inheritdoc/>
             /// <inheritdoc/>
             protected internal override void AddEntry(object key, object value)
             protected internal override void AddEntry(object key, object value)
             {
             {
-                IDictionary dictionary = property.GetValue<IDictionary>();
-                dictionary.Add(key, value);
+                SerializableProperty keyProperty = (SerializableProperty)key;
+                SerializableProperty valueProperty = (SerializableProperty)value;
+
+                dictionary.Add(keyProperty.GetValue<object>(), valueProperty.GetValue<object>());
+                numElements = dictionary.Count;
 
 
                 UpdateKeys();
                 UpdateKeys();
             }
             }
@@ -227,8 +228,10 @@ namespace BansheeEditor
             /// <inheritdoc/>
             /// <inheritdoc/>
             protected internal override void RemoveEntry(object key)
             protected internal override void RemoveEntry(object key)
             {
             {
-                IDictionary dictionary = property.GetValue<IDictionary>();
-                dictionary.Remove(key);
+                SerializableProperty keyProperty = (SerializableProperty)key;
+
+                dictionary.Remove(keyProperty.GetValue<object>());
+                numElements = dictionary.Count;
 
 
                 UpdateKeys();
                 UpdateKeys();
             }
             }
@@ -237,29 +240,59 @@ namespace BansheeEditor
             protected internal override object CreateKey()
             protected internal override object CreateKey()
             {
             {
                 SerializableDictionary dictionary = property.GetDictionary();
                 SerializableDictionary dictionary = property.GetDictionary();
-                return SerializableUtility.Create(dictionary.KeyType);
+
+                DictionaryDataWrapper data = new DictionaryDataWrapper();
+                data.value = SerializableUtility.Create(dictionary.KeyType);
+
+                SerializableProperty keyProperty = new SerializableProperty(dictionary.KeyPropertyType,
+                    dictionary.KeyType,
+                    () => data.value, (x) => data.value = x);
+
+                return keyProperty;
             }
             }
 
 
             /// <inheritdoc/>
             /// <inheritdoc/>
             protected internal override object CreateValue()
             protected internal override object CreateValue()
             {
             {
                 SerializableDictionary dictionary = property.GetDictionary();
                 SerializableDictionary dictionary = property.GetDictionary();
-                return SerializableUtility.Create(dictionary.ValueType);
+
+                DictionaryDataWrapper data = new DictionaryDataWrapper();
+                data.value = SerializableUtility.Create(dictionary.ValueType);
+
+                SerializableProperty valueProperty = new SerializableProperty(dictionary.ValuePropertyType,
+                    dictionary.ValueType,
+                    () => data.value, (x) => data.value = x);
+
+                return valueProperty;
             }
             }
 
 
             /// <inheritdoc/>
             /// <inheritdoc/>
             protected override void CreateDictionary()
             protected override void CreateDictionary()
             {
             {
-                property.SetValue(property.CreateDictionaryInstance());
+                dictionary = property.CreateDictionaryInstance();
+                numElements = dictionary.Count;
+                property.SetValue(dictionary);
+
                 UpdateKeys();
                 UpdateKeys();
             }
             }
 
 
             /// <inheritdoc/>
             /// <inheritdoc/>
             protected override void DeleteDictionary()
             protected override void DeleteDictionary()
             {
             {
+                dictionary = null;
+                numElements = 0;
                 property.SetValue<object>(null);
                 property.SetValue<object>(null);
+
                 UpdateKeys();
                 UpdateKeys();
             }
             }
+
+            /// <summary>
+            /// Wraps a dictionary key or a value.
+            /// </summary>
+            class DictionaryDataWrapper
+            {
+                public object value;
+            }
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -280,8 +313,6 @@ namespace BansheeEditor
                     fieldKey = CreateInspectable("Key", 0, Depth + 1,
                     fieldKey = CreateInspectable("Key", 0, Depth + 1,
                         new InspectableFieldLayout(layout), property);
                         new InspectableFieldLayout(layout), property);
                 }
                 }
-                else
-                    fieldKey.Refresh(0);
 
 
                 return fieldKey.GetTitleLayout();
                 return fieldKey.GetTitleLayout();
             }
             }
@@ -296,32 +327,12 @@ namespace BansheeEditor
                     fieldValue = CreateInspectable("Value", 0, Depth + 1,
                     fieldValue = CreateInspectable("Value", 0, Depth + 1,
                         new InspectableFieldLayout(layout), property);
                         new InspectableFieldLayout(layout), property);
                 }
                 }
-                else
-                    fieldValue.Refresh(0);
             }
             }
 
 
             /// <inheritdoc/>
             /// <inheritdoc/>
             protected internal override InspectableState Refresh()
             protected internal override InspectableState Refresh()
             {
             {
-                //InspectableState state = fieldKey.Refresh(0) | fieldValue.Refresh(0);
-                InspectableState state = InspectableState.NotModified;
-                if (fieldKey.IsModified())
-                    state = InspectableState.Modified;
-
-                if (state.HasFlag(InspectableState.Modified) && fieldKey.ShouldRebuildOnModify())
-                {
-                    if (fieldValue.IsModified())
-                        state |= InspectableState.Modified;
-
-                    BuildGUI();
-                }
-                else
-                {
-                    if (fieldValue.Refresh(0))
-                        state |= InspectableState.Modified;
-                }
-
-                return state;
+                return fieldValue.Refresh(0);
             }
             }
         }
         }
     }
     }

+ 5 - 35
MBansheeEditor/Inspector/InspectableField.cs

@@ -37,15 +37,10 @@ namespace BansheeEditor
         /// </summary>
         /// </summary>
         /// <param name="layoutIndex">Index in the parent's layout at which to insert the GUI elements for this field.
         /// <param name="layoutIndex">Index in the parent's layout at which to insert the GUI elements for this field.
         ///                           </param>
         ///                           </param>
-        /// <returns>True if the field or any of its children were modified.</returns>
-        public virtual bool Refresh(int layoutIndex)
+        /// <returns>State representing was anything modified between two last calls to <see cref="Refresh"/>.</returns>
+        public virtual InspectableState Refresh(int layoutIndex)
         {
         {
-            bool isModified = IsModified();
-
-            if (isModified)
-                Update(layoutIndex);
-
-            return isModified;
+            return InspectableState.NotModified;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -68,36 +63,11 @@ namespace BansheeEditor
             return null;
             return null;
         }
         }
 
 
-        /// <summary>
-        /// Checks have the values in the referenced serializable property have been changed compare to the value currently
-        /// displayed in the field.
-        /// </summary>
-        /// <returns>True if the value has been modified and needs updating.</returns>
-        public virtual bool IsModified()
-        {
-            return false;
-        }
-
-        /// <summary>
-        /// Checks does the field GUI has to be rebuilt if the field is marked as modified.
-        /// </summary>
-        /// <returns>True if field GUI has to be rebuilt if the field is marked as modified.</returns>
-        public virtual bool ShouldRebuildOnModify()
-        {
-            return false;
-        }
-
-        /// <summary>
-        /// Reconstructs the GUI by using the most up to date values from the referenced serializable property.
-        /// </summary>
-        /// <param name="layoutIndex">Index in the parent's layout at which to insert the GUI elements for this field.</param>
-        protected internal abstract void Update(int layoutIndex);
-
         /// <summary>
         /// <summary>
         /// Initializes the GUI elements for the field.
         /// Initializes the GUI elements for the field.
         /// </summary>
         /// </summary>
         /// <param name="layoutIndex">Index at which to insert the GUI elements.</param>
         /// <param name="layoutIndex">Index at which to insert the GUI elements.</param>
-        protected internal abstract void BuildGUI(int layoutIndex);
+        protected internal abstract void Initialize(int layoutIndex);
 
 
         /// <summary>
         /// <summary>
         /// Destroys all GUI elements in the inspectable field.
         /// Destroys all GUI elements in the inspectable field.
@@ -183,7 +153,7 @@ namespace BansheeEditor
             if (field == null)
             if (field == null)
                 throw new Exception("No inspector exists for the provided field type.");
                 throw new Exception("No inspector exists for the provided field type.");
 
 
-            field.BuildGUI(layoutIndex);
+            field.Initialize(layoutIndex);
             field.Refresh(layoutIndex);
             field.Refresh(layoutIndex);
 
 
             return field;
             return field;

+ 21 - 19
MBansheeEditor/Inspector/InspectableFloat.cs

@@ -7,8 +7,8 @@ namespace BansheeEditor
     /// </summary>
     /// </summary>
     public class InspectableFloat : InspectableField
     public class InspectableFloat : InspectableField
     {
     {
-        private float propertyValue;
         private GUIFloatField guiFloatField;
         private GUIFloatField guiFloatField;
+        private InspectableState state;
 
 
         /// <summary>
         /// <summary>
         /// Creates a new inspectable float GUI for the specified property.
         /// Creates a new inspectable float GUI for the specified property.
@@ -25,38 +25,30 @@ namespace BansheeEditor
         }
         }
 
 
         /// <inheritoc/>
         /// <inheritoc/>
-        protected internal override void BuildGUI(int layoutIndex)
+        protected internal override void Initialize(int layoutIndex)
         {
         {
             if (property.Type == SerializableProperty.FieldType.Float)
             if (property.Type == SerializableProperty.FieldType.Float)
             {
             {
                 guiFloatField = new GUIFloatField(new GUIContent(title));
                 guiFloatField = new GUIFloatField(new GUIContent(title));
                 guiFloatField.OnChanged += OnFieldValueChanged;
                 guiFloatField.OnChanged += OnFieldValueChanged;
+                guiFloatField.OnConfirmed += OnFieldValueConfirm;
+                guiFloatField.OnFocusLost += OnFieldValueConfirm;
 
 
                 layout.AddElement(layoutIndex, guiFloatField);
                 layout.AddElement(layoutIndex, guiFloatField);
             }
             }
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public override bool IsModified()
+        public override InspectableState Refresh(int layoutIndex)
         {
         {
-            float newPropertyValue = property.GetValue<float>();
-            if (propertyValue != newPropertyValue)
-                return true;
-                
-            return base.IsModified();
-        }
+            if (guiFloatField != null && !guiFloatField.HasInputFocus)
+                guiFloatField.Value = property.GetValue<int>();
 
 
-        /// <inheritdoc/>
-        protected internal override void Update(int layoutIndex)
-        {
-            propertyValue = property.GetValue<float>();
-            if (guiFloatField != null)
-            {
-                if (guiFloatField.HasInputFocus)
-                    return;
+            InspectableState oldState = state;
+            if (state.HasFlag(InspectableState.Modified))
+                state = InspectableState.NotModified;
 
 
-                guiFloatField.Value = propertyValue;
-            }
+            return oldState;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -66,6 +58,16 @@ namespace BansheeEditor
         private void OnFieldValueChanged(float newValue)
         private void OnFieldValueChanged(float newValue)
         {
         {
             property.SetValue(newValue);
             property.SetValue(newValue);
+            state |= InspectableState.ModifyInProgress;
+        }
+
+        /// <summary>
+        /// Triggered when the user confirms input in the float field.
+        /// </summary>
+        private void OnFieldValueConfirm()
+        {
+            if (state.HasFlag(InspectableState.ModifyInProgress))
+                state |= InspectableState.Modified;
         }
         }
     }
     }
 }
 }

+ 11 - 15
MBansheeEditor/Inspector/InspectableGameObjectRef.cs

@@ -7,8 +7,8 @@ namespace BansheeEditor
     /// </summary>
     /// </summary>
     public class InspectableGameObjectRef : InspectableField
     public class InspectableGameObjectRef : InspectableField
     {
     {
-        private GameObject propertyValue;
         private GUIGameObjectField guiField;
         private GUIGameObjectField guiField;
+        private InspectableState state;
 
 
         /// <summary>
         /// <summary>
         /// Creates a new inspectable game object reference GUI for the specified property.
         /// Creates a new inspectable game object reference GUI for the specified property.
@@ -25,7 +25,7 @@ namespace BansheeEditor
         }
         }
 
 
         /// <inheritoc/>
         /// <inheritoc/>
-        protected internal override void BuildGUI(int layoutIndex)
+        protected internal override void Initialize(int layoutIndex)
         {
         {
             if (property.Type == SerializableProperty.FieldType.GameObjectRef)
             if (property.Type == SerializableProperty.FieldType.GameObjectRef)
             {
             {
@@ -37,21 +37,16 @@ namespace BansheeEditor
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public override bool IsModified()
+        public override InspectableState Refresh(int layoutIndex)
         {
         {
-            GameObject newPropertyValue = property.GetValue<GameObject>();
-            if (propertyValue != newPropertyValue)
-                return true;
-                
-            return base.IsModified();
-        }
-
-        /// <inheritdoc/>
-        protected internal override void Update(int layoutIndex)
-        {
-            propertyValue = property.GetValue<GameObject>();
             if (guiField != null)
             if (guiField != null)
-                guiField.Value = propertyValue;
+                guiField.Value = property.GetValue<GameObject>();
+
+            InspectableState oldState = state;
+            if (state.HasFlag(InspectableState.Modified))
+                state = InspectableState.NotModified;
+
+            return oldState;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -61,6 +56,7 @@ namespace BansheeEditor
         private void OnFieldValueChanged(GameObject newValue)
         private void OnFieldValueChanged(GameObject newValue)
         {
         {
             property.SetValue(newValue);
             property.SetValue(newValue);
+            state = InspectableState.Modified;
         }
         }
     }
     }
 }
 }

+ 21 - 19
MBansheeEditor/Inspector/InspectableInt.cs

@@ -7,8 +7,8 @@ namespace BansheeEditor
     /// </summary>
     /// </summary>
     public class InspectableInt : InspectableField
     public class InspectableInt : InspectableField
     {
     {
-        private int propertyValue;
         private GUIIntField guiIntField;
         private GUIIntField guiIntField;
+        private InspectableState state;
 
 
         /// <summary>
         /// <summary>
         /// Creates a new inspectable integer GUI for the specified property.
         /// Creates a new inspectable integer GUI for the specified property.
@@ -25,38 +25,30 @@ namespace BansheeEditor
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        protected internal override void BuildGUI(int layoutIndex)
+        protected internal override void Initialize(int layoutIndex)
         {
         {
             if (property.Type == SerializableProperty.FieldType.Int)
             if (property.Type == SerializableProperty.FieldType.Int)
             {
             {
                 guiIntField = new GUIIntField(new GUIContent(title));
                 guiIntField = new GUIIntField(new GUIContent(title));
                 guiIntField.OnChanged += OnFieldValueChanged;
                 guiIntField.OnChanged += OnFieldValueChanged;
+                guiIntField.OnConfirmed += OnFieldValueConfirm;
+                guiIntField.OnFocusLost += OnFieldValueConfirm;
 
 
                 layout.AddElement(layoutIndex, guiIntField);
                 layout.AddElement(layoutIndex, guiIntField);
             }
             }
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public override bool IsModified()
+        public override InspectableState Refresh(int layoutIndex)
         {
         {
-            int newPropertyValue = property.GetValue<int>();
-            if (propertyValue != newPropertyValue)
-                return true;
+            if (guiIntField != null && !guiIntField.HasInputFocus)
+                guiIntField.Value = property.GetValue<int>();
 
 
-            return base.IsModified();
-        }
-
-        /// <inheritdoc/>
-        protected internal override void Update(int layoutIndex)
-        {
-            propertyValue = property.GetValue<int>();
-            if (guiIntField != null)
-            {
-                if (guiIntField.HasInputFocus)
-                    return;
+            InspectableState oldState = state;
+            if (state.HasFlag(InspectableState.Modified))
+                state = InspectableState.NotModified;
 
 
-                guiIntField.Value = propertyValue;
-            }
+            return oldState;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -66,6 +58,16 @@ namespace BansheeEditor
         private void OnFieldValueChanged(int newValue)
         private void OnFieldValueChanged(int newValue)
         {
         {
             property.SetValue(newValue);
             property.SetValue(newValue);
+            state |= InspectableState.ModifyInProgress;
+        }
+
+        /// <summary>
+        /// Triggered when the user confirms input in the integer field.
+        /// </summary>
+        private void OnFieldValueConfirm()
+        {
+            if(state.HasFlag(InspectableState.ModifyInProgress))
+                state |= InspectableState.Modified;
         }
         }
     }
     }
 }
 }

+ 53 - 76
MBansheeEditor/Inspector/InspectableList.cs

@@ -11,8 +11,6 @@ namespace BansheeEditor
     /// </summary>
     /// </summary>
     public class InspectableList : InspectableField
     public class InspectableList : InspectableField
     {
     {
-        private object propertyValue; // TODO - This will unnecessarily hold references to the object
-        private int numArrayElements;
         private InspectableListGUI listGUIField;
         private InspectableListGUI listGUIField;
 
 
         /// <summary>
         /// <summary>
@@ -36,70 +34,26 @@ namespace BansheeEditor
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public override bool IsModified()
+        public override InspectableState Refresh(int layoutIndex)
         {
         {
-            object newPropertyValue = property.GetValue<object>();
-            if (propertyValue == null)
-                return newPropertyValue != null;
-
-            if (newPropertyValue == null)
-                return propertyValue != null;
-
-            SerializableList list = property.GetList();
-            if (list.GetLength() != numArrayElements)
-                return true;
-
-            return base.IsModified();
-        }
-
-        /// <inheritdoc/>
-        public override bool Refresh(int layoutIndex)
-        {
-            bool isModified = IsModified();
-
-            if (isModified)
-                Update(layoutIndex);
-
-            isModified |= listGUIField.Refresh().HasFlag(InspectableState.Modified);
-
-            return isModified;
+            return listGUIField.Refresh();
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public override bool ShouldRebuildOnModify()
-        {
-            return true;
-        }
-
-        /// <inheritdoc/>
-        protected internal override void BuildGUI(int layoutIndex)
+        protected internal override void Initialize(int layoutIndex)
         {
         {
             GUILayout arrayLayout = layout.AddLayoutY(layoutIndex);
             GUILayout arrayLayout = layout.AddLayoutY(layoutIndex);
 
 
             listGUIField = InspectableListGUI.Create(title, property, arrayLayout, depth);
             listGUIField = InspectableListGUI.Create(title, property, arrayLayout, depth);
         }
         }
 
 
-        /// <inheritdoc/>
-        protected internal override void Update(int layoutIndex)
-        {
-            propertyValue = property.GetValue<object>();
-            if (propertyValue != null)
-            {
-                SerializableList list = property.GetList();
-                numArrayElements = list.GetLength();
-            }
-            else
-                numArrayElements = 0;
-
-            layout.DestroyElements();
-            BuildGUI(layoutIndex);
-        }
-
         /// <summary>
         /// <summary>
         /// Handles creation of GUI elements for a GUI list field that displays a <see cref="SerializableList"/> object.
         /// Handles creation of GUI elements for a GUI list field that displays a <see cref="SerializableList"/> object.
         /// </summary>
         /// </summary>
         private class InspectableListGUI : GUIListFieldBase
         private class InspectableListGUI : GUIListFieldBase
         {
         {
+            private IList list;
+            private int numElements;
             private SerializableProperty property;
             private SerializableProperty property;
 
 
             /// <summary>
             /// <summary>
@@ -115,6 +69,10 @@ namespace BansheeEditor
                 : base(title, layout, depth)
                 : base(title, layout, depth)
             {
             {
                 this.property = property;
                 this.property = property;
+                list = property.GetValue<IList>();
+
+                if (list != null)
+                    numElements = list.Count;
             }
             }
             
             
             /// <summary>
             /// <summary>
@@ -134,6 +92,38 @@ namespace BansheeEditor
                 return listGUI;
                 return listGUI;
             }
             }
 
 
+            /// <inheritdoc/>
+            public override InspectableState Refresh()
+            {
+                // Check if any modifications to the array were made outside the inspector
+                IList newList = property.GetValue<IList>();
+                if (list == null && newList != null)
+                {
+                    list = newList;
+                    numElements = list.Count;
+                    BuildGUI();
+                }
+                else if (newList == null && list != null)
+                {
+                    list = null;
+                    numElements = 0;
+                    BuildGUI();
+                }
+                else
+                {
+                    if (list != null)
+                    {
+                        if (numElements != list.Count)
+                        {
+                            numElements = list.Count;
+                            BuildGUI();
+                        }
+                    }
+                }
+
+                return base.Refresh();
+            }
+
             /// <inheritdoc/>
             /// <inheritdoc/>
             protected override GUIListFieldRow CreateRow()
             protected override GUIListFieldRow CreateRow()
             {
             {
@@ -143,14 +133,12 @@ namespace BansheeEditor
             /// <inheritdoc/>
             /// <inheritdoc/>
             protected override bool IsNull()
             protected override bool IsNull()
             {
             {
-                IList list = property.GetValue<IList>();
                 return list == null;
                 return list == null;
             }
             }
 
 
             /// <inheritdoc/>
             /// <inheritdoc/>
             protected override int GetNumRows()
             protected override int GetNumRows()
             {
             {
-                IList list = property.GetValue<IList>();
                 if (list != null)
                 if (list != null)
                     return list.Count;
                     return list.Count;
 
 
@@ -175,7 +163,9 @@ namespace BansheeEditor
             /// <inheritdoc/>
             /// <inheritdoc/>
             protected override void CreateList()
             protected override void CreateList()
             {
             {
-                property.SetValue(property.CreateListInstance(0));
+                list = property.CreateListInstance(0);
+                property.SetValue(list);
+                numElements = 0;
             }
             }
 
 
             /// <inheritdoc/>
             /// <inheritdoc/>
@@ -184,45 +174,47 @@ namespace BansheeEditor
                 int size = guiSizeField.Value;
                 int size = guiSizeField.Value;
 
 
                 IList newList = property.CreateListInstance(size);
                 IList newList = property.CreateListInstance(size);
-                IList list = property.GetValue<IList>();
 
 
                 int maxSize = MathEx.Min(size, list.Count);
                 int maxSize = MathEx.Min(size, list.Count);
                 for (int i = 0; i < maxSize; i++)
                 for (int i = 0; i < maxSize; i++)
                     newList[i] = list[i];
                     newList[i] = list[i];
 
 
                 property.SetValue(newList);
                 property.SetValue(newList);
+                list = newList;
+                numElements = list.Count;
             }
             }
 
 
             /// <inheritdoc/>
             /// <inheritdoc/>
             protected override void ClearList()
             protected override void ClearList()
             {
             {
                 property.SetValue<object>(null);
                 property.SetValue<object>(null);
+                list = null;
+                numElements = 0;
             }
             }
 
 
             /// <inheritdoc/>
             /// <inheritdoc/>
             protected internal override void DeleteElement(int index)
             protected internal override void DeleteElement(int index)
             {
             {
-                IList list = property.GetValue<IList>();
-
                 if (index >= 0 && index < list.Count)
                 if (index >= 0 && index < list.Count)
                     list.RemoveAt(index);
                     list.RemoveAt(index);
+
+                numElements = list.Count;
             }
             }
 
 
             /// <inheritdoc/>
             /// <inheritdoc/>
             protected internal override void CloneElement(int index)
             protected internal override void CloneElement(int index)
             {
             {
                 SerializableList serializableList = property.GetList();
                 SerializableList serializableList = property.GetList();
-                IList list = property.GetValue<IList>();
 
 
                 if (index >= 0 && index < list.Count)
                 if (index >= 0 && index < list.Count)
                     list.Add(SerializableUtility.Clone(serializableList.GetProperty(index).GetValue<object>()));
                     list.Add(SerializableUtility.Clone(serializableList.GetProperty(index).GetValue<object>()));
+
+                numElements = list.Count;
             }
             }
 
 
             /// <inheritdoc/>
             /// <inheritdoc/>
             protected internal override void MoveUpElement(int index)
             protected internal override void MoveUpElement(int index)
             {
             {
-                IList list = property.GetValue<IList>();
-
                 if ((index - 1) >= 0)
                 if ((index - 1) >= 0)
                 {
                 {
                     object previousEntry = list[index - 1];
                     object previousEntry = list[index - 1];
@@ -235,8 +227,6 @@ namespace BansheeEditor
             /// <inheritdoc/>
             /// <inheritdoc/>
             protected internal override void MoveDownElement(int index)
             protected internal override void MoveDownElement(int index)
             {
             {
-                IList list = property.GetValue<IList>();
-
                 if ((index + 1) < list.Count)
                 if ((index + 1) < list.Count)
                 {
                 {
                     object nextEntry = list[index + 1];
                     object nextEntry = list[index + 1];
@@ -264,8 +254,6 @@ namespace BansheeEditor
                     field = CreateInspectable(SeqIndex + ".", 0, Depth + 1,
                     field = CreateInspectable(SeqIndex + ".", 0, Depth + 1,
                         new InspectableFieldLayout(layout), property);
                         new InspectableFieldLayout(layout), property);
                 }
                 }
-                else
-                    field.Refresh(0);
 
 
                 return field.GetTitleLayout();
                 return field.GetTitleLayout();
             }
             }
@@ -273,18 +261,7 @@ namespace BansheeEditor
             /// <inheritdoc/>
             /// <inheritdoc/>
             protected internal override InspectableState Refresh()
             protected internal override InspectableState Refresh()
             {
             {
-                //InspectableState state = field.Refresh(0);
-                InspectableState state = InspectableState.NotModified;
-                if (field.IsModified())
-                    state = InspectableState.Modified;
-
-                if (state.HasFlag(InspectableState.Modified))
-                {
-                    if (field.ShouldRebuildOnModify())
-                        BuildGUI();
-                }
-
-                return state;
+                return field.Refresh(0);
             }
             }
         }
         }
     }
     }

+ 159 - 105
MBansheeEditor/Inspector/InspectableObject.cs

@@ -1,4 +1,5 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System;
 using BansheeEngine;
 using BansheeEngine;
 
 
 namespace BansheeEditor
 namespace BansheeEditor
@@ -14,10 +15,13 @@ namespace BansheeEditor
         private object propertyValue;
         private object propertyValue;
         private List<InspectableField> children = new List<InspectableField>();
         private List<InspectableField> children = new List<InspectableField>();
 
 
+        private GUILayoutY guiLayout;
         private GUILayoutX guiChildLayout;
         private GUILayoutX guiChildLayout;
         private GUILayoutX guiTitleLayout;
         private GUILayoutX guiTitleLayout;
+        private GUILayoutX guiInternalTitleLayout;
         private bool isExpanded;
         private bool isExpanded;
         private bool forceUpdate = true;
         private bool forceUpdate = true;
+        private State state;
 
 
         /// <summary>
         /// <summary>
         /// Creates a new inspectable array GUI for the specified property.
         /// Creates a new inspectable array GUI for the specified property.
@@ -40,139 +44,179 @@ namespace BansheeEditor
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public override bool IsModified()
+        public override InspectableState Refresh(int layoutIndex)
         {
         {
+            // Check if modified internally and rebuild if needed
+            object newPropertyValue = property.GetValue<object>();
             if (forceUpdate)
             if (forceUpdate)
-                return true;
+            {
+                propertyValue = newPropertyValue;
+                BuildGUI(layoutIndex);
                 
                 
-            object newPropertyValue = property.GetValue<object>();
-            if (propertyValue == null)
-                return newPropertyValue != null;
-
-            if (newPropertyValue == null)
-                return propertyValue != null;
-
-            return base.IsModified();
-        }
-
-        /// <inheritdoc/>
-        public override bool Refresh(int layoutIndex)
-        {
-            bool isModified = base.Refresh(layoutIndex);
+                forceUpdate = false;
+            }
+            else if (propertyValue == null && newPropertyValue != null)
+            {
+                propertyValue = newPropertyValue;
+                BuildGUI(layoutIndex);
+                
+            }
+            else if (newPropertyValue == null && propertyValue != null)
+            {
+                propertyValue = null;
+                BuildGUI(layoutIndex);
+            }
 
 
+            InspectableState state = InspectableState.NotModified;
             int currentIndex = 0;
             int currentIndex = 0;
             for (int i = 0; i < children.Count; i++)
             for (int i = 0; i < children.Count; i++)
             {
             {
-                isModified |= children[i].Refresh(currentIndex);
+                state |= children[i].Refresh(currentIndex);
                 currentIndex += children[i].GetNumLayoutElements();
                 currentIndex += children[i].GetNumLayoutElements();
             }
             }
 
 
-            return isModified;
+            return state;
         }
         }
 
 
-        /// <inheritdoc/>
-        public override bool ShouldRebuildOnModify()
-        {
-            return true;
-        }
-
-        /// <inheritdoc/>
-        protected internal override void BuildGUI(int index)
+        /// <summary>
+        /// Rebuilds the GUI object header if needed. 
+        /// </summary>
+        /// <param name="layoutIndex">Index at which to insert the GUI elements.</param>
+        protected void BuildGUI(int layoutIndex)
         {
         {
-            guiTitleLayout = null;
-            guiChildLayout = null;
-
-            layout.DestroyElements();
-
-            if (property.Type != SerializableProperty.FieldType.Object)
-                return;
-
-            if (propertyValue == null)
+            Action BuildEmptyGUI = () =>
             {
             {
-                guiChildLayout = null;
-                guiTitleLayout = layout.AddLayoutX(index);
+                guiInternalTitleLayout = guiTitleLayout.InsertLayoutX(0);
 
 
-                guiTitleLayout.AddElement(new GUILabel(title));
-                guiTitleLayout.AddElement(new GUILabel("Empty", GUIOption.FixedWidth(100)));
+                guiInternalTitleLayout.AddElement(new GUILabel(title));
+                guiInternalTitleLayout.AddElement(new GUILabel("Empty", GUIOption.FixedWidth(100)));
 
 
                 if (!property.IsValueType)
                 if (!property.IsValueType)
                 {
                 {
                     GUIContent createIcon = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.Create));
                     GUIContent createIcon = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.Create));
                     GUIButton createBtn = new GUIButton(createIcon, GUIOption.FixedWidth(30));
                     GUIButton createBtn = new GUIButton(createIcon, GUIOption.FixedWidth(30));
                     createBtn.OnClick += OnCreateButtonClicked;
                     createBtn.OnClick += OnCreateButtonClicked;
-                    guiTitleLayout.AddElement(createBtn);
+                    guiInternalTitleLayout.AddElement(createBtn);
                 }
                 }
-            }
-            else
-            {
-                guiTitleLayout = layout.AddLayoutX(index);
-
-                GUIToggle guiFoldout = new GUIToggle(title, EditorStyles.Foldout);
-                guiFoldout.Value = isExpanded;
-                guiFoldout.OnToggled += OnFoldoutToggled;
-                guiTitleLayout.AddElement(guiFoldout);
-
-                GUIContent clearIcon = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.Clear));
-                GUIButton clearBtn = new GUIButton(clearIcon, GUIOption.FixedWidth(20));
-                clearBtn.OnClick += OnClearButtonClicked;
-                guiTitleLayout.AddElement(clearBtn);
-
-                if (isExpanded)
-                {
-                    SerializableObject serializableObject = property.GetObject();
-                    SerializableField[] fields = serializableObject.Fields;
-
-                    if (fields.Length > 0)
-                    {
-                        guiChildLayout = layout.AddLayoutX(index);
-                        guiChildLayout.AddSpace(IndentAmount);
-
-                        GUIPanel guiContentPanel = guiChildLayout.AddPanel();
-                        GUILayoutX guiIndentLayoutX = guiContentPanel.AddLayoutX();
-                        guiIndentLayoutX.AddSpace(IndentAmount);
-                        GUILayoutY guiIndentLayoutY = guiIndentLayoutX.AddLayoutY();
-                        guiIndentLayoutY.AddSpace(IndentAmount);
-                        GUILayoutY guiContentLayout = guiIndentLayoutY.AddLayoutY();
-                        guiIndentLayoutY.AddSpace(IndentAmount);
-                        guiIndentLayoutX.AddSpace(IndentAmount);
-                        guiChildLayout.AddSpace(IndentAmount);
-
-                        short backgroundDepth = (short)(Inspector.START_BACKGROUND_DEPTH - depth - 1);
-                        string bgPanelStyle = depth % 2 == 0 ? EditorStyles.InspectorContentBgAlternate : EditorStyles.InspectorContentBg;
-                        GUIPanel backgroundPanel = guiContentPanel.AddPanel(backgroundDepth);
-                        GUITexture inspectorContentBg = new GUITexture(null, bgPanelStyle);
-                        backgroundPanel.AddElement(inspectorContentBg);
-
-                        int currentIndex = 0;
-                        foreach (var field in fields)
-                        {
-                            if (!field.Inspectable)
-                                continue;
-
-                            InspectableField inspectable = CreateInspectable(field.Name, currentIndex, depth + 1,
-                                new InspectableFieldLayout(guiContentLayout), field.GetProperty());
-
-                            children.Add(inspectable);
-                            currentIndex += inspectable.GetNumLayoutElements();
-                        }
-                    }
-                }
-                else
-                    guiChildLayout = null;
-            }
+            };
+
+           Action BuildFilledGUI = () =>
+           {
+               guiInternalTitleLayout = guiTitleLayout.InsertLayoutX(0);
+
+               GUIToggle guiFoldout = new GUIToggle(title, EditorStyles.Foldout);
+               guiFoldout.Value = isExpanded;
+               guiFoldout.OnToggled += OnFoldoutToggled;
+               guiInternalTitleLayout.AddElement(guiFoldout);
+
+               GUIContent clearIcon = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.Clear));
+               GUIButton clearBtn = new GUIButton(clearIcon, GUIOption.FixedWidth(20));
+               clearBtn.OnClick += OnClearButtonClicked;
+               guiInternalTitleLayout.AddElement(clearBtn);
+
+               if (isExpanded)
+               {
+                   SerializableObject serializableObject = property.GetObject();
+                   SerializableField[] fields = serializableObject.Fields;
+
+                   if (fields.Length > 0)
+                   {
+                       guiChildLayout = guiLayout.AddLayoutX();
+                       guiChildLayout.AddSpace(IndentAmount);
+
+                       GUIPanel guiContentPanel = guiChildLayout.AddPanel();
+                       GUILayoutX guiIndentLayoutX = guiContentPanel.AddLayoutX();
+                       guiIndentLayoutX.AddSpace(IndentAmount);
+                       GUILayoutY guiIndentLayoutY = guiIndentLayoutX.AddLayoutY();
+                       guiIndentLayoutY.AddSpace(IndentAmount);
+                       GUILayoutY guiContentLayout = guiIndentLayoutY.AddLayoutY();
+                       guiIndentLayoutY.AddSpace(IndentAmount);
+                       guiIndentLayoutX.AddSpace(IndentAmount);
+                       guiChildLayout.AddSpace(IndentAmount);
+
+                       short backgroundDepth = (short) (Inspector.START_BACKGROUND_DEPTH - depth - 1);
+                       string bgPanelStyle = depth%2 == 0
+                           ? EditorStyles.InspectorContentBgAlternate
+                           : EditorStyles.InspectorContentBg;
+                       GUIPanel backgroundPanel = guiContentPanel.AddPanel(backgroundDepth);
+                       GUITexture inspectorContentBg = new GUITexture(null, bgPanelStyle);
+                       backgroundPanel.AddElement(inspectorContentBg);
+
+                       int currentIndex = 0;
+                       foreach (var field in fields)
+                       {
+                           if (!field.Inspectable)
+                               continue;
+
+                           InspectableField inspectable = CreateInspectable(field.Name, currentIndex, depth + 1,
+                               new InspectableFieldLayout(guiContentLayout), field.GetProperty());
+
+                           children.Add(inspectable);
+                           currentIndex += inspectable.GetNumLayoutElements();
+                       }
+                   }
+               }
+               else
+                   guiChildLayout = null;
+           };
+
+           if (state == State.None)
+           {
+               if (propertyValue != null)
+               {
+                   BuildFilledGUI();
+                   state = State.Filled;
+               }
+               else
+               {
+                   BuildEmptyGUI();
+
+                   state = State.Empty;
+               }
+           }
+           else if (state == State.Empty)
+           {
+               if (propertyValue != null)
+               {
+                   guiInternalTitleLayout.Destroy();
+                   BuildFilledGUI();
+                   state = State.Filled;
+               }
+           }
+           else if (state == State.Filled)
+           {
+               foreach (var child in children)
+                   child.Destroy();
+
+               children.Clear();
+               guiInternalTitleLayout.Destroy();
+
+               if (guiChildLayout != null)
+               {
+                   guiChildLayout.Destroy();
+                   guiChildLayout = null;
+               }
+
+               if (propertyValue == null)
+               {
+                   BuildEmptyGUI();
+                   state = State.Empty;
+               }
+               else
+               {
+                   BuildFilledGUI();
+               }
+           }
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        protected internal override void Update(int layoutIndex)
+        protected internal override void Initialize(int index)
         {
         {
-            foreach (var child in children)
-                child.Destroy();
-
-            children.Clear();
+            guiLayout = layout.AddLayoutY(index);
+            guiTitleLayout = guiLayout.AddLayoutX();
 
 
             propertyValue = property.GetValue<object>();
             propertyValue = property.GetValue<object>();
-            BuildGUI(layoutIndex);
-            forceUpdate = false;
+            BuildGUI(index);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -202,5 +246,15 @@ namespace BansheeEditor
         {
         {
             property.SetValue<object>(null);
             property.SetValue<object>(null);
         }
         }
+
+        /// <summary>
+        /// Possible states object GUI can be in.
+        /// </summary>
+        private enum State
+        {
+            None,
+            Empty,
+            Filled
+        }
     }
     }
 }
 }

+ 10 - 14
MBansheeEditor/Inspector/InspectableResourceRef.cs

@@ -7,8 +7,8 @@ namespace BansheeEditor
     /// </summary>
     /// </summary>
     public class InspectableResourceRef : InspectableField
     public class InspectableResourceRef : InspectableField
     {
     {
-        private Resource propertyValue;
         private GUIResourceField guiField;
         private GUIResourceField guiField;
+        private InspectableState state;
 
 
         /// <summary>
         /// <summary>
         /// Creates a new inspectable resource reference GUI for the specified property.
         /// Creates a new inspectable resource reference GUI for the specified property.
@@ -25,7 +25,7 @@ namespace BansheeEditor
         }
         }
 
 
         /// <inheritoc/>
         /// <inheritoc/>
-        protected internal override void BuildGUI(int layoutIndex)
+        protected internal override void Initialize(int layoutIndex)
         {
         {
             if (property.Type == SerializableProperty.FieldType.ResourceRef)
             if (property.Type == SerializableProperty.FieldType.ResourceRef)
             {
             {
@@ -37,21 +37,16 @@ namespace BansheeEditor
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public override bool IsModified()
+        public override InspectableState Refresh(int layoutIndex)
         {
         {
-            Resource newPropertyValue = property.GetValue<Resource>();
-            if (propertyValue != newPropertyValue)
-                return true;
+            if (guiField != null)
+                guiField.Value = property.GetValue<Resource>();
 
 
-            return base.IsModified();
-        }
+            InspectableState oldState = state;
+            if (state.HasFlag(InspectableState.Modified))
+                state = InspectableState.NotModified;
 
 
-        /// <inheritdoc/>
-        protected internal override void Update(int layoutIndex)
-        {
-            propertyValue = property.GetValue<Resource>();
-            if (guiField != null)
-                guiField.Value = propertyValue;
+            return oldState;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -61,6 +56,7 @@ namespace BansheeEditor
         private void OnFieldValueChanged(Resource newValue)
         private void OnFieldValueChanged(Resource newValue)
         {
         {
             property.SetValue(newValue);
             property.SetValue(newValue);
+            state = InspectableState.Modified;
         }
         }
     }
     }
 }
 }

+ 22 - 20
MBansheeEditor/Inspector/InspectableString.cs

@@ -7,8 +7,8 @@ namespace BansheeEditor
     /// </summary>
     /// </summary>
     public class InspectableString : InspectableField
     public class InspectableString : InspectableField
     {
     {
-        private string propertyValue;
         private GUITextField guiField;
         private GUITextField guiField;
+        private InspectableState state;
 
 
         /// <summary>
         /// <summary>
         /// Creates a new inspectable string GUI for the specified property.
         /// Creates a new inspectable string GUI for the specified property.
@@ -25,47 +25,49 @@ namespace BansheeEditor
         }
         }
 
 
         /// <inheritoc/>
         /// <inheritoc/>
-        protected internal override void BuildGUI(int layoutIndex)
+        protected internal override void Initialize(int layoutIndex)
         {
         {
             if (property.Type == SerializableProperty.FieldType.String)
             if (property.Type == SerializableProperty.FieldType.String)
             {
             {
                 guiField = new GUITextField(new GUIContent(title));
                 guiField = new GUITextField(new GUIContent(title));
                 guiField.OnChanged += OnFieldValueChanged;
                 guiField.OnChanged += OnFieldValueChanged;
+                guiField.OnConfirmed += OnFieldValueConfirm;
+                guiField.OnFocusLost += OnFieldValueConfirm;
 
 
                 layout.AddElement(layoutIndex, guiField);
                 layout.AddElement(layoutIndex, guiField);
             }
             }
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public override bool IsModified()
+        public override InspectableState Refresh(int layoutIndex)
         {
         {
-            string newPropertyValue = property.GetValue<string>();
-            if (propertyValue != newPropertyValue)
-                return true;
+            if (guiField != null && !guiField.HasInputFocus)
+                guiField.Value = property.GetValue<string>();
 
 
-            return base.IsModified();
-        }
-
-        /// <inheritdoc/>
-        protected internal override void Update(int layoutIndex)
-        {
-            propertyValue = property.GetValue<string>();
-            if (guiField != null)
-            {
-                if (guiField.HasInputFocus)
-                    return;
+            InspectableState oldState = state;
+            if (state.HasFlag(InspectableState.Modified))
+                state = InspectableState.NotModified;
 
 
-                guiField.Value = propertyValue;
-            }
+            return oldState;
         }
         }
 
 
         /// <summary>
         /// <summary>
         /// Triggered when the user inputs a new string.
         /// Triggered when the user inputs a new string.
         /// </summary>
         /// </summary>
-        /// <param name="newValue">New value of the string field.</param>
+        /// <param name="newValue">New value of the text field.</param>
         private void OnFieldValueChanged(string newValue)
         private void OnFieldValueChanged(string newValue)
         {
         {
             property.SetValue(newValue);
             property.SetValue(newValue);
+            state |= InspectableState.ModifyInProgress;
+        }
+
+        /// <summary>
+        /// Triggered when the user confirms input in the text field.
+        /// </summary>
+        private void OnFieldValueConfirm()
+        {
+            if (state.HasFlag(InspectableState.ModifyInProgress))
+                state |= InspectableState.Modified;
         }
         }
     }
     }
 }
 }

+ 21 - 19
MBansheeEditor/Inspector/InspectableVector2.cs

@@ -7,8 +7,8 @@ namespace BansheeEditor
     /// </summary>
     /// </summary>
     public class InspectableVector2 : InspectableField
     public class InspectableVector2 : InspectableField
     {
     {
-        private Vector2 propertyValue;
         private GUIVector2Field guiField;
         private GUIVector2Field guiField;
+        private InspectableState state;
 
 
         /// <summary>
         /// <summary>
         /// Creates a new inspectable 2D vector GUI for the specified property.
         /// Creates a new inspectable 2D vector GUI for the specified property.
@@ -25,38 +25,30 @@ namespace BansheeEditor
         }
         }
 
 
         /// <inheritoc/>
         /// <inheritoc/>
-        protected internal override void BuildGUI(int layoutIndex)
+        protected internal override void Initialize(int layoutIndex)
         {
         {
             if (property.Type == SerializableProperty.FieldType.Vector2)
             if (property.Type == SerializableProperty.FieldType.Vector2)
             {
             {
                 guiField = new GUIVector2Field(new GUIContent(title));
                 guiField = new GUIVector2Field(new GUIContent(title));
                 guiField.OnChanged += OnFieldValueChanged;
                 guiField.OnChanged += OnFieldValueChanged;
+                guiField.OnConfirmed += OnFieldValueConfirm;
+                guiField.OnFocusLost += OnFieldValueConfirm;
 
 
                 layout.AddElement(layoutIndex, guiField);
                 layout.AddElement(layoutIndex, guiField);
             }
             }
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public override bool IsModified()
+        public override InspectableState Refresh(int layoutIndex)
         {
         {
-            Vector2 newPropertyValue = property.GetValue<Vector2>();
-            if (propertyValue != newPropertyValue)
-                return true;
+            if (guiField != null && !guiField.HasInputFocus)
+                guiField.Value = property.GetValue<Vector2>();
 
 
-            return base.IsModified();
-        }
-
-        /// <inheritdoc/>
-        protected internal override void Update(int layoutIndex)
-        {
-            propertyValue = property.GetValue<Vector2>();
-            if (guiField != null)
-            {
-                if (guiField.HasInputFocus)
-                    return;
+            InspectableState oldState = state;
+            if (state.HasFlag(InspectableState.Modified))
+                state = InspectableState.NotModified;
 
 
-                guiField.Value = propertyValue;
-            }
+            return oldState;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -66,6 +58,16 @@ namespace BansheeEditor
         private void OnFieldValueChanged(Vector2 newValue)
         private void OnFieldValueChanged(Vector2 newValue)
         {
         {
             property.SetValue(newValue);
             property.SetValue(newValue);
+            state |= InspectableState.ModifyInProgress;
+        }
+
+        /// <summary>
+        /// Triggered when the user confirms input in the 2D vector field.
+        /// </summary>
+        private void OnFieldValueConfirm()
+        {
+            if (state.HasFlag(InspectableState.ModifyInProgress))
+                state |= InspectableState.Modified;
         }
         }
     }
     }
 }
 }

+ 21 - 19
MBansheeEditor/Inspector/InspectableVector3.cs

@@ -7,8 +7,8 @@ namespace BansheeEditor
     /// </summary>
     /// </summary>
     public class InspectableVector3 : InspectableField
     public class InspectableVector3 : InspectableField
     {
     {
-        private Vector3 propertyValue;
         private GUIVector3Field guiField;
         private GUIVector3Field guiField;
+        private InspectableState state;
 
 
         /// <summary>
         /// <summary>
         /// Creates a new inspectable 3D vector GUI for the specified property.
         /// Creates a new inspectable 3D vector GUI for the specified property.
@@ -25,38 +25,30 @@ namespace BansheeEditor
         }
         }
 
 
         /// <inheritoc/>
         /// <inheritoc/>
-        protected internal override void BuildGUI(int layoutIndex)
+        protected internal override void Initialize(int layoutIndex)
         {
         {
             if (property.Type == SerializableProperty.FieldType.Vector3)
             if (property.Type == SerializableProperty.FieldType.Vector3)
             {
             {
                 guiField = new GUIVector3Field(new GUIContent(title));
                 guiField = new GUIVector3Field(new GUIContent(title));
                 guiField.OnChanged += OnFieldValueChanged;
                 guiField.OnChanged += OnFieldValueChanged;
+                guiField.OnConfirmed += OnFieldValueConfirm;
+                guiField.OnFocusLost += OnFieldValueConfirm;
 
 
                 layout.AddElement(layoutIndex, guiField);
                 layout.AddElement(layoutIndex, guiField);
             }
             }
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public override bool IsModified()
+        public override InspectableState Refresh(int layoutIndex)
         {
         {
-            Vector3 newPropertyValue = property.GetValue<Vector3>();
-            if (propertyValue != newPropertyValue)
-                return true;
+            if (guiField != null && !guiField.HasInputFocus)
+                guiField.Value = property.GetValue<Vector3>();
 
 
-            return base.IsModified();
-        }
-
-        /// <inheritdoc/>
-        protected internal override void Update(int layoutIndex)
-        {
-            propertyValue = property.GetValue<Vector3>();
-            if (guiField != null)
-            {
-                if (guiField.HasInputFocus)
-                    return;
+            InspectableState oldState = state;
+            if (state.HasFlag(InspectableState.Modified))
+                state = InspectableState.NotModified;
 
 
-                guiField.Value = propertyValue;
-            }
+            return oldState;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -66,6 +58,16 @@ namespace BansheeEditor
         private void OnFieldValueChanged(Vector3 newValue)
         private void OnFieldValueChanged(Vector3 newValue)
         {
         {
             property.SetValue(newValue);
             property.SetValue(newValue);
+            state |= InspectableState.ModifyInProgress;
+        }
+
+        /// <summary>
+        /// Triggered when the user confirms input in the 3D vector field.
+        /// </summary>
+        private void OnFieldValueConfirm()
+        {
+            if (state.HasFlag(InspectableState.ModifyInProgress))
+                state |= InspectableState.Modified;
         }
         }
     }
     }
 }
 }

+ 21 - 19
MBansheeEditor/Inspector/InspectableVector4.cs

@@ -7,8 +7,8 @@ namespace BansheeEditor
     /// </summary>
     /// </summary>
     public class InspectableVector4 : InspectableField
     public class InspectableVector4 : InspectableField
     {
     {
-        private Vector4 propertyValue;
         private GUIVector4Field guiField;
         private GUIVector4Field guiField;
+        private InspectableState state;
 
 
         /// <summary>
         /// <summary>
         /// Creates a new inspectable 4D vector GUI for the specified property.
         /// Creates a new inspectable 4D vector GUI for the specified property.
@@ -25,38 +25,30 @@ namespace BansheeEditor
         }
         }
 
 
         /// <inheritoc/>
         /// <inheritoc/>
-        protected internal override void BuildGUI(int layoutIndex)
+        protected internal override void Initialize(int layoutIndex)
         {
         {
             if (property.Type == SerializableProperty.FieldType.Vector4)
             if (property.Type == SerializableProperty.FieldType.Vector4)
             {
             {
                 guiField = new GUIVector4Field(new GUIContent(title));
                 guiField = new GUIVector4Field(new GUIContent(title));
                 guiField.OnChanged += OnFieldValueChanged;
                 guiField.OnChanged += OnFieldValueChanged;
+                guiField.OnConfirmed += OnFieldValueConfirm;
+                guiField.OnFocusLost += OnFieldValueConfirm;
 
 
                 layout.AddElement(layoutIndex, guiField);
                 layout.AddElement(layoutIndex, guiField);
             }
             }
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public override bool IsModified()
+        public override InspectableState Refresh(int layoutIndex)
         {
         {
-            Vector4 newPropertyValue = property.GetValue<Vector4>();
-            if (propertyValue != newPropertyValue)
-                return true;
+            if (guiField != null && !guiField.HasInputFocus)
+                guiField.Value = property.GetValue<Vector4>();
 
 
-            return base.IsModified();
-        }
-
-        /// <inheritdoc/>
-        protected internal override void Update(int layoutIndex)
-        {
-            propertyValue = property.GetValue<Vector4>();
-            if (guiField != null)
-            {
-                if (guiField.HasInputFocus)
-                    return;
+            InspectableState oldState = state;
+            if (state.HasFlag(InspectableState.Modified))
+                state = InspectableState.NotModified;
 
 
-                guiField.Value = propertyValue;
-            }
+            return oldState;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -66,6 +58,16 @@ namespace BansheeEditor
         private void OnFieldValueChanged(Vector4 newValue)
         private void OnFieldValueChanged(Vector4 newValue)
         {
         {
             property.SetValue(newValue);
             property.SetValue(newValue);
+            state |= InspectableState.ModifyInProgress;
+        }
+
+        /// <summary>
+        /// Triggered when the user confirms input in the 3D vector field.
+        /// </summary>
+        private void OnFieldValueConfirm()
+        {
+            if (state.HasFlag(InspectableState.ModifyInProgress))
+                state |= InspectableState.Modified;
         }
         }
     }
     }
 }
 }

+ 2 - 1
MBansheeEditor/Inspector/Inspector.cs

@@ -97,6 +97,7 @@ namespace BansheeEditor
         /// <summary>
         /// <summary>
         /// Checks if contents of the inspector have been modified, and updates them if needed.
         /// Checks if contents of the inspector have been modified, and updates them if needed.
         /// </summary>
         /// </summary>
-        protected internal abstract void Refresh();
+        /// <returns>State representing was anything modified between two last calls to <see cref="Refresh"/>.</returns>
+        protected internal abstract InspectableState Refresh();
     }
     }
 }
 }

+ 4 - 4
MBansheeEditor/Inspector/InspectorUtility.cs

@@ -58,9 +58,9 @@ namespace BansheeEditor
     {
     {
         /// <summary>Object was not modified this frame.</summary>
         /// <summary>Object was not modified this frame.</summary>
         NotModified,
         NotModified,
-        /// <summary>Object was modified but was not confirmed, therefore does not require saving.</summary>
-        Modified = 1,
-        /// <summary>Object was modified and confirmed, therefore it requires saving.</summary>
-        ModifiedConfirm = 2
+        /// <summary>Object is currently being modified.</summary>
+        ModifyInProgress = 1,
+        /// <summary>Object was modified and modifications were confirmed.</summary>
+        Modified = 3
     }
     }
 }
 }

+ 66 - 11
MBansheeEditor/Inspector/InspectorWindow.cs

@@ -59,6 +59,8 @@ namespace BansheeEditor
         private GUITexture scrollAreaHighlight;
         private GUITexture scrollAreaHighlight;
 
 
         private SceneObject activeSO;
         private SceneObject activeSO;
+        private InspectableState modifyState;
+        private int undoCommandIdx = -1;
         private GUITextBox soNameInput;
         private GUITextBox soNameInput;
         private GUILayout soPrefabLayout;
         private GUILayout soPrefabLayout;
         private bool soHasPrefab;
         private bool soHasPrefab;
@@ -224,7 +226,9 @@ namespace BansheeEditor
             GUILabel nameLbl = new GUILabel(new LocEdString("Name"), GUIOption.FixedWidth(50));
             GUILabel nameLbl = new GUILabel(new LocEdString("Name"), GUIOption.FixedWidth(50));
             soNameInput = new GUITextBox(false, GUIOption.FlexibleWidth(180));
             soNameInput = new GUITextBox(false, GUIOption.FlexibleWidth(180));
             soNameInput.Text = activeSO.Name;
             soNameInput.Text = activeSO.Name;
-            soNameInput.OnChanged += (x) => { if (activeSO != null) activeSO.Name = x; };
+            soNameInput.OnChanged += OnSceneObjectRename;
+            soNameInput.OnConfirmed += OnModifyConfirm;
+            soNameInput.OnFocusLost += OnModifyConfirm;
 
 
             nameLayout.AddElement(nameLbl);
             nameLayout.AddElement(nameLbl);
             nameLayout.AddElement(soNameInput);
             nameLayout.AddElement(soNameInput);
@@ -242,6 +246,14 @@ namespace BansheeEditor
             soPosY.OnChanged += (y) => OnPositionChanged(1, y);
             soPosY.OnChanged += (y) => OnPositionChanged(1, y);
             soPosZ.OnChanged += (z) => OnPositionChanged(2, z);
             soPosZ.OnChanged += (z) => OnPositionChanged(2, z);
 
 
+            soPosX.OnConfirmed += OnModifyConfirm;
+            soPosY.OnConfirmed += OnModifyConfirm;
+            soPosZ.OnConfirmed += OnModifyConfirm;
+
+            soPosX.OnFocusLost += OnModifyConfirm;
+            soPosY.OnFocusLost += OnModifyConfirm;
+            soPosZ.OnFocusLost += OnModifyConfirm;
+
             positionLayout.AddElement(positionLbl);
             positionLayout.AddElement(positionLbl);
             positionLayout.AddElement(soPosX);
             positionLayout.AddElement(soPosX);
             positionLayout.AddSpace(10);
             positionLayout.AddSpace(10);
@@ -262,6 +274,14 @@ namespace BansheeEditor
             soRotY.OnChanged += (y) => OnRotationChanged(1, y);
             soRotY.OnChanged += (y) => OnRotationChanged(1, y);
             soRotZ.OnChanged += (z) => OnRotationChanged(2, z);
             soRotZ.OnChanged += (z) => OnRotationChanged(2, z);
 
 
+            soRotX.OnConfirmed += OnModifyConfirm;
+            soRotY.OnConfirmed += OnModifyConfirm;
+            soRotZ.OnConfirmed += OnModifyConfirm;
+
+            soRotX.OnFocusLost += OnModifyConfirm;
+            soRotY.OnFocusLost += OnModifyConfirm;
+            soRotZ.OnFocusLost += OnModifyConfirm;
+
             rotationLayout.AddElement(rotationLbl);
             rotationLayout.AddElement(rotationLbl);
             rotationLayout.AddElement(soRotX);
             rotationLayout.AddElement(soRotX);
             rotationLayout.AddSpace(10);
             rotationLayout.AddSpace(10);
@@ -280,7 +300,15 @@ namespace BansheeEditor
 
 
             soScaleX.OnChanged += (x) => OnScaleChanged(0, x);
             soScaleX.OnChanged += (x) => OnScaleChanged(0, x);
             soScaleY.OnChanged += (y) => OnScaleChanged(1, y);
             soScaleY.OnChanged += (y) => OnScaleChanged(1, y);
-            soScaleX.OnChanged += (z) => OnScaleChanged(2, z);
+            soScaleZ.OnChanged += (z) => OnScaleChanged(2, z);
+
+            soScaleX.OnConfirmed += OnModifyConfirm;
+            soScaleY.OnConfirmed += OnModifyConfirm;
+            soScaleZ.OnConfirmed += OnModifyConfirm;
+
+            soScaleX.OnFocusLost += OnModifyConfirm;
+            soScaleY.OnFocusLost += OnModifyConfirm;
+            soScaleZ.OnFocusLost += OnModifyConfirm;
 
 
             scaleLayout.AddElement(scaleLbl);
             scaleLayout.AddElement(scaleLbl);
             scaleLayout.AddElement(soScaleX);
             scaleLayout.AddElement(soScaleX);
@@ -309,8 +337,6 @@ namespace BansheeEditor
                 return;
                 return;
 
 
             soNameInput.Text = activeSO.Name;
             soNameInput.Text = activeSO.Name;
-            soNameInput.OnConfirmed += OnSceneObjectRename;
-            soNameInput.OnFocusLost += OnSceneObjectRename;
 
 
             bool hasPrefab = PrefabUtility.IsPrefabInstance(activeSO);
             bool hasPrefab = PrefabUtility.IsPrefabInstance(activeSO);
             if (soHasPrefab != hasPrefab || forceUpdate)
             if (soHasPrefab != hasPrefab || forceUpdate)
@@ -414,8 +440,14 @@ namespace BansheeEditor
                 {
                 {
                     RefreshSceneObjectFields(false);
                     RefreshSceneObjectFields(false);
 
 
+                    InspectableState componentModifyState = InspectableState.NotModified;
                     for (int i = 0; i < inspectorComponents.Count; i++)
                     for (int i = 0; i < inspectorComponents.Count; i++)
-                        inspectorComponents[i].inspector.Refresh();
+                        componentModifyState |= inspectorComponents[i].inspector.Refresh();
+
+                    if (componentModifyState.HasFlag(InspectableState.ModifyInProgress))
+                        EditorApplication.SetSceneDirty();
+
+                    modifyState |= componentModifyState;
                 }
                 }
             }
             }
             else if (currentType == InspectorType.Resource)
             else if (currentType == InspectorType.Resource)
@@ -492,9 +524,9 @@ namespace BansheeEditor
 
 
                         if (DragDrop.DropInProgress)
                         if (DragDrop.DropInProgress)
                         {
                         {
-                            UndoRedo.RecordSO(activeSO, "Added component " + draggedComponentType.Name);
                             activeSO.AddComponent(draggedComponentType);
                             activeSO.AddComponent(draggedComponentType);
 
 
+                            modifyState = InspectableState.Modified;
                             EditorApplication.SetSceneDirty();
                             EditorApplication.SetSceneDirty();
                         }
                         }
                     }
                     }
@@ -512,7 +544,11 @@ namespace BansheeEditor
         /// <param name="paths">A set of absolute resource paths that were selected.</param>
         /// <param name="paths">A set of absolute resource paths that were selected.</param>
         private void OnSelectionChanged(SceneObject[] objects, string[] paths)
         private void OnSelectionChanged(SceneObject[] objects, string[] paths)
         {
         {
+            if (currentType == InspectorType.SceneObject && modifyState == InspectableState.NotModified)
+                UndoRedo.PopCommand(undoCommandIdx);
+
             Clear();
             Clear();
+            modifyState = InspectableState.NotModified;
 
 
             if (objects.Length == 0 && paths.Length == 0)
             if (objects.Length == 0 && paths.Length == 0)
             {
             {
@@ -544,7 +580,13 @@ namespace BansheeEditor
             }
             }
             else if (objects.Length == 1)
             else if (objects.Length == 1)
             {
             {
-                SetObjectToInspect(objects[0]);
+                if (objects[0] != null)
+                {
+                    UndoRedo.RecordSO(objects[0]);
+                    undoCommandIdx = UndoRedo.TopCommandId;
+
+                    SetObjectToInspect(objects[0]);
+                }
             }
             }
             else if (paths.Length == 1)
             else if (paths.Length == 1)
             {
             {
@@ -571,9 +613,9 @@ namespace BansheeEditor
         {
         {
             if (activeSO != null)
             if (activeSO != null)
             {
             {
-                UndoRedo.RecordSO(activeSO, "Removed component " + componentType.Name);
                 activeSO.RemoveComponent(componentType);
                 activeSO.RemoveComponent(componentType);
 
 
+                modifyState = InspectableState.Modified;
                 EditorApplication.SetSceneDirty();
                 EditorApplication.SetSceneDirty();
             }
             }
         }
         }
@@ -647,16 +689,26 @@ namespace BansheeEditor
         /// <summary>
         /// <summary>
         /// Triggered when the user changes the name of the currently active scene object.
         /// Triggered when the user changes the name of the currently active scene object.
         /// </summary>
         /// </summary>
-        private void OnSceneObjectRename()
+        private void OnSceneObjectRename(string name)
         {
         {
             if (activeSO != null)
             if (activeSO != null)
             {
             {
-                UndoRedo.RecordSO(activeSO, "Renamed scene object \"" + soNameInput.Text + "\"");
-                activeSO.Name = soNameInput.Text;
+                activeSO.Name = name;
+
+                modifyState |= InspectableState.ModifyInProgress;
                 EditorApplication.SetSceneDirty();
                 EditorApplication.SetSceneDirty();
             }
             }
         }
         }
 
 
+        /// <summary>
+        /// Triggered when the scene object modification is confirmed by the user.
+        /// </summary>
+        private void OnModifyConfirm()
+        {
+            if (modifyState.HasFlag(InspectableState.ModifyInProgress))
+                modifyState = InspectableState.Modified;
+        }
+
         /// <summary>
         /// <summary>
         /// Triggered when the position value in the currently active <see cref="SceneObject"/> changes. Updates the 
         /// Triggered when the position value in the currently active <see cref="SceneObject"/> changes. Updates the 
         /// necessary GUI elements.
         /// necessary GUI elements.
@@ -681,6 +733,7 @@ namespace BansheeEditor
                 activeSO.LocalPosition = position;
                 activeSO.LocalPosition = position;
             }
             }
 
 
+            modifyState = InspectableState.ModifyInProgress;
             EditorApplication.SetSceneDirty();
             EditorApplication.SetSceneDirty();
         }
         }
 
 
@@ -708,6 +761,7 @@ namespace BansheeEditor
                 activeSO.LocalRotation = Quaternion.FromEuler(angles);
                 activeSO.LocalRotation = Quaternion.FromEuler(angles);
             }
             }
 
 
+            modifyState = InspectableState.ModifyInProgress;
             EditorApplication.SetSceneDirty();
             EditorApplication.SetSceneDirty();
         }
         }
 
 
@@ -726,6 +780,7 @@ namespace BansheeEditor
             scale[idx] = value;
             scale[idx] = value;
             activeSO.LocalScale = scale;
             activeSO.LocalScale = scale;
 
 
+            modifyState = InspectableState.ModifyInProgress;
             EditorApplication.SetSceneDirty();
             EditorApplication.SetSceneDirty();
         }
         }
 
 

+ 116 - 16
MBansheeEditor/Inspectors/CameraInspector.cs

@@ -27,6 +27,7 @@ namespace BansheeEditor
         private GUIListBoxField layersField = new GUIListBoxField(Layers.Names, true, new LocEdString("Layers"));
         private GUIListBoxField layersField = new GUIListBoxField(Layers.Names, true, new LocEdString("Layers"));
 
 
         private ulong layersValue = 0;
         private ulong layersValue = 0;
+        private InspectableState modifyState;
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
         protected internal override void Initialize()
         protected internal override void Initialize()
@@ -35,11 +36,11 @@ namespace BansheeEditor
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        protected internal override void Refresh()
+        protected internal override InspectableState Refresh()
         {
         {
             Camera camera = InspectedObject as Camera;
             Camera camera = InspectedObject as Camera;
             if (camera == null)
             if (camera == null)
-                return;
+                return InspectableState.NotModified;
 
 
             ProjectionType projType = camera.ProjectionType;
             ProjectionType projType = camera.ProjectionType;
             if (projectionTypeField.Value != (ulong)projType)
             if (projectionTypeField.Value != (ulong)projType)
@@ -72,6 +73,12 @@ namespace BansheeEditor
                 layersField.States = states;
                 layersField.States = states;
                 layersValue = camera.Layers;
                 layersValue = camera.Layers;
             }
             }
+
+            InspectableState oldState = modifyState;
+            if (modifyState.HasFlag(InspectableState.Modified))
+                modifyState = InspectableState.NotModified;
+
+            return oldState;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -86,27 +93,100 @@ namespace BansheeEditor
                 projectionTypeField.OnSelectionChanged += x =>
                 projectionTypeField.OnSelectionChanged += x =>
                 {
                 {
                     camera.ProjectionType = (ProjectionType)x;
                     camera.ProjectionType = (ProjectionType)x;
+                    MarkAsModified();
+                    ConfirmModify();
                     ToggleTypeSpecificFields((ProjectionType)x);
                     ToggleTypeSpecificFields((ProjectionType)x);
                 };
                 };
 
 
-                fieldOfView.OnChanged += x => camera.FieldOfView = x;
-                orthoHeight.OnChanged += x => camera.OrthoHeight = x;
-                aspectField.OnChanged += x => camera.AspectRatio = x;
-                nearPlaneField.OnChanged += x => camera.NearClipPlane = x;
-                farPlaneField.OnChanged += x => camera.FarClipPlane = x;
+                fieldOfView.OnChanged += x => { camera.FieldOfView = x; MarkAsModified(); };
+                fieldOfView.OnFocusLost += ConfirmModify;
+
+                orthoHeight.OnChanged += x => { camera.OrthoHeight = x; MarkAsModified(); };
+                orthoHeight.OnConfirmed += ConfirmModify;
+                orthoHeight.OnFocusLost += ConfirmModify;
+
+                aspectField.OnChanged += x => { camera.AspectRatio = x; MarkAsModified(); };
+                aspectField.OnConfirmed += ConfirmModify;
+                aspectField.OnFocusLost += ConfirmModify;
+
+                nearPlaneField.OnChanged += x => { camera.NearClipPlane = x; MarkAsModified(); };
+                nearPlaneField.OnConfirmed += ConfirmModify;
+                nearPlaneField.OnFocusLost += ConfirmModify;
+
+                farPlaneField.OnChanged += x => { camera.FarClipPlane = x; MarkAsModified(); };
+                farPlaneField.OnConfirmed += ConfirmModify;
+                farPlaneField.OnFocusLost += ConfirmModify;
+
                 viewportXField.OnChanged += x =>
                 viewportXField.OnChanged += x =>
-                { Rect2 rect = camera.ViewportRect; rect.x = x; camera.ViewportRect = rect; };
+                {
+                    Rect2 rect = camera.ViewportRect; 
+                    rect.x = x; 
+                    camera.ViewportRect = rect;
+
+                    MarkAsModified();
+                };
+                viewportXField.OnConfirmed += ConfirmModify;
+                viewportXField.OnFocusLost += ConfirmModify;
+
                 viewportYField.OnChanged += x =>
                 viewportYField.OnChanged += x =>
-                { Rect2 rect = camera.ViewportRect; rect.y = x; camera.ViewportRect = rect; };
+                {
+                    Rect2 rect = camera.ViewportRect; 
+                    rect.y = x; 
+                    camera.ViewportRect = rect;
+
+                    MarkAsModified();
+                };
+                viewportYField.OnConfirmed += ConfirmModify;
+                viewportYField.OnFocusLost += ConfirmModify;
+
                 viewportWidthField.OnChanged += x =>
                 viewportWidthField.OnChanged += x =>
-                { Rect2 rect = camera.ViewportRect; rect.width = x; camera.ViewportRect = rect; };
+                {
+                    Rect2 rect = camera.ViewportRect; 
+                    rect.width = x; 
+                    camera.ViewportRect = rect;
+
+                    MarkAsModified();
+                };
+                viewportWidthField.OnConfirmed += ConfirmModify;
+                viewportWidthField.OnFocusLost += ConfirmModify;
+
                 viewportHeightField.OnChanged += x =>
                 viewportHeightField.OnChanged += x =>
-                { Rect2 rect = camera.ViewportRect; rect.height = x; camera.ViewportRect = rect; };
-                clearFlagsFields.OnSelectionChanged += x => camera.ClearFlags = (ClearFlags)x;
-                clearStencilField.OnChanged += x => camera.ClearStencil = (ushort)x;
-                clearDepthField.OnChanged += x => camera.ClearDepth = x;
-                clearColorField.OnChanged += x => camera.ClearColor = x;
-                priorityField.OnChanged += x => camera.Priority = x;
+                {
+                    Rect2 rect = camera.ViewportRect; 
+                    rect.height = x; 
+                    camera.ViewportRect = rect;
+
+                    MarkAsModified();
+                };
+                viewportHeightField.OnConfirmed += ConfirmModify;
+                viewportHeightField.OnFocusLost += ConfirmModify;
+
+                clearFlagsFields.OnSelectionChanged += x =>
+                {
+                    camera.ClearFlags = (ClearFlags) x;
+                    MarkAsModified();
+                    ConfirmModify();
+                };
+
+                clearStencilField.OnChanged += x => { camera.ClearStencil = (ushort) x; };
+                clearStencilField.OnConfirmed += ConfirmModify;
+                clearStencilField.OnFocusLost += ConfirmModify;
+
+                clearDepthField.OnChanged += x => { camera.ClearDepth = x; };
+                clearDepthField.OnConfirmed += ConfirmModify;
+                clearDepthField.OnFocusLost += ConfirmModify;
+
+                clearColorField.OnChanged += x =>
+                {
+                    camera.ClearColor = x;
+                    MarkAsModified();
+                    ConfirmModify();
+                };
+
+                priorityField.OnChanged += x => { camera.Priority = x; MarkAsModified(); };
+                priorityField.OnConfirmed += ConfirmModify;
+                priorityField.OnFocusLost += ConfirmModify;
+
                 layersField.OnSelectionChanged += x =>
                 layersField.OnSelectionChanged += x =>
                 {
                 {
                     ulong layers = 0;
                     ulong layers = 0;
@@ -116,6 +196,9 @@ namespace BansheeEditor
 
 
                     layersValue = layers;
                     layersValue = layers;
                     camera.Layers = layers;
                     camera.Layers = layers;
+
+                    MarkAsModified();
+                    ConfirmModify();
                 };
                 };
 
 
                 Layout.AddElement(projectionTypeField);
                 Layout.AddElement(projectionTypeField);
@@ -164,5 +247,22 @@ namespace BansheeEditor
                 orthoHeight.Active = false;
                 orthoHeight.Active = false;
             }
             }
         }
         }
+
+        /// <summary>
+        /// Marks the contents of the inspector as modified.
+        /// </summary>
+        protected void MarkAsModified()
+        {
+            modifyState |= InspectableState.ModifyInProgress;
+        }
+
+        /// <summary>
+        /// Confirms any queued modifications.
+        /// </summary>
+        protected void ConfirmModify()
+        {
+            if (modifyState.HasFlag(InspectableState.ModifyInProgress))
+                modifyState |= InspectableState.Modified;
+        }
     }
     }
 }
 }

+ 3 - 3
MBansheeEditor/Inspectors/FontInspector.cs

@@ -29,7 +29,7 @@ namespace BansheeEditor
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        protected internal override void Refresh()
+        protected internal override InspectableState Refresh()
         {
         {
             FontImportOptions newImportOptions = GetImportOptions();
             FontImportOptions newImportOptions = GetImportOptions();
 
 
@@ -66,6 +66,8 @@ namespace BansheeEditor
             antialiasingField.Value = newImportOptions.Antialiasing;
             antialiasingField.Value = newImportOptions.Antialiasing;
             dpiField.Value = newImportOptions.DPI;
             dpiField.Value = newImportOptions.DPI;
             importOptions = newImportOptions;
             importOptions = newImportOptions;
+
+            return InspectableState.NotModified;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -218,8 +220,6 @@ namespace BansheeEditor
             /// <inheritdoc/>
             /// <inheritdoc/>
             internal protected override InspectableState Refresh()
             internal protected override InspectableState Refresh()
             {
             {
-                InspectableState state = InspectableState.NotModified;
-
                 CharRange newValue = GetValue<CharRange>();
                 CharRange newValue = GetValue<CharRange>();
                 rangeStartField.Value = newValue.start;
                 rangeStartField.Value = newValue.start;
                 rangeEndField.Value = newValue.end;
                 rangeEndField.Value = newValue.end;

+ 8 - 6
MBansheeEditor/Inspectors/GUISkinInspector.cs

@@ -21,9 +21,11 @@ namespace BansheeEditor
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        protected internal override void Refresh()
+        protected internal override InspectableState Refresh()
         {
         {
-            valuesField.Refresh(); ;
+            valuesField.Refresh();
+
+            return InspectableState.NotModified;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -356,7 +358,7 @@ namespace BansheeEditor
             public InspectableState Refresh()
             public InspectableState Refresh()
             {
             {
                 InspectableState oldModifiedState = modifiedState;
                 InspectableState oldModifiedState = modifiedState;
-                if (modifiedState.HasFlag(InspectableState.ModifiedConfirm))
+                if (modifiedState.HasFlag(InspectableState.Modified))
                     modifiedState = InspectableState.NotModified;
                     modifiedState = InspectableState.NotModified;
 
 
                 if (style == null)
                 if (style == null)
@@ -407,7 +409,7 @@ namespace BansheeEditor
             /// </summary>
             /// </summary>
             private void MarkAsModified()
             private void MarkAsModified()
             {
             {
-                modifiedState |= InspectableState.Modified;
+                modifiedState |= InspectableState.ModifyInProgress;
             }
             }
 
 
             /// <summary>
             /// <summary>
@@ -415,8 +417,8 @@ namespace BansheeEditor
             /// </summary>
             /// </summary>
             private void ConfirmModify()
             private void ConfirmModify()
             {
             {
-                if (modifiedState.HasFlag(InspectableState.Modified))
-                    modifiedState |= InspectableState.ModifiedConfirm;
+                if (modifiedState.HasFlag(InspectableState.ModifyInProgress))
+                    modifiedState |= InspectableState.Modified;
             }
             }
 
 
             /// <summary>
             /// <summary>

+ 53 - 8
MBansheeEditor/Inspectors/LightInspector.cs

@@ -17,6 +17,8 @@ namespace BansheeEditor
         private GUISliderField spotFalloffAngleField = new GUISliderField(1, 180, new LocEdString("Spot falloff angle"));
         private GUISliderField spotFalloffAngleField = new GUISliderField(1, 180, new LocEdString("Spot falloff angle"));
         private GUIToggleField castShadowField = new GUIToggleField(new LocEdString("Cast shadow"));
         private GUIToggleField castShadowField = new GUIToggleField(new LocEdString("Cast shadow"));
 
 
+        private InspectableState modifyState;
+
         /// <inheritdoc/>
         /// <inheritdoc/>
         protected internal override void Initialize()
         protected internal override void Initialize()
         {
         {
@@ -31,12 +33,33 @@ namespace BansheeEditor
                     ToggleTypeSpecificFields((LightType) x);
                     ToggleTypeSpecificFields((LightType) x);
                 };
                 };
 
 
-                colorField.OnChanged += x => light.Color = x;
-                rangeField.OnChanged += x => light.Range = x;
-                intensityField.OnChanged += x => light.Intensity = x;
-                spotAngleField.OnChanged += x => light.SpotAngle = x;
-                spotFalloffAngleField.OnChanged += x => light.SpotFalloffAngle = x;
-                castShadowField.OnChanged += x => light.CastsShadow = x;
+                colorField.OnChanged += x =>
+                {
+                    light.Color = x;
+                    MarkAsModified();
+                    ConfirmModify();
+                };
+
+                rangeField.OnChanged += x => { light.Range = x; MarkAsModified(); };
+                rangeField.OnConfirmed += ConfirmModify;
+                rangeField.OnFocusLost += ConfirmModify;
+
+                intensityField.OnChanged += x => { light.Intensity = x; MarkAsModified(); };
+                intensityField.OnConfirmed += ConfirmModify;
+                intensityField.OnFocusLost += ConfirmModify;
+
+                spotAngleField.OnChanged += x => { light.SpotAngle = x; MarkAsModified(); };
+                spotAngleField.OnFocusLost += ConfirmModify;
+
+                spotFalloffAngleField.OnChanged += x => { light.SpotFalloffAngle = x; MarkAsModified(); };
+                spotFalloffAngleField.OnFocusLost += ConfirmModify;
+
+                castShadowField.OnChanged += x =>
+                {
+                    light.CastsShadow = x;
+                    MarkAsModified();
+                    ConfirmModify();
+                };
                 
                 
                 Layout.AddElement(lightTypeField);
                 Layout.AddElement(lightTypeField);
                 Layout.AddElement(colorField);
                 Layout.AddElement(colorField);
@@ -51,11 +74,11 @@ namespace BansheeEditor
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        protected internal override void Refresh()
+        protected internal override InspectableState Refresh()
         {
         {
             Light light = InspectedObject as Light;
             Light light = InspectedObject as Light;
             if (light == null)
             if (light == null)
-                return;
+                return InspectableState.NotModified;
 
 
             LightType lightType = light.Type;
             LightType lightType = light.Type;
             if (lightTypeField.Value != (ulong)lightType)
             if (lightTypeField.Value != (ulong)lightType)
@@ -70,6 +93,12 @@ namespace BansheeEditor
             spotAngleField.Value = light.SpotAngle.Degrees;
             spotAngleField.Value = light.SpotAngle.Degrees;
             spotFalloffAngleField.Value = light.SpotFalloffAngle.Degrees;
             spotFalloffAngleField.Value = light.SpotFalloffAngle.Degrees;
             castShadowField.Value = light.CastsShadow;
             castShadowField.Value = light.CastsShadow;
+
+            InspectableState oldState = modifyState;
+            if (modifyState.HasFlag(InspectableState.Modified))
+                modifyState = InspectableState.NotModified;
+
+            return oldState;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -98,5 +127,21 @@ namespace BansheeEditor
             }
             }
         }
         }
 
 
+        /// <summary>
+        /// Marks the contents of the inspector as modified.
+        /// </summary>
+        protected void MarkAsModified()
+        {
+            modifyState |= InspectableState.ModifyInProgress;
+        }
+
+        /// <summary>
+        /// Confirms any queued modifications.
+        /// </summary>
+        protected void ConfirmModify()
+        {
+            if (modifyState.HasFlag(InspectableState.ModifyInProgress))
+                modifyState |= InspectableState.Modified;
+        }
     }
     }
 }
 }

+ 4 - 2
MBansheeEditor/Inspectors/MaterialInspector.cs

@@ -33,11 +33,11 @@ namespace BansheeEditor
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        protected internal override void Refresh()
+        protected internal override InspectableState Refresh()
         {
         {
             Material material = InspectedObject as Material;
             Material material = InspectedObject as Material;
             if (material == null)
             if (material == null)
-                return;
+                return InspectableState.NotModified;
 
 
             if (material.Shader != shaderField.Value)
             if (material.Shader != shaderField.Value)
             {
             {
@@ -50,6 +50,8 @@ namespace BansheeEditor
                 foreach (var param in guiParams)
                 foreach (var param in guiParams)
                     param.Refresh(material);
                     param.Refresh(material);
             }
             }
+
+            return InspectableState.NotModified;
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 3 - 1
MBansheeEditor/Inspectors/MeshInspector.cs

@@ -53,7 +53,7 @@ namespace BansheeEditor
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        protected internal override void Refresh()
+        protected internal override InspectableState Refresh()
         {
         {
             MeshImportOptions newImportOptions = GetImportOptions();
             MeshImportOptions newImportOptions = GetImportOptions();
 
 
@@ -66,6 +66,8 @@ namespace BansheeEditor
             cpuReadableField.Value = newImportOptions.CPUReadable;
             cpuReadableField.Value = newImportOptions.CPUReadable;
 
 
             importOptions = newImportOptions;
             importOptions = newImportOptions;
+
+            return InspectableState.NotModified;
         }
         }
 
 
 
 

+ 4 - 2
MBansheeEditor/Inspectors/PlainTextInspector.cs

@@ -37,11 +37,11 @@ namespace BansheeEditor
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        protected internal override void Refresh()
+        protected internal override InspectableState Refresh()
         {
         {
             PlainText plainText = InspectedObject as PlainText;
             PlainText plainText = InspectedObject as PlainText;
             if (plainText == null)
             if (plainText == null)
-                return;
+                return InspectableState.NotModified;
 
 
             string newText = plainText.Text;
             string newText = plainText.Text;
             string newShownText = plainText.Text.Substring(0, MathEx.Min(newText.Length, MAX_SHOWN_CHARACTERS));
             string newShownText = plainText.Text.Substring(0, MathEx.Min(newText.Length, MAX_SHOWN_CHARACTERS));
@@ -51,6 +51,8 @@ namespace BansheeEditor
                 textLabel.SetContent(newShownText);
                 textLabel.SetContent(newShownText);
                 shownText = newShownText;
                 shownText = newShownText;
             }
             }
+
+            return InspectableState.NotModified;
         }
         }
     }
     }
 }
 }

+ 2 - 2
MBansheeEditor/Inspectors/PrefabInspector.cs

@@ -16,9 +16,9 @@ namespace BansheeEditor
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        protected internal override void Refresh()
+        protected internal override InspectableState Refresh()
         {
         {
-
+            return InspectableState.NotModified;
         }
         }
     }
     }
 }
 }

+ 44 - 4
MBansheeEditor/Inspectors/RenderableInspector.cs

@@ -16,6 +16,8 @@ namespace BansheeEditor
         private List<MaterialParamGUI[]> materialParams = new List<MaterialParamGUI[]>();
         private List<MaterialParamGUI[]> materialParams = new List<MaterialParamGUI[]>();
 
 
         private ulong layersValue = 0;
         private ulong layersValue = 0;
+        private InspectableState modifyState;
+
         private Material[] materials;
         private Material[] materials;
         private GUILayout materialsLayout;
         private GUILayout materialsLayout;
 
 
@@ -26,11 +28,11 @@ namespace BansheeEditor
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        protected internal override void Refresh()
+        protected internal override InspectableState Refresh()
         {
         {
             Renderable renderable = InspectedObject as Renderable;
             Renderable renderable = InspectedObject as Renderable;
             if (renderable == null)
             if (renderable == null)
-                return;
+                return InspectableState.NotModified;
 
 
             bool rebuildMaterialsGUI = false;
             bool rebuildMaterialsGUI = false;
 
 
@@ -85,6 +87,12 @@ namespace BansheeEditor
                     }
                     }
                 }
                 }
             }
             }
+
+            InspectableState oldState = modifyState;
+            if (modifyState.HasFlag(InspectableState.Modified))
+                modifyState = InspectableState.NotModified;
+
+            return oldState;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -108,8 +116,20 @@ namespace BansheeEditor
             materials = renderable.Materials;
             materials = renderable.Materials;
             materialsField = GUIArrayField<Material, MaterialArrayRow>.Create(new LocEdString("Materials"), materials, Layout);
             materialsField = GUIArrayField<Material, MaterialArrayRow>.Create(new LocEdString("Materials"), materials, Layout);
 
 
-            materialsField.OnChanged += x => { renderable.Materials = x; };
-            meshField.OnChanged += x => renderable.Mesh = x as Mesh;
+            materialsField.OnChanged += x =>
+            {
+                renderable.Materials = x;
+                MarkAsModified();
+                ConfirmModify();
+            };
+
+            meshField.OnChanged += x =>
+            {
+                renderable.Mesh = x as Mesh;
+                MarkAsModified();
+                ConfirmModify();
+            };
+
             layersField.OnSelectionChanged += x =>
             layersField.OnSelectionChanged += x =>
             {
             {
                 ulong layers = 0;
                 ulong layers = 0;
@@ -119,6 +139,9 @@ namespace BansheeEditor
 
 
                 layersValue = layers;
                 layersValue = layers;
                 renderable.Layers = layers;
                 renderable.Layers = layers;
+
+                MarkAsModified();
+                ConfirmModify();
             };
             };
 
 
             materialsLayout = Layout.AddLayoutY();
             materialsLayout = Layout.AddLayoutY();
@@ -151,6 +174,23 @@ namespace BansheeEditor
             }
             }
         }
         }
 
 
+        /// <summary>
+        /// Marks the contents of the inspector as modified.
+        /// </summary>
+        protected void MarkAsModified()
+        {
+            modifyState |= InspectableState.ModifyInProgress;
+        }
+
+        /// <summary>
+        /// Confirms any queued modifications.
+        /// </summary>
+        protected void ConfirmModify()
+        {
+            if (modifyState.HasFlag(InspectableState.ModifyInProgress))
+                modifyState |= InspectableState.Modified;
+        }
+
         /// <summary>
         /// <summary>
         /// Row element used for displaying GUI for material array elements.
         /// Row element used for displaying GUI for material array elements.
         /// </summary>
         /// </summary>

+ 4 - 2
MBansheeEditor/Inspectors/ScriptCodeInspector.cs

@@ -46,11 +46,11 @@ namespace BansheeEditor
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        protected internal override void Refresh()
+        protected internal override InspectableState Refresh()
         {
         {
             ScriptCode scriptCode = InspectedObject as ScriptCode;
             ScriptCode scriptCode = InspectedObject as ScriptCode;
             if (scriptCode == null)
             if (scriptCode == null)
-                return;
+                return InspectableState.NotModified;
 
 
             isEditorField.Value = scriptCode.EditorScript;
             isEditorField.Value = scriptCode.EditorScript;
 
 
@@ -62,6 +62,8 @@ namespace BansheeEditor
                 textLabel.SetContent(newShownText);
                 textLabel.SetContent(newShownText);
                 shownText = newShownText;
                 shownText = newShownText;
             }
             }
+
+            return InspectableState.NotModified;
         }
         }
     }
     }
 }
 }

+ 2 - 1
MBansheeEditor/Inspectors/ShaderInspector.cs

@@ -16,8 +16,9 @@ namespace BansheeEditor
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        protected internal override void Refresh()
+        protected internal override InspectableState Refresh()
         {
         {
+            return InspectableState.NotModified;
         }
         }
     }
     }
 }
 }

+ 4 - 2
MBansheeEditor/Inspectors/SpriteTextureInspector.cs

@@ -44,15 +44,17 @@ namespace BansheeEditor
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        protected internal override void Refresh()
+        protected internal override InspectableState Refresh()
         {
         {
             SpriteTexture spriteTexture = InspectedObject as SpriteTexture;
             SpriteTexture spriteTexture = InspectedObject as SpriteTexture;
             if (spriteTexture == null)
             if (spriteTexture == null)
-                return;
+                return InspectableState.NotModified;
 
 
             textureField.Value = spriteTexture.Texture;
             textureField.Value = spriteTexture.Texture;
             offsetField.Value = spriteTexture.Offset;
             offsetField.Value = spriteTexture.Offset;
             scaleField.Value = spriteTexture.Scale;
             scaleField.Value = spriteTexture.Scale;
+
+            return InspectableState.NotModified;
         }
         }
     }
     }
 }
 }

+ 3 - 1
MBansheeEditor/Inspectors/StringTableInspector.cs

@@ -22,7 +22,7 @@ namespace BansheeEditor
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        protected internal override void Refresh()
+        protected internal override InspectableState Refresh()
         {
         {
             // Note: We're ignoring changes to the string table made externally here in order to avoid a lot of checks.
             // Note: We're ignoring changes to the string table made externally here in order to avoid a lot of checks.
             if ((Language) languageField.Value != StringTables.ActiveLanguage)
             if ((Language) languageField.Value != StringTables.ActiveLanguage)
@@ -32,6 +32,8 @@ namespace BansheeEditor
             }
             }
 
 
             valuesField.Refresh();
             valuesField.Refresh();
+
+            return InspectableState.NotModified;
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 3 - 1
MBansheeEditor/Inspectors/Texture2DInspector.cs

@@ -47,7 +47,7 @@ namespace BansheeEditor
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        protected internal override void Refresh()
+        protected internal override InspectableState Refresh()
         {
         {
             TextureImportOptions newImportOptions = GetImportOptions();
             TextureImportOptions newImportOptions = GetImportOptions();
 
 
@@ -58,6 +58,8 @@ namespace BansheeEditor
             cpuReadableField.Value = newImportOptions.CPUReadable;
             cpuReadableField.Value = newImportOptions.CPUReadable;
 
 
             importOptions = newImportOptions;
             importOptions = newImportOptions;
+
+            return InspectableState.NotModified;
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 23 - 0
MBansheeEditor/UndoRedo.cs

@@ -13,6 +13,14 @@ namespace BansheeEditor
     /// </summary>
     /// </summary>
     public static class UndoRedo
     public static class UndoRedo
     {
     {
+        /// <summary>
+        /// Returns the unique identifier of the command currently at the top of the undo stack.
+        /// </summary>
+        public static int TopCommandId
+        {
+            get { return Internal_GetTopCommandId(); }
+        }
+
         /// <summary>
         /// <summary>
         /// Executes the last command on the undo stack, undoing its operations.
         /// Executes the last command on the undo stack, undoing its operations.
         /// </summary>
         /// </summary>
@@ -183,6 +191,15 @@ namespace BansheeEditor
             Internal_PopGroup(name);
             Internal_PopGroup(name);
         }
         }
 
 
+        /// <summary>
+        /// Removes a command with the specified identifier from undo/redo stack without executing it.
+        /// </summary>
+        /// <param name="id">Identifier of the command as returned by <see cref="GetTopCommandId"/></param>
+        public static void PopCommand(int id)
+        {
+            Internal_PopCommand(id);
+        }
+
         [MethodImpl(MethodImplOptions.InternalCall)]
         [MethodImpl(MethodImplOptions.InternalCall)]
         internal static extern void Internal_Undo();
         internal static extern void Internal_Undo();
 
 
@@ -195,6 +212,12 @@ namespace BansheeEditor
         [MethodImpl(MethodImplOptions.InternalCall)]
         [MethodImpl(MethodImplOptions.InternalCall)]
         internal static extern void Internal_PopGroup(string name);
         internal static extern void Internal_PopGroup(string name);
 
 
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        internal static extern void Internal_PopCommand(int id);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        internal static extern int Internal_GetTopCommandId();
+
         [MethodImpl(MethodImplOptions.InternalCall)]
         [MethodImpl(MethodImplOptions.InternalCall)]
         internal static extern void Internal_RecordSO(IntPtr soPtr, string description);
         internal static extern void Internal_RecordSO(IntPtr soPtr, string description);
 
 

+ 3 - 0
MBansheeEditor/UnitTestTypes.cs

@@ -23,6 +23,9 @@ namespace BansheeEditor
         public List<UT1_SerzObj> listComplex;
         public List<UT1_SerzObj> listComplex;
         public List<UT1_SerzCls> listComplex2;
         public List<UT1_SerzCls> listComplex2;
 
 
+        public Dictionary<int, string> dictA;
+        public Dictionary<string, UT1_SerzObj> dictB;
+
         public UT1_Component2 otherComponent;
         public UT1_Component2 otherComponent;
         public SceneObject otherSO;
         public SceneObject otherSO;
     }
     }

+ 7 - 0
MBansheeEditor/UnitTests.cs

@@ -59,6 +59,13 @@ namespace BansheeEditor
             dbgComponent.listComplex2[0].anotherValue2 = "ListComplexAnotherValue";
             dbgComponent.listComplex2[0].anotherValue2 = "ListComplexAnotherValue";
             dbgComponent.listComplex2.Add(null);
             dbgComponent.listComplex2.Add(null);
 
 
+            dbgComponent.dictA = new Dictionary<int, string>();
+            dbgComponent.dictA[5] = "value";
+            dbgComponent.dictA[10] = "anotherValue";
+
+            dbgComponent.dictB = new Dictionary<string, UT1_SerzObj>();
+            dbgComponent.dictB["key1"] = new UT1_SerzObj(99, "DictComplexValue");
+
             dbgComponent.otherComponent = dbgComponent2;
             dbgComponent.otherComponent = dbgComponent2;
             dbgComponent.otherSO = otherSO;
             dbgComponent.otherSO = otherSO;
 
 

+ 5 - 0
MBansheeEngine/SerializableProperty.cs

@@ -61,6 +61,8 @@ namespace BansheeEngine
             this.internalType = internalType;
             this.internalType = internalType;
             this.getter = getter;
             this.getter = getter;
             this.setter = setter;
             this.setter = setter;
+
+            Internal_CreateInstance(this, internalType);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -236,6 +238,9 @@ namespace BansheeEngine
             return Internal_CreateManagedDictionaryInstance(mCachedPtr);
             return Internal_CreateManagedDictionaryInstance(mCachedPtr);
         }
         }
 
 
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern void Internal_CreateInstance(SerializableProperty instance, Type type);
+
         [MethodImpl(MethodImplOptions.InternalCall)]
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern SerializableObject Internal_CreateObject(IntPtr nativeInstance);
         private static extern SerializableObject Internal_CreateObject(IntPtr nativeInstance);
 
 

+ 2 - 0
SBansheeEditor/Include/BsScriptUndoRedo.h

@@ -23,6 +23,8 @@ namespace BansheeEngine
 		static void internal_Redo();
 		static void internal_Redo();
 		static void internal_PushGroup(MonoString* name);
 		static void internal_PushGroup(MonoString* name);
 		static void internal_PopGroup(MonoString* name);
 		static void internal_PopGroup(MonoString* name);
+		static UINT32 internal_GetTopCommandId();
+		static void internal_PopCommand(UINT32 id);
 		static void internal_RecordSO(ScriptSceneObject* soPtr, MonoString* description);
 		static void internal_RecordSO(ScriptSceneObject* soPtr, MonoString* description);
 		static MonoObject* internal_CloneSO(ScriptSceneObject* soPtr, MonoString* description);
 		static MonoObject* internal_CloneSO(ScriptSceneObject* soPtr, MonoString* description);
 		static MonoArray* internal_CloneSOMulti(MonoArray* soPtrs, MonoString* description);
 		static MonoArray* internal_CloneSOMulti(MonoArray* soPtrs, MonoString* description);

+ 12 - 0
SBansheeEditor/Source/BsScriptUndoRedo.cpp

@@ -29,6 +29,8 @@ namespace BansheeEngine
 		metaData.scriptClass->addInternalCall("Internal_Redo", &ScriptUndoRedo::internal_Redo);
 		metaData.scriptClass->addInternalCall("Internal_Redo", &ScriptUndoRedo::internal_Redo);
 		metaData.scriptClass->addInternalCall("Internal_PushGroup", &ScriptUndoRedo::internal_PushGroup);
 		metaData.scriptClass->addInternalCall("Internal_PushGroup", &ScriptUndoRedo::internal_PushGroup);
 		metaData.scriptClass->addInternalCall("Internal_PopGroup", &ScriptUndoRedo::internal_PopGroup);
 		metaData.scriptClass->addInternalCall("Internal_PopGroup", &ScriptUndoRedo::internal_PopGroup);
+		metaData.scriptClass->addInternalCall("Internal_GetTopCommandId", &ScriptUndoRedo::internal_GetTopCommandId);
+		metaData.scriptClass->addInternalCall("Internal_PopCommand", &ScriptUndoRedo::internal_PopCommand);
 		metaData.scriptClass->addInternalCall("Internal_RecordSO", &ScriptUndoRedo::internal_RecordSO);
 		metaData.scriptClass->addInternalCall("Internal_RecordSO", &ScriptUndoRedo::internal_RecordSO);
 		metaData.scriptClass->addInternalCall("Internal_CloneSO", &ScriptUndoRedo::internal_CloneSO);
 		metaData.scriptClass->addInternalCall("Internal_CloneSO", &ScriptUndoRedo::internal_CloneSO);
 		metaData.scriptClass->addInternalCall("Internal_CloneSOMulti", &ScriptUndoRedo::internal_CloneSOMulti);
 		metaData.scriptClass->addInternalCall("Internal_CloneSOMulti", &ScriptUndoRedo::internal_CloneSOMulti);
@@ -61,6 +63,16 @@ namespace BansheeEngine
 		UndoRedo::instance().popGroup(nativeName);
 		UndoRedo::instance().popGroup(nativeName);
 	}
 	}
 
 
+	UINT32 ScriptUndoRedo::internal_GetTopCommandId()
+	{
+		return UndoRedo::instance().getTopCommandId();
+	}
+
+	void ScriptUndoRedo::internal_PopCommand(UINT32 id)
+	{
+		UndoRedo::instance().popCommand(id);
+	}
+
 	void ScriptUndoRedo::internal_RecordSO(ScriptSceneObject* soPtr, MonoString* description)
 	void ScriptUndoRedo::internal_RecordSO(ScriptSceneObject* soPtr, MonoString* description)
 	{
 	{
 		WString nativeDescription = MonoUtil::monoToWString(description);
 		WString nativeDescription = MonoUtil::monoToWString(description);

+ 6 - 8
SBansheeEngine/Include/BsManagedSerializableDictionaryRTTI.h

@@ -62,8 +62,7 @@ namespace BansheeEngine
 
 
 		ManagedSerializableDictionaryKeyValue& getEntry(ManagedSerializableDictionary* obj, UINT32 arrayIdx)
 		ManagedSerializableDictionaryKeyValue& getEntry(ManagedSerializableDictionary* obj, UINT32 arrayIdx)
 		{ 
 		{ 
-			ManagedSerializableDictionaryPtr data = any_cast<ManagedSerializableDictionaryPtr>(obj->mRTTIData);
-			Vector<ManagedSerializableDictionaryKeyValue>& sequentialData = any_cast_ref<Vector<ManagedSerializableDictionaryKeyValue>>(data->mRTTIData);
+			Vector<ManagedSerializableDictionaryKeyValue>& sequentialData = any_cast_ref<Vector<ManagedSerializableDictionaryKeyValue>>(obj->mRTTIData);
 
 
 			return sequentialData[arrayIdx];
 			return sequentialData[arrayIdx];
 		}
 		}
@@ -75,8 +74,7 @@ namespace BansheeEngine
 
 
 		UINT32 getNumEntries(ManagedSerializableDictionary* obj) 
 		UINT32 getNumEntries(ManagedSerializableDictionary* obj) 
 		{ 
 		{ 
-			ManagedSerializableDictionaryPtr data = any_cast<ManagedSerializableDictionaryPtr>(obj->mRTTIData);
-			Vector<ManagedSerializableDictionaryKeyValue>& sequentialData = any_cast_ref<Vector<ManagedSerializableDictionaryKeyValue>>(data->mRTTIData);
+			Vector<ManagedSerializableDictionaryKeyValue>& sequentialData = any_cast_ref<Vector<ManagedSerializableDictionaryKeyValue>>(obj->mRTTIData);
 
 
 			return (UINT32)sequentialData.size();
 			return (UINT32)sequentialData.size();
 		}
 		}
@@ -94,7 +92,7 @@ namespace BansheeEngine
 				&ManagedSerializableDictionaryRTTI::setEntry, &ManagedSerializableDictionaryRTTI::setNumEntries);
 				&ManagedSerializableDictionaryRTTI::setEntry, &ManagedSerializableDictionaryRTTI::setNumEntries);
 		}
 		}
 
 
-		virtual void onSerializationStarted(IReflectable* obj)
+		virtual void onSerializationStarted(IReflectable* obj) override
 		{
 		{
 			ManagedSerializableDictionary* serializableObject = static_cast<ManagedSerializableDictionary*>(obj);
 			ManagedSerializableDictionary* serializableObject = static_cast<ManagedSerializableDictionary*>(obj);
 
 
@@ -107,19 +105,19 @@ namespace BansheeEngine
 			serializableObject->mRTTIData = sequentialData;
 			serializableObject->mRTTIData = sequentialData;
 		}
 		}
 
 
-		virtual void onSerializationEnded(IReflectable* obj)
+		virtual void onSerializationEnded(IReflectable* obj) override
 		{
 		{
 			ManagedSerializableDictionary* serializableObject = static_cast<ManagedSerializableDictionary*>(obj);
 			ManagedSerializableDictionary* serializableObject = static_cast<ManagedSerializableDictionary*>(obj);
 			serializableObject->mRTTIData = nullptr;
 			serializableObject->mRTTIData = nullptr;
 		}
 		}
 
 
-		virtual const String& getRTTIName()
+		virtual const String& getRTTIName() override
 		{
 		{
 			static String name = "ScriptSerializableDictionary";
 			static String name = "ScriptSerializableDictionary";
 			return name;
 			return name;
 		}
 		}
 
 
-		virtual UINT32 getRTTIId()
+		virtual UINT32 getRTTIId() override
 		{
 		{
 			return TID_ScriptSerializableDictionary;
 			return TID_ScriptSerializableDictionary;
 		}
 		}

+ 2 - 0
SBansheeEngine/Include/BsScriptSerializableProperty.h

@@ -38,6 +38,8 @@ namespace BansheeEngine
 		/************************************************************************/
 		/************************************************************************/
 		/* 								CLR HOOKS						   		*/
 		/* 								CLR HOOKS						   		*/
 		/************************************************************************/
 		/************************************************************************/
+		static void internal_CreateInstance(MonoObject* instance, MonoReflectionType* reflType);
+
 		static MonoObject* internal_createObject(ScriptSerializableProperty* nativeInstance);
 		static MonoObject* internal_createObject(ScriptSerializableProperty* nativeInstance);
 		static MonoObject* internal_createArray(ScriptSerializableProperty* nativeInstance);
 		static MonoObject* internal_createArray(ScriptSerializableProperty* nativeInstance);
 		static MonoObject* internal_createList(ScriptSerializableProperty* nativeInstance);
 		static MonoObject* internal_createList(ScriptSerializableProperty* nativeInstance);

+ 23 - 0
SBansheeEngine/Source/BsScriptSerializableProperty.cpp

@@ -9,6 +9,7 @@
 #include "BsScriptSerializableArray.h"
 #include "BsScriptSerializableArray.h"
 #include "BsScriptSerializableList.h"
 #include "BsScriptSerializableList.h"
 #include "BsScriptSerializableDictionary.h"
 #include "BsScriptSerializableDictionary.h"
+#include "BsScriptAssemblyManager.h"
 #include "BsManagedSerializableObject.h"
 #include "BsManagedSerializableObject.h"
 #include "BsManagedSerializableArray.h"
 #include "BsManagedSerializableArray.h"
 #include "BsManagedSerializableList.h"
 #include "BsManagedSerializableList.h"
@@ -26,6 +27,7 @@ namespace BansheeEngine
 
 
 	void ScriptSerializableProperty::initRuntimeData()
 	void ScriptSerializableProperty::initRuntimeData()
 	{
 	{
+		metaData.scriptClass->addInternalCall("Internal_CreateInstance", &ScriptSerializableProperty::internal_CreateInstance);
 		metaData.scriptClass->addInternalCall("Internal_CreateObject", &ScriptSerializableProperty::internal_createObject);
 		metaData.scriptClass->addInternalCall("Internal_CreateObject", &ScriptSerializableProperty::internal_createObject);
 		metaData.scriptClass->addInternalCall("Internal_CreateArray", &ScriptSerializableProperty::internal_createArray);
 		metaData.scriptClass->addInternalCall("Internal_CreateArray", &ScriptSerializableProperty::internal_createArray);
 		metaData.scriptClass->addInternalCall("Internal_CreateList", &ScriptSerializableProperty::internal_createList);
 		metaData.scriptClass->addInternalCall("Internal_CreateList", &ScriptSerializableProperty::internal_createList);
@@ -45,6 +47,27 @@ namespace BansheeEngine
 		return nativeInstance;
 		return nativeInstance;
 	}
 	}
 
 
+	void ScriptSerializableProperty::internal_CreateInstance(MonoObject* instance, MonoReflectionType* reflType)
+	{
+		if (reflType == nullptr)
+			return;
+
+		MonoType* type = mono_reflection_type_get_type(reflType);
+		::MonoClass* monoClass = mono_type_get_class(type);
+		MonoClass* engineClass = MonoManager::instance().findClass(monoClass);
+
+		ManagedSerializableTypeInfoPtr typeInfo = ScriptAssemblyManager::instance().getTypeInfo(engineClass);
+		if (typeInfo == nullptr)
+		{
+			LOGWRN("Cannot create an instance of type \"" +
+				engineClass->getFullName() + "\", it is not marked as serializable.");
+			return;
+		}
+
+		ScriptSerializableProperty* nativeInstance = new (bs_alloc<ScriptSerializableProperty>()) 
+			ScriptSerializableProperty(instance, typeInfo);
+	}
+
 	MonoObject* ScriptSerializableProperty::internal_createObject(ScriptSerializableProperty* nativeInstance)
 	MonoObject* ScriptSerializableProperty::internal_createObject(ScriptSerializableProperty* nativeInstance)
 	{
 	{
 		ScriptSerializableObject* newObject = ScriptSerializableObject::create(nativeInstance);
 		ScriptSerializableObject* newObject = ScriptSerializableObject::create(nativeInstance);