Browse Source

Flat Layer structure wip

flabbet 1 year ago
parent
commit
821e616e6b
19 changed files with 165 additions and 58 deletions
  1. 5 5
      src/PixiEditor.AvaloniaUI/Models/DocumentModels/DocumentUpdater.cs
  2. 1 1
      src/PixiEditor.AvaloniaUI/Models/Rendering/AffectedAreasGatherer.cs
  3. 13 0
      src/PixiEditor.AvaloniaUI/ViewModels/Document/NodeGraphViewModel.cs
  4. 0 3
      src/PixiEditor.AvaloniaUI/ViewModels/Document/StructureMemberViewModel.cs
  5. 1 1
      src/PixiEditor.AvaloniaUI/Views/Layers/LayersManager.axaml
  6. 4 4
      src/PixiEditor.ChangeableDocument/ChangeInfos/NodeGraph/ConnectProperty_ChangeInfo.cs
  7. 1 1
      src/PixiEditor.ChangeableDocument/ChangeInfos/Structure/DeleteStructureMember_ChangeInfo.cs
  8. 16 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/InputProperty.cs
  9. 2 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CircleNode.cs
  10. 3 5
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FolderNode.cs
  11. 14 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ImageLayerNode.cs
  12. 0 3
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/LayerNode.cs
  13. 5 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/MergeNode.cs
  14. 7 5
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Node.cs
  15. 6 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/OutputNode.cs
  16. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/StructureNode.cs
  17. 13 4
      src/PixiEditor.ChangeableDocument/Changeables/Graph/OutputProperty.cs
  18. 1 1
      src/PixiEditor.ChangeableDocument/Changes/Structure/CreateStructureMember_Change.cs
  19. 72 22
      src/PixiEditor.ChangeableDocument/Changes/Structure/DeleteStructureMember_Change.cs

+ 5 - 5
src/PixiEditor.AvaloniaUI/Models/DocumentModels/DocumentUpdater.cs

@@ -520,8 +520,8 @@ internal class DocumentUpdater
     
     private void ProcessConnectProperty(ConnectProperty_ChangeInfo info)
     {
-        NodeViewModel outputNode = info.SourceNodeId.HasValue ? doc.StructureHelper.FindNode<NodeViewModel>(info.SourceNodeId.Value) : null;
-        NodeViewModel inputNode = doc.StructureHelper.FindNode<NodeViewModel>(info.TargetNodeId);
+        NodeViewModel outputNode = info.OutputNodeId.HasValue ? doc.StructureHelper.FindNode<NodeViewModel>(info.OutputNodeId.Value) : null;
+        NodeViewModel inputNode = doc.StructureHelper.FindNode<NodeViewModel>(info.InputNodeId);
 
         if (inputNode != null && outputNode != null)
         {
@@ -529,15 +529,15 @@ internal class DocumentUpdater
             {
                 InputNode = inputNode,
                 OutputNode = outputNode,
-                InputProperty = inputNode.FindInputProperty(info.TargetProperty),
-                OutputProperty = outputNode.FindOutputProperty(info.SourceProperty)
+                InputProperty = inputNode.FindInputProperty(info.InputProperty),
+                OutputProperty = outputNode.FindOutputProperty(info.OutputProperty)
             };
             
             doc.NodeGraphHandler.SetConnection(connection);
         }
         else
         {
-            doc.NodeGraphHandler.RemoveConnection(info.TargetNodeId, info.TargetProperty);
+            doc.NodeGraphHandler.RemoveConnection(info.InputNodeId, info.OutputProperty);
         }
     }
     

+ 1 - 1
src/PixiEditor.AvaloniaUI/Models/Rendering/AffectedAreasGatherer.cs

@@ -61,7 +61,7 @@ internal class AffectedAreasGatherer
                     break;
                 case DeleteStructureMember_ChangeInfo info:
                     AddWholeCanvasToMainImage();
-                    AddWholeCanvasToImagePreviews(info.ParentGuid);
+                    AddWholeCanvasToImagePreviews(info.Id); // TODO: ParentGuid was here, make sure previews are updated correctly
                     break;
                 case MoveStructureMember_ChangeInfo info:
                     AddAllToMainImage(info.Id, ActiveFrame);

