Browse Source

WIP: UndoRedo refactor
- Multiple subsequent changes to input fields won't be recorded as separate undo commands

BearishSun 6 years ago
parent
commit
cccd150961
51 changed files with 501 additions and 192 deletions
  1. 4 0
      Source/EditorCore/GUI/BsGUIVector2Field.cpp
  2. 4 0
      Source/EditorCore/GUI/BsGUIVector2Field.h
  3. 4 0
      Source/EditorCore/GUI/BsGUIVector3Field.cpp
  4. 4 0
      Source/EditorCore/GUI/BsGUIVector3Field.h
  5. 5 0
      Source/EditorCore/GUI/BsGUIVector4Field.cpp
  6. 4 0
      Source/EditorCore/GUI/BsGUIVector4Field.h
  7. 0 5
      Source/EditorManaged/General/EditorApplication.cs
  8. 7 0
      Source/EditorManaged/Generated/GUIVector2Field.generated.cs
  9. 7 0
      Source/EditorManaged/Generated/GUIVector3Field.generated.cs
  10. 7 0
      Source/EditorManaged/Generated/GUIVector4Field.generated.cs
  11. 21 0
      Source/EditorManaged/Generated/info.xml
  12. 1 0
      Source/EditorManaged/Window/MenuItems.cs
  13. 51 12
      Source/EditorManaged/Windows/Inspector/InspectableAABox.cs
  14. 32 11
      Source/EditorManaged/Windows/Inspector/InspectableArray.cs
  15. 3 1
      Source/EditorManaged/Windows/Inspector/InspectableBool.cs
  16. 3 1
      Source/EditorManaged/Windows/Inspector/InspectableColor.cs
  17. 3 1
      Source/EditorManaged/Windows/Inspector/InspectableColorDistribution.cs
  18. 3 1
      Source/EditorManaged/Windows/Inspector/InspectableColorGradient.cs
  19. 3 1
      Source/EditorManaged/Windows/Inspector/InspectableCurve.cs
  20. 8 4
      Source/EditorManaged/Windows/Inspector/InspectableDegree.cs
  21. 21 10
      Source/EditorManaged/Windows/Inspector/InspectableDictionary.cs
  22. 3 1
      Source/EditorManaged/Windows/Inspector/InspectableEnum.cs
  23. 28 13
      Source/EditorManaged/Windows/Inspector/InspectableEuler.cs
  24. 15 21
      Source/EditorManaged/Windows/Inspector/InspectableField.cs
  25. 8 4
      Source/EditorManaged/Windows/Inspector/InspectableFloat.cs
  26. 3 3
      Source/EditorManaged/Windows/Inspector/InspectableFloatDistribution.cs
  27. 3 1
      Source/EditorManaged/Windows/Inspector/InspectableGameObjectRef.cs
  28. 10 4
      Source/EditorManaged/Windows/Inspector/InspectableInt.cs
  29. 4 1
      Source/EditorManaged/Windows/Inspector/InspectableLayerMask.cs
  30. 34 11
      Source/EditorManaged/Windows/Inspector/InspectableList.cs
  31. 6 3
      Source/EditorManaged/Windows/Inspector/InspectableObject.cs
  32. 4 2
      Source/EditorManaged/Windows/Inspector/InspectableQuaternion.cs
  33. 3 1
      Source/EditorManaged/Windows/Inspector/InspectableRRef.cs
  34. 8 4
      Source/EditorManaged/Windows/Inspector/InspectableRadian.cs
  35. 3 3
      Source/EditorManaged/Windows/Inspector/InspectableRangedFloat.cs
  36. 3 3
      Source/EditorManaged/Windows/Inspector/InspectableRangedInt.cs
  37. 3 1
      Source/EditorManaged/Windows/Inspector/InspectableResource.cs
  38. 8 4
      Source/EditorManaged/Windows/Inspector/InspectableString.cs
  39. 3 1
      Source/EditorManaged/Windows/Inspector/InspectableTexture.cs
  40. 14 5
      Source/EditorManaged/Windows/Inspector/InspectableVector2.cs
  41. 8 4
      Source/EditorManaged/Windows/Inspector/InspectableVector2Distribution.cs
  42. 14 5
      Source/EditorManaged/Windows/Inspector/InspectableVector3.cs
  43. 8 4
      Source/EditorManaged/Windows/Inspector/InspectableVector3Distribution.cs
  44. 14 5
      Source/EditorManaged/Windows/Inspector/InspectableVector4.cs
  45. 66 41
      Source/EditorManaged/Windows/Inspector/InspectorWindow.cs
  46. 8 0
      Source/EditorScript/Generated/BsScriptGUIVector2Field.generated.cpp
  47. 3 0
      Source/EditorScript/Generated/BsScriptGUIVector2Field.generated.h
  48. 8 0
      Source/EditorScript/Generated/BsScriptGUIVector3Field.generated.cpp
  49. 3 0
      Source/EditorScript/Generated/BsScriptGUIVector3Field.generated.h
  50. 8 0
      Source/EditorScript/Generated/BsScriptGUIVector4Field.generated.cpp
  51. 3 0
      Source/EditorScript/Generated/BsScriptGUIVector4Field.generated.h

+ 4 - 0
Source/EditorCore/GUI/BsGUIVector2Field.cpp

@@ -22,9 +22,13 @@ namespace bs
 
 		mFieldX->onValueChanged.connect([this](float val) { valueChanged(val, VectorComponent::X); });
 		mFieldY->onValueChanged.connect([this](float val) { valueChanged(val, VectorComponent::Y); });
+
 		mFieldX->onConfirm.connect([this]() { inputConfirmed(VectorComponent::X); });
 		mFieldY->onConfirm.connect([this]() { inputConfirmed(VectorComponent::Y); });
 
+		mFieldX->onFocusChanged.connect([this](bool val) { onComponentFocusChanged(val, VectorComponent::X); });
+		mFieldY->onFocusChanged.connect([this](bool val) { onComponentFocusChanged(val, VectorComponent::Y); });
+
 		mLayout->addElement(mFieldX);
 		mLayout->addNewElement<GUIFixedSpace>(5);
 		mLayout->addElement(mFieldY);

+ 4 - 0
Source/EditorCore/GUI/BsGUIVector2Field.h

@@ -59,6 +59,10 @@ namespace bs
 		BS_SCRIPT_EXPORT()
 		Event<void(float, VectorComponent)> onComponentChanged;
 
+		/**	Triggered when an individual component loses or gains focus. */
+		BS_SCRIPT_EXPORT()
+		Event<void(bool, VectorComponent)> onComponentFocusChanged;
+
 		/** Triggered when the user hits the Enter key with any of the component input boxes in focus. */
 		BS_SCRIPT_EXPORT()
 		Event<void(VectorComponent)> onConfirm;

+ 4 - 0
Source/EditorCore/GUI/BsGUIVector3Field.cpp

@@ -29,6 +29,10 @@ namespace bs
 		mFieldY->onConfirm.connect([this]() { inputConfirmed(VectorComponent::Y); });
 		mFieldZ->onConfirm.connect([this]() { inputConfirmed(VectorComponent::Z); });
 
+		mFieldX->onFocusChanged.connect([this](bool val) { onComponentFocusChanged(val, VectorComponent::X); });
+		mFieldY->onFocusChanged.connect([this](bool val) { onComponentFocusChanged(val, VectorComponent::Y); });
+		mFieldZ->onFocusChanged.connect([this](bool val) { onComponentFocusChanged(val, VectorComponent::Z); });
+
 		mLayout->addElement(mFieldX);
 		mLayout->addNewElement<GUIFixedSpace>(5);
 		mLayout->addElement(mFieldY);

+ 4 - 0
Source/EditorCore/GUI/BsGUIVector3Field.h

@@ -62,6 +62,10 @@ namespace bs
 		BS_SCRIPT_EXPORT()
 		Event<void(float, VectorComponent)> onComponentChanged;
 
+		/**	Triggered when an individual component loses or gains focus. */
+		BS_SCRIPT_EXPORT()
+		Event<void(bool, VectorComponent)> onComponentFocusChanged;
+
 		/** Triggered when the user hits the Enter key with any of the component input boxes in focus. */
 		BS_SCRIPT_EXPORT()
 		Event<void(VectorComponent)> onConfirm;

+ 5 - 0
Source/EditorCore/GUI/BsGUIVector4Field.cpp

@@ -32,6 +32,11 @@ namespace bs
 		mFieldZ->onConfirm.connect([this]() { inputConfirmed(VectorComponent::Z); });
 		mFieldW->onConfirm.connect([this]() { inputConfirmed(VectorComponent::W); });
 
+		mFieldX->onFocusChanged.connect([this](bool val) { onComponentFocusChanged(val, VectorComponent::X); });
+		mFieldY->onFocusChanged.connect([this](bool val) { onComponentFocusChanged(val, VectorComponent::Y); });
+		mFieldZ->onFocusChanged.connect([this](bool val) { onComponentFocusChanged(val, VectorComponent::Z); });
+		mFieldW->onFocusChanged.connect([this](bool val) { onComponentFocusChanged(val, VectorComponent::W); });
+
 		mLayout->removeElement(mLabel);
 
 		GUILayout* layout = mLayout->addNewElement<GUILayoutY>();

+ 4 - 0
Source/EditorCore/GUI/BsGUIVector4Field.h

@@ -65,6 +65,10 @@ namespace bs
 		BS_SCRIPT_EXPORT()
 		Event<void(float, VectorComponent)> onComponentChanged;
 
+		/**	Triggered when an individual component loses or gains focus. */
+		BS_SCRIPT_EXPORT()
+		Event<void(bool, VectorComponent)> onComponentFocusChanged;
+
 		/** Triggered when the user hits the Enter key with any of the component input boxes in focus. */
 		BS_SCRIPT_EXPORT()
 		Event<void(VectorComponent)> onConfirm;

+ 0 - 5
Source/EditorManaged/General/EditorApplication.cs

