Browse Source

Backend representation of nodes in ui

flabbet 1 year ago
parent
commit
b741d1d561
20 changed files with 252 additions and 89 deletions
  1. 23 3
      src/PixiEditor.AvaloniaUI/Models/DocumentModels/DocumentUpdater.cs
  2. 6 2
      src/PixiEditor.AvaloniaUI/Models/DocumentModels/Public/DocumentStructureModule.cs
  3. 3 1
      src/PixiEditor.AvaloniaUI/Models/Handlers/INodeGraphHandler.cs
  4. 8 4
      src/PixiEditor.AvaloniaUI/ViewModels/Document/DocumentViewModel.cs
  5. 20 1
      src/PixiEditor.AvaloniaUI/ViewModels/Document/NodeGraphViewModel.cs
  6. 14 2
      src/PixiEditor.AvaloniaUI/ViewModels/Nodes/NodeViewModel.cs
  7. 7 0
      src/PixiEditor.ChangeableDocument/ChangeInfos/NodeGraph/CreateNode_ChangeInfo.cs
  8. 3 0
      src/PixiEditor.ChangeableDocument/ChangeInfos/NodeGraph/DeleteNode_ChangeInfo.cs
  9. 12 29
      src/PixiEditor.ChangeableDocument/ChangeInfos/Structure/CreateFolder_ChangeInfo.cs
  10. 12 11
      src/PixiEditor.ChangeableDocument/ChangeInfos/Structure/CreateLayer_ChangeInfo.cs
  11. 32 17
      src/PixiEditor.ChangeableDocument/Changeables/Document.cs
  12. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/InputProperty.cs
  13. 8 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IBackgroundInput.cs
  14. 2 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyNode.cs
  15. 6 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FolderNode.cs
  16. 2 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Node.cs
  17. 4 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/OutputNode.cs
  18. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/StructureNode.cs
  19. 44 0
      src/PixiEditor.ChangeableDocument/Changes/NodeGraph/CreateNode_Change.cs
  20. 44 15
      src/PixiEditor.ChangeableDocument/Changes/Structure/CreateStructureMember_Change.cs

+ 23 - 3
src/PixiEditor.AvaloniaUI/Models/DocumentModels/DocumentUpdater.cs

@@ -5,10 +5,12 @@ using PixiEditor.AvaloniaUI.Models.DocumentPassthroughActions;
 using PixiEditor.AvaloniaUI.Models.Handlers;
 using PixiEditor.AvaloniaUI.Models.Layers;
 using PixiEditor.AvaloniaUI.ViewModels.Document;
+using PixiEditor.AvaloniaUI.ViewModels.Nodes;
 using PixiEditor.ChangeableDocument.Actions.Generated;
 using PixiEditor.ChangeableDocument.ChangeInfos;
 using PixiEditor.ChangeableDocument.ChangeInfos.Animation;
 using PixiEditor.ChangeableDocument.ChangeInfos.Drawing;
+using PixiEditor.ChangeableDocument.ChangeInfos.NodeGraph;
 using PixiEditor.ChangeableDocument.ChangeInfos.Properties;
 using PixiEditor.ChangeableDocument.ChangeInfos.Root;
 using PixiEditor.ChangeableDocument.ChangeInfos.Root.ReferenceLayerChangeInfos;
@@ -151,6 +153,12 @@ internal class DocumentUpdater
             case ClearSelectedKeyFrames_PassthroughAction info:
                 ClearSelectedKeyFrames(info);
                 break;
+            case CreateNode_ChangeInfo info:
+                ProcessCreateNode(info);
+                break;
+            case DeleteNode_ChangeInfo info:
+                ProcessDeleteNode(info);
+                break;
         }
     }
 