+ 13 - 0
src/PixiEditor.AvaloniaUI/ViewModels/Document/NodeGraphViewModel.cs

@@ -3,6 +3,7 @@ using PixiEditor.AvaloniaUI.Models.DocumentModels;
 using PixiEditor.AvaloniaUI.Models.Handlers;
 using PixiEditor.AvaloniaUI.ViewModels.Nodes;
 using PixiEditor.ChangeableDocument.Actions.Generated;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.Numerics;
 
 namespace PixiEditor.AvaloniaUI.ViewModels.Document;
@@ -12,6 +13,7 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler
     public DocumentViewModel DocumentViewModel { get; }
     public ObservableCollection<INodeHandler> AllNodes { get; } = new();
     public ObservableCollection<NodeConnectionViewModel> Connections { get; } = new();
+    public ObservableCollection<IStructureMemberHandler> StructureTree { get; } = new();
     public INodeHandler? OutputNode { get; private set; }
     
     private DocumentInternalParts Internals { get; }
@@ -28,6 +30,11 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler
         {
             OutputNode = node; // TODO: this is not really correct yet, a way to check what node type is added is needed
         }
+
+        if (node is IStructureMemberHandler handler)
+        {
+            StructureTree.Add(handler);
+        }
         
         AllNodes.Add(node);
     }
@@ -35,6 +42,12 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler
     public void RemoveNode(Guid nodeId)
     {
         var node = AllNodes.FirstOrDefault(x => x.Id == nodeId);
+        
+        if (node is IStructureMemberHandler handler)
+        {
+            StructureTree.Remove(handler);
+        }
+        
         if (node != null)
         {
             AllNodes.Remove(node);

+ 0 - 3
src/PixiEditor.AvaloniaUI/ViewModels/Document/StructureMemberViewModel.cs

@@ -18,9 +18,6 @@ namespace PixiEditor.AvaloniaUI.ViewModels.Document;
 #nullable enable
 internal abstract class StructureMemberViewModel : NodeViewModel, IStructureMemberHandler
 {
-    public DocumentViewModel Document { get; }
-    protected DocumentInternalParts Internals { get; }
-
     private string name = "";
 
     public StructureMemberViewModel()

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

@@ -125,7 +125,7 @@
                 Background="{DynamicResource ThemeBackgroundBrush}"
                 Grid.Row="3" VerticalAlignment="Bottom"/>
             <TreeView DockPanel.Dock="Top" Name="treeView" BorderThickness="0"
-                      ItemsSource="{Binding DataContext.ActiveDocument.NodeGraph.Children, ElementName=layersManager}">
+                      ItemsSource="{Binding DataContext.ActiveDocument.NodeGraph.StructureTree, ElementName=layersManager}">
                 <TreeView.ItemsPanel>
                     <ItemsPanelTemplate>
                         <panels:ReversedOrderStackPanel />

+ 4 - 4
src/PixiEditor.ChangeableDocument/ChangeInfos/NodeGraph/ConnectProperty_ChangeInfo.cs

@@ -1,7 +1,7 @@
 namespace PixiEditor.ChangeableDocument.ChangeInfos.NodeGraph;
 
 public record ConnectProperty_ChangeInfo(
-    Guid? SourceNodeId,
-    Guid TargetNodeId,
-    string? SourceProperty,
-    string TargetProperty) : IChangeInfo;
+    Guid? OutputNodeId,
+    Guid InputNodeId,
+    string? OutputProperty,
+    string InputProperty) : IChangeInfo;

+ 1 - 1
src/PixiEditor.ChangeableDocument/ChangeInfos/Structure/DeleteStructureMember_ChangeInfo.cs

@@ -2,4 +2,4 @@
 
 namespace PixiEditor.ChangeableDocument.ChangeInfos.Structure;
 
-public record class DeleteStructureMember_ChangeInfo(Guid Id, Guid ParentGuid) : DeleteNode_ChangeInfo(Id);
+public record class DeleteStructureMember_ChangeInfo(Guid Id) : DeleteNode_ChangeInfo(Id);

+ 16 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/InputProperty.cs

@@ -27,6 +27,22 @@ public class InputProperty : IInputProperty
     {
         if(Value is ICloneable cloneable)
             return new InputProperty(forNode, InternalPropertyName, DisplayName, cloneable.Clone(), ValueType);
+
+        if (Value is Enum enumVal)
+        {
+            return new InputProperty(forNode, InternalPropertyName, DisplayName, enumVal, ValueType);
+        }
+
+        if (Value is null)
+        {
+            object? nullValue = null;
+            if (ValueType.IsValueType)
+            {
+                nullValue = Activator.CreateInstance(ValueType);
+            }
+            
+            return new InputProperty(forNode, InternalPropertyName, DisplayName, nullValue, ValueType);
+        }
         
         if(!Value.GetType().IsPrimitive && Value.GetType() != typeof(string))
             throw new InvalidOperationException("Value is not cloneable and not a primitive type");

+ 2 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CircleNode.cs

@@ -42,4 +42,6 @@ public class CircleNode : Node
     {
         return Radius.Value > 0 && StrokeWidth.Value > 0;
     }
+
+    public override Node CreateCopy() => new CircleNode();
 }

+ 3 - 5
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FolderNode.cs

@@ -11,8 +11,11 @@ public class FolderNode : StructureNode, IReadOnlyFolderNode
         return true;
     }
 
+    public override Node CreateCopy() => new FolderNode { MemberName = MemberName };
+
     public override ChunkyImage? OnExecute(KeyFrameTime frame)
     {
+        Output.Value = Background.Value;
         return Background.Value;
     }
 
@@ -46,11 +49,6 @@ public class FolderNode : StructureNode, IReadOnlyFolderNode
       return bounds;*/
     }
 
