You're breathtaking!
This commit is contained in:
182
Source/Editor/Undo/Actions/AddRemoveScriptAction.cs
Normal file
182
Source/Editor/Undo/Actions/AddRemoveScriptAction.cs
Normal file
@@ -0,0 +1,182 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
using Object = FlaxEngine.Object;
|
||||
|
||||
namespace FlaxEditor.Actions
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="IUndoAction"/> used to add/remove <see cref="Script"/> from the <see cref="Actor"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="IUndoAction" />
|
||||
[Serializable]
|
||||
sealed class AddRemoveScript : IUndoAction
|
||||
{
|
||||
[Serialize]
|
||||
private bool _isAdd;
|
||||
|
||||
[Serialize]
|
||||
private Guid _scriptId;
|
||||
|
||||
[Serialize]
|
||||
private Guid _prefabId;
|
||||
|
||||
[Serialize]
|
||||
private Guid _prefabObjectId;
|
||||
|
||||
[Serialize]
|
||||
private string _scriptTypeName;
|
||||
|
||||
[Serialize]
|
||||
private string _scriptData;
|
||||
|
||||
[Serialize]
|
||||
private Guid _parentId;
|
||||
|
||||
[Serialize]
|
||||
private int _orderInParent;
|
||||
|
||||
[Serialize]
|
||||
private bool _enabled;
|
||||
|
||||
internal AddRemoveScript(bool isAdd, Script script)
|
||||
{
|
||||
_isAdd = isAdd;
|
||||
_scriptId = script.ID;
|
||||
_scriptTypeName = script.TypeName;
|
||||
_prefabId = script.PrefabID;
|
||||
_prefabObjectId = script.PrefabObjectID;
|
||||
_scriptData = FlaxEngine.Json.JsonSerializer.Serialize(script);
|
||||
_parentId = script.Actor.ID;
|
||||
_orderInParent = script.OrderInParent;
|
||||
_enabled = script.Enabled;
|
||||
}
|
||||
|
||||
internal AddRemoveScript(bool isAdd, Actor parentActor, ScriptType scriptType)
|
||||
{
|
||||
_isAdd = isAdd;
|
||||
_scriptId = Guid.NewGuid();
|
||||
_scriptTypeName = scriptType.TypeName;
|
||||
_scriptData = null;
|
||||
_parentId = parentActor.ID;
|
||||
_orderInParent = -1;
|
||||
_enabled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new added script undo action.
|
||||
/// </summary>
|
||||
/// <param name="script">The new script.</param>
|
||||
/// <returns>The action.</returns>
|
||||
public static AddRemoveScript Added(Script script)
|
||||
{
|
||||
if (script == null)
|
||||
throw new ArgumentNullException(nameof(script));
|
||||
return new AddRemoveScript(true, script);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new add script undo action.
|
||||
/// </summary>
|
||||
/// <param name="parentActor">The parent actor.</param>
|
||||
/// <param name="scriptType">The script type.</param>
|
||||
/// <returns>The action.</returns>
|
||||
public static AddRemoveScript Add(Actor parentActor, ScriptType scriptType)
|
||||
{
|
||||
if (parentActor == null)
|
||||
throw new ArgumentNullException(nameof(parentActor));
|
||||
if (!scriptType)
|
||||
throw new ArgumentNullException(nameof(scriptType));
|
||||
return new AddRemoveScript(true, parentActor, scriptType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new remove script undo action.
|
||||
/// </summary>
|
||||
/// <param name="script">The script.</param>
|
||||
/// <returns>The action.</returns>
|
||||
public static AddRemoveScript Remove(Script script)
|
||||
{
|
||||
if (script == null)
|
||||
throw new ArgumentNullException(nameof(script));
|
||||
return new AddRemoveScript(false, script);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ActionString => _isAdd ? "Add script" : "Remove script";
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Do()
|
||||
{
|
||||
if (_isAdd)
|
||||
DoAdd();
|
||||
else
|
||||
DoRemove();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Undo()
|
||||
{
|
||||
if (_isAdd)
|
||||
DoRemove();
|
||||
else
|
||||
DoAdd();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
_scriptTypeName = null;
|
||||
_scriptData = null;
|
||||
}
|
||||
|
||||
private void DoRemove()
|
||||
{
|
||||
// Remove script (it could be removed by sth else, just check it)
|
||||
var script = Object.Find<Script>(ref _scriptId);
|
||||
if (!script)
|
||||
{
|
||||
Editor.LogWarning("Missing script.");
|
||||
return;
|
||||
}
|
||||
if (script.Actor)
|
||||
Editor.Instance.Scene.MarkSceneEdited(script.Scene);
|
||||
Object.Destroy(ref script);
|
||||
}
|
||||
|
||||
private void DoAdd()
|
||||
{
|
||||
// Restore script
|
||||
var parentActor = Object.Find<Actor>(ref _parentId);
|
||||
if (parentActor == null)
|
||||
{
|
||||
Editor.LogWarning("Missing parent actor.");
|
||||
return;
|
||||
}
|
||||
var type = TypeUtils.GetType(_scriptTypeName);
|
||||
if (!type)
|
||||
{
|
||||
Editor.LogWarning("Cannot find script type " + _scriptTypeName);
|
||||
return;
|
||||
}
|
||||
var script = type.CreateInstance() as Script;
|
||||
if (script == null)
|
||||
{
|
||||
Editor.LogWarning("Cannot create script of type " + _scriptTypeName);
|
||||
return;
|
||||
}
|
||||
Object.Internal_ChangeID(Object.GetUnmanagedPtr(script), ref _scriptId);
|
||||
if (_scriptData != null)
|
||||
FlaxEngine.Json.JsonSerializer.Deserialize(script, _scriptData);
|
||||
script.Enabled = _enabled;
|
||||
script.Parent = parentActor;
|
||||
if (_orderInParent != -1)
|
||||
script.OrderInParent = _orderInParent;
|
||||
if (_prefabObjectId != Guid.Empty)
|
||||
SceneObject.Internal_LinkPrefab(Object.GetUnmanagedPtr(script), ref _prefabId, ref _prefabObjectId);
|
||||
Editor.Instance.Scene.MarkSceneEdited(parentActor.Scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
170
Source/Editor/Undo/Actions/BreakPrefabLinkAction.cs
Normal file
170
Source/Editor/Undo/Actions/BreakPrefabLinkAction.cs
Normal file
@@ -0,0 +1,170 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEngine;
|
||||
using Object = FlaxEngine.Object;
|
||||
|
||||
namespace FlaxEditor.Actions
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="IUndoAction"/> used to break/restore <see cref="Prefab"/> connection for the collection of <see cref="Actor"/> and <see cref="Script"/> objects.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This action assumes that all objects in the given actor hierarchy are using the same prefab asset.
|
||||
/// </remarks>
|
||||
/// <seealso cref="IUndoAction" />
|
||||
[Serializable]
|
||||
sealed class BreakPrefabLinkAction : IUndoAction
|
||||
{
|
||||
[Serialize]
|
||||
private readonly bool _isBreak;
|
||||
|
||||
[Serialize]
|
||||
private Guid _actorId;
|
||||
|
||||
[Serialize]
|
||||
private Guid _prefabId;
|
||||
|
||||
[Serialize]
|
||||
private Dictionary<Guid, Guid> _prefabObjectIds;
|
||||
|
||||
private BreakPrefabLinkAction(bool isBreak, Guid actorId, Guid prefabId)
|
||||
{
|
||||
_isBreak = isBreak;
|
||||
_actorId = actorId;
|
||||
_prefabId = prefabId;
|
||||
}
|
||||
|
||||
private BreakPrefabLinkAction(bool isBreak, Actor actor)
|
||||
{
|
||||
_isBreak = isBreak;
|
||||
_actorId = actor.ID;
|
||||
_prefabId = actor.PrefabID;
|
||||
|
||||
_prefabObjectIds = new Dictionary<Guid, Guid>(1024);
|
||||
CollectIds(actor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new undo action that in state for breaking prefab connection.
|
||||
/// </summary>
|
||||
/// <param name="actor">The target actor.</param>
|
||||
/// <returns>The action.</returns>
|
||||
public static BreakPrefabLinkAction Break(Actor actor)
|
||||
{
|
||||
if (actor == null)
|
||||
throw new ArgumentNullException(nameof(actor));
|
||||
return new BreakPrefabLinkAction(true, actor.ID, Guid.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new undo action that in state for linked prefab connection. Action on perform will undo that.
|
||||
/// </summary>
|
||||
/// <param name="actor">The target actor.</param>
|
||||
/// <returns>The action.</returns>
|
||||
public static BreakPrefabLinkAction Linked(Actor actor)
|
||||
{
|
||||
if (actor == null)
|
||||
throw new ArgumentNullException(nameof(actor));
|
||||
if (!actor.HasPrefabLink)
|
||||
throw new FlaxException("Cannot register missing prefab link.");
|
||||
return new BreakPrefabLinkAction(false, actor);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ActionString => _isBreak ? "Break prefab link" : "Link prefab";
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Do()
|
||||
{
|
||||
if (_isBreak)
|
||||
DoBreak();
|
||||
else
|
||||
DoLink();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Undo()
|
||||
{
|
||||
if (_isBreak)
|
||||
DoLink();
|
||||
else
|
||||
DoBreak();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
_prefabObjectIds.Clear();
|
||||
}
|
||||
|
||||
private void DoLink()
|
||||
{
|
||||
if (_prefabObjectIds == null)
|
||||
throw new FlaxException("Cannot link prefab. Missing objects Ids mapping.");
|
||||
|
||||
var actor = Object.Find<Actor>(ref _actorId);
|
||||
if (actor == null)
|
||||
throw new FlaxException("Cannot link prefab. Missing actor.");
|
||||
|
||||
// Restore cached links
|
||||
foreach (var e in _prefabObjectIds)
|
||||
{
|
||||
var objId = e.Key;
|
||||
var prefabObjId = e.Value;
|
||||
|
||||
var obj = Object.Find<Object>(ref objId);
|
||||
if (obj is Actor)
|
||||
{
|
||||
Actor.Internal_LinkPrefab(Object.GetUnmanagedPtr(obj), ref _prefabId, ref prefabObjId);
|
||||
}
|
||||
else if (obj is Script)
|
||||
{
|
||||
Script.Internal_LinkPrefab(Object.GetUnmanagedPtr(obj), ref _prefabId, ref prefabObjId);
|
||||
}
|
||||
}
|
||||
|
||||
Editor.Instance.Scene.MarkSceneEdited(actor.Scene);
|
||||
Editor.Instance.Windows.PropertiesWin.Presenter.BuildLayout();
|
||||
}
|
||||
|
||||
private void CollectIds(Actor actor)
|
||||
{
|
||||
_prefabObjectIds.Add(actor.ID, actor.PrefabObjectID);
|
||||
|
||||
for (int i = 0; i < actor.ChildrenCount; i++)
|
||||
{
|
||||
CollectIds(actor.GetChild(i));
|
||||
}
|
||||
|
||||
for (int i = 0; i < actor.ScriptsCount; i++)
|
||||
{
|
||||
var script = actor.GetScript(i);
|
||||
_prefabObjectIds.Add(script.ID, script.PrefabObjectID);
|
||||
}
|
||||
}
|
||||
|
||||
private void DoBreak()
|
||||
{
|
||||
var actor = Object.Find<Actor>(ref _actorId);
|
||||
if (actor == null)
|
||||
throw new FlaxException("Cannot break prefab link. Missing actor.");
|
||||
if (!actor.HasPrefabLink)
|
||||
throw new FlaxException("Cannot break missing prefab link.");
|
||||
|
||||
if (_prefabObjectIds == null)
|
||||
_prefabObjectIds = new Dictionary<Guid, Guid>(1024);
|
||||
else
|
||||
_prefabObjectIds.Clear();
|
||||
CollectIds(actor);
|
||||
|
||||
_prefabId = actor.PrefabID;
|
||||
|
||||
actor.BreakPrefabLink();
|
||||
|
||||
Editor.Instance.Scene.MarkSceneEdited(actor.Scene);
|
||||
Editor.Instance.Windows.PropertiesWin.Presenter.BuildLayout();
|
||||
}
|
||||
}
|
||||
}
|
||||
99
Source/Editor/Undo/Actions/ChangeScriptAction.cs
Normal file
99
Source/Editor/Undo/Actions/ChangeScriptAction.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using FlaxEditor.Modules;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.Actions
|
||||
{
|
||||
/// <summary>
|
||||
/// Change <see cref="Script"/> order or enable/disable undo action.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.IUndoAction" />
|
||||
/// <seealso cref="FlaxEditor.ISceneEditAction" />
|
||||
[Serializable]
|
||||
class ChangeScriptAction : IUndoAction, ISceneEditAction
|
||||
{
|
||||
[Serialize]
|
||||
private Guid _scriptId;
|
||||
|
||||
[Serialize]
|
||||
private bool _enableA;
|
||||
|
||||
[Serialize]
|
||||
private int _orderA;
|
||||
|
||||
[Serialize]
|
||||
private bool _enableB;
|
||||
|
||||
[Serialize]
|
||||
private int _orderB;
|
||||
|
||||
private ChangeScriptAction(Script script, bool enable, int order)
|
||||
{
|
||||
_scriptId = script.ID;
|
||||
_enableA = script.Enabled;
|
||||
_orderA = script.OrderInParent;
|
||||
_enableB = enable;
|
||||
_orderB = order;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates new undo action that changes script order in parent actor scripts collection.
|
||||
/// </summary>
|
||||
/// <param name="script">The script to reorder.</param>
|
||||
/// <param name="newOrder">New index.</param>
|
||||
/// <returns>The action (not performed yet).</returns>
|
||||
public static ChangeScriptAction ChangeOrder(Script script, int newOrder)
|
||||
{
|
||||
return new ChangeScriptAction(script, script.Enabled, newOrder);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates new undo action that enables/disables script.
|
||||
/// </summary>
|
||||
/// <param name="script">The script to enable or disable.</param>
|
||||
/// <param name="newEnabled">New enable state.</param>
|
||||
/// <returns>The action (not performed yet).</returns>
|
||||
public static ChangeScriptAction ChangeEnabled(Script script, bool newEnabled)
|
||||
{
|
||||
return new ChangeScriptAction(script, newEnabled, script.OrderInParent);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ActionString => "Edit script";
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Do()
|
||||
{
|
||||
var script = FlaxEngine.Object.Find<Script>(ref _scriptId);
|
||||
if (script == null)
|
||||
return;
|
||||
script.Enabled = _enableB;
|
||||
script.OrderInParent = _orderB;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Undo()
|
||||
{
|
||||
var script = FlaxEngine.Object.Find<Script>(ref _scriptId);
|
||||
if (script == null)
|
||||
return;
|
||||
script.Enabled = _enableA;
|
||||
script.OrderInParent = _orderA;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void MarkSceneEdited(SceneModule sceneModule)
|
||||
{
|
||||
var script = FlaxEngine.Object.Find<Script>(ref _scriptId);
|
||||
if (script != null)
|
||||
sceneModule.MarkSceneEdited(script.Scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
154
Source/Editor/Undo/Actions/DeleteActorsAction.cs
Normal file
154
Source/Editor/Undo/Actions/DeleteActorsAction.cs
Normal file
@@ -0,0 +1,154 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.SceneGraph;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.Actions
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="IUndoAction"/> used to delete a selection of <see cref="ActorNode"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.IUndoAction" />
|
||||
[Serializable]
|
||||
class DeleteActorsAction : IUndoAction
|
||||
{
|
||||
[Serialize]
|
||||
private byte[] _data;
|
||||
|
||||
[Serialize]
|
||||
private Guid[] _prefabIds;
|
||||
|
||||
[Serialize]
|
||||
private Guid[] _prefabObjectIds;
|
||||
|
||||
[Serialize]
|
||||
private bool _isInverted;
|
||||
|
||||
/// <summary>
|
||||
/// The node parents.
|
||||
/// </summary>
|
||||
[Serialize]
|
||||
protected List<ActorNode> _nodeParents;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DeleteActorsAction"/> class.
|
||||
/// </summary>
|
||||
/// <param name="objects">The objects.</param>
|
||||
/// <param name="isInverted">If set to <c>true</c> action will be inverted - instead of delete it will be create actors.</param>
|
||||
internal DeleteActorsAction(List<SceneGraphNode> objects, bool isInverted = false)
|
||||
{
|
||||
_isInverted = isInverted;
|
||||
_nodeParents = new List<ActorNode>(objects.Count);
|
||||
var actorNodes = new List<ActorNode>(objects.Count);
|
||||
var actors = new List<Actor>(objects.Count);
|
||||
for (int i = 0; i < objects.Count; i++)
|
||||
{
|
||||
if (objects[i] is ActorNode node)
|
||||
{
|
||||
actorNodes.Add(node);
|
||||
actors.Add(node.Actor);
|
||||
}
|
||||
}
|
||||
actorNodes.BuildNodesParents(_nodeParents);
|
||||
|
||||
_data = Actor.ToBytes(actors.ToArray());
|
||||
|
||||
_prefabIds = new Guid[actors.Count];
|
||||
_prefabObjectIds = new Guid[actors.Count];
|
||||
for (int i = 0; i < actors.Count; i++)
|
||||
{
|
||||
_prefabIds[i] = actors[i].PrefabID;
|
||||
_prefabObjectIds[i] = actors[i].PrefabObjectID;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ActionString => _isInverted ? "Create actors" : "Delete actors";
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Do()
|
||||
{
|
||||
if (_isInverted)
|
||||
Create();
|
||||
else
|
||||
Delete();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Undo()
|
||||
{
|
||||
if (_isInverted)
|
||||
Delete();
|
||||
else
|
||||
Create();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
_data = null;
|
||||
_prefabIds = null;
|
||||
_prefabObjectIds = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the objects.
|
||||
/// </summary>
|
||||
protected virtual void Delete()
|
||||
{
|
||||
// Remove objects
|
||||
for (int i = 0; i < _nodeParents.Count; i++)
|
||||
{
|
||||
var node = _nodeParents[i];
|
||||
Editor.Instance.Scene.MarkSceneEdited(node.ParentScene);
|
||||
node.Delete();
|
||||
}
|
||||
_nodeParents.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the node.
|
||||
/// </summary>
|
||||
/// <param name="id">The actor id.</param>
|
||||
/// <returns>The scene graph node.</returns>
|
||||
protected virtual SceneGraphNode GetNode(Guid id)
|
||||
{
|
||||
return SceneGraphFactory.FindNode(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the removed objects (from data).
|
||||
/// </summary>
|
||||
protected virtual void Create()
|
||||
{
|
||||
// Restore objects
|
||||
var actors = Actor.FromBytes(_data);
|
||||
if (actors == null)
|
||||
return;
|
||||
for (int i = 0; i < actors.Length; i++)
|
||||
{
|
||||
Guid prefabId = _prefabIds[i];
|
||||
if (prefabId != Guid.Empty)
|
||||
{
|
||||
Actor.Internal_LinkPrefab(FlaxEngine.Object.GetUnmanagedPtr(actors[i]), ref prefabId, ref _prefabObjectIds[i]);
|
||||
}
|
||||
}
|
||||
var actorNodes = new List<ActorNode>(actors.Length);
|
||||
for (int i = 0; i < actors.Length; i++)
|
||||
{
|
||||
var foundNode = GetNode(actors[i].ID);
|
||||
if (foundNode is ActorNode node)
|
||||
{
|
||||
actorNodes.Add(node);
|
||||
}
|
||||
}
|
||||
actorNodes.BuildNodesParents(_nodeParents);
|
||||
for (int i = 0; i < _nodeParents.Count; i++)
|
||||
{
|
||||
Editor.Instance.Scene.MarkSceneEdited(_nodeParents[i].ParentScene);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
198
Source/Editor/Undo/Actions/PasteActorsAction.cs
Normal file
198
Source/Editor/Undo/Actions/PasteActorsAction.cs
Normal file
@@ -0,0 +1,198 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FlaxEditor.SceneGraph;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.Actions
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="IUndoAction"/> used to paste a set of <see cref="ActorNode"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.IUndoAction" />
|
||||
[Serializable]
|
||||
class PasteActorsAction : IUndoAction
|
||||
{
|
||||
[Serialize]
|
||||
private Dictionary<Guid, Guid> _idsMapping;
|
||||
|
||||
[Serialize]
|
||||
private byte[] _data;
|
||||
|
||||
[Serialize]
|
||||
private Guid _pasteParent;
|
||||
|
||||
/// <summary>
|
||||
/// The node parents.
|
||||
/// </summary>
|
||||
[Serialize]
|
||||
protected List<Guid> _nodeParents;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PasteActorsAction"/> class.
|
||||
/// </summary>
|
||||
/// <param name="data">The data.</param>
|
||||
/// <param name="objectIds">The object ids.</param>
|
||||
/// <param name="pasteParent">The paste parent object id.</param>
|
||||
/// <param name="name">The action name.</param>
|
||||
protected PasteActorsAction(byte[] data, Guid[] objectIds, ref Guid pasteParent, string name)
|
||||
{
|
||||
ActionString = name;
|
||||
|
||||
_pasteParent = pasteParent;
|
||||
_idsMapping = new Dictionary<Guid, Guid>(objectIds.Length * 4);
|
||||
for (int i = 0; i < objectIds.Length; i++)
|
||||
{
|
||||
_idsMapping[objectIds[i]] = Guid.NewGuid();
|
||||
}
|
||||
|
||||
_nodeParents = new List<Guid>(objectIds.Length);
|
||||
_data = data;
|
||||
}
|
||||
|
||||
internal static PasteActorsAction Paste(byte[] data, Guid pasteParent)
|
||||
{
|
||||
var objectIds = Actor.TryGetSerializedObjectsIds(data);
|
||||
if (objectIds == null)
|
||||
return null;
|
||||
|
||||
return new PasteActorsAction(data, objectIds, ref pasteParent, "Paste actors");
|
||||
}
|
||||
|
||||
internal static PasteActorsAction Duplicate(byte[] data, Guid pasteParent)
|
||||
{
|
||||
var objectIds = Actor.TryGetSerializedObjectsIds(data);
|
||||
if (objectIds == null)
|
||||
return null;
|
||||
|
||||
return new PasteActorsAction(data, objectIds, ref pasteParent, "Duplicate actors");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Links the broken parent reference (missing parent). By default links the actor to the first scene.
|
||||
/// </summary>
|
||||
/// <param name="actor">The actor.</param>
|
||||
protected virtual void LinkBrokenParentReference(Actor actor)
|
||||
{
|
||||
// Link to the first scene root
|
||||
if (Level.ScenesCount == 0)
|
||||
throw new Exception("Failed to paste actor with a broken reference. No loaded scenes.");
|
||||
actor.SetParent(Level.GetScene(0), false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ActionString { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Performs the paste/duplicate action and outputs created objects nodes.
|
||||
/// </summary>
|
||||
/// <param name="nodes">The nodes.</param>
|
||||
/// <param name="nodeParents">The node parents.</param>
|
||||
public virtual void Do(out List<ActorNode> nodes, out List<ActorNode> nodeParents)
|
||||
{
|
||||
// Restore objects
|
||||
var actors = Actor.FromBytes(_data, _idsMapping);
|
||||
if (actors == null)
|
||||
{
|
||||
nodes = null;
|
||||
nodeParents = null;
|
||||
return;
|
||||
}
|
||||
nodes = new List<ActorNode>(actors.Length);
|
||||
for (int i = 0; i < actors.Length; i++)
|
||||
{
|
||||
var actor = actors[i];
|
||||
|
||||
// Check if has no parent linked (broken reference eg. old parent not existing)
|
||||
if (actor.Parent == null)
|
||||
{
|
||||
LinkBrokenParentReference(actor);
|
||||
}
|
||||
|
||||
var node = GetNode(actor.ID);
|
||||
if (node is ActorNode actorNode)
|
||||
{
|
||||
nodes.Add(actorNode);
|
||||
}
|
||||
}
|
||||
|
||||
nodeParents = nodes.BuildNodesParents();
|
||||
|
||||
// Cache pasted nodes ids (parents only)
|
||||
_nodeParents.Clear();
|
||||
_nodeParents.Capacity = Mathf.Max(_nodeParents.Capacity, nodeParents.Count);
|
||||
for (int i = 0; i < nodeParents.Count; i++)
|
||||
{
|
||||
_nodeParents.Add(nodeParents[i].ID);
|
||||
}
|
||||
|
||||
var pasteParentNode = Editor.Instance.Scene.GetActorNode(_pasteParent);
|
||||
if (pasteParentNode != null)
|
||||
{
|
||||
// Move pasted actors to the parent target (if specified and valid)
|
||||
for (int i = 0; i < nodeParents.Count; i++)
|
||||
{
|
||||
nodeParents[i].Actor.SetParent(pasteParentNode.Actor, false);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < nodeParents.Count; i++)
|
||||
{
|
||||
// Fix name collisions (only for parents)
|
||||
var node = nodeParents[i];
|
||||
var parent = node.Actor?.Parent;
|
||||
if (parent != null)
|
||||
{
|
||||
string name = node.Name;
|
||||
Actor[] children = parent.Children;
|
||||
if (children.Any(x => x.Name == name))
|
||||
{
|
||||
// Generate new name
|
||||
node.Actor.Name = StringUtils.IncrementNameNumber(name, x => children.All(y => y.Name != x));
|
||||
}
|
||||
}
|
||||
|
||||
Editor.Instance.Scene.MarkSceneEdited(node.ParentScene);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the node.
|
||||
/// </summary>
|
||||
/// <param name="id">The actor id.</param>
|
||||
/// <returns>The scene graph node.</returns>
|
||||
protected virtual SceneGraphNode GetNode(Guid id)
|
||||
{
|
||||
return SceneGraphFactory.FindNode(id);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Do()
|
||||
{
|
||||
Do(out _, out _);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Undo()
|
||||
{
|
||||
// Remove objects
|
||||
for (int i = 0; i < _nodeParents.Count; i++)
|
||||
{
|
||||
var node = GetNode(_nodeParents[i]);
|
||||
Editor.Instance.Scene.MarkSceneEdited(node.ParentScene);
|
||||
node.Delete();
|
||||
}
|
||||
_nodeParents.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Dispose()
|
||||
{
|
||||
_nodeParents?.Clear();
|
||||
_idsMapping?.Clear();
|
||||
_data = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
67
Source/Editor/Undo/Actions/SelectionChangeAction.cs
Normal file
67
Source/Editor/Undo/Actions/SelectionChangeAction.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using FlaxEditor.SceneGraph;
|
||||
|
||||
namespace FlaxEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Objects selection change action.
|
||||
/// </summary>
|
||||
/// <seealso cref="IUndoAction" />
|
||||
[Serializable]
|
||||
public class SelectionChangeAction : UndoActionBase<SelectionChangeAction.DataStorage>
|
||||
{
|
||||
/// <summary>
|
||||
/// The undo data.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public struct DataStorage
|
||||
{
|
||||
/// <summary>
|
||||
/// The 'before' selection.
|
||||
/// </summary>
|
||||
public SceneGraphNode[] Before;
|
||||
|
||||
/// <summary>
|
||||
/// The 'after' selection.
|
||||
/// </summary>
|
||||
public SceneGraphNode[] After;
|
||||
}
|
||||
|
||||
private Action<SceneGraphNode[]> _callback;
|
||||
|
||||
/// <summary>
|
||||
/// User selection has changed
|
||||
/// </summary>
|
||||
/// <param name="before">Previously selected nodes</param>
|
||||
/// <param name="after">Newly selected nodes</param>
|
||||
/// <param name="callback">Selection change callback</param>
|
||||
public SelectionChangeAction(SceneGraphNode[] before, SceneGraphNode[] after, Action<SceneGraphNode[]> callback)
|
||||
{
|
||||
Data = new DataStorage
|
||||
{
|
||||
Before = before,
|
||||
After = after,
|
||||
};
|
||||
_callback = callback;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ActionString => "Selection change";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Do()
|
||||
{
|
||||
var data = Data;
|
||||
_callback(data.After);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Undo()
|
||||
{
|
||||
var data = Data;
|
||||
_callback(data.Before);
|
||||
}
|
||||
}
|
||||
}
|
||||
143
Source/Editor/Undo/Actions/TransformObjectsAction.cs
Normal file
143
Source/Editor/Undo/Actions/TransformObjectsAction.cs
Normal file
@@ -0,0 +1,143 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.Modules;
|
||||
using FlaxEditor.SceneGraph;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="IUndoAction"/> used to transform a selection of <see cref="SceneGraphNode"/>.
|
||||
/// The same logic could be achieved using <see cref="UndoMultiBlock"/> but it would be slower.
|
||||
/// Since we use this kind of action very ofter (for <see cref="FlaxEditor.Gizmo.TransformGizmo"/> operations) it's better to provide faster implementation.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.IUndoAction" />
|
||||
[Serializable]
|
||||
public sealed class TransformObjectsAction : UndoActionBase<TransformObjectsAction.DataStorage>, ISceneEditAction
|
||||
{
|
||||
/// <summary>
|
||||
/// The undo data.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public struct DataStorage
|
||||
{
|
||||
/// <summary>
|
||||
/// The scene of the selected objects.
|
||||
/// </summary>
|
||||
public Scene Scene;
|
||||
|
||||
/// <summary>
|
||||
/// The selection pool.
|
||||
/// </summary>
|
||||
public SceneGraphNode[] Selection;
|
||||
|
||||
/// <summary>
|
||||
/// The 'before' state.
|
||||
/// </summary>
|
||||
public Transform[] Before;
|
||||
|
||||
/// <summary>
|
||||
/// The 'after' state.
|
||||
/// </summary>
|
||||
public Transform[] After;
|
||||
|
||||
/// <summary>
|
||||
/// The cached bounding box that contains all selected items in 'before' state.
|
||||
/// </summary>
|
||||
public BoundingBox BeforeBounds;
|
||||
|
||||
/// <summary>
|
||||
/// The cached bounding box that contains all selected items in 'after' state.
|
||||
/// </summary>
|
||||
public BoundingBox AfterBounds;
|
||||
|
||||
/// <summary>
|
||||
/// True if navigation system has been modified during editing the selected objects (navmesh auto-rebuild is required).
|
||||
/// </summary>
|
||||
public bool NavigationDirty;
|
||||
}
|
||||
|
||||
internal TransformObjectsAction(List<SceneGraphNode> selection, List<Transform> before, ref BoundingBox boundsBefore, bool navigationDirty)
|
||||
{
|
||||
var after = Utilities.Utils.GetTransformsAndBounds(selection, out var afterBounds);
|
||||
|
||||
// TODO: support moving objects from more than one scene
|
||||
var scene = selection[0].ParentScene?.Scene;
|
||||
|
||||
var data = new DataStorage
|
||||
{
|
||||
Scene = scene,
|
||||
Selection = selection.ToArray(),
|
||||
After = after,
|
||||
Before = before.ToArray(),
|
||||
BeforeBounds = boundsBefore,
|
||||
AfterBounds = afterBounds,
|
||||
NavigationDirty = navigationDirty,
|
||||
};
|
||||
Data = data;
|
||||
|
||||
InvalidateBounds(ref data);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ActionString => "Transform object(s)";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Do()
|
||||
{
|
||||
var data = Data;
|
||||
for (int i = 0; i < data.Selection.Length; i++)
|
||||
{
|
||||
data.Selection[i].Transform = data.After[i];
|
||||
}
|
||||
InvalidateBounds(ref data);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Undo()
|
||||
{
|
||||
var data = Data;
|
||||
for (int i = 0; i < data.Selection.Length; i++)
|
||||
{
|
||||
data.Selection[i].Transform = data.Before[i];
|
||||
}
|
||||
InvalidateBounds(ref data);
|
||||
}
|
||||
|
||||
private void InvalidateBounds(ref DataStorage data)
|
||||
{
|
||||
if (!data.NavigationDirty)
|
||||
return;
|
||||
|
||||
var editor = Editor.Instance;
|
||||
bool isPlayMode = editor.StateMachine.IsPlayMode;
|
||||
var options = editor.Options.Options;
|
||||
|
||||
// Auto NavMesh rebuild
|
||||
if (!isPlayMode && options.General.AutoRebuildNavMesh && data.Scene != null)
|
||||
{
|
||||
// Handle simple case where objects were moved just a little and use one navmesh build request to improve performance
|
||||
if (data.BeforeBounds.Intersects(ref data.AfterBounds))
|
||||
{
|
||||
Navigation.BuildNavMesh(data.Scene, BoundingBox.Merge(data.BeforeBounds, data.AfterBounds), options.General.AutoRebuildNavMeshTimeoutMs);
|
||||
}
|
||||
else
|
||||
{
|
||||
Navigation.BuildNavMesh(data.Scene, data.BeforeBounds, options.General.AutoRebuildNavMeshTimeoutMs);
|
||||
Navigation.BuildNavMesh(data.Scene, data.AfterBounds, options.General.AutoRebuildNavMeshTimeoutMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ISceneEditAction.MarkSceneEdited(SceneModule sceneModule)
|
||||
{
|
||||
var data = Data;
|
||||
for (int i = 0; i < data.Selection.Length; i++)
|
||||
{
|
||||
sceneModule.MarkSceneEdited(data.Selection[i].ParentScene);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
103
Source/Editor/Undo/EditorUndo.cs
Normal file
103
Source/Editor/Undo/EditorUndo.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using FlaxEditor.History;
|
||||
using FlaxEditor.Options;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="Undo"/> customized for the <see cref="Editor"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.Undo" />
|
||||
public class EditorUndo : Undo
|
||||
{
|
||||
private readonly Editor _editor;
|
||||
|
||||
internal EditorUndo(Editor editor)
|
||||
: base(500)
|
||||
{
|
||||
_editor = editor;
|
||||
|
||||
editor.Options.OptionsChanged += OnOptionsChanged;
|
||||
}
|
||||
|
||||
private void OnOptionsChanged(EditorOptions options)
|
||||
{
|
||||
Capacity = options.General.UndoActionsCapacity;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Enabled
|
||||
{
|
||||
get => _editor.StateMachine.CurrentState.CanUseUndoRedo;
|
||||
set => throw new AccessViolationException("Cannot change enabled state of the editor main undo.");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnAction(IUndoAction action)
|
||||
{
|
||||
CheckSceneEdited(action);
|
||||
base.OnAction(action);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnUndo(IUndoAction action)
|
||||
{
|
||||
CheckSceneEdited(action);
|
||||
base.OnUndo(action);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnRedo(IUndoAction action)
|
||||
{
|
||||
CheckSceneEdited(action);
|
||||
base.OnRedo(action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the any scene has been edited after performing the given action.
|
||||
/// </summary>
|
||||
/// <param name="action">The action.</param>
|
||||
private void CheckSceneEdited(IUndoAction action)
|
||||
{
|
||||
// Note: this is automatic tracking system to check if undo action modifies scene objects
|
||||
|
||||
// Skip if all scenes are already modified
|
||||
if (Editor.Instance.Scene.IsEverySceneEdited())
|
||||
return;
|
||||
|
||||
// ReSharper disable once SuspiciousTypeConversion.Global
|
||||
if (action is ISceneEditAction sceneEditAction)
|
||||
{
|
||||
sceneEditAction.MarkSceneEdited(Editor.Instance.Scene);
|
||||
}
|
||||
else if (action is UndoActionObject undoActionObject)
|
||||
{
|
||||
var data = undoActionObject.PrepareData();
|
||||
|
||||
if (data.TargetInstance is SceneGraph.SceneGraphNode node)
|
||||
{
|
||||
Editor.Instance.Scene.MarkSceneEdited(node.ParentScene);
|
||||
}
|
||||
else if (data.TargetInstance is Actor actor)
|
||||
{
|
||||
Editor.Instance.Scene.MarkSceneEdited(actor.Scene);
|
||||
}
|
||||
else if (data.TargetInstance is Script script && script.Actor != null)
|
||||
{
|
||||
Editor.Instance.Scene.MarkSceneEdited(script.Actor.Scene);
|
||||
}
|
||||
}
|
||||
else if (action is MultiUndoAction multiUndoAction)
|
||||
{
|
||||
// Process child actions
|
||||
for (int i = 0; i < multiUndoAction.Actions.Length; i++)
|
||||
{
|
||||
CheckSceneEdited(multiUndoAction.Actions[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Source/Editor/Undo/ISceneEditAction.cs
Normal file
18
Source/Editor/Undo/ISceneEditAction.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEditor.Modules;
|
||||
|
||||
namespace FlaxEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for undo action that can modify scene data (actors, scripts, etc.)
|
||||
/// </summary>
|
||||
public interface ISceneEditAction
|
||||
{
|
||||
/// <summary>
|
||||
/// Marks the scenes edited.
|
||||
/// </summary>
|
||||
/// <param name="sceneModule">The scene module.</param>
|
||||
void MarkSceneEdited(SceneModule sceneModule);
|
||||
}
|
||||
}
|
||||
23
Source/Editor/Undo/IUndoAction.cs
Normal file
23
Source/Editor/Undo/IUndoAction.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEditor.History;
|
||||
|
||||
namespace FlaxEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for <see cref="Undo"/> actions.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.History.IHistoryAction" />
|
||||
public interface IUndoAction : IHistoryAction
|
||||
{
|
||||
/// <summary>
|
||||
/// Performs this action.
|
||||
/// </summary>
|
||||
void Do();
|
||||
|
||||
/// <summary>
|
||||
/// Undoes this action.
|
||||
/// </summary>
|
||||
void Undo();
|
||||
}
|
||||
}
|
||||
79
Source/Editor/Undo/MultiUndoAction.cs
Normal file
79
Source/Editor/Undo/MultiUndoAction.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="IUndoAction"/> that contains one or more child actions performed at once. Allows to merge different actions.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.IUndoAction" />
|
||||
[Serializable]
|
||||
[HideInEditor]
|
||||
public class MultiUndoAction : IUndoAction
|
||||
{
|
||||
/// <summary>
|
||||
/// The child actions.
|
||||
/// </summary>
|
||||
[Serialize]
|
||||
public readonly IUndoAction[] Actions;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MultiUndoAction"/> class.
|
||||
/// </summary>
|
||||
/// <param name="actions">The actions to include within this multi action.</param>
|
||||
public MultiUndoAction(params IUndoAction[] actions)
|
||||
{
|
||||
Actions = actions?.ToArray() ?? throw new ArgumentNullException();
|
||||
if (Actions.Length == 0)
|
||||
throw new ArgumentException("Empty actions collection.");
|
||||
ActionString = Actions[0].ActionString;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MultiUndoAction"/> class.
|
||||
/// </summary>
|
||||
/// <param name="actions">The actions to include within this multi action.</param>
|
||||
/// <param name="actionString">The action string.</param>
|
||||
public MultiUndoAction(IEnumerable<IUndoAction> actions, string actionString = null)
|
||||
{
|
||||
Actions = actions?.ToArray() ?? throw new ArgumentNullException();
|
||||
if (Actions.Length == 0)
|
||||
throw new ArgumentException("Empty actions collection.");
|
||||
ActionString = actionString ?? Actions[0].ActionString;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ActionString { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Do()
|
||||
{
|
||||
for (int i = 0; i < Actions.Length; i++)
|
||||
{
|
||||
Actions[i].Do();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Undo()
|
||||
{
|
||||
for (int i = Actions.Length - 1; i >= 0; i--)
|
||||
{
|
||||
Actions[i].Undo();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
for (int i = 0; i < Actions.Length; i++)
|
||||
{
|
||||
Actions[i].Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
422
Source/Editor/Undo/Undo.cs
Normal file
422
Source/Editor/Undo/Undo.cs
Normal file
@@ -0,0 +1,422 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FlaxEditor.History;
|
||||
using FlaxEditor.Utilities;
|
||||
using FlaxEngine.Collections;
|
||||
|
||||
namespace FlaxEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// The undo/redo actions recording object.
|
||||
/// </summary>
|
||||
public class Undo : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Undo system event.
|
||||
/// </summary>
|
||||
/// <param name="action">The action.</param>
|
||||
public delegate void UndoEventDelegate(IUndoAction action);
|
||||
|
||||
internal interface IUndoInternal
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates the undo action object on recording end.
|
||||
/// </summary>
|
||||
/// <param name="snapshotInstance">The snapshot object.</param>
|
||||
/// <returns>The undo action. May be null if no changes found.</returns>
|
||||
IUndoAction End(object snapshotInstance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stack of undo actions for future disposal.
|
||||
/// </summary>
|
||||
private readonly OrderedDictionary<object, IUndoInternal> _snapshots = new OrderedDictionary<object, IUndoInternal>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the undo operations stack.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The undo operations stack.
|
||||
/// </value>
|
||||
public HistoryStack UndoOperationsStack { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when undo operation is done.
|
||||
/// </summary>
|
||||
public event UndoEventDelegate UndoDone;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when redo operation is done.
|
||||
/// </summary>
|
||||
public event UndoEventDelegate RedoDone;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when action is done and appended to the <see cref="Undo"/>.
|
||||
/// </summary>
|
||||
public event UndoEventDelegate ActionDone;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this <see cref="Undo"/> is enabled.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if enabled; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public virtual bool Enabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether can do undo on last performed action.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if can perform undo; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool CanUndo => UndoOperationsStack.HistoryCount > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether can do redo on last undone action.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if can perform redo; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool CanRedo => UndoOperationsStack.ReverseCount > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the first name of the undo action.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The first name of the undo action.
|
||||
/// </value>
|
||||
public string FirstUndoName => UndoOperationsStack.PeekHistory().ActionString;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the first name of the redo action.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The first name of the redo action.
|
||||
/// </value>
|
||||
public string FirstRedoName => UndoOperationsStack.PeekReverse().ActionString;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the capacity of the undo history buffers.
|
||||
/// </summary>
|
||||
public int Capacity
|
||||
{
|
||||
get => UndoOperationsStack.HistoryActionsLimit;
|
||||
set => UndoOperationsStack.HistoryActionsLimit = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal class for keeping reference of undo action.
|
||||
/// </summary>
|
||||
internal class UndoInternal : IUndoInternal
|
||||
{
|
||||
public string ActionString;
|
||||
public object SnapshotInstance;
|
||||
public ObjectSnapshot Snapshot;
|
||||
|
||||
public UndoInternal(object snapshotInstance, string actionString)
|
||||
{
|
||||
ActionString = actionString;
|
||||
SnapshotInstance = snapshotInstance;
|
||||
Snapshot = ObjectSnapshot.CaptureSnapshot(snapshotInstance);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IUndoAction End(object snapshotInstance)
|
||||
{
|
||||
var diff = Snapshot.Compare(snapshotInstance);
|
||||
if (diff.Count == 0)
|
||||
return null;
|
||||
return new UndoActionObject(diff, ActionString, SnapshotInstance);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Undo"/> class.
|
||||
/// </summary>
|
||||
/// <param name="historyActionsLimit">The history actions limit.</param>
|
||||
public Undo(int historyActionsLimit = 1000)
|
||||
{
|
||||
UndoOperationsStack = new HistoryStack(historyActionsLimit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Begins recording for undo action.
|
||||
/// </summary>
|
||||
/// <param name="snapshotInstance">Instance of an object to record.</param>
|
||||
/// <param name="actionString">Name of action to be displayed in undo stack.</param>
|
||||
public void RecordBegin(object snapshotInstance, string actionString)
|
||||
{
|
||||
if (!Enabled)
|
||||
return;
|
||||
|
||||
_snapshots.Add(snapshotInstance, new UndoInternal(snapshotInstance, actionString));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ends recording for undo action.
|
||||
/// </summary>
|
||||
/// <param name="snapshotInstance">Instance of an object to finish recording, if null take last provided.</param>
|
||||
/// <param name="customActionBefore">Custom action to append to the undo block action before recorded modifications apply.</param>
|
||||
/// <param name="customActionAfter">Custom action to append to the undo block action after recorded modifications apply.</param>
|
||||
public void RecordEnd(object snapshotInstance = null, IUndoAction customActionBefore = null, IUndoAction customActionAfter = null)
|
||||
{
|
||||
if (!Enabled)
|
||||
return;
|
||||
|
||||
if (snapshotInstance == null)
|
||||
{
|
||||
snapshotInstance = _snapshots.Last().Key;
|
||||
}
|
||||
var action = _snapshots[snapshotInstance].End(snapshotInstance);
|
||||
_snapshots.Remove(snapshotInstance);
|
||||
|
||||
// It may be null if no changes has been found during recording
|
||||
if (action != null)
|
||||
{
|
||||
// Batch with a custom action if provided
|
||||
if (customActionBefore != null && customActionAfter != null)
|
||||
{
|
||||
action = new MultiUndoAction(new[] { customActionBefore, action, customActionAfter });
|
||||
}
|
||||
else if (customActionBefore != null)
|
||||
{
|
||||
action = new MultiUndoAction(new[] { customActionBefore, action });
|
||||
}
|
||||
else if (customActionAfter != null)
|
||||
{
|
||||
action = new MultiUndoAction(new[] { action, customActionAfter });
|
||||
}
|
||||
|
||||
UndoOperationsStack.Push(action);
|
||||
OnAction(action);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal class for keeping reference of undo action that modifies collection of objects.
|
||||
/// </summary>
|
||||
internal class UndoMultiInternal : IUndoInternal
|
||||
{
|
||||
public string ActionString;
|
||||
public object[] SnapshotInstances;
|
||||
public ObjectSnapshot[] Snapshot;
|
||||
|
||||
public UndoMultiInternal(object[] snapshotInstances, string actionString)
|
||||
{
|
||||
ActionString = actionString;
|
||||
SnapshotInstances = snapshotInstances;
|
||||
Snapshot = new ObjectSnapshot[snapshotInstances.Length];
|
||||
for (var i = 0; i < snapshotInstances.Length; i++)
|
||||
{
|
||||
Snapshot[i] = ObjectSnapshot.CaptureSnapshot(snapshotInstances[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IUndoAction End(object snapshotInstance)
|
||||
{
|
||||
var snapshotInstances = (object[])snapshotInstance;
|
||||
if (snapshotInstances == null || snapshotInstances.Length != SnapshotInstances.Length)
|
||||
throw new ArgumentException("Invalid multi undo action objects.");
|
||||
var actions = new List<UndoActionObject>();
|
||||
for (int i = 0; i < snapshotInstances.Length; i++)
|
||||
{
|
||||
var diff = Snapshot[i].Compare(snapshotInstances[i]);
|
||||
if (diff.Count == 0)
|
||||
continue;
|
||||
actions.Add(new UndoActionObject(diff, ActionString, SnapshotInstances[i]));
|
||||
}
|
||||
if (actions.Count == 0)
|
||||
return null;
|
||||
return new MultiUndoAction(actions);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Begins recording for undo action.
|
||||
/// </summary>
|
||||
/// <param name="snapshotInstances">Instances of objects to record.</param>
|
||||
/// <param name="actionString">Name of action to be displayed in undo stack.</param>
|
||||
public void RecordMultiBegin(object[] snapshotInstances, string actionString)
|
||||
{
|
||||
if (!Enabled)
|
||||
return;
|
||||
|
||||
_snapshots.Add(snapshotInstances, new UndoMultiInternal(snapshotInstances, actionString));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ends recording for undo action.
|
||||
/// </summary>
|
||||
/// <param name="snapshotInstance">Instance of an object to finish recording, if null take last provided.</param>
|
||||
/// <param name="customActionBefore">Custom action to append to the undo block action before recorded modifications apply.</param>
|
||||
/// <param name="customActionAfter">Custom action to append to the undo block action after recorded modifications apply.</param>
|
||||
public void RecordMultiEnd(object[] snapshotInstance = null, IUndoAction customActionBefore = null, IUndoAction customActionAfter = null)
|
||||
{
|
||||
if (!Enabled)
|
||||
return;
|
||||
|
||||
if (snapshotInstance == null)
|
||||
{
|
||||
snapshotInstance = (object[])_snapshots.Last().Key;
|
||||
}
|
||||
var action = _snapshots[snapshotInstance].End(snapshotInstance);
|
||||
_snapshots.Remove(snapshotInstance);
|
||||
|
||||
// It may be null if no changes has been found during recording
|
||||
if (action != null)
|
||||
{
|
||||
// Batch with a custom action if provided
|
||||
if (customActionBefore != null && customActionAfter != null)
|
||||
{
|
||||
action = new MultiUndoAction(new[] { customActionBefore, action, customActionAfter });
|
||||
}
|
||||
else if (customActionBefore != null)
|
||||
{
|
||||
action = new MultiUndoAction(new[] { customActionBefore, action });
|
||||
}
|
||||
else if (customActionAfter != null)
|
||||
{
|
||||
action = new MultiUndoAction(new[] { action, customActionAfter });
|
||||
}
|
||||
|
||||
UndoOperationsStack.Push(action);
|
||||
OnAction(action);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates new undo action for provided instance of object.
|
||||
/// </summary>
|
||||
/// <param name="snapshotInstance">Instance of an object to record</param>
|
||||
/// <param name="actionString">Name of action to be displayed in undo stack.</param>
|
||||
/// <param name="actionsToSave">Action in after witch recording will be finished.</param>
|
||||
public void RecordAction(object snapshotInstance, string actionString, Action actionsToSave)
|
||||
{
|
||||
RecordBegin(snapshotInstance, actionString);
|
||||
actionsToSave?.Invoke();
|
||||
RecordEnd(snapshotInstance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates new undo action for provided instance of object.
|
||||
/// </summary>
|
||||
/// <param name="snapshotInstance">Instance of an object to record</param>
|
||||
/// <param name="actionString">Name of action to be displayed in undo stack.</param>
|
||||
/// <param name="actionsToSave">Action in after witch recording will be finished.</param>
|
||||
public void RecordAction<T>(T snapshotInstance, string actionString, Action<T> actionsToSave)
|
||||
where T : new()
|
||||
{
|
||||
RecordBegin(snapshotInstance, actionString);
|
||||
actionsToSave?.Invoke(snapshotInstance);
|
||||
RecordEnd(snapshotInstance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates new undo action for provided instance of object.
|
||||
/// </summary>
|
||||
/// <param name="snapshotInstance">Instance of an object to record</param>
|
||||
/// <param name="actionString">Name of action to be displayed in undo stack.</param>
|
||||
/// <param name="actionsToSave">Action in after witch recording will be finished.</param>
|
||||
public void RecordAction(object snapshotInstance, string actionString, Action<object> actionsToSave)
|
||||
{
|
||||
RecordBegin(snapshotInstance, actionString);
|
||||
actionsToSave?.Invoke(snapshotInstance);
|
||||
RecordEnd(snapshotInstance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the action to the history.
|
||||
/// </summary>
|
||||
/// <param name="action">The action.</param>
|
||||
public void AddAction(IUndoAction action)
|
||||
{
|
||||
if (action == null)
|
||||
throw new ArgumentNullException();
|
||||
|
||||
if (!Enabled)
|
||||
return;
|
||||
|
||||
UndoOperationsStack.Push(action);
|
||||
|
||||
OnAction(action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Undo last recorded action
|
||||
/// </summary>
|
||||
public void PerformUndo()
|
||||
{
|
||||
if (!Enabled || !CanUndo)
|
||||
return;
|
||||
|
||||
var action = (IUndoAction)UndoOperationsStack.PopHistory();
|
||||
action.Undo();
|
||||
|
||||
OnUndo(action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Redo last undone action
|
||||
/// </summary>
|
||||
public void PerformRedo()
|
||||
{
|
||||
if (!Enabled || !CanRedo)
|
||||
return;
|
||||
|
||||
var action = (IUndoAction)UndoOperationsStack.PopReverse();
|
||||
action.Do();
|
||||
|
||||
OnRedo(action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when <see cref="Undo"/> performs action.
|
||||
/// </summary>
|
||||
/// <param name="action">The action.</param>
|
||||
protected virtual void OnAction(IUndoAction action)
|
||||
{
|
||||
ActionDone?.Invoke(action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when <see cref="Undo"/> performs undo action.
|
||||
/// </summary>
|
||||
/// <param name="action">The action.</param>
|
||||
protected virtual void OnUndo(IUndoAction action)
|
||||
{
|
||||
UndoDone?.Invoke(action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when <see cref="Undo"/> performs redo action.
|
||||
/// </summary>
|
||||
/// <param name="action">The action.</param>
|
||||
protected virtual void OnRedo(IUndoAction action)
|
||||
{
|
||||
RedoDone?.Invoke(action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the history.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
_snapshots.Clear();
|
||||
UndoOperationsStack.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
UndoDone = null;
|
||||
RedoDone = null;
|
||||
ActionDone = null;
|
||||
|
||||
Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
89
Source/Editor/Undo/UndoActionBase.cs
Normal file
89
Source/Editor/Undo/UndoActionBase.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using FlaxEditor.SceneGraph;
|
||||
using FlaxEngine;
|
||||
using Newtonsoft.Json;
|
||||
using JsonSerializer = FlaxEngine.Json.JsonSerializer;
|
||||
|
||||
namespace FlaxEditor
|
||||
{
|
||||
internal class SceneTreeNodeConverter : JsonConverter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
|
||||
{
|
||||
Guid id = Guid.Empty;
|
||||
if (value is SceneGraphNode obj)
|
||||
id = obj.ID;
|
||||
|
||||
writer.WriteValue(id.ToString("N"));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
|
||||
{
|
||||
if (reader.TokenType == JsonToken.String)
|
||||
{
|
||||
var id = Guid.Parse((string)reader.Value);
|
||||
return SceneGraphFactory.FindNode(id);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return typeof(SceneGraphNode).IsAssignableFrom(objectType);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base class for <see cref="IUndoAction"/> implementations. Stores undo data serialized and preserves references to the game objects.
|
||||
/// </summary>
|
||||
/// <typeparam name="TData">The type of the data. Must have <see cref="SerializableAttribute"/>.</typeparam>
|
||||
/// <seealso cref="FlaxEditor.IUndoAction" />
|
||||
[Serializable]
|
||||
public abstract class UndoActionBase<TData> : IUndoAction where TData : struct
|
||||
{
|
||||
/// <summary>
|
||||
/// The serialized data (Json text).
|
||||
/// </summary>
|
||||
[Serialize]
|
||||
protected string _data;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the serialized undo data.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The data.
|
||||
/// </value>
|
||||
[NoSerialize]
|
||||
public TData Data
|
||||
{
|
||||
get => JsonConvert.DeserializeObject<TData>(_data, JsonSerializer.Settings);
|
||||
protected set => _data = JsonConvert.SerializeObject(value, Formatting.None, JsonSerializer.Settings);
|
||||
/*protected set
|
||||
{
|
||||
_data = JsonConvert.SerializeObject(value, Formatting.Indented, JsonSerializer.Settings);
|
||||
Debug.Info(_data);
|
||||
}*/
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract string ActionString { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract void Do();
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract void Undo();
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Dispose()
|
||||
{
|
||||
_data = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
49
Source/Editor/Undo/UndoBlock.cs
Normal file
49
Source/Editor/Undo/UndoBlock.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class to record undo operations in a block with <c>using</c> keyword.
|
||||
/// <example>
|
||||
/// using(new UndoBlock(undo, obj, "Rename"))
|
||||
/// {
|
||||
/// obj.Name = "super name";
|
||||
/// }
|
||||
/// </example>
|
||||
/// </summary>
|
||||
/// <seealso cref="System.IDisposable" />
|
||||
[HideInEditor]
|
||||
public class UndoBlock : IDisposable
|
||||
{
|
||||
private readonly Undo _undo;
|
||||
private readonly object _snapshotUndoInternal;
|
||||
private readonly IUndoAction _customActionBefore;
|
||||
private readonly IUndoAction _customActionAfter;
|
||||
|
||||
/// <summary>
|
||||
/// Creates new undo object for recording actions with using pattern.
|
||||
/// </summary>
|
||||
/// <param name="undo">The undo/redo object.</param>
|
||||
/// <param name="snapshotInstance">Instance of an object to record.</param>
|
||||
/// <param name="actionString">Name of action to be displayed in undo stack.</param>
|
||||
/// <param name="customActionBefore">Custom action to append to the undo block action before recorded modifications apply.</param>
|
||||
/// <param name="customActionAfter">Custom action to append to the undo block action after recorded modifications apply.</param>
|
||||
public UndoBlock(Undo undo, object snapshotInstance, string actionString, IUndoAction customActionBefore = null, IUndoAction customActionAfter = null)
|
||||
{
|
||||
_snapshotUndoInternal = snapshotInstance;
|
||||
_undo = undo;
|
||||
_undo.RecordBegin(_snapshotUndoInternal, actionString);
|
||||
_customActionBefore = customActionBefore;
|
||||
_customActionAfter = customActionAfter;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
_undo.RecordEnd(_snapshotUndoInternal, _customActionBefore, _customActionAfter);
|
||||
}
|
||||
}
|
||||
}
|
||||
52
Source/Editor/Undo/UndoMultiBlock.cs
Normal file
52
Source/Editor/Undo/UndoMultiBlock.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class to record undo operations in a block with <c>using</c> keyword. Records changes for one or more objects.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// using(new UndoMultiBlock(undo, objs, "Rename objects"))
|
||||
/// {
|
||||
/// foreach(var e in objs)
|
||||
/// e.Name = "super name";
|
||||
/// }
|
||||
/// </example>
|
||||
/// <seealso cref="System.IDisposable" />
|
||||
[HideInEditor]
|
||||
public class UndoMultiBlock : IDisposable
|
||||
{
|
||||
private readonly object[] _snapshotUndoInternal;
|
||||
private readonly Undo _undo;
|
||||
private readonly IUndoAction _customActionBefore;
|
||||
private readonly IUndoAction _customActionAfter;
|
||||
|
||||
/// <summary>
|
||||
/// Creates new undo object for recording actions with using pattern.
|
||||
/// </summary>
|
||||
/// <param name="undo">The undo/redo object.</param>
|
||||
/// <param name="snapshotInstances">Instances of objects to record.</param>
|
||||
/// <param name="actionString">Name of action to be displayed in undo stack.</param>
|
||||
/// <param name="customActionBefore">Custom action to append to the undo block action before recorded modifications apply.</param>
|
||||
/// <param name="customActionAfter">Custom action to append to the undo block action after recorded modifications apply.</param>
|
||||
public UndoMultiBlock(Undo undo, IEnumerable<object> snapshotInstances, string actionString, IUndoAction customActionBefore = null, IUndoAction customActionAfter = null)
|
||||
{
|
||||
_snapshotUndoInternal = snapshotInstances.ToArray();
|
||||
_undo = undo;
|
||||
_undo.RecordMultiBegin(_snapshotUndoInternal, actionString);
|
||||
_customActionBefore = customActionBefore;
|
||||
_customActionAfter = customActionAfter;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
_undo.RecordMultiEnd(_snapshotUndoInternal, _customActionBefore, _customActionAfter);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user