@@ -425,11 +425,6 @@ namespace bs.Editor
         /// </summary>
         internal void OnEditorUpdate()
         {
-            // Record game-object undo diffs. It's fine doing this here as we only record game objects during input
-            // callbacks, which are called before update. If that changes then we might need to move this at a later
-            // stage in the frame.
-            GameObjectUndo.ResolveDiffs();
-
             // Update managers
             ProjectLibrary.Update();
             codeManager.Update();

+ 7 - 0
Source/EditorManaged/Generated/GUIVector2Field.generated.cs

@@ -106,6 +106,9 @@ namespace bs.Editor
 		/// <summary>Reports the new value of an individual vector component when the user changes it.</summary>
 		public event Action<float, VectorComponent> OnComponentChanged;
 
+		/// <summary>Triggered when an individual component loses or gains focus.</summary>
+		public event Action<bool, VectorComponent> OnComponentFocusChanged;
+
 		/// <summary>Triggered when the user hits the Enter key with any of the component input boxes in focus.</summary>
 		public event Action<VectorComponent> OnConfirm;
 
@@ -141,6 +144,10 @@ namespace bs.Editor
 		{
 			OnComponentChanged?.Invoke(p0, p1);
 		}
+		private void Internal_onComponentFocusChanged(bool p0, VectorComponent p1)
+		{
+			OnComponentFocusChanged?.Invoke(p0, p1);
+		}
 		private void Internal_onConfirm(VectorComponent p0)
 		{
 			OnConfirm?.Invoke(p0);

+ 7 - 0
Source/EditorManaged/Generated/GUIVector3Field.generated.cs

@@ -106,6 +106,9 @@ namespace bs.Editor
 		/// <summary>Reports the new value of an individual vector component when the user changes it.</summary>
 		public event Action<float, VectorComponent> OnComponentChanged;
 
+		/// <summary>Triggered when an individual component loses or gains focus.</summary>
+		public event Action<bool, VectorComponent> OnComponentFocusChanged;
+
 		/// <summary>Triggered when the user hits the Enter key with any of the component input boxes in focus.</summary>
 		public event Action<VectorComponent> OnConfirm;
 
@@ -141,6 +144,10 @@ namespace bs.Editor
 		{
 			OnComponentChanged?.Invoke(p0, p1);
 		}
+		private void Internal_onComponentFocusChanged(bool p0, VectorComponent p1)
+		{
+			OnComponentFocusChanged?.Invoke(p0, p1);
+		}
 		private void Internal_onConfirm(VectorComponent p0)
 		{
 			OnConfirm?.Invoke(p0);

+ 7 - 0
Source/EditorManaged/Generated/GUIVector4Field.generated.cs

@@ -106,6 +106,9 @@ namespace bs.Editor
 		/// <summary>Reports the new value of an individual vector component when the user changes it.</summary>
 		public event Action<float, VectorComponent> OnComponentChanged;
 
+		/// <summary>Triggered when an individual component loses or gains focus.</summary>
+		public event Action<bool, VectorComponent> OnComponentFocusChanged;
+
 		/// <summary>Triggered when the user hits the Enter key with any of the component input boxes in focus.</summary>
 		public event Action<VectorComponent> OnConfirm;
 
@@ -141,6 +144,10 @@ namespace bs.Editor
 		{
 			OnComponentChanged?.Invoke(p0, p1);
 		}
+		private void Internal_onComponentFocusChanged(bool p0, VectorComponent p1)
+		{
+			OnComponentFocusChanged?.Invoke(p0, p1);
+		}
 		private void Internal_onConfirm(VectorComponent p0)
 		{
 			OnConfirm?.Invoke(p0);

+ 21 - 0
Source/EditorManaged/Generated/info.xml

@@ -69,6 +69,13 @@
 			<param name="p1" type="VectorComponent">
 			</param>
 		</event>
+		<event native="onComponentFocusChanged" script="OnComponentFocusChanged" static="false">
+			<doc>Triggered when an individual component loses or gains focus.</doc>
+			<param name="p0" type="bool">
+			</param>
+			<param name="p1" type="VectorComponent">
+			</param>
+		</event>
 		<event native="onConfirm" script="OnConfirm" static="false">
 			<doc>Triggered when the user hits the Enter key with any of the component input boxes in focus.</doc>
 			<param name="p0" type="VectorComponent">
@@ -274,6 +281,13 @@
 			<param name="p1" type="VectorComponent">
 			</param>
 		</event>
+		<event native="onComponentFocusChanged" script="OnComponentFocusChanged" static="false">
+			<doc>Triggered when an individual component loses or gains focus.</doc>
+			<param name="p0" type="bool">
+			</param>
+			<param name="p1" type="VectorComponent">
+			</param>
+		</event>
 		<event native="onConfirm" script="OnConfirm" static="false">
 			<doc>Triggered when the user hits the Enter key with any of the component input boxes in focus.</doc>
 			<param name="p0" type="VectorComponent">
@@ -294,6 +308,13 @@
 			<param name="p1" type="VectorComponent">
 			</param>
 		</event>
+		<event native="onComponentFocusChanged" script="OnComponentFocusChanged" static="false">
+			<doc>Triggered when an individual component loses or gains focus.</doc>
+			<param name="p0" type="bool">
+			</param>
+			<param name="p1" type="VectorComponent">
+			</param>
+		</event>
 		<event native="onConfirm" script="OnConfirm" static="false">
 			<doc>Triggered when the user hits the Enter key with any of the component input boxes in focus.</doc>
 			<param name="p0" type="VectorComponent">

+ 1 - 0
Source/EditorManaged/Window/MenuItems.cs

@@ -840,6 +840,7 @@ namespace bs.Editor
                 return;
             }
 
+            GameObjectUndo.ResolveDiffs();
             UndoRedo.Global.Undo();
         }
 

+ 51 - 12
Source/EditorManaged/Windows/Inspector/InspectableAABox.cs

@@ -55,13 +55,21 @@ namespace bs.Editor
                 Vector3 min = x - bounds.Size * 0.5f;
                 Vector3 max = x + bounds.Size * 0.5f;
 
-                RecordStateForUndoIfNeeded();
                 property.SetValue(new AABox(min, max));
                 state |= InspectableState.ModifyInProgress;
             };
-            centerField.OnConfirm += x => OnFieldValueConfirm();
-            centerField.OnFocusLost += OnFieldValueConfirm;
-            centerField.OnFocusGained += RecordStateForUndoRequested;
+            centerField.OnConfirm += x =>
+            {
+                OnFieldValueConfirm();
+                StartUndo("center." + x.ToString());
+            };
+            centerField.OnComponentFocusChanged += (focus, comp) =>
+            {
+                if (focus)
+                    StartUndo("center." + comp.ToString());
+                else
+                    OnFieldValueConfirm();
+            };
 
             sizeField.OnValueChanged += x =>
             {
@@ -69,13 +77,21 @@ namespace bs.Editor
                 Vector3 min = bounds.Center - x * 0.5f;
                 Vector3 max = bounds.Center + x * 0.5f;
 
-                RecordStateForUndoIfNeeded();
                 property.SetValue(new AABox(min, max));
                 state |= InspectableState.ModifyInProgress;
             };
-            sizeField.OnConfirm += x => OnFieldValueConfirm();
-            sizeField.OnFocusLost += OnFieldValueConfirm;
-            sizeField.OnFocusGained += RecordStateForUndoRequested;
+            sizeField.OnConfirm += x =>
+            {
+                OnFieldValueConfirm();
+                StartUndo("size." + x.ToString());
+            };
+            sizeField.OnComponentFocusChanged += (focus, comp) =>
+            {
+                if (focus)
+                    StartUndo("size." + comp.ToString());
+                else
+                    OnFieldValueConfirm();
+            };
         }
 
         /// <inheritdoc/>
@@ -99,10 +115,31 @@ namespace bs.Editor
         /// <inheritdoc />
         public override void SetHasFocus(string subFieldName = null)
         {
-            if (subFieldName == "center")
-                centerField.Focus = true;
-            else
-                sizeField.Focus = true;
+            if (subFieldName != null && subFieldName.StartsWith("center."))
+            {
+                string component = subFieldName.Remove(0, "center.".Length);
+                if (component == "X")
+                    centerField.SetInputFocus(VectorComponent.X, true);
+                else if (component == "Y")
+                    centerField.SetInputFocus(VectorComponent.Y, true);
+                else if (component == "Z")
+                    centerField.SetInputFocus(VectorComponent.Z, true);
+                else
+                    centerField.SetInputFocus(VectorComponent.X, true);
+            }
+
+            if (subFieldName != null && subFieldName.StartsWith("size."))
+            {
+                string component = subFieldName.Remove(0, "size.".Length);
+                if (component == "X")
+                    sizeField.SetInputFocus(VectorComponent.X, true);
+                else if (component == "Y")
+                    sizeField.SetInputFocus(VectorComponent.Y, true);
+                else if (component == "Z")
+                    sizeField.SetInputFocus(VectorComponent.Z, true);
+                else
+                    sizeField.SetInputFocus(VectorComponent.X, true);
+            }
         }
 
         /// <summary>
@@ -112,6 +149,8 @@ namespace bs.Editor
         {
             if (state.HasFlag(InspectableState.ModifyInProgress))
                 state |= InspectableState.Modified;
+
+            EndUndo();
         }
     }
 

+ 32 - 11
Source/EditorManaged/Windows/Inspector/InspectableArray.cs

@@ -311,17 +311,19 @@ namespace bs.Editor
             /// <inheritdoc/>
             protected override void CreateList()
             {
-                RecordStateForUndo();
+                StartUndo();
 
                 array = property.CreateArrayInstance(new int[1] { 0 });
                 property.SetValue(array);
                 numElements = 0;
+
+                EndUndo();
             }
 
             /// <inheritdoc/>
             protected override void ResizeList()
             {
-                RecordStateForUndo();
+                StartUndo();
 
                 int size = guiSizeField.Value; // TODO - Support multi-rank arrays
 
@@ -334,6 +336,8 @@ namespace bs.Editor
                 property.SetValue(newArray);
                 array = newArray;
                 numElements = size;
+
+                EndUndo();
             }
 
             /// <inheritdoc/>
