فهرست منبع

Added import node and fixed closed docs redo case

Krzysztof Krysiński 2 ماه پیش
والد
کامیت
fb14448c2c

+ 89 - 0
src/PixiEditor.ChangeableDocument/Changes/NodeGraph/ImportNode_Change.cs

@@ -0,0 +1,89 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using PixiEditor.ChangeableDocument.ChangeInfos.NodeGraph;
+
+namespace PixiEditor.ChangeableDocument.Changes.NodeGraph;
+
+internal class ImportNode_Change : Change
+{
+    private ICrossDocumentPipe<IReadOnlyNode> sourceDocumentPipe;
+    private Guid duplicateGuid;
+    private ConnectionsData connectionsData;
+    private Node? cloned;
+
+    [GenerateMakeChangeAction]
+    public ImportNode_Change(ICrossDocumentPipe<IReadOnlyNode> pipe, Guid newGuid)
+    {
+        sourceDocumentPipe = pipe;
+        duplicateGuid = newGuid;
+    }
+
+    public override bool InitializeAndValidate(Document target)
+    {
+        if (sourceDocumentPipe is not { CanOpen: true })
+            return false;
+
+        if (!sourceDocumentPipe.IsOpen)
+        {
+            sourceDocumentPipe.Open();
+        }
+
+        IReadOnlyNode? node = sourceDocumentPipe.TryAccessData();
+        if (node == null || target.NodeGraph.OutputNode == null)
+            return false;
+
+        connectionsData = NodeOperations.CreateConnectionsData(target.NodeGraph.OutputNode);
+        return true;
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply,
+        out bool ignoreInUndo)
+    {
+        ignoreInUndo = false;
+
+        var node = cloned ?? sourceDocumentPipe.TryAccessData();
+        if (node is not Node graphNode)
+        {
+            ignoreInUndo = true;
+            return new None();
+        }
+
+        var clone = (Node)graphNode.Clone();
+        clone.Id = duplicateGuid;
+        cloned = clone;
+
+        target.NodeGraph.AddNode(clone);
+
+        return CreateNode_ChangeInfo.CreateFromNode(clone);
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
+    {
+        var node = target.FindNode(duplicateGuid);
+
+        target.NodeGraph.RemoveNode(node);
+        node.Dispose();
+
+        List<IChangeInfo> changes = new();
+
+        changes.AddRange(NodeOperations.DetachNode(target.NodeGraph, node));
+        changes.Add(new DeleteNode_ChangeInfo(node.Id));
+
+        if (connectionsData is not null)
+        {
+            Node originalNode = target.NodeGraph.OutputNode;
+            changes.AddRange(
+                NodeOperations.ConnectStructureNodeProperties(connectionsData, originalNode, target.NodeGraph));
+        }
+
+        return changes;
+    }
+
+    public override void Dispose()
+    {
+        base.Dispose();
+        sourceDocumentPipe.Dispose();
+        cloned?.Dispose();
+    }
+}

+ 52 - 28
src/PixiEditor.ChangeableDocument/Changes/Structure/ImportFolder_Change.cs

@@ -15,7 +15,10 @@ internal class ImportFolder_Change : Change
     private ICrossDocumentPipe<IReadOnlyFolderNode> sourcefolderPipe;
     private Guid duplicateGuid;
     private Guid[] contentGuids;
-    private Guid[] contentDuplicateGuids;
+
+    private FolderNode? clonedFolderNode;
+    private List<Node> clonedContentNodes = new();
+    private Dictionary<Guid, Guid> contentGuidToNodeMap;
 
     private Guid[]? childGuidsToUse;
 
@@ -55,7 +58,7 @@ internal class ImportFolder_Change : Change
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply,
         out bool ignoreInUndo)
     {
-        var readOnlyFolderNode = sourcefolderPipe.TryAccessData();
+        var readOnlyFolderNode = clonedFolderNode ?? sourcefolderPipe.TryAccessData();
 
         if (readOnlyFolderNode is not FolderNode folderNode || target.NodeGraph.OutputNode == null)
         {
@@ -65,6 +68,7 @@ internal class ImportFolder_Change : Change
 
         FolderNode clone = (FolderNode)folderNode.Clone();
         clone.Id = duplicateGuid;
+        clonedFolderNode = clone;
 
         InputProperty<Painter?> targetInput = target.NodeGraph.OutputNode.InputProperties.FirstOrDefault(x =>
             x.ValueType == typeof(Painter)) as InputProperty<Painter?>;
@@ -99,11 +103,11 @@ internal class ImportFolder_Change : Change
         changes.AddRange(NodeOperations.DetachStructureNode(member));
         changes.Add(new DeleteStructureMember_ChangeInfo(member.Id));
 
-        if (contentDuplicateGuids is not null)
+        if (clonedContentNodes is not null)
         {
-            foreach (Guid contentGuid in contentDuplicateGuids)
+            foreach (var content in clonedContentNodes)
             {
-                Node contentNode = target.FindNodeOrThrow<Node>(contentGuid);
+                Node contentNode = target.FindNodeOrThrow<Node>(content.Id);
                 changes.AddRange(NodeOperations.DetachNode(target.NodeGraph, contentNode));
                 changes.Add(new DeleteNode_ChangeInfo(contentNode.Id));
 
@@ -127,42 +131,62 @@ internal class ImportFolder_Change : Change
     private void DuplicateContent(Document target, FolderNode clone, FolderNode existingLayer,
         List<IChangeInfo> operations)
     {
-        Dictionary<Guid, Guid> nodeMap = new Dictionary<Guid, Guid>();
-
-        nodeMap[existingLayer.Id] = clone.Id;
-        int counter = 0;
-        List<Guid> contentGuidList = new();
-
-        existingLayer.Content.Connection?.Node.TraverseBackwards(x =>
+        if (contentGuidToNodeMap == null)
         {
-            if (x is not Node targetNode)
-                return false;
+            contentGuidToNodeMap = new Dictionary<Guid, Guid>();
 
-            Node? node = targetNode.Clone();
+            contentGuidToNodeMap[existingLayer.Id] = clone.Id;
+            int counter = 0;
 
-            if (node is not FolderNode && childGuidsToUse is not null && counter < childGuidsToUse.Length)
+            existingLayer.Content.Connection?.Node.TraverseBackwards(x =>
             {
-                node.Id = childGuidsToUse[counter];
-                counter++;
-            }
+                if (x is not Node targetNode)
+                    return false;
 
-            nodeMap[x.Id] = node.Id;
-            contentGuidList.Add(node.Id);
+                Node? node = targetNode.Clone();
+                clonedContentNodes.Add(node.Clone(true));
 
-            target.NodeGraph.AddNode(node);
+                if (node is not FolderNode && childGuidsToUse is not null && counter < childGuidsToUse.Length)
+                {
+                    node.Id = childGuidsToUse[counter];
+                    counter++;
+                }
 
-            operations.Add(CreateNode_ChangeInfo.CreateFromNode(node));
-            return true;
-        });
+                contentGuidToNodeMap[x.Id] = node.Id;
+
+                target.NodeGraph.AddNode(node);
+
+                operations.Add(CreateNode_ChangeInfo.CreateFromNode(node));
+                return true;
+            });
+        }
+        else
+        {
+            foreach (var clonedContentNode in clonedContentNodes)
+            {
+                var toAdd = clonedContentNode.Clone(true);
+                target.NodeGraph.AddNode(toAdd);
+                operations.Add(CreateNode_ChangeInfo.CreateFromNode(toAdd));
+            }
+        }
 
         foreach (var data in contentConnectionsData)
         {
-            var updatedData = data.Value.WithUpdatedIds(nodeMap);
-            Guid targetNodeId = nodeMap[data.Key];
+            var updatedData = data.Value.WithUpdatedIds(contentGuidToNodeMap);
+            Guid targetNodeId = contentGuidToNodeMap[data.Key];
             operations.AddRange(NodeOperations.ConnectStructureNodeProperties(updatedData,
                 target.FindNodeOrThrow<Node>(targetNodeId), target.NodeGraph));
         }
+    }
 
-        contentDuplicateGuids = contentGuidList.ToArray();
+    public override void Dispose()
+    {
+        base.Dispose();
+        sourcefolderPipe.Dispose();
+        clonedFolderNode?.Dispose();
+        foreach (var node in clonedContentNodes)
+        {
+            node?.Dispose();
+        }
     }
 }

+ 8 - 2
src/PixiEditor.ChangeableDocument/Changes/Structure/ImportLayer_Change.cs

@@ -14,6 +14,8 @@ internal class ImportLayer_Change : Change
     private Dictionary<Guid, VecD> originalPositions;
     private ConnectionsData? connectionsData;
 
+    private LayerNode? clonedLayer;
+
     private Guid duplicateGuid;
 
     [GenerateMakeChangeAction]
@@ -37,9 +39,10 @@ internal class ImportLayer_Change : Change
         if (layer == null || target.NodeGraph.OutputNode == null)
             return false;
 
+        if (target.NodeGraph.OutputNode == null) return false;
+
         connectionsData = NodeOperations.CreateConnectionsData(target.NodeGraph.OutputNode);
 
-        if (target.NodeGraph.OutputNode == null) return false;
 
         return true;
     }
