Browse Source

folder node branching implemented

flabbet 1 year ago
parent
commit
8d9a9b1559

+ 1 - 4
src/PixiEditor.AvaloniaUI/Models/DocumentModels/DocumentStructureHelper.cs

@@ -66,10 +66,7 @@ internal class DocumentStructureHelper
         {
             Guid guid = Guid.NewGuid();
             //put member above the layer
-            List<IStructureMemberHandler> path = doc.StructureHelper.FindPath(layer.Id);
-            if (path.Count < 1)
-                throw new InvalidOperationException("Couldn't find a path to the selected member");
-            INodeHandler parent = path.Count == 1 ? doc.NodeGraphHandler.OutputNode : path[1];
+            INodeHandler parent = doc.StructureHelper.GetFirstForwardNode(layer);
             internals.ActionAccumulator.AddActions(new CreateStructureMember_Action(parent.Id, guid, type));
             name ??= GetUniqueName(type == StructureMemberType.Layer ? new LocalizedString("NEW_LAYER") : new LocalizedString("NEW_FOLDER"), parent);
             internals.ActionAccumulator.AddActions(new StructureMemberName_Action(guid, name));

+ 32 - 11
src/PixiEditor.AvaloniaUI/Models/DocumentModels/Public/DocumentStructureModule.cs

@@ -6,17 +6,21 @@ namespace PixiEditor.AvaloniaUI.Models.DocumentModels.Public;
 internal class DocumentStructureModule
 {
     private readonly IDocument doc;
+
     public DocumentStructureModule(IDocument owner)
     {
         this.doc = owner;
     }
 
-    public IStructureMemberHandler FindOrThrow(Guid guid) => Find(guid) ?? throw new ArgumentException("Could not find member with guid " + guid.ToString());
+    public IStructureMemberHandler FindOrThrow(Guid guid) => Find(guid) ??
+                                                             throw new ArgumentException(
+                                                                 "Could not find member with guid " + guid.ToString());
+
     public IStructureMemberHandler? Find(Guid guid)
     {
-        return FindNode<IStructureMemberHandler>(guid); 
+        return FindNode<IStructureMemberHandler>(guid);
     }
-    
+
     public T? FindNode<T>(Guid guid) where T : class, INodeHandler
     {
         return doc.NodeGraphHandler.AllNodes.FirstOrDefault(x => x.Id == guid && x is T) as T;
@@ -26,7 +30,9 @@ internal class DocumentStructureModule
     {
         return FindFirstWhere(predicate, doc.NodeGraphHandler);
     }
-    private IStructureMemberHandler? FindFirstWhere(Predicate<IStructureMemberHandler> predicate, INodeGraphHandler graphVM)
+
+    private IStructureMemberHandler? FindFirstWhere(Predicate<IStructureMemberHandler> predicate,
+        INodeGraphHandler graphVM)
     {
         IStructureMemberHandler? result = null;
         graphVM.TryTraverse(node =>
@@ -39,7 +45,7 @@ internal class DocumentStructureModule
 
             return true;
         });
-        
+
         return result;
     }
 
@@ -52,7 +58,7 @@ internal class DocumentStructureModule
             1 => (path[0], null),
             >= 2 => (path[0], (IFolderHandler)path[1]),
             _ => (null, null),
-        }; 
+        };
     }
 
     public (IStructureMemberHandler, IFolderHandler) FindChildAndParentOrThrow(Guid childGuid)
@@ -62,14 +68,14 @@ internal class DocumentStructureModule
             throw new ArgumentException("Couldn't find child and parent");
         return (path[0], (IFolderHandler)path[1]);
     }
-    
+
     public List<IStructureMemberHandler> FindPath(Guid guid)
     {
         List<INodeHandler>? list = new List<INodeHandler>();
         FillPath(doc.NodeGraphHandler.OutputNode, guid, list);
         return list.Cast<IStructureMemberHandler>().ToList();
     }
-    
+
     /// <summary>
     ///     Returns all layers in the document.
     /// </summary>
