| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679 |
- //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
- //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
- using System;
- using System.Collections.Generic;
- using System.Runtime.CompilerServices;
- using bs;
- namespace bs.Editor
- {
- /** @addtogroup Utility-Editor
- * @{
- */
- /// <summary>
- /// Handles undo & redo operations for changes made on game objects. Game objects can be recorded just before a change
- /// is made and the system will calculate the difference between that state and the state at the end of current frame.
- /// This difference will then be recorded as a undo/redo operation. The undo/redo operation will also take care of
- /// selecting the object & field it is acting upon.
- /// </summary>
- internal class GameObjectUndo
- {
- /// <summary>
- /// Contains information about a component that needs its diff recorded.
- /// </summary>
- private struct ComponentToRecord
- {
- private Component obj;
- private string path;
- private SerializedObject orgState;
- /// <summary>
- /// Creates a new object instance, recording the current state of the component.
- /// </summary>
- /// <param name="obj">Component to record the state of.</param>
- /// <param name="path">
- /// Path to the field which should be focused when performing the undo/redo operation. This should be the path
- /// as provided by <see cref="InspectableField"/>.
- /// </param>
- internal ComponentToRecord(Component obj, string path)
- {
- this.obj = obj;
- this.path = path;
- orgState = SerializedObject.Create(obj);
- }
- /// <summary>
- /// Generates the diff from the previously recorded state and the current state. If there is a difference
- /// an undo command is recorded.
- /// </summary>
- internal void RecordCommand()
- {
- if (obj.IsDestroyed)
- return;
- SerializedObject newState = SerializedObject.Create(obj);
- SerializedDiff oldToNew = SerializedDiff.Create(orgState, newState);
- if (oldToNew == null || oldToNew.IsEmpty)
- return;
- SerializedDiff newToOld = SerializedDiff.Create(newState, orgState);
- UndoRedo.Global.RegisterCommand(new RecordComponentUndo(obj, path, oldToNew, newToOld));
- }
- }
- /// <summary>
- /// Contains information about scene objects that needs their diff recorded. Note this will not record the entire
- /// scene object, but rather just its name, transform, active state and potentially other similar properties.
- /// It's components as well as hierarchy state are ignored.
- /// </summary>
- private struct SceneObjectHeaderToRecord
- {
- private SceneObject[] objs;
- private string path;
- private SceneObjectState[] orgStates;
- /// <summary>
- /// Creates a new object instance, recording the current state of the scene object header.
- /// </summary>
- /// <param name="obj">Scene object to record the state of.</param>
- /// <param name="path">
- /// Path to the field which should be focused when performing the undo/redo operation.
- /// </param>
- internal SceneObjectHeaderToRecord(SceneObject obj, string path)
- {
- this.objs = new [] { obj };
- this.path = path;
- orgStates = new [] { SceneObjectState.Create(obj) };
- }
- /// <summary>
- /// Creates a new object instance, recording the current state of the scene object header for multiple scene
- /// objects.
- /// </summary>
- /// <param name="objs">Scene objects to record the state of.</param>
- /// <param name="path">
- /// Path to the field which should be focused when performing the undo/redo operation.
- /// </param>
- internal SceneObjectHeaderToRecord(SceneObject[] objs, string path)
- {
- this.objs = objs;
- this.path = path;
- orgStates = new SceneObjectState[objs.Length];
- for(int i = 0; i < orgStates.Length; i++)
- orgStates[i] = SceneObjectState.Create(objs[i]);
- }
- /// <summary>
- /// Generates the diff from the previously recorded state and the current state. If there is a difference
- /// an undo command is recorded.
- /// </summary>
- internal void RecordCommand()
- {
- if (objs == null)
- return;
- List<SceneObjectHeaderUndo> headers = new List<SceneObjectHeaderUndo>();
- for (int i = 0; i < objs.Length; i++)
- {
- SceneObject obj = objs[i];
- SceneObjectState orgState = orgStates[i];
- if (obj.IsDestroyed)
- continue;
- SceneObjectDiff oldToNew = SceneObjectDiff.Create(orgState, SceneObjectState.Create(obj));
- if (oldToNew.flags == 0)
- continue;
- SceneObjectDiff newToOld = SceneObjectDiff.Create(SceneObjectState.Create(obj), orgState);
- headers.Add(new SceneObjectHeaderUndo(obj, newToOld, oldToNew));
- }
- if (headers.Count > 0)
- UndoRedo.Global.RegisterCommand(new RecordSceneObjectHeaderUndo(headers, path));
- }
- }
- /// <summary>
- /// Contains information about a scene object that needs its diff recorded. Unlike
- /// <see cref="SceneObjectHeaderToRecord"/> this will record the entire scene object,
- /// including its components and optionally the child hierarchy.
- /// </summary>
- private struct SceneObjectToRecord
- {
- private SceneObject obj;
- private string description;
- private SerializedSceneObject orgState;
- /// <summary>
- /// Creates a new object instance, recording the current state of the scene object.
- /// </summary>
- /// <param name="obj">Scene object to record the state of.</param>
- /// <param name="hierarchy">If true, the child scene objects will be recorded as well.</param>
- /// <param name="description">
- /// Optional description that describes the change that is happening.
- /// </param>
- internal SceneObjectToRecord(SceneObject obj, bool hierarchy, string description)
- {
- this.obj = obj;
- this.description = description;
- orgState = new SerializedSceneObject(obj, hierarchy);
- }
- /// <summary>
- /// Generates the diff from the previously recorded state and the current state. If there is a difference
- /// an undo command is recorded.
- /// </summary>
- internal void RecordCommand()
- {
- if (obj.IsDestroyed)
- return;
- var newState = new SerializedSceneObject(obj);
- UndoRedo.Global.RegisterCommand(new RecordSceneObjectUndo(obj, orgState, newState, description));
- }
- }
- private static List<ComponentToRecord> components = new List<ComponentToRecord>();
- private static List<SceneObjectHeaderToRecord> sceneObjectHeaders = new List<SceneObjectHeaderToRecord>();
- private static List<SceneObjectToRecord> sceneObjects = new List<SceneObjectToRecord>();
- /// <summary>
- /// Records the current state of the provided component, and generates a diff with the next state at the end of the
- /// frame. If change is detected an undo operation will be recorded. Generally you want to call this just before
- /// you are about to make a change to the component.
- /// </summary>
- /// <param name="obj">Component to record the state of.</param>
- /// <param name="fieldPath">
- /// Path to the field which should be focused when performing the undo/redo operation. This should be the path
- /// as provided by <see cref="InspectableField"/>.
- /// </param>
- public static void RecordComponent(Component obj, string fieldPath)
- {
- ComponentToRecord cmp = new ComponentToRecord(obj, fieldPath);
- components.Add(cmp);
- }
- /// <summary>
- /// Records the current state of the provided scene object header, and generates a diff with the next state at the
- /// end of the frame. If change is detected an undo operation will be recorded. Generally you want to call this
- /// just before you are about to make a change to the scene object header.
- ///
- /// Note this will not record the entire scene object, but rather just its name, transform, active state and
- /// potentially other similar properties. It's components as well as hierarchy state are ignored.
- /// </summary>
- /// <param name="obj">Scene object to record the state of.</param>
- /// <param name="fieldName">
- /// Name to the field which should be focused when performing the undo/redo operation.
- /// </param>
- public static void RecordSceneObjectHeader(SceneObject obj, string fieldName)
- {
- SceneObjectHeaderToRecord so = new SceneObjectHeaderToRecord(obj, fieldName);
- sceneObjectHeaders.Add(so);
- }
- /// <summary>
- /// Records the current state of the provided scene object header, and generates a diff with the next state at the
- /// end of the frame. If change is detected an undo operation will be recorded. Generally you want to call this
- /// just before you are about to make a change to the scene object header.
- ///
- /// Note this will not record the entire scene object, but rather just its name, transform, active state and
- /// potentially other similar properties. It's components as well as hierarchy state are ignored.
- /// </summary>
- /// <param name="objs">Scene objects to record the state of.</param>
- public static void RecordSceneObjectHeader(SceneObject[] objs)
- {
- SceneObjectHeaderToRecord so = new SceneObjectHeaderToRecord(objs, null);
- sceneObjectHeaders.Add(so);
- }
- /// <summary>
- /// Records the current state of the provided scene object header, and generates a diff with the next state at the
- /// end of the frame. If change is detected an undo operation will be recorded. Generally you want to call this
- /// just before you are about to make a change to the scene object.
- ///
- /// Note this records the complete state of a scene object, including its header, its components and optionally its
- /// child hierarchy.
- /// </summary>
- /// <param name="obj">Scene object to record.</param>
- /// <param name="hierarchy">
- /// If true the child objects will be recorded as well, otherwise just the provided object.
- /// </param>
- /// <param name="description">Optional description specifying the type of changes about to be made.</param>
- public static void RecordSceneObject(SceneObject obj, bool hierarchy, string description)
- {
- SceneObjectToRecord so = new SceneObjectToRecord(obj, hierarchy, description);
- sceneObjects.Add(so);
- }
- /// <summary>
- /// Generates diffs for any objects that were previously recorded using any of the Record* methods. The diff is
- /// generated by comparing the state at the time Record* was called, compared to the current object state.
- /// </summary>
- public static void ResolveDiffs()
- {
- foreach (var entry in components)
- entry.RecordCommand();
- foreach (var entry in sceneObjectHeaders)
- entry.RecordCommand();
- foreach (var entry in sceneObjects)
- entry.RecordCommand();
- components.Clear();
- sceneObjectHeaders.Clear();
- sceneObjects.Clear();
- }
- }
- /// <summary>
- /// Contains information about scene object state, excluding information about its components and hierarchy.
- /// </summary>
- internal struct SceneObjectState
- {
- internal string name;
- internal Vector3 position;
- internal Quaternion rotation;
- internal Vector3 scale;
- internal bool active;
- /// <summary>
- /// Initializes the state from a scene object.
- /// </summary>
- /// <param name="so">Scene object to initialize the state from.</param>
- /// <returns>New state object.</returns>
- internal static SceneObjectState Create(SceneObject so)
- {
- SceneObjectState state = new SceneObjectState();
- state.name = so.Name;
- state.position = so.LocalPosition;
- state.rotation = so.LocalRotation;
- state.scale = so.LocalScale;
- state.active = so.Active;
- return state;
- }
- }
- /// <summary>
- /// Contains the difference between two <see cref="SceneObjectState"/> objects and allows the changes to be applied to
- /// a <see cref="SceneObject"/>. The value of the different fields is stored as its own state, while the flags field
- /// specified which of the properties is actually different.
- /// </summary>
- internal struct SceneObjectDiff
- {
- internal SceneObjectState state;
- internal SceneObjectDiffFlags flags;
- /// <summary>
- /// Creates a diff object storing the difference between two <see cref="SceneObjectState"/> objects.
- /// </summary>
- /// <param name="oldState">State of the scene object to compare from.</param>
- /// <param name="newState">State of the scene object to compare to.</param>
- /// <returns>Difference between the two scene object states.</returns>
- internal static SceneObjectDiff Create(SceneObjectState oldState, SceneObjectState newState)
- {
- SceneObjectDiff diff = new SceneObjectDiff();
- diff.state = new SceneObjectState();
- if (oldState.name != newState.name)
- {
- diff.state.name = newState.name;
- diff.flags |= SceneObjectDiffFlags.Name;
- }
- if (oldState.position != newState.position)
- {
- diff.state.position = newState.position;
- diff.flags |= SceneObjectDiffFlags.Position;
- }
- if (oldState.rotation != newState.rotation)
- {
- diff.state.rotation = newState.rotation;
- diff.flags |= SceneObjectDiffFlags.Rotation;
- }
- if (oldState.scale != newState.scale)
- {
- diff.state.scale = newState.scale;
- diff.flags |= SceneObjectDiffFlags.Scale;
- }
- if (oldState.active != newState.active)
- {
- diff.state.active = newState.active;
- diff.flags |= SceneObjectDiffFlags.Active;
- }
- return diff;
- }
- /// <summary>
- /// Applies the diff to an actual scene object.
- /// </summary>
- /// <param name="sceneObject">Scene object to apply the diff to.</param>
- internal void Apply(SceneObject sceneObject)
- {
- if (flags.HasFlag(SceneObjectDiffFlags.Name))
- sceneObject.Name = state.name;
- if (flags.HasFlag(SceneObjectDiffFlags.Position))
- sceneObject.LocalPosition = state.position;
- if (flags.HasFlag(SceneObjectDiffFlags.Rotation))
- sceneObject.LocalRotation = state.rotation;
- if (flags.HasFlag(SceneObjectDiffFlags.Scale))
- sceneObject.LocalScale = state.scale;
- if (flags.HasFlag(SceneObjectDiffFlags.Active))
- sceneObject.Active = state.active;
- }
- }
- /// <summary>
- /// Contains information about two separate states of a scene object header.
- /// </summary>
- internal struct SceneObjectHeaderUndo
- {
- internal SceneObject obj;
- internal SceneObjectDiff newToOld;
- internal SceneObjectDiff oldToNew;
- internal SceneObjectHeaderUndo(SceneObject obj, SceneObjectDiff newToOld, SceneObjectDiff oldToNew)
- {
- this.obj = obj;
- this.newToOld = newToOld;
- this.oldToNew = oldToNew;
- }
- }
- /// <summary>
- /// Stores the field changes in a <see cref="SceneObject"/> as a difference between two states. Allows those changes to
- /// be reverted and re-applied. Does not record changes to scene object components or hierarchy, but just the fields
- /// considered its header (such as name, local transform and active state).
- /// </summary>
- [SerializeObject]
- internal class RecordSceneObjectHeaderUndo : UndoableCommand
- {
- private string fieldPath;
- private List<SceneObjectHeaderUndo> headers;
- private RecordSceneObjectHeaderUndo() { }
- /// <summary>
- /// Creates the new scene object header undo command.
- /// </summary>
- /// <param name="headers">Information about recorded states of scene object headers.</param>
- /// <param name="fieldPath">
- /// Optional path that controls which is the field being modified and should receive input focus when the command
- /// is executed. Note that the diffs applied have no restriction on how many fields they can modify at once, but
- /// only one field will receive focus.</param>
- public RecordSceneObjectHeaderUndo(List<SceneObjectHeaderUndo> headers, string fieldPath)
- {
- this.headers = headers;
- this.fieldPath = fieldPath;
- }
- /// <inheritdoc/>
- protected override void Commit()
- {
- if (headers == null)
- return;
- foreach (var header in headers)
- {
- if (header.obj == null)
- continue;
- if (header.obj.IsDestroyed)
- {
- Debug.LogWarning("Attempting to commit state on a destroyed game-object.");
- continue;
- }
- header.oldToNew.Apply(header.obj);
- }
- FocusOnField();
- RefreshInspector();
- }
- /// <inheritdoc/>
- protected override void Revert()
- {
- if (headers == null)
- return;
- foreach (var header in headers)
- {
- if (header.obj == null)
- continue;
- if (header.obj.IsDestroyed)
- {
- Debug.LogWarning("Attempting to revert state on a destroyed game-object.");
- continue;
- }
- header.newToOld.Apply(header.obj);
- }
- FocusOnField();
- RefreshInspector();
- }
- /// <summary>
- /// Selects the component's scene object and focuses on the specific field in the inspector, if the inspector
- /// window is open.
- /// </summary>
- private void FocusOnField()
- {
- if (headers != null && headers.Count > 0)
- {
- if (headers.Count == 1)
- {
- if (Selection.SceneObject != headers[0].obj)
- Selection.SceneObject = headers[0].obj;
- }
- else
- {
- List<SceneObject> objs = new List<SceneObject>();
- foreach (var header in headers)
- {
- if(header.obj != null)
- objs.Add(header.obj);
- }
- Selection.SceneObjects = objs.ToArray();
- }
- if (!string.IsNullOrEmpty(fieldPath))
- {
- InspectorWindow inspectorWindow = EditorWindow.GetWindow<InspectorWindow>();
- inspectorWindow?.FocusOnField(headers[0].obj.UUID, fieldPath);
- }
- }
- }
- /// <summary>
- /// Updates the values of the fields displayed in the inspector window.
- /// </summary>
- private void RefreshInspector()
- {
- InspectorWindow inspectorWindow = EditorWindow.GetWindow<InspectorWindow>();
- inspectorWindow?.RefreshSceneObjectFields(true);
- }
- }
- /// <summary>
- /// Stores the field changes in a <see cref="SceneObject"/> as a difference between two states. Allows those changes to
- /// be reverted and re-applied. Unlike <see cref="bs.Editor.RecordSceneObjectHeaderUndo"/> this command records the
- /// full scene object state, including changes to its components and optionally any child objects.
- /// </summary>
- [SerializeObject]
- internal class RecordSceneObjectUndo : UndoableCommand
- {
- private SceneObject obj;
- private SerializedSceneObject oldState;
- private SerializedSceneObject newState;
- private RecordSceneObjectUndo() { }
- /// <summary>
- /// Creates the new scene object undo command.
- /// </summary>
- /// <param name="obj">Scene object on which to apply the performed changes.</param>
- /// <param name="oldState">Recorded original state of the scene object(s).</param>
- /// <param name="newState">Recorded new state of the scene object(s).</param>
- /// <param name="description">
- /// Optional description of the changes made to the scene object between the two states.
- /// </param>
- public RecordSceneObjectUndo(SceneObject obj, SerializedSceneObject oldState, SerializedSceneObject newState,
- string description)
- {
- this.obj = obj;
- this.oldState = oldState;
- this.newState = newState;
- }
- /// <inheritdoc/>
- protected override void Commit()
- {
- newState?.Restore();
- FocusOnObject();
- RefreshInspector();
- }
- /// <inheritdoc/>
- protected override void Revert()
- {
- oldState?.Restore();
- FocusOnObject();
- RefreshInspector();
- }
- /// <summary>
- /// Selects the scene object if not already selected.
- /// </summary>
- private void FocusOnObject()
- {
- if (obj != null)
- {
- if (Selection.SceneObject != obj)
- Selection.SceneObject = obj;
- }
- }
- /// <summary>
- /// Updates the values of the fields displayed in the inspector window.
- /// </summary>
- private void RefreshInspector()
- {
- InspectorWindow inspectorWindow = EditorWindow.GetWindow<InspectorWindow>();
- inspectorWindow?.RefreshSceneObjectFields(true);
- }
- }
- /// <summary>
- /// Stores the field changes in a <see cref="Component"/> as a difference between two states. Allows those changes to
- /// be reverted and re-applied.
- /// </summary>
- [SerializeObject]
- internal class RecordComponentUndo : UndoableCommand
- {
- private Component obj;
- private string fieldPath;
- private SerializedDiff newToOld;
- private SerializedDiff oldToNew;
- private RecordComponentUndo() { }
- /// <summary>
- /// Creates the new component undo command.
- /// </summary>
- /// <param name="obj">Component on which to apply the performed changes.</param>
- /// <param name="fieldPath">
- /// Optional path that controls which is the field being modified and should receive input focus when the command
- /// is executed. Note that the diffs applied have no restriction on how many fields they can modify at once, but
- /// only one field will receive focus.</param>
- /// <param name="oldToNew">
- /// Difference that can be applied to the old object in order to get the new object state.
- /// </param>
- /// <param name="newToOld">
- /// Difference that can be applied to the new object in order to get the old object state.
- /// </param>
- public RecordComponentUndo(Component obj, string fieldPath, SerializedDiff oldToNew, SerializedDiff newToOld)
- {
- this.obj = obj;
- this.fieldPath = fieldPath;
- this.oldToNew = oldToNew;
- this.newToOld = newToOld;
- }
- /// <inheritdoc/>
- protected override void Commit()
- {
- if (oldToNew == null || obj == null)
- return;
- if (obj.IsDestroyed)
- {
- Debug.LogWarning("Attempting to commit state on a destroyed game-object.");
- return;
- }
- oldToNew.Apply(obj);
- FocusOnField();
- }
- /// <inheritdoc/>
- protected override void Revert()
- {
- if (newToOld == null || obj == null)
- return;
- if (obj.IsDestroyed)
- {
- Debug.LogWarning("Attempting to revert state on a destroyed game-object.");
- return;
- }
- newToOld.Apply(obj);
- FocusOnField();
- }
- /// <summary>
- /// Selects the component's scene object and focuses on the specific field in the inspector, if the inspector
- /// window is open.
- /// </summary>
- private void FocusOnField()
- {
- SceneObject so = obj.SceneObject;
- if (so != null)
- {
- if (Selection.SceneObject != so)
- Selection.SceneObject = so;
- if (!string.IsNullOrEmpty(fieldPath))
- {
- InspectorWindow inspectorWindow = EditorWindow.GetWindow<InspectorWindow>();
- inspectorWindow?.FocusOnField(obj.UUID, fieldPath);
- }
- }
- }
- }
- /** @} */
- }
|