@@ -49,7 +52,7 @@ internal class ImportLayer_Change : Change
     {
         ignoreInUndo = false;
 
-        var layer = sourceDocumentPipe.TryAccessData();
+        var layer = clonedLayer ?? sourceDocumentPipe.TryAccessData();
         if (layer is not LayerNode layerNode)
         {
             ignoreInUndo = true;
@@ -58,6 +61,8 @@ internal class ImportLayer_Change : Change
 
         var clone = (LayerNode)layerNode.Clone();
         clone.Id = duplicateGuid;
+        clonedLayer = clone;
+
         ResizeImageData(clone, target.Size);
 
         var targetInput = target.NodeGraph.OutputNode?.InputProperties.FirstOrDefault(x =>
@@ -108,6 +113,7 @@ internal class ImportLayer_Change : Change
     public override void Dispose()
     {
         sourceDocumentPipe?.Dispose();
+        clonedLayer?.Dispose();
     }
 
     private void ResizeImageData(LayerNode layerNode, VecI docSize)

+ 26 - 5
src/PixiEditor/Models/Controllers/ClipboardController.cs

@@ -598,9 +598,9 @@ internal static class ClipboardController
         return false;
     }
 
-    public static async Task CopyNodes(Guid[] nodeIds)
+    public static async Task CopyNodes(Guid[] nodeIds, Guid docId)
     {
-        await CopyIds(nodeIds, ClipboardDataFormats.NodeIdList);
+        await CopyIds(nodeIds, ClipboardDataFormats.NodeIdList, docId);
     }
 
     public static async Task<Guid[]> GetNodeIds()
@@ -653,21 +653,42 @@ internal static class ClipboardController
         return formats.Contains(format);
     }
 
-    public static async Task CopyCels(Guid[] celIds)
+    public static async Task CopyCels(Guid[] celIds, Guid docId)
     {
-        await CopyIds(celIds, ClipboardDataFormats.CelIdList);
+        await CopyIds(celIds, ClipboardDataFormats.CelIdList, docId);
     }
 
-    public static async Task CopyIds(Guid[] ids, string format)
+    public static async Task CopyIds(Guid[] ids, string format, Guid docId)
     {
         await Clipboard.ClearAsync();
 
         DataObject data = new DataObject();
 
+        data.Set(ClipboardDataFormats.DocumentFormat, Encoding.UTF8.GetBytes(docId.ToString()));
+
         byte[] idsBytes = Encoding.UTF8.GetBytes(string.Join(";", ids.Select(x => x.ToString())));
 
         data.Set(format, idsBytes);
 
         await Clipboard.SetDataObjectAsync(data);
     }
+
+    public static async Task<Guid> GetDocumentId()
+    {
+        var data = await TryGetDataObject();
+        if (data == null)
+            return Guid.Empty;
+
+        foreach (var dataObject in data)
+        {
+            if (dataObject.Contains(ClipboardDataFormats.DocumentFormat))
+            {
+                byte[] guidBytes = (byte[])dataObject.Get(ClipboardDataFormats.DocumentFormat);
+                string guidString = System.Text.Encoding.UTF8.GetString(guidBytes);
+                return Guid.Parse(guidString);
+            }
+        }
+
+        return Guid.Empty;
+    }
 }