@@ -77,17 +83,17 @@ internal class DocumentStructureModule
     public List<ILayerHandler> GetAllLayers()
     {
         List<ILayerHandler> layers = new List<ILayerHandler>();
-        
+
         doc.NodeGraphHandler.TryTraverse(node =>
         {
             if (node is ILayerHandler layer)
                 layers.Add(layer);
             return true;
         });
-        
+
         return layers;
     }
-    
+
     private bool FillPath(INodeHandler node, Guid guid, List<INodeHandler> toFill)
     {
         if (node.Id == guid)
@@ -116,4 +122,19 @@ internal class DocumentStructureModule
 
         return found;
     }
+
+    public INodeHandler? GetFirstForwardNode(INodeHandler startNode)
+    {
+        INodeHandler? result = null;
+        startNode.TraverseForwards(node =>
+        {
+            if(node == startNode)
+                return true;
+            
+            result = node;
+            return false;
+        });
+
+        return result;
+    }
 }

+ 23 - 8
src/PixiEditor.AvaloniaUI/ViewModels/Document/StructureTree.cs

@@ -7,7 +7,7 @@ internal class StructureTree
 {
     public ObservableCollection<IStructureMemberHandler> Members { get; } = new();
    
-    private Dictionary<IStructureMemberHandler, ObservableCollection<IStructureMemberHandler>> _memberMap = new();
+    private Dictionary<INodeHandler, ObservableCollection<IStructureMemberHandler>> _memberMap = new();
 
     public void Update(NodeGraphViewModel nodeGraphViewModel)
     {
@@ -23,9 +23,18 @@ internal class StructureTree
         ObservableCollection<IStructureMemberHandler> lastRoot = Members;
         nodeGraphViewModel.OutputNode.TraverseBackwards((node, previous) =>
         {
-            if (node is IStructureMemberHandler structureMemberHandler)
+            if (previous != null && _memberMap.TryGetValue(previous, out var value))
             {
-                membersMet.Add(structureMemberHandler);
+                if (lastRoot != value)
+                {
+                    lastRoot = value;
+                    relativeFolderIndex = lastRoot.Count - 1;
+                }
+            }
+            
+            if(node is IStructureMemberHandler structureMember)
+            {
+                membersMet.Add(structureMember);
             }
             
             if (previous is IFolderHandler folder)
@@ -37,6 +46,7 @@ internal class StructureTree
             if (node is IFolderHandler handler)
             {
                 UpdateMember(handler, relativeFolderIndex, lastRoot);
+                relativeFolderIndex++;
             }
             if (node is ILayerHandler layerHandler)
             {
@@ -44,6 +54,8 @@ internal class StructureTree
                 relativeFolderIndex++;
             }
             
+            _memberMap.TryAdd(node, lastRoot);
+            
             return true;
         });
         
@@ -53,8 +65,9 @@ internal class StructureTree
         {
             if (!membersMet.Contains(member.Key))
             {
-                toRemove.Add(member.Key);
-                member.Value.Remove(member.Key);
+                if(member.Key is not IStructureMemberHandler structureMemberHandler) continue;
+                toRemove.Add(structureMemberHandler);
+                member.Value.Remove(structureMemberHandler);
             }
         }
         
@@ -69,7 +82,8 @@ internal class StructureTree
         bool existsInMembers = _memberMap.ContainsKey(member);
         if(!existsInMembers)
         {
-            root.Insert(relativeIndex, member);
+            int clampIndex = Math.Clamp(relativeIndex, 0, root.Count);
+            root.Insert(clampIndex, member);
             _memberMap.Add(member, root);
             return;
         }
@@ -85,9 +99,10 @@ internal class StructureTree
         }
             
         bool existsAtIndex = root.Count > relativeIndex && root[relativeIndex] == member;
-        if (!existsAtIndex)
+        if (!existsAtIndex) //TODO: this is inefficient, causes a lot of reordering, make the algorithm better
         {
-            root.Move(root.IndexOf(member), relativeIndex);
+            int clampedIndex = Math.Clamp(relativeIndex, 0, root.Count - 1);
+            root.Move(root.IndexOf(member), clampedIndex);
         }
     }
 }

+ 1 - 1
src/PixiEditor.AvaloniaUI/Views/Layers/LayersManager.axaml