@@ -324,8 +332,6 @@ internal class DocumentUpdater
 
     private void ProcessCreateStructureMember(CreateStructureMember_ChangeInfo info)
     {
-        IFolderHandler? parentFolderVM = (IFolderHandler)doc.StructureHelper.FindOrThrow(info.ParentGuid);
-
         IStructureMemberHandler memberVM;
         if (info is CreateLayer_ChangeInfo layerInfo)
         {
@@ -348,16 +354,19 @@ internal class DocumentUpdater
         memberVM.SetHasMask(info.HasMask);
         memberVM.SetMaskIsVisible(info.MaskIsVisible);
         memberVM.SetBlendMode(info.BlendMode);
+        
+        doc.NodeGraphHandler.AddNode(memberVM);
 
         //parentFolderVM.Children.Insert(info.Index, memberVM);
 
-        if (info is CreateFolder_ChangeInfo folderInfo)
+        /*if (info is CreateFolder_ChangeInfo folderInfo)
         {
             foreach (CreateStructureMember_ChangeInfo childInfo in folderInfo.Children)
             {
                 ProcessCreateStructureMember(childInfo);
             }
         }
+        */
 
         if (doc.SelectedStructureMember is not null)
         {
@@ -458,4 +467,15 @@ internal class DocumentUpdater
     {
         doc.AnimationHandler.ClearSelectedKeyFrames();
     }
+    
+    private void ProcessCreateNode(CreateNode_ChangeInfo info)
+    {
+        NodeViewModel node = new NodeViewModel(info.NodeName, info.Id, info.Position);
+        doc.NodeGraphHandler.AddNode(node);
+    }
+    
+    private void ProcessDeleteNode(DeleteNode_ChangeInfo info)
+    {
+        doc.NodeGraphHandler.RemoveNode(info.Id);
+    }
 }

+ 6 - 2
src/PixiEditor.AvaloniaUI/Models/DocumentModels/Public/DocumentStructureModule.cs

@@ -14,8 +14,12 @@ internal class DocumentStructureModule
     public IStructureMemberHandler FindOrThrow(Guid guid) => Find(guid) ?? throw new ArgumentException("Could not find member with guid " + guid.ToString());
     public IStructureMemberHandler? Find(Guid guid)
     {
-        List<IStructureMemberHandler>? list = FindPath(guid);
-        return list.Count > 0 ? list[0] : null;
+        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;
     }
 
     public IStructureMemberHandler? FindFirstWhere(Predicate<IStructureMemberHandler> predicate)

+ 3 - 1
src/PixiEditor.AvaloniaUI/Models/Handlers/INodeGraphHandler.cs

@@ -8,5 +8,7 @@ public interface INodeGraphHandler
    public ObservableCollection<INodeHandler> AllNodes { get; }
    public ObservableCollection<NodeConnectionViewModel> Connections { get; }
    public INodeHandler OutputNode { get; }
-   bool TryTraverse(Func<INodeHandler, bool> func);
+   public bool TryTraverse(Func<INodeHandler, bool> func);
+   public void AddNode(INodeHandler node);
+   public void RemoveNode(Guid nodeId);
 }

+ 8 - 4
src/PixiEditor.AvaloniaUI/ViewModels/Document/DocumentViewModel.cs

@@ -26,6 +26,7 @@ using PixiEditor.ChangeableDocument.Actions;
 using PixiEditor.ChangeableDocument.Actions.Generated;
 using PixiEditor.ChangeableDocument.Actions.Undo;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.ChangeInfos;
 using PixiEditor.ChangeableDocument.Enums;
@@ -258,15 +259,18 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
 
         if (builderInstance.ReferenceLayer is { } refLayer)
         {
-            acc
-                .AddActions(new SetReferenceLayer_Action(refLayer.Shape, refLayer.ImageBgra8888Bytes.ToImmutableArray(),
+            acc.AddActions(new SetReferenceLayer_Action(refLayer.Shape, refLayer.ImageBgra8888Bytes.ToImmutableArray(),
                     refLayer.ImageSize));
         }
 
         viewModel.Swatches = new ObservableCollection<PaletteColor>(builderInstance.Swatches);
         viewModel.Palette = new ObservableRangeCollection<PaletteColor>(builderInstance.Palette);
-
-        AddMembers(viewModel.NodeGraph.OutputNode.Id, builderInstance.Children);
+        
+        Guid outputNodeGuid = Guid.NewGuid();
+        
+        acc.AddActions(new CreateNode_Action(typeof(OutputNode), outputNodeGuid));
+        
+        AddMembers(outputNodeGuid, builderInstance.Children);
         AddAnimationData(builderInstance.AnimationData);
 
         acc.AddFinishedActions(new DeleteRecordedChanges_Action());

+ 20 - 1
src/PixiEditor.AvaloniaUI/ViewModels/Document/NodeGraphViewModel.cs

@@ -9,12 +9,31 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler
     public DocumentViewModel DocumentViewModel { get; }
     public ObservableCollection<INodeHandler> AllNodes { get; } = new();
     public ObservableCollection<NodeConnectionViewModel> Connections { get; } = new();
-    public INodeHandler? OutputNode { get; }
+    public INodeHandler? OutputNode { get; private set; }
 
     public NodeGraphViewModel(DocumentViewModel documentViewModel)
     {
         DocumentViewModel = documentViewModel;
     }
+    
+    public void AddNode(INodeHandler node)
+    {
+        if(OutputNode == null)
+        {
+            OutputNode = node; // TODO: this is not really correct yet, a way to check what node type is added is needed
+        }
+        
+        AllNodes.Add(node);
+    }
+    
+    public void RemoveNode(Guid nodeId)
+    {
+        var node = AllNodes.FirstOrDefault(x => x.Id == nodeId);
+        if (node != null)
+        {
+            AllNodes.Remove(node);
+        }
+    }
 
     public bool TryTraverse(Func<INodeHandler, bool> func)
     {

+ 14 - 2
src/PixiEditor.AvaloniaUI/ViewModels/Nodes/NodeViewModel.cs

@@ -11,12 +11,24 @@ public class NodeViewModel : ObservableObject, INodeHandler
 {
     private string nodeName;
     private VecD position;
-    private ObservableCollection<IInputPropertyHandler> inputs;
-    private ObservableCollection<IOutputPropertyHandler> outputs;
+    private ObservableCollection<IInputPropertyHandler> inputs = new();
+    private ObservableCollection<IOutputPropertyHandler> outputs = new();
     private Surface resultPreview;
 
     protected Guid id;
 
+    public NodeViewModel()
+    {
+        
+    }
+
+    public NodeViewModel(string nodeName, Guid id, VecD position)
+    {
+        this.nodeName = nodeName;
+        this.id = id;
+        this.position = position;
+    }
+
     public Guid Id
     {
         get => id;

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

@@ -0,0 +1,7 @@
+using PixiEditor.Numerics;
+
+namespace PixiEditor.ChangeableDocument.ChangeInfos.NodeGraph;
+
+public record CreateNode_ChangeInfo(string NodeName, VecD Position ,Guid Id) : IChangeInfo
+{
+}

+ 3 - 0
src/PixiEditor.ChangeableDocument/ChangeInfos/NodeGraph/DeleteNode_ChangeInfo.cs

@@ -0,0 +1,3 @@
+namespace PixiEditor.ChangeableDocument.ChangeInfos.NodeGraph;
+
+public record DeleteNode_ChangeInfo(Guid Id) : IChangeInfo;

+ 12 - 29
src/PixiEditor.ChangeableDocument/ChangeInfos/Structure/CreateFolder_ChangeInfo.cs

@@ -1,4 +1,5 @@
 using System.Collections.Immutable;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.Enums;
 
 namespace PixiEditor.ChangeableDocument.ChangeInfos.Structure;
@@ -14,40 +15,22 @@ public record class CreateFolder_ChangeInfo : CreateStructureMember_ChangeInfo
         BlendMode blendMode,
         Guid guidValue,
         bool hasMask,
-        bool maskIsVisible,
-        ImmutableList<CreateStructureMember_ChangeInfo> children) : base(parentGuid, index, opacity, isVisible, clipToMemberBelow, name, blendMode, guidValue, hasMask, maskIsVisible)
+        bool maskIsVisible) : base(parentGuid, index, opacity, isVisible, clipToMemberBelow, name, blendMode, guidValue, hasMask, maskIsVisible)
     {
-        Children = children;
     }
 
-    public ImmutableList<CreateStructureMember_ChangeInfo> Children { get; }
-
-    /*internal static CreateFolder_ChangeInfo FromFolder(Guid parentGuid, int index, Folder folder)
+    internal static CreateFolder_ChangeInfo FromFolder(Guid parentGuid, int index, FolderNode folder)
     {
-        var builder = ImmutableList.CreateBuilder<CreateStructureMember_ChangeInfo>();
-        for (int i = 0; i < folder.Children.Count; i++)
-        {
-            var child = folder.Children[i];
-            CreateStructureMember_ChangeInfo info = child switch
-            {
-                Folder innerFolder => CreateFolder_ChangeInfo.FromFolder(folder.GuidValue, i, innerFolder),
-                Layer innerLayer => CreateLayer_ChangeInfo.FromLayer(folder.GuidValue, i, innerLayer),
-                _ => throw new NotSupportedException(),
-            };
-            builder.Add(info);
-        }
         return new CreateFolder_ChangeInfo(
             parentGuid,
             index,
-            folder.Opacity,
-            folder.IsVisible,
-            folder.ClipToMemberBelow,
-            folder.Name,
-            folder.BlendMode,
-            folder.GuidValue,
-            folder.Mask is not null,
-            folder.MaskIsVisible,
-            builder.ToImmutable()
-            );
-    }*/
+            folder.Opacity.Value,
+            folder.IsVisible.Value,
+            folder.ClipToMemberBelow.Value,
+            folder.MemberName,
+            folder.BlendMode.Value,
+            folder.Id,
+            folder.Mask.Value is not null,
+            folder.MaskIsVisible.Value);
+    }
 }

+ 12 - 11
src/PixiEditor.ChangeableDocument/ChangeInfos/Structure/CreateLayer_ChangeInfo.cs

@@ -1,4 +1,5 @@
-using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Enums;
 
 namespace PixiEditor.ChangeableDocument.ChangeInfos.Structure;
@@ -22,20 +23,20 @@ public record class CreateLayer_ChangeInfo : CreateStructureMember_ChangeInfo
 
     public bool LockTransparency { get; }
 
-    /*internal static CreateLayer_ChangeInfo FromLayer(Guid parentGuid, int index, Layer layer)
+    internal static CreateLayer_ChangeInfo FromLayer(Guid parentGuid, int index, LayerNode layer)
     {
         return new CreateLayer_ChangeInfo(
             parentGuid,
             index,
-            layer.Opacity,
-            layer.IsVisible,
-            layer.ClipToMemberBelow,
-            layer.Name,
-            layer.BlendMode,
-            layer.GuidValue,
-            layer.Mask is not null,
-            layer.MaskIsVisible,
+            layer.Opacity.Value,
+            layer.IsVisible.Value,
+            layer.ClipToMemberBelow.Value,
+            layer.MemberName,
+            layer.BlendMode.Value,
+            layer.Id,
+            layer.Mask.Value is not null,
+            layer.MaskIsVisible.Value,
             layer is ITransparencyLockable { LockTransparency: true }
             );
-    }*/
+    }
 }