@@ -344,18 +348,20 @@ namespace bs.Editor
                     CreateList();
                 else
                 {
-                    RecordStateForUndo();
+                    StartUndo();
 
                     property.SetValue<object>(null);
                     array = null;
                     numElements = 0;
+
+                    EndUndo();
                 }
             }
 
             /// <inheritdoc/>
             protected internal override void DeleteElement(int index)
             {
-                RecordStateForUndo();
+                StartUndo();
 
                 int size = MathEx.Max(0, array.Length - 1);
                 Array newArray = property.CreateArrayInstance(new int[] { size });
@@ -373,12 +379,14 @@ namespace bs.Editor
                 property.SetValue(newArray);
                 array = newArray;
                 numElements = array.Length;
+
+                EndUndo();
             }
 
             /// <inheritdoc/>
             protected internal override void CloneElement(int index)
             {
-                RecordStateForUndo();
+                StartUndo();
 
                 SerializableArray array = property.GetArray();
 
@@ -403,6 +411,8 @@ namespace bs.Editor
                 property.SetValue(newArray);
                 this.array = newArray;
                 numElements = newArray.Length;
+
+                EndUndo();
             }
 
             /// <inheritdoc/>
@@ -410,7 +420,7 @@ namespace bs.Editor
             {
                 if ((index - 1) >= 0)
                 {
-                    RecordStateForUndo();
+                    StartUndo();
 
                     object previousEntry = array.GetValue(index - 1);
 
@@ -428,7 +438,7 @@ namespace bs.Editor
             {
                 if ((index + 1) < array.Length)
                 {
-                    RecordStateForUndo();
+                    StartUndo();
 
                     object nextEntry = array.GetValue(index + 1);
 
@@ -438,17 +448,28 @@ namespace bs.Editor
                     // Natively wrapped arrays are passed by copy
                     if(style.StyleFlags.HasFlag(InspectableFieldStyleFlags.NativeWrapper))
                         property.SetValue(array);
+
+                    EndUndo();
                 }
             }
 
             /// <summary>
-            /// Records the current state of the field for the purposes of undo/redo. Generally this should be called just
-            /// before making changes to the field value.
+            /// Notifies the system to start recording a new undo command. Any changes to the field after this is called
+            /// will be recorded in the command. User must call <see cref="EndUndo"/> after field is done being changed.
             /// </summary>
-            protected void RecordStateForUndo()
+            protected void StartUndo()
             {
                 if (context.Component != null)
-                    UndoRedo.RecordSO(context.Component.SceneObject, false, "Field change: \"" + path + "\"");
+                    GameObjectUndo.RecordComponent(context.Component, path);
+            }
+
+            /// <summary>
+            /// Finishes recording an undo command started via <see cref="StartUndo"/>. If any changes are detected on the
+            /// field an undo command is recorded onto the undo-redo stack, otherwise nothing is done.
+            /// </summary>
+            protected void EndUndo()
+            {
+                GameObjectUndo.ResolveDiffs();
             }
         }
 

+ 3 - 1
Source/EditorManaged/Windows/Inspector/InspectableBool.cs

@@ -71,10 +71,12 @@ namespace bs.Editor
         /// <param name="newValue">New value of the toggle button.</param>
         private void OnFieldValueChanged(bool newValue)
         {
-            RecordStateForUndo();
+            StartUndo();
 
             property.SetValue(newValue);
             state = InspectableState.Modified;
+
+            EndUndo();
         }
     }
 

+ 3 - 1
Source/EditorManaged/Windows/Inspector/InspectableColor.cs

@@ -65,10 +65,12 @@ namespace bs.Editor
         /// <param name="newValue">New value of the color field.</param>
         private void OnFieldValueChanged(Color newValue)
         {
-            RecordStateForUndo();
+            StartUndo();
 
             property.SetValue(newValue);
             state = InspectableState.Modified;
+
+            EndUndo();
         }
     }
 

+ 3 - 1
Source/EditorManaged/Windows/Inspector/InspectableColorDistribution.cs

@@ -62,10 +62,12 @@ namespace bs.Editor
         /// </summary>
         private void OnFieldValueChanged()
         {
-            RecordStateForUndo();
+            StartUndo();
 
             property.SetValue(guiDistributionField.Value);
             state |= InspectableState.ModifyInProgress | InspectableState.Modified;
+
+            EndUndo();
         }
     }
 

+ 3 - 1
Source/EditorManaged/Windows/Inspector/InspectableColorGradient.cs

@@ -63,10 +63,12 @@ namespace bs.Editor
         /// <param name="newValue">New value of the gradient field.</param>
         private void OnFieldValueChanged(ColorGradient newValue)
         {
-            RecordStateForUndo();
+            StartUndo();
 
             property.SetValue(newValue);
             state = InspectableState.Modified;
+
+            EndUndo();
         }
     }
 

+ 3 - 1
Source/EditorManaged/Windows/Inspector/InspectableCurve.cs

@@ -63,10 +63,12 @@ namespace bs.Editor
         /// <param name="newValue">New curve.</param>
         private void OnFieldValueChanged(AnimationCurve newValue)
         {
-            RecordStateForUndo();
+            StartUndo();
 
             property.SetValue(newValue);
             state = InspectableState.Modified;
+
+            EndUndo();
         }
     }
 

+ 8 - 4
Source/EditorManaged/Windows/Inspector/InspectableDegree.cs

@@ -50,9 +50,13 @@ namespace bs.Editor
                         guiFloatField.SetRange(style.RangeStyle.Min, style.RangeStyle.Max);
                 }
                 guiFloatField.OnChanged += OnFieldValueChanged;
-                guiFloatField.OnConfirmed += OnFieldValueConfirm;
+                guiFloatField.OnConfirmed += () =>
+                {
+                    OnFieldValueConfirm();
+                    StartUndo();
+                };
                 guiFloatField.OnFocusLost += OnFieldValueConfirm;
-                guiFloatField.OnFocusGained += RecordStateForUndoRequested;
+                guiFloatField.OnFocusGained += StartUndo;
 
                 layout.AddElement(layoutIndex, guiFloatField);
             }
@@ -83,8 +87,6 @@ namespace bs.Editor
         /// <param name="newValue">New value of the float field.</param>
         private void OnFieldValueChanged(float newValue)
         {
-            RecordStateForUndoIfNeeded();
-
             property.SetValue(new Degree(newValue));
             state |= InspectableState.ModifyInProgress;
         }
@@ -96,6 +98,8 @@ namespace bs.Editor
         {
             if (state.HasFlag(InspectableState.ModifyInProgress))
                 state |= InspectableState.Modified;
+
+            EndUndo();
         }
     }
 

+ 21 - 10
Source/EditorManaged/Windows/Inspector/InspectableDictionary.cs

@@ -333,7 +333,7 @@ namespace bs.Editor
             /// <inheritdoc/>
             protected internal override void AddEntry(object key, object value)
             {
-                RecordStateForUndo();
+                StartUndo();
 
                 SerializableProperty keyProperty = (SerializableProperty)key;
                 SerializableProperty valueProperty = (SerializableProperty)value;
@@ -341,19 +341,21 @@ namespace bs.Editor
                 dictionary.Add(keyProperty.GetValue<object>(), valueProperty.GetValue<object>());
                 numElements = dictionary.Count;
 
+                EndUndo();
                 UpdateKeys();
             }
 
             /// <inheritdoc/>
             protected internal override void RemoveEntry(object key)
             {
-                RecordStateForUndo();
+                StartUndo();
 
                 SerializableProperty keyProperty = (SerializableProperty)key;
 
                 dictionary.Remove(keyProperty.GetValue<object>());
                 numElements = dictionary.Count;
 
+                EndUndo();
                 UpdateKeys();
             }
 
@@ -390,8 +392,6 @@ namespace bs.Editor
             /// <inheritdoc/>
             protected internal override KeyValuePair<object, object> CloneElement(int index)
             {
-                RecordStateForUndo();
-
                 SerializableProperty keyProperty = (SerializableProperty)GetKey(index);
                 SerializableProperty valueProperty = (SerializableProperty)GetValue(keyProperty);
 
@@ -417,35 +417,46 @@ namespace bs.Editor
             /// <inheritdoc/>
             protected override void CreateDictionary()
             {
-                RecordStateForUndo();
+                StartUndo();
 
                 dictionary = property.CreateDictionaryInstance();
                 numElements = dictionary.Count;
                 property.SetValue(dictionary);
 
+                EndUndo();
                 UpdateKeys();
             }
 
             /// <inheritdoc/>
             protected override void DeleteDictionary()
             {
-                RecordStateForUndo();
+                StartUndo();
 
                 dictionary = null;
                 numElements = 0;
                 property.SetValue<object>(null);
 
+                EndUndo();
                 UpdateKeys();
             }
 
             /// <summary>
-            /// Records the current state of the field for the purposes of undo/redo. Generally this should be called just
-            /// before making changes to the field value.
+            /// Notifies the system to start recording a new undo command. Any changes to the field after this is called
+            /// will be recorded in the command. User must call <see cref="EndUndo"/> after field is done being changed.
             /// </summary>
-            protected void RecordStateForUndo()
+            protected void StartUndo()
             {
                 if (context.Component != null)
-                    UndoRedo.RecordSO(context.Component.SceneObject, false, "Field change: \"" + path + "\"");
+                    GameObjectUndo.RecordComponent(context.Component, path);
+            }
+
+            /// <summary>
+            /// Finishes recording an undo command started via <see cref="StartUndo"/>. If any changes are detected on the
+            /// field an undo command is recorded onto the undo-redo stack, otherwise nothing is done.
+            /// </summary>
+            protected void EndUndo()
+            {
+                GameObjectUndo.ResolveDiffs();
             }
 
             /// <summary>

+ 3 - 1
Source/EditorManaged/Windows/Inspector/InspectableEnum.cs

@@ -63,10 +63,12 @@ namespace bs.Editor
         /// <param name="newValue">Newly selected list box value.</param>
         private void OnFieldValueChanged(ulong newValue)
         {
-            RecordStateForUndo();
+            StartUndo();
 
             property.SetValue(newValue);
             state = InspectableState.Modified;
+
+            EndUndo();
         }
     }
 