@@ -128,7 +128,7 @@
                       ItemsSource="{Binding DataContext.ActiveDocument.NodeGraph.StructureTree.Members, ElementName=layersManager}">
                 <TreeView.ItemsPanel>
                     <ItemsPanelTemplate>
-                        <panels:ReversedOrderStackPanel />
+                        <StackPanel />
                     </ItemsPanelTemplate>
                 </TreeView.ItemsPanel>
                 <TreeView.Resources>

+ 7 - 0
src/PixiEditor.ChangeableDocument/ChangeInfos/NodeGraph/CreateNode_ChangeInfo.cs

@@ -18,4 +18,11 @@ public record CreateNode_ChangeInfo(
         return properties.Select(p => new NodePropertyInfo(p.InternalPropertyName, p.DisplayName, p.ValueType, isInput, guid))
             .ToImmutableArray();
     }
+
+    public static CreateNode_ChangeInfo CreateFromNode(IReadOnlyNode node)
+    {
+        return new CreateNode_ChangeInfo(node.GetType().Name, node.Position, node.Id,
+            CreatePropertyInfos(node.InputProperties, true, node.Id),
+            CreatePropertyInfos(node.OutputProperties, false, node.Id));
+    }
 }

+ 4 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/MergeNode.cs

@@ -1,9 +1,10 @@
 using PixiEditor.ChangeableDocument.Changeables.Animations;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
-public class MergeNode : Node
+public class MergeNode : Node, IBackgroundInput
 {
     public InputProperty<ChunkyImage?> Top { get; }
     public InputProperty<ChunkyImage?> Bottom { get; }
@@ -58,4 +59,6 @@ public class MergeNode : Node
         
         return Output.Value;
     }
+
+    InputProperty<ChunkyImage> IBackgroundInput.Background => Bottom;
 }

+ 1 - 4
src/PixiEditor.ChangeableDocument/Changes/NodeGraph/CreateNode_Change.cs

@@ -58,10 +58,7 @@ internal class CreateNode_Change : Change
         target.NodeGraph.AddNode(node);
         ignoreInUndo = false;
         
-        var inputInfos = CreateNode_ChangeInfo.CreatePropertyInfos(node.InputProperties, true, id);
-        var outputInfos = CreateNode_ChangeInfo.CreatePropertyInfos(node.OutputProperties, false, id);
-        
-        return new CreateNode_ChangeInfo(nodeType.Name, node.Position, id, inputInfos, outputInfos);
+        return CreateNode_ChangeInfo.CreateFromNode(node); 
     }
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)

+ 50 - 25
src/PixiEditor.ChangeableDocument/Changes/Structure/CreateStructureMember_Change.cs

@@ -1,8 +1,10 @@
-using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.ChangeInfos.NodeGraph;
 using PixiEditor.ChangeableDocument.ChangeInfos.Structure;
 using PixiEditor.ChangeableDocument.Enums;
+using PixiEditor.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changes.Structure;
 
@@ -39,28 +41,39 @@ internal class CreateStructureMember_Change : Change
         };
 
         document.TryFindNode<Node>(parentFolderGuid, out var parentNode);
-        document.NodeGraph.AddNode(member);
+
+        List<IChangeInfo> changes = new() { CreateChangeInfo(member) };
 
         IBackgroundInput backgroundInput = (IBackgroundInput)parentNode;
-        List<ConnectProperty_ChangeInfo> connectPropertyChangeInfo = AppendMember(backgroundInput, member);
         
-        List<IChangeInfo> changes = new()
+        if (member is FolderNode folder)
         {
-            CreateChangeInfo(member),
-        };
-        
-        changes.AddRange(connectPropertyChangeInfo);
+            MergeNode mergeNode = new() { Id = Guid.NewGuid() };
+            document.NodeGraph.AddNode(mergeNode);
+            document.NodeGraph.AddNode(member);
+            
+            changes.Add(CreateNode_ChangeInfo.CreateFromNode(mergeNode));
+            AppendFolder(backgroundInput, folder, mergeNode, changes);
+        }
+        else
+        {
+            document.NodeGraph.AddNode(member);
+            List<ConnectProperty_ChangeInfo> connectPropertyChangeInfo =
+                AppendMember(backgroundInput.Background, member.Output, member.Background, member.Id);
+            changes.AddRange(connectPropertyChangeInfo);
+        }
+
 
         ignoreInUndo = false;
