Files
mafiesto4 b3ceda5a6e Revert 10c8454e8f to use more common paste logic in scene tree
(duplicate can paste at the same tree level, paste puts new object into the selection by default)
2026-04-08 22:19:21 +02:00

350 lines
12 KiB
C#

// Copyright (c) Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using FlaxEditor.Actions;
using FlaxEditor.SceneGraph;
using FlaxEngine;
using Object = FlaxEngine.Object;
namespace FlaxEditor.Windows.Assets
{
public sealed partial class PrefabWindow
{
/// <summary>
/// Implementation of <see cref="IUndoAction"/> used to change the root actor of the prefab.
/// </summary>
/// <seealso cref="FlaxEditor.IUndoAction" />
public class SetRootAction : IUndoAction
{
private PrefabWindow _window;
private readonly Guid _before;
private readonly Guid _after;
/// <summary>
/// Initializes a new instance of the <see cref="SetRootAction"/> class.
/// </summary>
/// <param name="window">The window reference.</param>
/// <param name="before">The root before.</param>
/// <param name="after">The root after.</param>
internal SetRootAction(PrefabWindow window, Actor before, Actor after)
{
_window = window;
_before = before.ID;
_after = after.ID;
}
private void Set(Guid oldRootId, Guid newRootId)
{
var oldRoot = Object.Find<Actor>(ref oldRootId);
var newRoot = Object.Find<Actor>(ref newRootId);
_window.Graph.MainActor = null;
_window.Viewport.Instance = null;
if (SceneGraphFactory.Nodes.TryGetValue(oldRootId, out var oldRootNode))
oldRootNode.Dispose();
if (SceneGraphFactory.Nodes.TryGetValue(newRootId, out var newRootNode))
newRootNode.Dispose();
newRoot.Parent = null;
oldRoot.Parent = newRoot;
_window.Graph.MainActor = newRoot;
_window.Viewport.Instance = newRoot;
}
/// <inheritdoc />
public string ActionString => "Set root";
/// <inheritdoc />
public void Do()
{
Set(_before, _after);
}
/// <inheritdoc />
public void Undo()
{
Set(_after, _before);
}
/// <inheritdoc />
public void Dispose()
{
_window = null;
}
}
/// <summary>
/// Changes the root object of the prefab.
/// </summary>
private void SetRoot()
{
var oldRoot = Graph.MainActor;
var newRoot = ((ActorNode)Selection[0]).Actor;
Deselect();
var action = new SetRootAction(this, oldRoot, newRoot);
action.Do();
Undo.AddAction(action);
}
/// <summary>
/// Cuts selected objects.
/// </summary>
public void Cut()
{
Copy();
Delete();
}
/// <summary>
/// Copies selected objects to system clipboard.
/// </summary>
public void Copy()
{
// Peek things that can be copied (copy all actors)
var objects = Selection.Where(x => x.CanCopyPaste).ToList().BuildAllNodes().Where(x => x.CanCopyPaste && x is ActorNode).ToList();
if (objects.Count == 0)
return;
// Serialize actors
var actors = objects.ConvertAll(x => ((ActorNode)x).Actor);
var data = Actor.ToBytes(actors.ToArray());
if (data == null)
{
Editor.LogError("Failed to copy actors data.");
return;
}
// Copy data
Clipboard.RawData = data;
}
/// <summary>
/// Pastes objects from the system clipboard.
/// </summary>
public void Paste()
{
Paste(null);
}
/// <summary>
/// Pastes the copied objects. Supports undo/redo.
/// </summary>
/// <param name="pasteTargetActor">The target actor to paste copied data.</param>
public void Paste(Actor pasteTargetActor)
{
// Get clipboard data
var data = Clipboard.RawData;
// Set paste target if only one actor is selected and no target provided
if (pasteTargetActor == null && Selection.Count == 1 && Selection[0] is ActorNode actorNode)
{
pasteTargetActor = actorNode.Actor;
}
// Create paste action
var pasteAction = CustomPasteActorsAction.CustomPaste(this, data, pasteTargetActor?.ID ?? Guid.Empty);
if (pasteAction != null)
{
OnPasteAction(pasteAction);
}
// Scroll to new selected node
ScrollToSelectedNode();
}
/// <summary>
/// Duplicates selected objects.
/// </summary>
public void Duplicate()
{
// Peek things that can be copied (copy all actors)
var objects = Selection.Where(x => x.CanDuplicate && x != Graph.Main).ToList().BuildAllNodes().Where(x => x.CanDuplicate && x is ActorNode).ToList();
if (objects.Count == 0)
return;
// Serialize actors
var actors = objects.ConvertAll(x => ((ActorNode)x).Actor);
var data = Actor.ToBytes(actors.ToArray());
if (data == null)
{
Editor.LogError("Failed to copy actors data.");
return;
}
// Create paste action (with selecting spawned objects)
var pasteAction = CustomPasteActorsAction.CustomDuplicate(this, data, Guid.Empty);
if (pasteAction != null)
{
OnPasteAction(pasteAction);
}
// Scroll to new selected node
ScrollToSelectedNode();
}
private void OnPasteAction(PasteActorsAction pasteAction)
{
pasteAction.Do(out _, out var nodeParents);
// Select spawned objects
var selectAction = new SelectionChangeAction(Selection.ToArray(), nodeParents.Cast<SceneGraphNode>().ToArray(), OnSelectionUndo);
selectAction.Do();
Undo.AddAction(new MultiUndoAction(pasteAction, selectAction));
OnSelectionChanges();
}
private class CustomDeleteActorsAction : DeleteActorsAction
{
public CustomDeleteActorsAction(List<SceneGraphNode> nodes, bool isInverted = false)
: base(nodes, isInverted)
{
}
/// <inheritdoc />
protected override void Delete()
{
var nodes = _nodeParents.ToArray();
// Unlink nodes from parents (actors spawned for prefab editing are not in a gameplay and may not send some important events)
for (int i = 0; i < nodes.Length; i++)
{
if (nodes[i] is ActorNode actorNode)
actorNode.Actor.Parent = null;
}
base.Delete();
// Remove nodes (actors in prefab are not in a gameplay and some events from the engine may not be send eg. ActorDeleted)
for (int i = 0; i < nodes.Length; i++)
{
nodes[i].Dispose();
}
}
/// <inheritdoc />
protected override SceneGraphNode GetNode(Guid id)
{
return SceneGraphFactory.GetNode(id);
}
}
private class CustomPasteActorsAction : PasteActorsAction
{
private PrefabWindow _window;
private CustomPasteActorsAction(PrefabWindow window, byte[] data, Guid[] objectIds, ref Guid pasteParent, string name)
: base(data, objectIds, ref pasteParent, name)
{
_window = window;
}
internal static CustomPasteActorsAction CustomPaste(PrefabWindow window, byte[] data, Guid pasteParent)
{
var objectIds = Actor.TryGetSerializedObjectsIds(data);
if (objectIds == null)
return null;
return new CustomPasteActorsAction(window, data, objectIds, ref pasteParent, "Paste actors");
}
internal static CustomPasteActorsAction CustomDuplicate(PrefabWindow window, byte[] data, Guid pasteParent)
{
var objectIds = Actor.TryGetSerializedObjectsIds(data);
if (objectIds == null)
return null;
return new CustomPasteActorsAction(window, data, objectIds, ref pasteParent, "Duplicate actors");
}
/// <inheritdoc />
protected override void LinkBrokenParentReference(ActorNode actorNode)
{
// Link to prefab root
actorNode.Actor.SetParent(_window.Graph.MainActor, false);
}
/// <inheritdoc />
protected override void CheckBrokenParentReference(ActorNode actorNode)
{
if (actorNode.Actor.Scene != null || actorNode.Root != _window.Graph.Root)
LinkBrokenParentReference(actorNode);
}
/// <inheritdoc />
public override void Undo()
{
var nodes = _nodeParents.ToArray();
for (int i = 0; i < nodes.Length; i++)
{
var node = SceneGraphFactory.FindNode(_nodeParents[i]);
if (node != null)
{
// Unlink nodes from parents (actors spawned for prefab editing are not in a gameplay and may not send some important events)
if (node is ActorNode actorNode)
actorNode.Actor.Parent = null;
// Remove objects
node.Delete();
// Remove nodes (actors in prefab are not in a gameplay and some events from the engine may not be send eg. ActorDeleted)
node.Dispose();
}
}
_nodeParents.Clear();
}
/// <inheritdoc />
protected override SceneGraphNode GetNode(Guid id)
{
return SceneGraphFactory.GetNode(id);
}
/// <inheritdoc />
public override void Dispose()
{
base.Dispose();
_window = null;
}
}
/// <summary>
/// Deletes selected objects.
/// </summary>
public void Delete()
{
// Peek things that can be removed
var objects = Selection.Where(x => x != null && x.CanDelete && x != Graph.Main).ToList().BuildAllNodes().Where(x => x != null && x.CanDelete).ToList();
if (objects.Count == 0)
return;
// Change selection
var action1 = new SelectionChangeAction(Selection.ToArray(), new SceneGraphNode[0], OnSelectionUndo);
// Delete objects
var action2 = new CustomDeleteActorsAction(objects);
// Merge actions and perform them
var action = new MultiUndoAction(new IUndoAction[]
{
action1,
action2
}, action2.ActionString);
action.Do();
Undo.AddAction(action);
_treePanel.PerformLayout();
_treePanel.PerformLayout();
}
}
}