-    public void InsertStructureMember(StructureNode structureMember)
-    {
-       
-    }
-
     /// <summary>
     /// Creates a clone of the folder, its mask and all of its children
     /// </summary>

+ 14 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ImageLayerNode.cs

@@ -1,5 +1,6 @@
 using PixiEditor.ChangeableDocument.Changeables.Animations;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using PixiEditor.DrawingApi.Core.Surface.ImageData;
 using PixiEditor.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
@@ -9,11 +10,13 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
     public InputProperty<bool> LockTransparency { get; }
 
     private List<ImageFrame> frames = new List<ImageFrame>();
+    private VecI size;
 
     public ImageLayerNode(VecI size)
     {
         LockTransparency = CreateInput<bool>("LockTransparency", "LOCK_TRANSPARENCY", false);
         frames.Add(new ImageFrame(Guid.NewGuid(), 0, 0, new(size)));
+        this.size = size;
     }
 
     public override bool Validate()
@@ -57,7 +60,17 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
             frame.Image.Dispose();
         }
     }
-    
+
+    public override Node CreateCopy()
+    {
+        return new ImageLayerNode(size)
+        {
+            frames = frames.Select(x =>
+                new ImageFrame(x.KeyFrameGuid, x.StartFrame, x.Duration, x.Image.CloneFromCommitted())).ToList(),
+            MemberName = MemberName,
+        };
+    }
+
     private VecI GetBiggerSize(VecI size1, VecI size2)
     {
         return new VecI(Math.Max(size1.X, size2.X), Math.Max(size1.Y, size2.Y));

+ 0 - 3
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/LayerNode.cs

@@ -6,7 +6,4 @@ namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
 public abstract class LayerNode : StructureNode, IReadOnlyLayerNode
 {
-    public LayerNode(Guid? id = null) : base(id)
-    {
-    }
 }

+ 5 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/MergeNode.cs

@@ -21,6 +21,11 @@ public class MergeNode : Node
         return Top.Value != null || Bottom.Value != null;
     }
 