-        
+
         return changes;
     }
-    
+
     private IChangeInfo CreateChangeInfo(StructureNode member)
     {
         return type switch
         {
-             StructureMemberType.Layer => CreateLayer_ChangeInfo.FromLayer(parentFolderGuid,
+            StructureMemberType.Layer => CreateLayer_ChangeInfo.FromLayer(parentFolderGuid,
                 (LayerNode)member),
             StructureMemberType.Folder => CreateFolder_ChangeInfo.FromFolder(parentFolderGuid,
                 (FolderNode)member),
@@ -82,40 +95,52 @@ internal class CreateStructureMember_Change : Change
 
         document.NodeGraph.RemoveNode(child);
 
-        List<IChangeInfo> changes = new()
-        {
-            new DeleteStructureMember_ChangeInfo(newMemberGuid),
-        };
+        List<IChangeInfo> changes = new() { new DeleteStructureMember_ChangeInfo(newMemberGuid), };
 
         if (childBackgroundConnection != null)
         {
             childBackgroundConnection?.ConnectTo(backgroundInput.Background);
-            ConnectProperty_ChangeInfo change = new(childBackgroundConnection.Node.Id, backgroundInput.Background.Node.Id, childBackgroundConnection.InternalPropertyName, backgroundInput.Background.InternalPropertyName);
+            ConnectProperty_ChangeInfo change = new(childBackgroundConnection.Node.Id,
+                backgroundInput.Background.Node.Id, childBackgroundConnection.InternalPropertyName,
+                backgroundInput.Background.InternalPropertyName);
             changes.Add(change);
         }
 
         return changes;
     }
 
-    private static List<ConnectProperty_ChangeInfo> AppendMember(IBackgroundInput backgroundInput, StructureNode member)
+    private static void AppendFolder(IBackgroundInput backgroundInput, FolderNode folder, MergeNode mergeNode, List<IChangeInfo> changes)
+    {
+        var appened = AppendMember(backgroundInput.Background, mergeNode.Output, mergeNode.Bottom, mergeNode.Id);
+        changes.AddRange(appened);
+        
+        appened = AppendMember(mergeNode.Top, folder.Output, folder.Background, folder.Id);
+        changes.AddRange(appened);
+    }
+
+    private static List<ConnectProperty_ChangeInfo> AppendMember(InputProperty<ChunkyImage?> parentInput,
+        OutputProperty<ChunkyImage> toAddOutput,
+        InputProperty<ChunkyImage> toAddInput, Guid memberId)
     {
         List<ConnectProperty_ChangeInfo> changes = new();
         IOutputProperty? previouslyConnected = null;
-        if (backgroundInput.Background.Connection != null)
+        if (parentInput.Connection != null)
         {
-            previouslyConnected = backgroundInput.Background.Connection;
+            previouslyConnected = parentInput.Connection;
         }
 
-        member.Output.ConnectTo(backgroundInput.Background);
+        toAddOutput.ConnectTo(parentInput);
 
         if (previouslyConnected != null)
         {
-            member.Background.Connection = previouslyConnected;
-            changes.Add(new ConnectProperty_ChangeInfo(previouslyConnected.Node.Id, member.Id, previouslyConnected.InternalPropertyName, member.Background.InternalPropertyName));
+            toAddInput.Connection = previouslyConnected;
+            changes.Add(new ConnectProperty_ChangeInfo(previouslyConnected.Node.Id, memberId,
+                previouslyConnected.InternalPropertyName, toAddInput.InternalPropertyName));
         }
-        
-        changes.Add(new ConnectProperty_ChangeInfo(member.Id, backgroundInput.Background.Node.Id, member.Output.InternalPropertyName, backgroundInput.Background.InternalPropertyName));
-        
+
+        changes.Add(new ConnectProperty_ChangeInfo(memberId, parentInput.Node.Id,
+            toAddOutput.InternalPropertyName, parentInput.InternalPropertyName));
+
         return changes;
     }
 }