+ 32 - 17
src/PixiEditor.ChangeableDocument/Changeables/Document.cs

@@ -132,6 +132,27 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
         });
     }
 
+    /// <summary>
+    ///     Checks if a node in NodeGraph with the given <paramref name="id"/> exists.
+    /// </summary>
+    /// <param name="id">The <see cref="Node.Id"/> of the node.</param>
+    /// <returns>True if the node exists, otherwise false.</returns>
+    public bool HasNode(Guid id)
+    {
+        return NodeGraph.Nodes.Any(x => x.Id == id);
+    }
+    
+    /// <summary>
+    ///     Checks if a node in NodeGraph with the given <paramref name="id"/> exists and is of type <typeparamref name="T"/>.
+    /// </summary>
+    /// <param name="id">The <see cref="Node.Id"/> of the node.</param>
+    /// <typeparam name="T">The type of the node.</typeparam>
+    /// <returns>True if the node exists and is of type <typeparamref name="T"/>, otherwise false.</returns>
+    public bool HasNode<T>(Guid id) where T : Node
+    {
+        return NodeGraph.Nodes.Any(x => x.Id == id && x is T);
+    }
+
     /// <summary>
     /// Checks if a member with the <paramref name="guid"/> exists
     /// </summary>
@@ -139,8 +160,7 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
     /// <returns>True if the member can be found, otherwise false</returns>
     public bool HasMember(Guid guid)
     {
-        var list = FindMemberPath(guid);
-        return list.Count > 0;
+        return HasNode<StructureNode>(guid); 
     }
 
     /// <summary>