+ 28 - 13
Source/EditorManaged/Windows/Inspector/InspectableEuler.cs

@@ -43,10 +43,19 @@ namespace bs.Editor
                 guiField = new GUIVector3Field(new GUIContent(title));
                 guiField.Value = quatValue.ToEuler();
 
-                guiField.OnValueChanged += OnFieldValueChanged;
-                guiField.OnConfirm += x => OnFieldValueConfirm();
-                guiField.OnFocusLost += OnFieldValueConfirm;
-                guiField.OnFocusGained += RecordStateForUndoRequested;
+                guiField.OnComponentChanged += OnFieldValueChanged;
+                guiField.OnConfirm += x =>
+                {
+                    OnFieldValueConfirm();
+                    StartUndo(x.ToString());
+                };
+                guiField.OnComponentFocusChanged += (focus, comp) =>
+                {
+                    if (focus)
+                        StartUndo(comp.ToString());
+                    else
+                        OnFieldValueConfirm();
+                };
 
                 layout.AddElement(layoutIndex, guiField);
             }
@@ -75,20 +84,24 @@ namespace bs.Editor
         /// <inheritdoc />
         public override void SetHasFocus(string subFieldName = null)
         {
-            guiField.Focus = true;
+            if (subFieldName == "X")
+                guiField.SetInputFocus(VectorComponent.X, true);
+            else if (subFieldName == "Y")
+                guiField.SetInputFocus(VectorComponent.Y, true);
+            else if (subFieldName == "Z")
+                guiField.SetInputFocus(VectorComponent.Z, true);
+            else
+                guiField.SetInputFocus(VectorComponent.X, true);
         }
 
         /// <summary>
-        /// Triggered when the user changes the field value.
+        /// Triggered when the user changes the field value of a single component.
         /// </summary>
-        /// <param name="newValue">New value of the 3D vector field.</param>
-        private void OnFieldValueChanged(Vector3 newValue)
+        /// <param name="newValue">New value of a single component in the 3D vector field.</param>
+        /// <param name="component">Component that was changed.</param>
+        private void OnFieldValueChanged(float newValue, VectorComponent component)
         {
-            RecordStateForUndoIfNeeded();
-
-            quatValue = Quaternion.FromEuler(newValue);
-
-            property.SetValue(quatValue);
+            property.SetValue(guiField.Value);
             state |= InspectableState.ModifyInProgress;
         }
 
@@ -99,6 +112,8 @@ namespace bs.Editor
         {
             if (state.HasFlag(InspectableState.ModifyInProgress))
                 state |= InspectableState.Modified;
+
+            EndUndo();
         }
     }
 

+ 15 - 21
Source/EditorManaged/Windows/Inspector/InspectableField.cs

@@ -26,7 +26,6 @@ namespace bs.Editor
         protected string path;
         protected int depth;
         protected SerializableProperty.FieldType type;
-        private bool undoRecordNeeded = true;
 
         /// <summary>
         /// Property this field is displaying contents of.
@@ -216,11 +215,19 @@ namespace bs.Editor
         }
 
         /// <summary>
-        /// Records the current state of the field for the purposes of undo/redo. Generally this should be called just
-        /// before making changes to the field value.
+        /// Zero parameter wrapper for <see cref="StartUndo(string)"/>
+        /// </summary>
+        protected void StartUndo()
+        {
+            StartUndo(null);
+        }
+
+        /// <summary>
+        /// Notifies the system to start recording a new undo command. Any changes to the field after this is called
+        /// will be recorded in the command. User must call <see cref="EndUndo"/> after field is done being changed.
         /// </summary>
         /// <param name="subPath">Additional path to append to the end of the current field path.</param>
-        protected void RecordStateForUndo(string subPath = null)
+        protected void StartUndo(string subPath)
         {
             if (context.Component != null)
             {
@@ -233,25 +240,12 @@ namespace bs.Editor
         }
 
         /// <summary>
-        /// Checks if the system needs to record the state of the current object for undo purposes, and performs the record
-        /// if needed. This can be requested by calling <see cref="RecordStateForUndoRequested"/>
-        /// </summary>
-        /// <param name="subPath">Additional path to append to the end of the current field path.</param>
-        protected void RecordStateForUndoIfNeeded(string subPath = null)
-        {
-            if (!undoRecordNeeded)
-                return;
-
-            RecordStateForUndo(subPath);
-            undoRecordNeeded = false;
-        }
-
-        /// <summary>
-        /// Notifies the system that the next call to <see cref="RecordStateForUndoIfNeeded"/> should record the state.
+        /// Finishes recording an undo command started via <see cref="StartUndo(string)"/>. If any changes are detected on
+        /// the field an undo command is recorded onto the undo-redo stack, otherwise nothing is done.
         /// </summary>
-        protected void RecordStateForUndoRequested()
+        protected void EndUndo()
         {
-            undoRecordNeeded = true;
+            GameObjectUndo.ResolveDiffs();
         }
 
         /// <summary>

+ 8 - 4
Source/EditorManaged/Windows/Inspector/InspectableFloat.cs

@@ -49,9 +49,13 @@ namespace bs.Editor
                         guiFloatField.SetRange(style.RangeStyle.Min, style.RangeStyle.Max);
                 }
                 guiFloatField.OnChanged += OnFieldValueChanged;
-                guiFloatField.OnConfirmed += OnFieldValueConfirm;
+                guiFloatField.OnConfirmed += () =>
+                {
+                    OnFieldValueConfirm();
+                    StartUndo();
+                };
                 guiFloatField.OnFocusLost += OnFieldValueConfirm;
-                guiFloatField.OnFocusGained += RecordStateForUndoRequested;
+                guiFloatField.OnFocusGained += StartUndo;
 
                 layout.AddElement(layoutIndex, guiFloatField);
             }
@@ -82,8 +86,6 @@ namespace bs.Editor
         /// <param name="newValue">New value of the float field.</param>
         private void OnFieldValueChanged(float newValue)
         {
-            RecordStateForUndoIfNeeded();
-
             property.SetValue(newValue);
             state |= InspectableState.ModifyInProgress;
         }
@@ -95,6 +97,8 @@ namespace bs.Editor
         {
             if (state.HasFlag(InspectableState.ModifyInProgress))
                 state |= InspectableState.Modified;
+
+            EndUndo();
         }
     }
 

+ 3 - 3
Source/EditorManaged/Windows/Inspector/InspectableFloatDistribution.cs

@@ -41,7 +41,7 @@ namespace bs.Editor
                 guiDistributionField.OnChanged += OnFieldValueChanged;
                 guiDistributionField.OnConfirmed += OnFieldValueConfirm;
                 guiDistributionField.OnFocusLost += OnFieldValueConfirm;
-                guiDistributionField.OnFocusGained += RecordStateForUndoRequested;
+                guiDistributionField.OnFocusGained += StartUndo;
 
                 layout.AddElement(layoutIndex, guiDistributionField);
             }
@@ -65,8 +65,6 @@ namespace bs.Editor
         /// </summary>
         private void OnFieldValueChanged()
         {
-            RecordStateForUndoIfNeeded();
-
             property.SetValue(guiDistributionField.Value);
             state |= InspectableState.ModifyInProgress;
         }
@@ -78,6 +76,8 @@ namespace bs.Editor
         {
             if (state.HasFlag(InspectableState.ModifyInProgress))
                 state |= InspectableState.Modified;
+
+            EndUndo();
         }
     }
 

+ 3 - 1
Source/EditorManaged/Windows/Inspector/InspectableGameObjectRef.cs

@@ -64,10 +64,12 @@ namespace bs.Editor
         /// <param name="newValue">New game object to reference.</param>
         private void OnFieldValueChanged(GameObject newValue)
         {
-            RecordStateForUndo();
+            StartUndo();
 
             property.SetValue(newValue);
             state = InspectableState.Modified;
+
+            EndUndo();
         }
     }
 

+ 10 - 4
Source/EditorManaged/Windows/Inspector/InspectableInt.cs

@@ -50,9 +50,13 @@ namespace bs.Editor
                         guiIntField.SetRange((int) style.RangeStyle.Min, (int) style.RangeStyle.Max);
                 }
                 guiIntField.OnChanged += OnFieldValueChanged;
-                guiIntField.OnConfirmed += OnFieldValueConfirm;
+                guiIntField.OnConfirmed += () =>
+                {
+                    OnFieldValueConfirm();
+                    StartUndo();
+                };
                 guiIntField.OnFocusLost += OnFieldValueConfirm;
-                guiIntField.OnFocusGained += RecordStateForUndoRequested;
+                guiIntField.OnFocusGained += StartUndo;
 
                 layout.AddElement(layoutIndex, guiIntField);
             }
@@ -83,8 +87,6 @@ namespace bs.Editor
         /// <param name="newValue">New value of the int field.</param>
         private void OnFieldValueChanged(int newValue)
         {
-            RecordStateForUndoIfNeeded();
-
             property.SetValue(newValue);
             state |= InspectableState.ModifyInProgress;
         }
@@ -94,8 +96,12 @@ namespace bs.Editor
         /// </summary>
         private void OnFieldValueConfirm()
         {
+            StartUndo();
+
             if(state.HasFlag(InspectableState.ModifyInProgress))
                 state |= InspectableState.Modified;
+
+            EndUndo();
         }
     }
 

+ 4 - 1
Source/EditorManaged/Windows/Inspector/InspectableLayerMask.cs

@@ -50,9 +50,12 @@ namespace bs.Editor
 
                     layersValue = layers;
 