+ 57 - 17
src/PixiEditor/Models/DocumentModels/Public/DocumentOperationsModule.cs

@@ -1,7 +1,4 @@
-using System.Collections;
-using System.Collections.Immutable;
-using System.Reactive.Disposables;
-using ChunkyImageLib.DataHolders;
+using System.Collections.Immutable;
 using PixiEditor.ChangeableDocument;
 using PixiEditor.ChangeableDocument.Actions;
 using PixiEditor.Helpers.Extensions;
@@ -24,7 +21,6 @@ using PixiEditor.Models.Position;
 using PixiEditor.Models.Tools;
 using Drawie.Numerics;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
-using PixiEditor.ViewModels.Document.Nodes;
 
 namespace PixiEditor.Models.DocumentModels.Public;
 #nullable enable
@@ -204,60 +200,98 @@ internal class DocumentOperationsModule : IDocumentOperations
     /// Duplicates the member with the <paramref name="guidValue"/>
     /// </summary>
     /// <param name="guidValue">The Guid of the member</param>
-    public void DuplicateMember(Guid guidValue)
+    public Guid? DuplicateMember(Guid guidValue)
     {
         if (Internals.ChangeController.IsBlockingChangeActive)
-            return;
+            return null;
 
         Internals.ChangeController.TryStopActiveExecutor();
 
         bool isFolder = Document.StructureHelper.Find(guidValue) is IFolderHandler;
+        Guid newGuid = Guid.NewGuid();
         if (!isFolder)
         {
-            Guid newGuid = Guid.NewGuid();
             Internals.ActionAccumulator.AddFinishedActions(
                 new DuplicateLayer_Action(guidValue, newGuid),
                 new CreateAnimationDataFromLayer_Action(newGuid));
         }
         else
         {
-            Guid newGuid = Guid.NewGuid();
             Internals.ActionAccumulator.AddFinishedActions(
                 new DuplicateFolder_Action(guidValue, newGuid, null),
                 new SetSelectedMember_PassthroughAction(newGuid));
         }
+
+        return newGuid;
     }
 