@@ -183,8 +203,7 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
     /// <param name="guid">The <see cref="StructureNode.Id"/> of the member</param>
     public StructureNode? FindMember(Guid guid)
     {
-        var list = FindMemberPath(guid);
-        return list.Count > 0 ? list[0] : null;
+        return FindNode<StructureNode>(guid);
     }
 
     /// <summary>
@@ -196,8 +215,12 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
     {
         return NodeGraph.Nodes.FirstOrDefault(x => x.Id == guid);
     }
-    
-   
+
+    public T FindNode<T>(Guid guid) where T : Node
+    {
+        return NodeGraph.Nodes.FirstOrDefault(x => x.Id == guid && x is T) as T;
+    }
+
     /// <summary>
     ///     Tries to find the node with the given <paramref name="id"/> and returns true if it was found.
     /// </summary>
@@ -219,15 +242,8 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
     /// <returns>True if the member could be found, otherwise false</returns>
     public bool TryFindMember(Guid guid, [NotNullWhen(true)] out StructureNode? member)
     {
-        var list = FindMemberPath(guid);
-        if (list.Count == 0)
-        {
-            member = null;
-            return false;
-        }
-
-        member = list[0];
-        return true;
+        member = FindMember(guid);
+        return member != null; 
     }
 
     /// <summary>
