// Copyright (c) 2012-2023 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 { /// /// Implementation of used to change the root actor of the prefab. /// /// public class SetRootAction : IUndoAction { private PrefabWindow _window; private readonly Guid _before; private readonly Guid _after; /// /// Initializes a new instance of the class. /// /// The window reference. /// The root before. /// The root after. 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(ref oldRootId); var newRoot = Object.Find(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; } /// public string ActionString => "Set root"; /// public void Do() { Set(_before, _after); } /// public void Undo() { Set(_after, _before); } /// public void Dispose() { _window = null; } } /// /// Changes the root object of the prefab. /// 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); } /// /// Cuts selected objects. /// public void Cut() { Copy(); Delete(); } /// /// Copies selected objects to system clipboard. /// 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; } /// /// Pastes objects from the system clipboard. /// public void Paste() { Paste(null); } /// /// Pastes the copied objects. Supports undo/redo. /// /// The target actor to paste copied data. 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.IsPrefabRoot ? actorNode.Actor : actorNode.Actor.Parent; } // Create paste action var pasteAction = CustomPasteActorsAction.CustomPaste(this, data, pasteTargetActor?.ID ?? Guid.Empty); if (pasteAction != null) { OnPasteAction(pasteAction); } // Scroll to new selected node ScrollToSelectedNode(); } /// /// Duplicates selected objects. /// 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().ToArray(), OnSelectionUndo); selectAction.Do(); Undo.AddAction(new MultiUndoAction(pasteAction, selectAction)); OnSelectionChanges(); } private class CustomDeleteActorsAction : DeleteActorsAction { public CustomDeleteActorsAction(List nodes, bool isInverted = false) : base(nodes, isInverted) { } /// 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(); } } /// 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"); } /// protected override void LinkBrokenParentReference(Actor actor) { // Link to prefab root actor.SetParent(_window.Graph.MainActor, false); } /// 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(); } /// protected override SceneGraphNode GetNode(Guid id) { return SceneGraphFactory.GetNode(id); } /// public override void Dispose() { base.Dispose(); _window = null; } } /// /// Deletes selected objects. /// 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(); } } }