EditorUndoManager

Author: Daniele Giardini (Holoville)

Description
This class allows to manage undo/redo inside any Unity Inspector or Window in an effortless and functional way. It stores undo snapshots only when they're needed (taking into account also TAB field changes and sliders), so that using Unity's undo/redo system correctly undoes/redoes the effective changes that took place.

Works also with windows who manage multiple sources (see examples).

UPDATE:

- Now also manages prefab instances correctly (otherwise values change in a prefab instance were not updated correctly when using a custom inspector).

Usage
This is an Editor class, thus you have to place it inside an Editor folder, then:
 * Inside your Inspector/Window class, create a HOEditorUndoManager variable.
 * Instantiate HOEditorUndoManager inside the OnEnable method.
 * Inside OnGUI/OnInspectorGUI, call [undoManagerInstance].CheckUndo BEFORE the first UnityGUI code.
 * Inside OnGUI/OnInspectorGUI, call [undoManagerInstance].CheckDirty AFTER all the UnityGUI code.

C# - HOEditorUndoManager.cs
method). /// /// /// Call  BEFORE the first UnityGUI call in  . /// /// /// Call   AFTER the last UnityGUI call in  . /// /// /// public class HOEditorUndoManager {	// VARS ///////////////////////////////////////////////////	private		Object				defTarget;	private		string				defName;	private		bool				autoSetDirty;	private		bool				listeningForGuiChanges;	private		bool				isMouseDown;	private		Object				waitingToRecordPrefab; // If different than NULL indicates the prefab instance that will need to record its state as soon as the mouse is released. 	// ***********************************************************************************	// CONSTRUCTOR	// ***********************************************************************************	/// 	/// Creates a new HOEditorUndoManager,	/// setting it so that the target is marked as dirty each time a new undo is stored. 	/// 	///  /// The default  you want to save undo info for. /// 	///  /// The default name of the thing to undo (displayed as "Undo [name]" in the main menu). /// 	public HOEditorUndoManager( Object p_target, string p_name ) : this( p_target, p_name, true ) {} /// 	/// Creates a new HOEditorUndoManager. /// 	///  /// The default  you want to save undo info for. /// 	///  /// The default name of the thing to undo (displayed as "Undo [name]" in the main menu). /// 	///  /// If TRUE, marks the target as dirty each time a new undo is stored. /// 	public HOEditorUndoManager( Object p_target, string p_name, bool p_autoSetDirty ) {		defTarget = p_target; defName = p_name; autoSetDirty = p_autoSetDirty; }	// ===================================================================================	// METHODS --- /// 	/// Call this method BEFORE any undoable UnityGUI call. /// Manages undo for the default target, with the default name. /// 	public void CheckUndo { CheckUndo( defTarget, defName ); } /// 	/// Call this method BEFORE any undoable UnityGUI call. /// Manages undo for the given target, with the default name. /// 	///  /// The  you want to save undo info for. /// 	public void CheckUndo( Object p_target ) { CheckUndo( p_target, defName ); } /// 	/// Call this method BEFORE any undoable UnityGUI call. /// Manages undo for the given target, with the given name. /// 	///  /// The  you want to save undo info for. /// 	///  /// The name of the thing to undo (displayed as "Undo [name]" in the main menu). /// 	public void CheckUndo( Object p_target, string p_name ) {		Event e = Event.current; if ( waitingToRecordPrefab != null ) { // Record eventual prefab instance modification. // TODO Avoid recording if nothing changed (no harm in doing so, but it would be nicer). switch ( e.type ) { case EventType.MouseDown : case EventType.MouseUp : case EventType.KeyDown : case EventType.KeyUp : PrefabUtility.RecordPrefabInstancePropertyModifications( waitingToRecordPrefab ); break; }		}		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). Undo.SetSnapshotTarget( p_target, p_name ); Undo.CreateSnapshot; Undo.ClearSnapshotTarget; // Not sure if this is necessary. listeningForGuiChanges = true; }	}	/// 	/// Call this method AFTER any undoable UnityGUI call. /// Manages undo for the default target, with the default name, /// and returns a value of TRUE if the target is marked as dirty. /// 	public bool CheckDirty { return CheckDirty( defTarget, defName ); } /// 	/// Call this method AFTER any undoable UnityGUI call. /// Manages undo for the given target, with the default name, /// and returns a value of TRUE if the target is marked as dirty. /// 	///  /// The  you want to save undo info for. /// 	public bool CheckDirty( Object p_target ) { return CheckDirty( p_target, defName ); } /// 	/// Call this method AFTER any undoable UnityGUI call. /// Manages undo for the given target, with the given name, /// and returns a value of TRUE if the target is marked as dirty. /// 	///  /// The  you want to save undo info for. /// 	/// <param name="p_name"> /// The name of the thing to undo (displayed as "Undo [name]" in the main menu). /// 	public bool CheckDirty( Object p_target, string p_name ) {		if ( listeningForGuiChanges && GUI.changed ) { // Some GUI value changed after pressing the mouse // or releasing the TAB key. // Register the previous snapshot as a valid undo. SetDirty( p_target, p_name ); return true; }		return false; }	/// 	/// Call this method AFTER any undoable UnityGUI call. /// Forces undo for the default target, with the default name. /// Used to undo operations that are performed by pressing a button, /// which doesn't set the GUI to a changed state. /// 	public void ForceDirty { ForceDirty( defTarget, defName ); } /// 	/// Call this method AFTER any undoable UnityGUI call. /// Forces undo for the given target, with the default name. /// Used to undo operations that are performed by pressing a button, /// which doesn't set the GUI to a changed state. /// 	/// <param name="p_target"> /// The <see cref="Object"/> you want to save undo info for. /// 	public void ForceDirty( Object p_target ) { ForceDirty( p_target, defName ); } /// 	/// Call this method AFTER any undoable UnityGUI call. /// Forces undo for the given target, with the given name. /// Used to undo operations that are performed by pressing a button, /// which doesn't set the GUI to a changed state. /// 	/// <param name="p_target"> /// The <see cref="Object"/> you want to save undo info for. /// 	/// <param name="p_name"> /// The name of the thing to undo (displayed as "Undo [name]" in the main menu). /// 	public void ForceDirty( Object p_target, string p_name ) {		if ( !listeningForGuiChanges ) { // Create a new snapshot. Undo.SetSnapshotTarget( p_target, p_name ); Undo.CreateSnapshot; Undo.ClearSnapshotTarget; }		SetDirty( p_target, p_name ); }	// ===================================================================================	// PRIVATE METHODS --- private void SetDirty( Object p_target, string p_name ) {		Undo.SetSnapshotTarget( p_target, p_name ); Undo.RegisterSnapshot; Undo.ClearSnapshotTarget; // Not sure if this is necessary. if ( autoSetDirty )		EditorUtility.SetDirty( p_target ); listeningForGuiChanges = false; if ( CheckTargetIsPrefabInstance( p_target ) ) { // Prefab instance: record immediately and also wait for value to be changed and than re-record it // (otherwise prefab instances are not updated correctly when using Custom Inspectors). PrefabUtility.RecordPrefabInstancePropertyModifications( p_target ); waitingToRecordPrefab = p_target; } else { waitingToRecordPrefab = null; }	}	private bool CheckTargetIsPrefabInstance( Object p_target ) {		return ( PrefabUtility.GetPrefabType( p_target ) == PrefabType.PrefabInstance ); } }

C# Example - Inspector with single source
How to use HOEditorUndoManager with a single source Inspector.

C# Example - Window with multiple sources
How to use HOEditorUndoManager with a single window that controls two different objects/sources.