-                    RecordStateForUndo();
+                    StartUndo();
+
                     property.SetValue(layers);
                     state |= InspectableState.ModifyInProgress | InspectableState.Modified;
+
+                    EndUndo();
                 };
 
                 layout.AddElement(layoutIndex, guiLayerMaskField);

+ 34 - 11
Source/EditorManaged/Windows/Inspector/InspectableList.cs

@@ -264,17 +264,19 @@ namespace bs.Editor
             /// <inheritdoc/>
             protected override void CreateList()
             {
-                RecordStateForUndo();
+                StartUndo();
 
                 list = property.CreateListInstance(0);
                 property.SetValue(list);
                 numElements = 0;
+
+                EndUndo();
             }
 
             /// <inheritdoc/>
             protected override void ResizeList()
             {
-                RecordStateForUndo();
+                StartUndo();
 
                 int size = guiSizeField.Value;
 
@@ -287,33 +289,39 @@ namespace bs.Editor
                 property.SetValue(newList);
                 list = newList;
                 numElements = list.Count;
+
+                EndUndo();
             }
 
             /// <inheritdoc/>
             protected override void ClearList()
             {
-                RecordStateForUndo();
+                StartUndo();
 
                 property.SetValue<object>(null);
                 list = null;
                 numElements = 0;
+
+                EndUndo();
             }
 
             /// <inheritdoc/>
             protected internal override void DeleteElement(int index)
             {
-                RecordStateForUndo();
+                StartUndo();
 
                 if (index >= 0 && index < list.Count)
                     list.RemoveAt(index);
 
                 numElements = list.Count;
+
+                EndUndo();
             }
 
             /// <inheritdoc/>
             protected internal override void CloneElement(int index)
             {
-                RecordStateForUndo();
+                StartUndo();
 
                 SerializableList serializableList = property.GetList();
 
@@ -321,12 +329,14 @@ namespace bs.Editor
                     list.Add(SerializableUtility.Clone(serializableList.GetProperty(index).GetValue<object>()));
 
                 numElements = list.Count;
+
+                EndUndo();
             }
 
             /// <inheritdoc/>
             protected internal override void MoveUpElement(int index)
             {
-                RecordStateForUndo();
+                StartUndo();
 
                 if ((index - 1) >= 0)
                 {
@@ -335,12 +345,14 @@ namespace bs.Editor
                     list[index - 1] = list[index];
                     list[index] = previousEntry;
                 }
+
+                EndUndo();
             }
 
             /// <inheritdoc/>
             protected internal override void MoveDownElement(int index)
             {
-                RecordStateForUndo();
+                StartUndo();
 
                 if ((index + 1) < list.Count)
                 {
@@ -349,16 +361,27 @@ namespace bs.Editor
                     list[index + 1] = list[index];
                     list[index] = nextEntry;
                 }
+
+                EndUndo();
             }
 
             /// <summary>
-            /// Records the current state of the field for the purposes of undo/redo. Generally this should be called just
-            /// before making changes to the field value.
+            /// Notifies the system to start recording a new undo command. Any changes to the field after this is called
+            /// will be recorded in the command. User must call <see cref="EndUndo"/> after field is done being changed.
             /// </summary>
-            protected void RecordStateForUndo()
+            protected void StartUndo()
             {
                 if (context.Component != null)
-                    UndoRedo.RecordSO(context.Component.SceneObject, false, "Field change: \"" + path + "\"");
+                    GameObjectUndo.RecordComponent(context.Component, path);
+            }
+
+            /// <summary>
+            /// Finishes recording an undo command started via <see cref="StartUndo"/>. If any changes are detected on the
+            /// field an undo command is recorded onto the undo-redo stack, otherwise nothing is done.
+            /// </summary>
+            protected void EndUndo()
+            {
+                GameObjectUndo.ResolveDiffs();
             }
         }
 

+ 6 - 3
Source/EditorManaged/Windows/Inspector/InspectableObject.cs

@@ -85,8 +85,9 @@ namespace bs.Editor
                         createContextMenu.AddItem(prefix + type.Name,
                             () =>
                             {
-                                RecordStateForUndo();
+                                StartUndo();
                                 property.SetValue(Activator.CreateInstance(type));
+                                EndUndo();
                             });
                     }
                 }
@@ -319,8 +320,9 @@ namespace bs.Editor
             {
                 if (instantiableTypes.Length > 0)
                 {
-                    RecordStateForUndo();
+                    StartUndo();
                     property.SetValue(Activator.CreateInstance(instantiableTypes[0]));
+                    EndUndo();
                 }
             }
             else
@@ -338,8 +340,9 @@ namespace bs.Editor
         /// </summary>
         private void OnClearButtonClicked()
         {
-            RecordStateForUndo();
+            StartUndo();
             property.SetValue<object>(null);
+            EndUndo();
         }
 
         /// <summary>

+ 4 - 2
Source/EditorManaged/Windows/Inspector/InspectableQuaternion.cs

@@ -42,7 +42,7 @@ namespace bs.Editor
                 guiField.OnValueChanged += OnFieldValueChanged;
                 guiField.OnConfirm += x => OnFieldValueConfirm();
                 guiField.OnFocusLost += OnFieldValueConfirm;
-                guiField.OnFocusGained += RecordStateForUndoRequested;
+                guiField.OnFocusGained += StartUndo;
 
                 layout.AddElement(layoutIndex, guiField);
             }
@@ -76,12 +76,14 @@ namespace bs.Editor
         /// <param name="newValue">New value of the 3D vector field.</param>
         private void OnFieldValueChanged(Vector4 newValue)
         {
-            RecordStateForUndoIfNeeded();
+            StartUndo();
 
             Quaternion quaternion = new Quaternion(newValue.x, newValue.y, newValue.y, newValue.w);
 
             property.SetValue(quaternion);
             state |= InspectableState.ModifyInProgress;
+
+            EndUndo();
         }
 
         /// <summary>

+ 3 - 1
Source/EditorManaged/Windows/Inspector/InspectableRRef.cs

@@ -73,10 +73,12 @@ namespace bs.Editor
             if (newValue != null && !newValue.IsLoaded && style.StyleFlags.HasFlag(InspectableFieldStyleFlags.LoadOnAssign))
                 Resources.Load<Resource>(newValue.UUID);
 
-            RecordStateForUndo();
+            StartUndo();
 
             property.SetValue(newValue);
             state = InspectableState.Modified;
+
+            EndUndo();
         }
     }
 

+ 8 - 4
Source/EditorManaged/Windows/Inspector/InspectableRadian.cs

@@ -50,9 +50,13 @@ namespace bs.Editor
                         guiFloatField.SetRange(style.RangeStyle.Min, style.RangeStyle.Max);
                 }
                 guiFloatField.OnChanged += OnFieldValueChanged;
-                guiFloatField.OnConfirmed += OnFieldValueConfirm;
+                guiFloatField.OnConfirmed += () =>
+                {
+                    OnFieldValueConfirm();
+                    StartUndo();
+                };
                 guiFloatField.OnFocusLost += OnFieldValueConfirm;
-                guiFloatField.OnFocusGained += RecordStateForUndoRequested;
+                guiFloatField.OnFocusGained += StartUndo;
 
                 layout.AddElement(layoutIndex, guiFloatField);
             }
@@ -83,8 +87,6 @@ namespace bs.Editor
         /// <param name="newValue">New value of the float field.</param>
         private void OnFieldValueChanged(float newValue)
         {
-            RecordStateForUndoIfNeeded();
-
             property.SetValue(new Radian(newValue));
             state |= InspectableState.ModifyInProgress;
         }
@@ -96,6 +98,8 @@ namespace bs.Editor
         {
             if (state.HasFlag(InspectableState.ModifyInProgress))
                 state |= InspectableState.Modified;
+
+            EndUndo();
         }
     }
 

+ 3 - 3
Source/EditorManaged/Windows/Inspector/InspectableRangedFloat.cs

@@ -44,7 +44,7 @@ namespace bs.Editor
                     guiFloatField.Step = style.StepStyle.Step;
                 guiFloatField.OnChanged += OnFieldValueChanged;
                 guiFloatField.OnFocusLost += OnFieldValueConfirm;
-                guiFloatField.OnFocusGained += RecordStateForUndoRequested;
+                guiFloatField.OnFocusGained += StartUndo;
 
                 layout.AddElement(layoutIndex, guiFloatField);
             }
@@ -75,8 +75,6 @@ namespace bs.Editor
         /// <param name="newValue">New value of the float field.</param>
         private void OnFieldValueChanged(float newValue)
         {
-            RecordStateForUndoIfNeeded();
-
             property.SetValue(newValue);
             state |= InspectableState.ModifyInProgress;
         }
@@ -88,6 +86,8 @@ namespace bs.Editor
         {
             if (state.HasFlag(InspectableState.ModifyInProgress))
                 state |= InspectableState.Modified;
+
+            EndUndo();
         }
     }
 

+ 3 - 3
Source/EditorManaged/Windows/Inspector/InspectableRangedInt.cs

@@ -44,7 +44,7 @@ namespace bs.Editor
                     guiIntField.Step = style.StepStyle.Step;
                 guiIntField.OnChanged += OnFieldValueChanged;
                 guiIntField.OnFocusLost += OnFieldValueConfirm;
-                guiIntField.OnFocusGained += RecordStateForUndoRequested;
+                guiIntField.OnFocusGained += StartUndo;
 
                 layout.AddElement(layoutIndex, guiIntField);
             }
@@ -75,8 +75,6 @@ namespace bs.Editor
         /// <param name="newValue">New value of the float field.</param>
         private void OnFieldValueChanged(float newValue)
         {
-            RecordStateForUndoIfNeeded();
-
             property.SetValue((int)newValue);
             state |= InspectableState.ModifyInProgress;
         }
@@ -88,6 +86,8 @@ namespace bs.Editor
         {
             if (state.HasFlag(InspectableState.ModifyInProgress))
                 state |= InspectableState.Modified;
+
+            EndUndo();
         }
     }
 