-    public void ImportMember(Guid layerId, IDocument sourceDocument)
+    public Guid? ImportMember(Guid layerId, IDocument sourceDocument)
     {
         if (Internals.ChangeController.IsBlockingChangeActive)
-            return;
+            return null;
 
         Internals.ChangeController.TryStopActiveExecutor();
 
         if (sourceDocument == this.Document)
         {
-            DuplicateMember(layerId);
-            return;
+            return DuplicateMember(layerId);
         }
 
         if (!sourceDocument.StructureHelper.TryFindNode(layerId, out IStructureMemberHandler? member))
-            return;
+            return null;
 
+        Guid newGuid = Guid.NewGuid();
         if (member is ILayerHandler layer)
         {
-            Guid newGuid = Guid.NewGuid();
             Internals.ActionAccumulator.AddFinishedActions(
                 new ImportLayer_Action(sourceDocument.ShareNode<IReadOnlyLayerNode>(layer.Id), newGuid),
                 new CreateAnimationDataFromLayer_Action(newGuid));
         }
         else if (member is IFolderHandler folder)
         {
-            Guid newGuid = Guid.NewGuid();
             Internals.ActionAccumulator.AddFinishedActions(
                 new ImportFolder_Action(sourceDocument.ShareNode<IReadOnlyFolderNode>(folder.Id), newGuid, null),
                 new SetSelectedMember_PassthroughAction(newGuid));
         }
+
+        return newGuid;
+    }
+
+    public Guid? ImportNode(Guid nodeId, IDocument sourceDocument)
+    {
+        if (Internals.ChangeController.IsBlockingChangeActive)
+            return null;
+
+        Internals.ChangeController.TryStopActiveExecutor();
+
+        if (sourceDocument == this.Document)
+        {
+            return DuplicateNode(nodeId);
+        }
+
+        if (!sourceDocument.StructureHelper.TryFindNode(nodeId, out INodeHandler? node))
+            return null;
+
+        Guid newGuid = Guid.NewGuid();
+        if (node is ILayerHandler)
+        {
+            Internals.ActionAccumulator.AddFinishedActions(
+                new ImportLayer_Action(sourceDocument.ShareNode<IReadOnlyLayerNode>(nodeId), newGuid),
+                new CreateAnimationDataFromLayer_Action(newGuid));
+        }
+        else if (node is IFolderHandler)
+        {
+            Internals.ActionAccumulator.AddFinishedActions(
+                new ImportFolder_Action(sourceDocument.ShareNode<IReadOnlyFolderNode>(nodeId), newGuid, null),
+                new SetSelectedMember_PassthroughAction(newGuid));
+        }
+        else
+        {
+            Internals.ActionAccumulator.AddFinishedActions(
+                new ImportNode_Action(sourceDocument.ShareNode<IReadOnlyNode>(nodeId), newGuid));
+        }
+
+        return newGuid;
     }
 
     /// <summary>