@@ -251,7 +267,6 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
     }
 
 
-
     /// <summary>
     /// Finds a member with the <paramref name="childGuid"/>  and its parent, throws a ArgumentException if they can't be found
     /// </summary>
@@ -295,7 +310,7 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
         FillNodePath(NodeGraph.OutputNode, guid, list);
         return list;
     }
-    
+
     /// <summary>
     /// Finds the path to the member with <paramref name="guid"/>, the first element will be the member
     /// </summary>

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/InputProperty.cs

@@ -10,7 +10,7 @@ public class InputProperty : IInputProperty
     public Node Node { get; }
     IReadOnlyNode INodeProperty.Node => Node;
     
-    public IOutputProperty Connection { get; set; }
+    public IOutputProperty? Connection { get; set; }
     
     internal InputProperty(Node node, string name, object defaultValue)
     {

+ 8 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IBackgroundInput.cs

@@ -0,0 +1,8 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+
+public interface IBackgroundInput
+{
+    InputProperty<ChunkyImage?> Background { get; }
+}

+ 2 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyNode.cs

@@ -1,4 +1,5 @@
 using PixiEditor.ChangeableDocument.Changeables.Animations;
+using PixiEditor.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 
@@ -8,6 +9,7 @@ public interface IReadOnlyNode
     public IReadOnlyCollection<IInputProperty> InputProperties { get; }
     public IReadOnlyCollection<IOutputProperty> OutputProperties { get; }
     public IReadOnlyCollection<IReadOnlyNode> ConnectedOutputNodes { get; }
+    public VecD Position { get; }
 
     public ChunkyImage? Execute(KeyFrameTime frame);
     public bool Validate();

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

@@ -4,7 +4,7 @@ using PixiEditor.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
-public class FolderNode : StructureNode, IReadOnlyFolderNode
+public class FolderNode : StructureNode, IReadOnlyFolderNode 
 {
     public override bool Validate()
     {
@@ -46,6 +46,11 @@ 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>

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

@@ -1,5 +1,6 @@
 using PixiEditor.ChangeableDocument.Changeables.Animations;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
@@ -18,6 +19,7 @@ public abstract class Node(Guid? id = null) : IReadOnlyNode, IDisposable
 
     IReadOnlyCollection<IInputProperty> IReadOnlyNode.InputProperties => inputs;
     IReadOnlyCollection<IOutputProperty> IReadOnlyNode.OutputProperties => outputs;
+    public VecD Position { get; set; }
 
     public ChunkyImage? Execute(KeyFrameTime frameTime)
     {

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

@@ -1,8 +1,9 @@
 using PixiEditor.ChangeableDocument.Changeables.Animations;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
-public class OutputNode : Node
+public class OutputNode : Node, IBackgroundInput
 {
     public InputProperty<ChunkyImage?> Input { get; } 
     public OutputNode()
@@ -19,4 +20,6 @@ public class OutputNode : Node
     {
         return Input.Value;
     }
+
+    InputProperty<ChunkyImage?> IBackgroundInput.Background => Input;
 }

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

@@ -5,7 +5,7 @@ using PixiEditor.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
-public abstract class StructureNode : Node, IReadOnlyStructureNode
+public abstract class StructureNode : Node, IReadOnlyStructureNode, IBackgroundInput
 {
     public InputProperty<ChunkyImage?> Background { get; }
     public InputProperty<float> Opacity { get; } 

+ 44 - 0
src/PixiEditor.ChangeableDocument/Changes/NodeGraph/CreateNode_Change.cs

@@ -0,0 +1,44 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.ChangeInfos.NodeGraph;
+using PixiEditor.Numerics;
+
+namespace PixiEditor.ChangeableDocument.Changes.NodeGraph;
+
+internal class CreateNode_Change : Change
+{
+    private Type nodeType;
+    private Guid id;
+    
+    [GenerateMakeChangeAction]
+    public CreateNode_Change(Type nodeType, Guid id)
+    {
+        this.id = id;
+        this.nodeType = nodeType;
+    }
+    
+    public override bool InitializeAndValidate(Document target)
+    {
+        return nodeType.IsSubclassOf(typeof(Node));
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
+    {
+        if(id == Guid.Empty)
+            id = Guid.NewGuid();
+        
+        Node node = (Node)Activator.CreateInstance(nodeType);
+        node.Position = new VecD(100, 100);
+        node.Id = id;
+        target.NodeGraph.AddNode(node);
+        ignoreInUndo = false;
+        return new CreateNode_ChangeInfo(nodeType.Name, node.Position, id);
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
+    {
+        Node node = target.FindNodeOrThrow<Node>(id);
+        target.NodeGraph.RemoveNode(node);
+        
+        return new DeleteNode_ChangeInfo(id);
+    }
+}

+ 44 - 15
src/PixiEditor.ChangeableDocument/Changes/Structure/CreateStructureMember_Change.cs

@@ -1,4 +1,5 @@
-using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.ChangeInfos.Structure;
 using PixiEditor.ChangeableDocument.Enums;
 
@@ -13,7 +14,8 @@ internal class CreateStructureMember_Change : Change
     private StructureMemberType type;
 
     [GenerateMakeChangeAction]
-    public CreateStructureMember_Change(Guid parentFolder, Guid newGuid, int parentFolderIndex, StructureMemberType type)
+    public CreateStructureMember_Change(Guid parentFolder, Guid newGuid, int parentFolderIndex,
+        StructureMemberType type)
     {
         this.parentFolderGuid = parentFolder;
         this.parentFolderIndex = parentFolderIndex;
@@ -23,13 +25,12 @@ internal class CreateStructureMember_Change : Change
 
     public override bool InitializeAndValidate(Document target)
     {
-        return target.HasMember(parentFolderGuid);
+        return target.TryFindNode<Node>(parentFolderGuid, out var targetNode) && targetNode is IBackgroundInput;
     }
 
-    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 folder = document.FindMemberOrThrow<FolderNode>(parentFolderGuid);
-
         StructureNode member = type switch
         {
             // TODO: Add support for other types
@@ -38,27 +39,55 @@ internal class CreateStructureMember_Change : Change
             _ => throw new NotSupportedException(),
         };
 
-        /*folder.Children = folder.Children.Insert(parentFolderIndex, member);
+        document.TryFindNode<Node>(parentFolderGuid, out var parentNode);
+        document.NodeGraph.AddNode(member);
+
+        IBackgroundInput backgroundInput = (IBackgroundInput)parentNode;
+        AppendMember(backgroundInput, member);
 
         ignoreInUndo = false;
         return type switch
         {
-            StructureMemberType.Layer => CreateLayer_ChangeInfo.FromLayer(parentFolderGuid, parentFolderIndex, (Layer)member),
-            StructureMemberType.Folder => CreateFolder_ChangeInfo.FromFolder(parentFolderGuid, parentFolderIndex, (Folder)member),
+            StructureMemberType.Layer => CreateLayer_ChangeInfo.FromLayer(parentFolderGuid, parentFolderIndex,
+                (LayerNode)member),
+            StructureMemberType.Folder => CreateFolder_ChangeInfo.FromFolder(parentFolderGuid, parentFolderIndex,
+                (FolderNode)member),
             _ => throw new NotSupportedException(),
-        };*/
-        
-        ignoreInUndo = false;
-        return new None();
+        };
     }
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document document)
     {
-        FolderNode folder = document.FindMemberOrThrow<FolderNode>(parentFolderGuid);
+        var container = document.FindNodeOrThrow<Node>(parentFolderGuid);
+        if (container is not IBackgroundInput backgroundInput)
+        {
+            throw new InvalidOperationException("Parent folder is not a valid container.");
+        }
+
         StructureNode child = document.FindMemberOrThrow(newMemberGuid);
+        var childBackgroundConnection = child.Background.Connection;
         child.Dispose();
-        //folder.Children = folder.Children.RemoveAt(folder.Children.FindIndex(member => member.GuidValue == newMemberGuid));
+
+        document.NodeGraph.RemoveNode(child);
+        
+        childBackgroundConnection?.ConnectTo(backgroundInput.Background);
 
         return new DeleteStructureMember_ChangeInfo(newMemberGuid, parentFolderGuid);
     }
+
+    private static void AppendMember(IBackgroundInput backgroundInput, StructureNode member)
+    {
+        IOutputProperty? previouslyConnected = null;
+        if (backgroundInput.Background.Connection != null)
+        {
+            previouslyConnected = backgroundInput.Background.Connection;
+        }
+
+        member.Output.ConnectTo(backgroundInput.Background);
+
+        if (previouslyConnected != null)
+        {
+            member.Background.Connection = previouslyConnected;
+        }
+    }
 }