+ 3 - 1
Source/EditorManaged/Windows/Inspector/InspectableResource.cs

@@ -64,10 +64,12 @@ namespace bs.Editor
         {
             Resource res = Resources.Load<Resource>(newValue.UUID);
 
-            RecordStateForUndo();
+            StartUndo();
 
             property.SetValue(res);
             state = InspectableState.Modified;
+
+            EndUndo();
         }
     }
 

+ 8 - 4
Source/EditorManaged/Windows/Inspector/InspectableString.cs

@@ -40,9 +40,13 @@ namespace bs.Editor
             {
                 guiField = new GUITextField(new GUIContent(title));
                 guiField.OnChanged += OnFieldValueChanged;
-                guiField.OnConfirmed += OnFieldValueConfirm;
+                guiField.OnConfirmed += () =>
+                {
+                    OnFieldValueConfirm();
+                    StartUndo();
+                };
                 guiField.OnFocusLost += OnFieldValueConfirm;
-                guiField.OnFocusGained += RecordStateForUndoRequested;
+                guiField.OnFocusGained += StartUndo;
 
                 layout.AddElement(layoutIndex, guiField);
             }
@@ -67,8 +71,6 @@ namespace bs.Editor
         /// <param name="newValue">New value of the text field.</param>
         private void OnFieldValueChanged(string newValue)
         {
-            RecordStateForUndoIfNeeded();
-
             property.SetValue(newValue);
             state |= InspectableState.ModifyInProgress;
         }
@@ -80,6 +82,8 @@ namespace bs.Editor
         {
             if (state.HasFlag(InspectableState.ModifyInProgress))
                 state |= InspectableState.Modified;
+
+            EndUndo();
         }
     }
 

+ 3 - 1
Source/EditorManaged/Windows/Inspector/InspectableTexture.cs

@@ -63,7 +63,7 @@ namespace bs.Editor
         /// <param name="newValue">New resource to reference.</param>
         private void OnFieldValueChanged(RRefBase newValue)
         {
-            RecordStateForUndo();
+            StartUndo();
 
             if (property.Type == SerializableProperty.FieldType.Resource)
                 property.SetValue(newValue.GenericValue);
@@ -71,6 +71,8 @@ namespace bs.Editor
                 property.SetValue(newValue);
 
             state = InspectableState.Modified;
+
+            EndUndo();
         }
     }
 

+ 14 - 5
Source/EditorManaged/Windows/Inspector/InspectableVector2.cs

@@ -40,9 +40,18 @@ namespace bs.Editor
             {
                 guiField = new GUIVector2Field(new GUIContent(title));
                 guiField.OnComponentChanged += OnFieldValueChanged;
-                guiField.OnConfirm += x => OnFieldValueConfirm();
-                guiField.OnFocusLost += OnFieldValueConfirm;
-                guiField.OnFocusGained += RecordStateForUndoRequested;
+                guiField.OnConfirm += x =>
+                {
+                    OnFieldValueConfirm();
+                    StartUndo(x.ToString());
+                };
+                guiField.OnComponentFocusChanged += (focus, comp) =>
+                {
+                    if(focus)
+                        StartUndo(comp.ToString());
+                    else
+                        OnFieldValueConfirm();
+                };
 
                 layout.AddElement(layoutIndex, guiField);
             }
@@ -79,8 +88,6 @@ namespace bs.Editor
         /// <param name="component">Component that was changed.</param>
         private void OnFieldValueChanged(float newValue, VectorComponent component)
         {
-            RecordStateForUndoIfNeeded(component.ToString());
-
             property.SetValue(guiField.Value);
             state |= InspectableState.ModifyInProgress;
         }
@@ -92,6 +99,8 @@ namespace bs.Editor
         {
             if (state.HasFlag(InspectableState.ModifyInProgress))
                 state |= InspectableState.Modified;
+
+            EndUndo();
         }
     }
 

+ 8 - 4
Source/EditorManaged/Windows/Inspector/InspectableVector2Distribution.cs

@@ -39,9 +39,13 @@ namespace bs.Editor
             {
                 guiDistributionField = new GUIVector2DistributionField(new GUIContent(title));
                 guiDistributionField.OnChanged += OnFieldValueChanged;
-                guiDistributionField.OnConfirmed += OnFieldValueConfirm;
+                guiDistributionField.OnConfirmed += () =>
+                {
+                    OnFieldValueConfirm();
+                    StartUndo();
+                };
                 guiDistributionField.OnFocusLost += OnFieldValueConfirm;
-                guiDistributionField.OnFocusGained += RecordStateForUndoRequested;
+                guiDistributionField.OnFocusGained += StartUndo;
 
                 layout.AddElement(layoutIndex, guiDistributionField);
             }
@@ -65,8 +69,6 @@ namespace bs.Editor
         /// </summary>
         private void OnFieldValueChanged()
         {
-            RecordStateForUndoIfNeeded();
-
             property.SetValue(guiDistributionField.Value);
             state |= InspectableState.ModifyInProgress;
         }
@@ -78,6 +80,8 @@ namespace bs.Editor
         {
             if (state.HasFlag(InspectableState.ModifyInProgress))
                 state |= InspectableState.Modified;
+
+            EndUndo();
         }
     }
 

+ 14 - 5
Source/EditorManaged/Windows/Inspector/InspectableVector3.cs

@@ -40,9 +40,18 @@ namespace bs.Editor
             {
                 guiField = new GUIVector3Field(new GUIContent(title));
                 guiField.OnComponentChanged += OnFieldValueChanged;
-                guiField.OnConfirm += x => OnFieldValueConfirm();
-                guiField.OnFocusLost += OnFieldValueConfirm;
-                guiField.OnFocusGained += RecordStateForUndoRequested;
+                guiField.OnConfirm += x =>
+                {
+                    OnFieldValueConfirm();
+                    StartUndo(x.ToString());
+                };
+                guiField.OnComponentFocusChanged += (focus, comp) =>
+                {
+                    if(focus)
+                        StartUndo(comp.ToString());
+                    else
+                        OnFieldValueConfirm();
+                };
 
                 layout.AddElement(layoutIndex, guiField);
             }
@@ -81,8 +90,6 @@ namespace bs.Editor
         /// <param name="component">Component that was changed.</param>
         private void OnFieldValueChanged(float newValue, VectorComponent component)
         {
-            RecordStateForUndoIfNeeded(component.ToString());
-
             property.SetValue(guiField.Value);
             state |= InspectableState.ModifyInProgress;
         }
@@ -94,6 +101,8 @@ namespace bs.Editor
         {
             if (state.HasFlag(InspectableState.ModifyInProgress))
                 state |= InspectableState.Modified;
+
+            EndUndo();
         }
     }
 

+ 8 - 4
Source/EditorManaged/Windows/Inspector/InspectableVector3Distribution.cs

@@ -39,9 +39,13 @@ namespace bs.Editor
             {
                 guiDistributionField = new GUIVector3DistributionField(new GUIContent(title));
                 guiDistributionField.OnChanged += OnFieldValueChanged;
-                guiDistributionField.OnConfirmed += OnFieldValueConfirm;
+                guiDistributionField.OnConfirmed += () =>
+                {
+                    OnFieldValueConfirm();
+                    StartUndo();
+                };
                 guiDistributionField.OnFocusLost += OnFieldValueConfirm;
-                guiDistributionField.OnFocusGained += RecordStateForUndoRequested;
+                guiDistributionField.OnFocusGained += StartUndo;
 
                 layout.AddElement(layoutIndex, guiDistributionField);
             }
@@ -65,8 +69,6 @@ namespace bs.Editor
         /// </summary>
         private void OnFieldValueChanged()
         {
-            RecordStateForUndoIfNeeded();
-
             property.SetValue(guiDistributionField.Value);
             state |= InspectableState.ModifyInProgress;
         }
@@ -78,6 +80,8 @@ namespace bs.Editor
         {
             if (state.HasFlag(InspectableState.ModifyInProgress))
                 state |= InspectableState.Modified;
+
+            EndUndo();
         }
     }
 

+ 14 - 5
Source/EditorManaged/Windows/Inspector/InspectableVector4.cs

@@ -40,9 +40,18 @@ namespace bs.Editor
             {
                 guiField = new GUIVector4Field(new GUIContent(title));
                 guiField.OnComponentChanged += OnFieldValueChanged;
-                guiField.OnConfirm += x => OnFieldValueConfirm();
-                guiField.OnFocusLost += OnFieldValueConfirm;
-                guiField.OnFocusGained += RecordStateForUndoRequested;
+                guiField.OnConfirm += x =>
+                {
+                    OnFieldValueConfirm();
+                    StartUndo(x.ToString());
+                };
+                guiField.OnComponentFocusChanged += (focus, comp) =>
+                {
+                    if (focus)
+                        StartUndo(comp.ToString());
+                    else
+                        OnFieldValueConfirm();
+                };
 
                 layout.AddElement(layoutIndex, guiField);
             }
@@ -83,8 +92,6 @@ namespace bs.Editor
         /// <param name="component">Component that was changed.</param>
         private void OnFieldValueChanged(float newValue, VectorComponent component)
         {
-            RecordStateForUndoIfNeeded(component.ToString());
-
             property.SetValue(guiField.Value);
             state |= InspectableState.ModifyInProgress;
         }
@@ -96,6 +103,8 @@ namespace bs.Editor
         {
             if (state.HasFlag(InspectableState.ModifyInProgress))
                 state |= InspectableState.Modified;
+
+            EndUndo();
         }
     }
 

+ 66 - 41
Source/EditorManaged/Windows/Inspector/InspectorWindow.cs

@@ -75,7 +75,6 @@ namespace bs.Editor
         private GUIVector3Field soPos;
         private GUIVector3Field soRot;
         private GUIVector3Field soScale;
-        private bool undoRecordNeeded = true;
 
         private Quaternion lastRotation;
 