@@ -311,7 +345,8 @@ internal class DocumentOperationsModule : IDocumentOperations
     /// <param name="anchor">Where the existing content should be put</param>
     public void ResizeCanvas(VecI newSize, ResizeAnchor anchor)
     {
-        if (Internals.ChangeController.IsBlockingChangeActive || newSize.X > Constants.MaxCanvasSize || newSize.Y > Constants.MaxCanvasSize ||
+        if (Internals.ChangeController.IsBlockingChangeActive || newSize.X > Constants.MaxCanvasSize ||
+            newSize.Y > Constants.MaxCanvasSize ||
             newSize.X < 1 ||
             newSize.Y < 1)
             return;
@@ -908,6 +943,11 @@ internal class DocumentOperationsModule : IDocumentOperations
             node.InternalName == OutputNode.UniqueName)
             return null;
 
+        if (node is IStructureMemberHandler)
+        {
+            return ImportMember(nodeId, Document);
+        }
+
         Guid newGuid = Guid.NewGuid();
 
         Internals.ActionAccumulator.AddFinishedActions(new DuplicateNode_Action(nodeId, newGuid));

+ 1 - 1
src/PixiEditor/Models/Handlers/IDocumentOperations.cs

@@ -7,7 +7,7 @@ namespace PixiEditor.Models.Handlers;
 internal interface IDocumentOperations
 {
     public void DeleteStructureMember(Guid memberGuidValue);
-    public void DuplicateMember(Guid memberGuidValue);
+    public Guid? DuplicateMember(Guid memberGuidValue);
     public void AddSoftSelectedMember(Guid memberGuidValue);
     public void MoveStructureMember(Guid memberGuidValue, Guid target, StructureMemberPlacement placement);
     public void SetSelectedMember(Guid memberId);

+ 17 - 3
src/PixiEditor/ViewModels/SubViewModels/ClipboardViewModel.cs

@@ -21,6 +21,8 @@ using PixiEditor.Models.Handlers;
 using PixiEditor.Models.IO;
 using PixiEditor.Models.Layers;
 using Drawie.Numerics;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.Helpers.Constants;
 using PixiEditor.Models.Commands;
 using PixiEditor.UI.Common.Fonts;
@@ -203,6 +205,7 @@ internal class ClipboardViewModel : SubViewModel<ViewModelMain>
 
         Dispatcher.UIThread.InvokeAsync(async () =>
         {
+            Guid documentId = await ClipboardController.GetDocumentId();
             Guid[] toDuplicate = await ClipboardController.GetNodeIds();
 
             List<Guid> newIds = new();
@@ -211,9 +214,20 @@ internal class ClipboardViewModel : SubViewModel<ViewModelMain>
 
             using var block = doc.Operations.StartChangeBlock();
 
+            DocumentViewModel targetDocument = Owner.DocumentManagerSubViewModel.ActiveDocument;
+
+            if (documentId != Owner.DocumentManagerSubViewModel.ActiveDocument.Id)
+            {
+                targetDocument = Owner.DocumentManagerSubViewModel.Documents.FirstOrDefault(x => x.Id == documentId);
+                if (targetDocument == null)
+                {
+                    return;
+                }
+            }
+
             foreach (var nodeId in toDuplicate)
             {
-                Guid? newId = doc.Operations.DuplicateNode(nodeId);
+                Guid? newId = doc.Operations.ImportNode(nodeId, targetDocument);
                 if (newId != null)
                 {
                     newIds.Add(newId.Value);
@@ -364,7 +378,7 @@ internal class ClipboardViewModel : SubViewModel<ViewModelMain>
         if (selectedNodes.Length == 0)
             return;
 
-        await ClipboardController.CopyNodes(selectedNodes);
+        await ClipboardController.CopyNodes(selectedNodes, doc.Id);
 
         areNodesInClipboard = true;
         ClearHasImageInClipboard();
@@ -384,7 +398,7 @@ internal class ClipboardViewModel : SubViewModel<ViewModelMain>
         if (selectedCels.Length == 0)
             return;
 
-        await ClipboardController.CopyCels(selectedCels);
+        await ClipboardController.CopyCels(selectedCels, doc.Id);
 
         areCelsInClipboard = true;
         ClearHasImageInClipboard();