Hello,
I'm trying to correctly register undos in a custom inspector, and it doesn't look that simple.
How it should be used (C#)
In theory, it should be simple:
Undo.RegisterUndo( (MyClass) target, "Undo name" );
When you call RegisterUndo, all the properties of the given object are registered, so that they can be succesfully undone.
The problem
RegisterUndo should be called immediately BEFORE a property is going to be changed. But how can you know it? Also, if for example you have an IntField, and you drag its value, RegisterUndo should be called only before starting the drag, and not each time the value changes. How do you achieve this?
Possible (non-working) solutions I investigated
- I tried to register undos when a user presses the left mouse button on a GUI control (using GUIUtility.hotControl), but hotControl changes even if you press other Inspectors of the same object (plus, there's no way to determine if you actually changed something after clicking on it - apart using tempVars for each GUI control, which looks terribly verbose)
- Using tempVars for each GUI control. But, apart from it being terribly verbose, dragging an IntField (or else) value breaks it.
The C# solution (thanks to mr. Statement)
EDIT: I created a helper class and placed it on Unify. See the “correct answer” for a link.
Working on mr. Statement answer, I found the correct solution. This solution registers all undos correctly, and prevents registering multiple or useless undos (checking if, after the left mouse button was pressed, something actually changed). Also, it works even if you switch field using the TAB key.
Here is an example Class, and its relative custom Inspector.
Sample Class
using UnityEngine;
using System.Collections;
public class HOEXInspectorUndo : MonoBehaviour
{
public enum SampleEnum
{
Enum_A,
Enum_B,
Enum_C,
Enum_D
}
public int sampleInt = 0;
public int sampleInt2 = 0;
public float sampleFloat = 10.52f;
public bool sampleBool = true;
public SampleEnum sampleEnum = SampleEnum.Enum_A;
}
Sample Class's custom Inspector
using UnityEditor;
using UnityEngine;
using System.Collections;
[CustomEditor( typeof( HOEXInspectorUndo ) )]
public class HOEXInspectorUndoEditor : Editor
{
private HOEXInspectorUndo src;
private bool listeningForGuiChanges;
private bool guiChanged;
private void OnEnable()
{
src = target as HOEXInspectorUndo;
}
override public void OnInspectorGUI()
{
CheckUndo();
src.sampleInt = EditorGUILayout.IntField( "Sample Int", src.sampleInt );
src.sampleInt2 = EditorGUILayout.IntSlider( "Sample Slider", src.sampleInt2, 0, 100 );
src.sampleFloat = EditorGUILayout.FloatField( "Sample Float", src.sampleFloat );
if ( GUILayout.Button( "Set Sample Float to 17.2" ) ) {
guiChanged = true;
src.sampleFloat = 17.2f;
}
src.sampleBool = EditorGUILayout.Toggle( "Sample Bool", src.sampleBool );
src.sampleEnum = ( HOEXInspectorUndo.SampleEnum ) EditorGUILayout.EnumPopup( "Sample Enum", src.sampleEnum );
if ( GUI.changed ) {
guiChanged = true;
}
}
private void CheckUndo()
{
Event e = Event.current;
if ( e.type == EventType.MouseDown && e.button == 0 || e.type == EventType.KeyUp && ( e.keyCode == KeyCode.Tab ) ) {
// When the LMB is pressed or the TAB key is released,
// store a snapshot, but don't register it as an undo
// ( so that if nothing changes we avoid storing a useless undo)
Debug.Log( "PREPARE UNDO SNAPSHOT" );
Undo.SetSnapshotTarget( src, "HOEXInspectorUndo" );
Undo.CreateSnapshot();
Undo.ClearSnapshotTarget();
listeningForGuiChanges = true;
guiChanged = false;
}
if ( listeningForGuiChanges && guiChanged ) {
// Some GUI value changed after pressing the mouse.
// Register the previous snapshot as a valid undo.
Debug.Log( "REGISTER UNDO" );
Undo.SetSnapshotTarget( src, "HOEXInspectorUndo" );
Undo.RegisterSnapshot();
Undo.ClearSnapshotTarget();
listeningForGuiChanges = false;
}
}
}