@@ -253,8 +252,12 @@ namespace bs.Editor
             soNameInput = new GUITextBox(false, GUIOption.FlexibleWidth(180));
             soNameInput.Text = activeSO.Name;
             soNameInput.OnChanged += OnSceneObjectRename;
-            soNameInput.OnConfirmed += OnModifyConfirm;
-            soNameInput.OnFocusGained += RecordStateForUndoRequested;
+            soNameInput.OnConfirmed += () =>
+            {
+                OnModifyConfirm();
+                StartUndo("name");
+            };
+            soNameInput.OnFocusGained += () => StartUndo("name");
             soNameInput.OnFocusLost += OnModifyConfirm;
 
             nameLayout.AddElement(soActiveToggle);
@@ -277,25 +280,52 @@ namespace bs.Editor
             sceneObjectLayout.AddElement(soPos);
 
             soPos.OnComponentChanged += OnPositionChanged;
-            soPos.OnConfirm += x => OnModifyConfirm();
-            soPos.OnFocusGained += RecordStateForUndoRequested;
-            soPos.OnFocusLost += OnModifyConfirm;
+            soPos.OnConfirm += x =>
+            {
+                OnModifyConfirm();
+                StartUndo("position." + x.ToString());
+            };
+            soPos.OnComponentFocusChanged += (focus, comp) =>
+            {
+                if (focus)
+                    StartUndo("position." + comp.ToString());
+                else
+                    OnModifyConfirm();
+            };
 
             soRot = new GUIVector3Field(new LocEdString("Rotation"), 50);
             sceneObjectLayout.AddElement(soRot);
 
             soRot.OnComponentChanged += OnRotationChanged;
-            soRot.OnConfirm += x => OnModifyConfirm();
-            soRot.OnFocusGained += RecordStateForUndoRequested;
-            soRot.OnFocusLost += OnModifyConfirm;
+            soRot.OnConfirm += x =>
+            {
+                OnModifyConfirm();
+                StartUndo("rotation." + x.ToString());
+            };
+            soRot.OnComponentFocusChanged += (focus, comp) =>
+            {
+                if (focus)
+                    StartUndo("rotation." + comp.ToString());
+                else
+                    OnModifyConfirm();
+            };
 
             soScale = new GUIVector3Field(new LocEdString("Scale"), 50);
             sceneObjectLayout.AddElement(soScale);
 
             soScale.OnComponentChanged += OnScaleChanged;
-            soScale.OnConfirm += x => OnModifyConfirm();
-            soScale.OnFocusGained += RecordStateForUndoRequested;
-            soScale.OnFocusLost += OnModifyConfirm;
+            soScale.OnConfirm += x =>
+            {
+                OnModifyConfirm();
+                StartUndo("scale." + x.ToString());
+            };
+            soScale.OnComponentFocusChanged += (focus, comp) =>
+            {
+                if (focus)
+                    StartUndo("scale." + comp.ToString());
+                else
+                    OnModifyConfirm();
+            };
 
             sceneObjectLayout.AddFlexibleSpace();
 
@@ -686,25 +716,25 @@ namespace bs.Editor
 
             if (activeSO.UUID == uuid)
             {
-                if(path == "Position.X")
+                if(path == "position.X")
                     soPos.SetInputFocus(VectorComponent.X, true);
-                else if(path == "Position.Y")
+                else if(path == "position.Y")
                     soPos.SetInputFocus(VectorComponent.Y, true);
-                else if(path == "Position.Z")
+                else if(path == "position.Z")
                     soPos.SetInputFocus(VectorComponent.Z, true);
-                else if(path == "Rotation.X")
+                else if(path == "rotation.X")
                     soRot.SetInputFocus(VectorComponent.X, true);
-                else if(path == "Rotation.Y")
+                else if(path == "rotation.Y")
                     soRot.SetInputFocus(VectorComponent.Y, true);
-                else if(path == "Rotation.Z")
+                else if(path == "rotation.Z")
                     soRot.SetInputFocus(VectorComponent.Z, true);
-                else if(path == "Scale.X")
+                else if(path == "scale.X")
                     soScale.SetInputFocus(VectorComponent.X, true);
-                else if(path == "Scale.Y")
+                else if(path == "scale.Y")
                     soScale.SetInputFocus(VectorComponent.Y, true);
-                else if(path == "Scale.Z")
+                else if(path == "scale.Z")
                     soScale.SetInputFocus(VectorComponent.Z, true);
-                else if (path == "Name")
+                else if (path == "name")
                     soNameInput.Focus = true;
             }
             else
@@ -735,7 +765,6 @@ namespace bs.Editor
         {
             if (activeSO != null)
             {
-                RecordSceneObjectHeaderForUndo("Name");
                 activeSO.Name = name;
 
                 modifyState |= InspectableState.ModifyInProgress;
@@ -751,8 +780,9 @@ namespace bs.Editor
         {
             if (activeSO != null)
             {
-                RecordSceneObjectHeaderForUndo("Active");
+                StartUndo("active");
                 activeSO.Active = active;
+                EndUndo();
             }
         }
 
@@ -763,6 +793,8 @@ namespace bs.Editor
         {
             if (modifyState.HasFlag(InspectableState.ModifyInProgress))
                 modifyState = InspectableState.Modified;
+
+            EndUndo();
         }
 
         /// <summary>
@@ -776,8 +808,6 @@ namespace bs.Editor
             if (activeSO == null)
                 return;
 
-            RecordSceneObjectHeaderForUndo("Position." + component);
-
             if (EditorApplication.ActiveCoordinateMode == HandleCoordinateMode.World)
                 activeSO.Position = soPos.Value;
             else
@@ -798,8 +828,6 @@ namespace bs.Editor
             if (activeSO == null)
                 return;
 
-            RecordSceneObjectHeaderForUndo("Rotation." + component);
-
             Quaternion rotation = Quaternion.FromEuler(soRot.Value);
             if (EditorApplication.ActiveCoordinateMode == HandleCoordinateMode.World)
                 activeSO.Rotation = rotation;
@@ -822,8 +850,6 @@ namespace bs.Editor
             if (activeSO == null)
                 return;
 
-            RecordSceneObjectHeaderForUndo("Scale." + component);
-
             activeSO.LocalScale = soScale.Value;
 
             modifyState = InspectableState.ModifyInProgress;
@@ -862,25 +888,24 @@ namespace bs.Editor
         }
 
         /// <summary>
-        /// Records the current state of a scene object field for the purposes of undo/redo. Generally this should be
-        /// called just before making changes to the field value.
+        /// Notifies the system to start recording a new undo command. Any changes to scene object fields after this is
+        /// called will be recorded in the command. User must call <see cref="EndUndo"/> after the field is done being
+        /// changed.
         /// </summary>
         /// <param name="name">Name of the field being changed.</param>
-        private void RecordSceneObjectHeaderForUndo(string name)
+        private void StartUndo(string name)
         {
-            if (!undoRecordNeeded)
-                return;
-
-            GameObjectUndo.RecordSceneObjectHeader(activeSO, name);
-            undoRecordNeeded = false;
+            if (activeSO != null)
+                GameObjectUndo.RecordSceneObjectHeader(activeSO, name);
         }
 
         /// <summary>
-        /// Notifies the system that the next call to <see cref="RecordSceneObjectHeaderForUndo"/> should record the state.
+        /// Finishes recording an undo command started via <see cref="StartUndo(string)"/>. If any changes are detected on
+        /// the field an undo command is recorded onto the undo-redo stack, otherwise nothing is done.
         /// </summary>
-        private void RecordStateForUndoRequested()
+        private void EndUndo()
         {
-            undoRecordNeeded = true;
+            GameObjectUndo.ResolveDiffs();
         }
     }
 

+ 8 - 0
Source/EditorScript/Generated/BsScriptGUIVector2Field.generated.cpp

@@ -14,6 +14,7 @@ namespace bs
 {
 	ScriptGUIVector2Field::onValueChangedThunkDef ScriptGUIVector2Field::onValueChangedThunk; 
 	ScriptGUIVector2Field::onComponentChangedThunkDef ScriptGUIVector2Field::onComponentChangedThunk; 
+	ScriptGUIVector2Field::onComponentFocusChangedThunkDef ScriptGUIVector2Field::onComponentFocusChangedThunk; 
 	ScriptGUIVector2Field::onConfirmThunkDef ScriptGUIVector2Field::onConfirmThunk; 
 
 	ScriptGUIVector2Field::ScriptGUIVector2Field(MonoObject* managedInstance, GUIVector2Field* value)
@@ -21,6 +22,7 @@ namespace bs
 	{
 		value->onValueChanged.connect(std::bind(&ScriptGUIVector2Field::onValueChanged, this, std::placeholders::_1));
 		value->onComponentChanged.connect(std::bind(&ScriptGUIVector2Field::onComponentChanged, this, std::placeholders::_1, std::placeholders::_2));
+		value->onComponentFocusChanged.connect(std::bind(&ScriptGUIVector2Field::onComponentFocusChanged, this, std::placeholders::_1, std::placeholders::_2));
 		value->onConfirm.connect(std::bind(&ScriptGUIVector2Field::onConfirm, this, std::placeholders::_1));
 	}
 
@@ -38,6 +40,7 @@ namespace bs
 
 		onValueChangedThunk = (onValueChangedThunkDef)metaData.scriptClass->getMethodExact("Internal_onValueChanged", "Vector2&")->getThunk();
 		onComponentChangedThunk = (onComponentChangedThunkDef)metaData.scriptClass->getMethodExact("Internal_onComponentChanged", "single,VectorComponent")->getThunk();
+		onComponentFocusChangedThunk = (onComponentFocusChangedThunkDef)metaData.scriptClass->getMethodExact("Internal_onComponentFocusChanged", "bool,VectorComponent")->getThunk();
 		onConfirmThunk = (onConfirmThunkDef)metaData.scriptClass->getMethodExact("Internal_onConfirm", "VectorComponent")->getThunk();
 	}
 
@@ -53,6 +56,11 @@ namespace bs
 		MonoUtil::invokeThunk(onComponentChangedThunk, getManagedInstance(), p0, p1);
 	}
 