+    public override Node CreateCopy()
+    {
+        return new MergeNode();
+    }
+
     public override ChunkyImage? OnExecute(KeyFrameTime frame)
     {
         if(Top.Value == null && Bottom.Value == null)

+ 7 - 5
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Node.cs

@@ -4,14 +4,14 @@ using PixiEditor.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
-public abstract class Node(Guid? id = null) : IReadOnlyNode, IDisposable
+public abstract class Node : IReadOnlyNode, IDisposable
 {
     private List<InputProperty> inputs = new();
     private List<OutputProperty> outputs = new();
 
     private List<IReadOnlyNode> _connectedNodes = new();
 
-    public Guid Id { get; internal set; } = id ?? Guid.NewGuid();
+    public Guid Id { get; internal set; } = Guid.NewGuid();
 
     public IReadOnlyCollection<InputProperty> InputProperties => inputs;
     public IReadOnlyCollection<OutputProperty> OutputProperties => outputs;
@@ -137,7 +137,7 @@ public abstract class Node(Guid? id = null) : IReadOnlyNode, IDisposable
     {
         foreach (var input in inputs)
         {
-            if (input.Value is IDisposable disposable)
+            if (input is { Connection: null, Value: IDisposable disposable })
             {
                 disposable.Dispose();
             }
@@ -152,9 +152,11 @@ public abstract class Node(Guid? id = null) : IReadOnlyNode, IDisposable
         }
     }
     
-    public virtual Node Clone()
+    public abstract Node CreateCopy();
+    
+    public Node Clone()
     {
-        var clone = (Node)MemberwiseClone();
+        var clone = CreateCopy();
         clone.Id = Guid.NewGuid();
         clone.inputs = new List<InputProperty>();
         clone.outputs = new List<OutputProperty>();

+ 6 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/OutputNode.cs

@@ -15,7 +15,12 @@ public class OutputNode : Node, IBackgroundInput
     {
         return Input.Value != null;
     }
-    
+
+    public override Node CreateCopy()
+    {
+        return new OutputNode();
+    }
+
     public override ChunkyImage? OnExecute(KeyFrameTime frame)
     {
         return Input.Value;

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

@@ -19,7 +19,7 @@ public abstract class StructureNode : Node, IReadOnlyStructureNode, IBackgroundI
     
     public string MemberName { get; set; } = string.Empty;
 
-    protected StructureNode(Guid? id = null) : base(id)
+    protected StructureNode()
     {
         Background = CreateInput<ChunkyImage?>("Background", "BACKGROUND", null);
         Opacity = CreateInput<float>("Opacity", "OPACITY", 1);

+ 13 - 4
src/PixiEditor.ChangeableDocument/Changeables/Graph/OutputProperty.cs

@@ -73,16 +73,25 @@ public class OutputProperty : IOutputProperty
 
     public OutputProperty Clone(Node clone)
     {
+        if (Value is null)
+        {
+            object defaultValue = null;
+            if(ValueType.IsValueType)
+                defaultValue = Activator.CreateInstance(ValueType);
+            return new OutputProperty(clone, InternalPropertyName, DisplayName, defaultValue, ValueType);
+        }
+
+        if (Value is Enum enumVal)
+        {
+            return new OutputProperty(clone, InternalPropertyName, DisplayName, enumVal, ValueType);
+        }
+
         if (Value is not ICloneable && !Value.GetType().IsPrimitive && Value.GetType() != typeof(string))
             throw new InvalidOperationException("Value is not cloneable and not a primitive type");
      
         object value = Value is ICloneable cloneableValue ? cloneableValue.Clone() : Value;
         
         var newOutput = new OutputProperty(clone, InternalPropertyName, DisplayName, value, ValueType);
-        foreach (var connection in Connections)
-        {
-            newOutput.ConnectTo(connection);
-        }
 
         return newOutput;
     }

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changes/Structure/CreateStructureMember_Change.cs

@@ -84,7 +84,7 @@ internal class CreateStructureMember_Change : Change
 
         List<IChangeInfo> changes = new()
         {
-            new DeleteStructureMember_ChangeInfo(newMemberGuid, parentFolderGuid),
+            new DeleteStructureMember_ChangeInfo(newMemberGuid),
         };
 
         if (childBackgroundConnection != null)

+ 72 - 22
src/PixiEditor.ChangeableDocument/Changes/Structure/DeleteStructureMember_Change.cs

@@ -1,4 +1,6 @@
-using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.ChangeInfos.NodeGraph;
 using PixiEditor.ChangeableDocument.ChangeInfos.Structure;
 
 namespace PixiEditor.ChangeableDocument.Changes.Structure;
@@ -6,8 +8,9 @@ namespace PixiEditor.ChangeableDocument.Changes.Structure;
 internal class DeleteStructureMember_Change : Change
 {
     private Guid memberGuid;
-    private Guid parentGuid;
     private int originalIndex;
+    private List<IInputProperty> originalOutputConnections = new();
+    private List<(IInputProperty, IOutputProperty?)> originalInputConnections = new();
     private StructureNode? savedCopy;
 
     [GenerateMakeChangeAction]
@@ -18,42 +21,89 @@ internal class DeleteStructureMember_Change : Change
 
     public override bool InitializeAndValidate(Document document)
     {
-        var (member, parent) = document.FindChildAndParent(memberGuid);
-        if (member is null || parent is null)
+        var member = document.FindMember(memberGuid);
+        if (member is null)
             return false;
 
-        //originalIndex = parent.Children.IndexOf(member);
-        parentGuid = parent.Id;
+        originalOutputConnections = member.Output.Connections.ToList();
+        originalInputConnections = member.InputProperties.Select(x => ((IInputProperty)x, x.Connection)).ToList();
         savedCopy = (StructureNode)member.Clone();
+        savedCopy.Id = memberGuid;
         return true;
     }
 
-    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document document, bool firstApply, out bool ignoreInUndo)
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document document, bool firstApply,
+        out bool ignoreInUndo)
     {
-        /*var (member, parent) = document.FindChildAndParentOrThrow(memberGuid);
-        parent.Children = parent.Children.Remove(member);
-        member.Dispose();
-        ignoreInUndo = false;
-        return new DeleteStructureMember_ChangeInfo(memberGuid, parentGuid);*/
-        
+        StructureNode node = document.FindMember(memberGuid);
+
+        var bgConnection = node.Background.Connection;
+        var outputConnections = node.Output.Connections.ToArray();
+
+        document.NodeGraph.RemoveNode(node);
+
+        List<IChangeInfo> changes = new();
+
+        if (outputConnections != null && bgConnection != null)
+        {
+            foreach (var connection in outputConnections)
+            {
+                bgConnection.ConnectTo(connection);
+                changes.Add(new ConnectProperty_ChangeInfo(bgConnection.Node.Id, connection.Node.Id,
+                    bgConnection.InternalPropertyName, connection.InternalPropertyName));
+            }
+        }
+
+        node.Dispose();
         ignoreInUndo = false;
-        return new None();
+
+        changes.Add(new DeleteStructureMember_ChangeInfo(memberGuid));
+        return changes;
     }
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document doc)
     {
-        /*var parent = doc.FindMemberOrThrow<FolderNode>(parentGuid);
+        var copy = (StructureNode)savedCopy!.Clone();
+        copy.Id = memberGuid;
 
-        var copy = savedCopy!.Clone();
-        parent.Children = parent.Children.Insert(originalIndex, copy);
-        return copy switch
+        doc.NodeGraph.AddNode(copy);
+
+        List<IChangeInfo> changes = new();
+
+        IChangeInfo createChange = copy switch
         {
-            LayerNode => CreateLayer_ChangeInfo.FromLayer(parentGuid, originalIndex, (LayerNode)copy),
-            FolderNode => CreateFolder_ChangeInfo.FromFolder(parentGuid, originalIndex, (FolderNode)copy),
+            LayerNode => CreateLayer_ChangeInfo.FromLayer(memberGuid, (LayerNode)copy),
+            FolderNode => CreateFolder_ChangeInfo.FromFolder(memberGuid, (FolderNode)copy),
             _ => throw new NotSupportedException(),
-        };*/
+        };
+        
+        changes.Add(createChange);
+
+        foreach (var connection in originalOutputConnections)
+        {
+            copy.Output.ConnectTo(connection);
+            changes.Add(new ConnectProperty_ChangeInfo(copy.Id, connection.Node.Id, copy.Output.InternalPropertyName,
+                connection.InternalPropertyName));
+        }
+
+        foreach (var connection in originalInputConnections)
+        {
+            if (connection.Item2 is null)
+                continue;
+
+            IInputProperty? input =
+                copy.InputProperties.FirstOrDefault(x => x.InternalPropertyName == connection.Item1.InternalPropertyName);
+
+            if (input != null)
+            {
+                connection.Item2.ConnectTo(input);
+                changes.Add(new ConnectProperty_ChangeInfo(connection.Item2.Node.Id, copy.Id,
+                    connection.Item2.InternalPropertyName,
+                    input.InternalPropertyName));
+            }
+        }
         
-        return new None();
+        return changes;
     }
 
     public override void Dispose()