+	void ScriptGUIVector2Field::onComponentFocusChanged(bool p0, VectorComponent p1)
+	{
+		MonoUtil::invokeThunk(onComponentFocusChangedThunk, getManagedInstance(), p0, p1);
+	}
+
 	void ScriptGUIVector2Field::onConfirm(VectorComponent p0)
 	{
 		MonoUtil::invokeThunk(onConfirmThunk, getManagedInstance(), p0);

+ 3 - 0
Source/EditorScript/Generated/BsScriptGUIVector2Field.generated.h

@@ -23,12 +23,15 @@ namespace bs
 	private:
 		void onValueChanged(const Vector2& p0);
 		void onComponentChanged(float p0, VectorComponent p1);
+		void onComponentFocusChanged(bool p0, VectorComponent p1);
 		void onConfirm(VectorComponent p0);
 
 		typedef void(BS_THUNKCALL *onValueChangedThunkDef) (MonoObject*, MonoObject* p0, MonoException**);
 		static onValueChangedThunkDef onValueChangedThunk;
 		typedef void(BS_THUNKCALL *onComponentChangedThunkDef) (MonoObject*, float p0, VectorComponent p1, MonoException**);
 		static onComponentChangedThunkDef onComponentChangedThunk;
+		typedef void(BS_THUNKCALL *onComponentFocusChangedThunkDef) (MonoObject*, bool p0, VectorComponent p1, MonoException**);
+		static onComponentFocusChangedThunkDef onComponentFocusChangedThunk;
 		typedef void(BS_THUNKCALL *onConfirmThunkDef) (MonoObject*, VectorComponent p0, MonoException**);
 		static onConfirmThunkDef onConfirmThunk;
 

+ 8 - 0
Source/EditorScript/Generated/BsScriptGUIVector3Field.generated.cpp

@@ -14,6 +14,7 @@ namespace bs
 {
 	ScriptGUIVector3Field::onValueChangedThunkDef ScriptGUIVector3Field::onValueChangedThunk; 
 	ScriptGUIVector3Field::onComponentChangedThunkDef ScriptGUIVector3Field::onComponentChangedThunk; 
+	ScriptGUIVector3Field::onComponentFocusChangedThunkDef ScriptGUIVector3Field::onComponentFocusChangedThunk; 
 	ScriptGUIVector3Field::onConfirmThunkDef ScriptGUIVector3Field::onConfirmThunk; 
 
 	ScriptGUIVector3Field::ScriptGUIVector3Field(MonoObject* managedInstance, GUIVector3Field* value)
@@ -21,6 +22,7 @@ namespace bs
 	{
 		value->onValueChanged.connect(std::bind(&ScriptGUIVector3Field::onValueChanged, this, std::placeholders::_1));
 		value->onComponentChanged.connect(std::bind(&ScriptGUIVector3Field::onComponentChanged, this, std::placeholders::_1, std::placeholders::_2));
+		value->onComponentFocusChanged.connect(std::bind(&ScriptGUIVector3Field::onComponentFocusChanged, this, std::placeholders::_1, std::placeholders::_2));
 		value->onConfirm.connect(std::bind(&ScriptGUIVector3Field::onConfirm, this, std::placeholders::_1));
 	}
 
@@ -38,6 +40,7 @@ namespace bs
 
 		onValueChangedThunk = (onValueChangedThunkDef)metaData.scriptClass->getMethodExact("Internal_onValueChanged", "Vector3&")->getThunk();
 		onComponentChangedThunk = (onComponentChangedThunkDef)metaData.scriptClass->getMethodExact("Internal_onComponentChanged", "single,VectorComponent")->getThunk();
+		onComponentFocusChangedThunk = (onComponentFocusChangedThunkDef)metaData.scriptClass->getMethodExact("Internal_onComponentFocusChanged", "bool,VectorComponent")->getThunk();
 		onConfirmThunk = (onConfirmThunkDef)metaData.scriptClass->getMethodExact("Internal_onConfirm", "VectorComponent")->getThunk();
 	}
 
@@ -53,6 +56,11 @@ namespace bs
 		MonoUtil::invokeThunk(onComponentChangedThunk, getManagedInstance(), p0, p1);
 	}
 
+	void ScriptGUIVector3Field::onComponentFocusChanged(bool p0, VectorComponent p1)
+	{
+		MonoUtil::invokeThunk(onComponentFocusChangedThunk, getManagedInstance(), p0, p1);
+	}
+
 	void ScriptGUIVector3Field::onConfirm(VectorComponent p0)
 	{
 		MonoUtil::invokeThunk(onConfirmThunk, getManagedInstance(), p0);

+ 3 - 0
Source/EditorScript/Generated/BsScriptGUIVector3Field.generated.h

@@ -23,12 +23,15 @@ namespace bs
 	private:
 		void onValueChanged(const Vector3& p0);
 		void onComponentChanged(float p0, VectorComponent p1);
+		void onComponentFocusChanged(bool p0, VectorComponent p1);
 		void onConfirm(VectorComponent p0);
 
 		typedef void(BS_THUNKCALL *onValueChangedThunkDef) (MonoObject*, MonoObject* p0, MonoException**);
 		static onValueChangedThunkDef onValueChangedThunk;
 		typedef void(BS_THUNKCALL *onComponentChangedThunkDef) (MonoObject*, float p0, VectorComponent p1, MonoException**);
 		static onComponentChangedThunkDef onComponentChangedThunk;
+		typedef void(BS_THUNKCALL *onComponentFocusChangedThunkDef) (MonoObject*, bool p0, VectorComponent p1, MonoException**);
+		static onComponentFocusChangedThunkDef onComponentFocusChangedThunk;
 		typedef void(BS_THUNKCALL *onConfirmThunkDef) (MonoObject*, VectorComponent p0, MonoException**);
 		static onConfirmThunkDef onConfirmThunk;
 

+ 8 - 0
Source/EditorScript/Generated/BsScriptGUIVector4Field.generated.cpp

@@ -14,6 +14,7 @@ namespace bs
 {
 	ScriptGUIVector4Field::onValueChangedThunkDef ScriptGUIVector4Field::onValueChangedThunk; 
 	ScriptGUIVector4Field::onComponentChangedThunkDef ScriptGUIVector4Field::onComponentChangedThunk; 
+	ScriptGUIVector4Field::onComponentFocusChangedThunkDef ScriptGUIVector4Field::onComponentFocusChangedThunk; 
 	ScriptGUIVector4Field::onConfirmThunkDef ScriptGUIVector4Field::onConfirmThunk; 
 
 	ScriptGUIVector4Field::ScriptGUIVector4Field(MonoObject* managedInstance, GUIVector4Field* value)
@@ -21,6 +22,7 @@ namespace bs
 	{
 		value->onValueChanged.connect(std::bind(&ScriptGUIVector4Field::onValueChanged, this, std::placeholders::_1));
 		value->onComponentChanged.connect(std::bind(&ScriptGUIVector4Field::onComponentChanged, this, std::placeholders::_1, std::placeholders::_2));
+		value->onComponentFocusChanged.connect(std::bind(&ScriptGUIVector4Field::onComponentFocusChanged, this, std::placeholders::_1, std::placeholders::_2));
 		value->onConfirm.connect(std::bind(&ScriptGUIVector4Field::onConfirm, this, std::placeholders::_1));
 	}
 
@@ -38,6 +40,7 @@ namespace bs
 
 		onValueChangedThunk = (onValueChangedThunkDef)metaData.scriptClass->getMethodExact("Internal_onValueChanged", "Vector4&")->getThunk();
 		onComponentChangedThunk = (onComponentChangedThunkDef)metaData.scriptClass->getMethodExact("Internal_onComponentChanged", "single,VectorComponent")->getThunk();
+		onComponentFocusChangedThunk = (onComponentFocusChangedThunkDef)metaData.scriptClass->getMethodExact("Internal_onComponentFocusChanged", "bool,VectorComponent")->getThunk();
 		onConfirmThunk = (onConfirmThunkDef)metaData.scriptClass->getMethodExact("Internal_onConfirm", "VectorComponent")->getThunk();
 	}
 
@@ -53,6 +56,11 @@ namespace bs
 		MonoUtil::invokeThunk(onComponentChangedThunk, getManagedInstance(), p0, p1);
 	}
 
+	void ScriptGUIVector4Field::onComponentFocusChanged(bool p0, VectorComponent p1)
+	{
+		MonoUtil::invokeThunk(onComponentFocusChangedThunk, getManagedInstance(), p0, p1);
+	}
+
 	void ScriptGUIVector4Field::onConfirm(VectorComponent p0)
 	{
 		MonoUtil::invokeThunk(onConfirmThunk, getManagedInstance(), p0);

+ 3 - 0
Source/EditorScript/Generated/BsScriptGUIVector4Field.generated.h

@@ -23,12 +23,15 @@ namespace bs
 	private:
 		void onValueChanged(const Vector4& p0);
 		void onComponentChanged(float p0, VectorComponent p1);
+		void onComponentFocusChanged(bool p0, VectorComponent p1);
 		void onConfirm(VectorComponent p0);
 
 		typedef void(BS_THUNKCALL *onValueChangedThunkDef) (MonoObject*, MonoObject* p0, MonoException**);
 		static onValueChangedThunkDef onValueChangedThunk;
 		typedef void(BS_THUNKCALL *onComponentChangedThunkDef) (MonoObject*, float p0, VectorComponent p1, MonoException**);
 		static onComponentChangedThunkDef onComponentChangedThunk;
+		typedef void(BS_THUNKCALL *onComponentFocusChangedThunkDef) (MonoObject*, bool p0, VectorComponent p1, MonoException**);
+		static onComponentFocusChangedThunkDef onComponentFocusChangedThunk;
 		typedef void(BS_THUNKCALL *onConfirmThunkDef) (MonoObject*, VectorComponent p0, MonoException**);
 		static onConfirmThunkDef onConfirmThunk;