Browse Source

Fixed a lot of Changes

flabbet 1 year ago
parent
commit
e9466c8092
69 changed files with 890 additions and 758 deletions
  1. 4 4
      src/PixiEditor.AvaloniaUI/Models/Rendering/MemberPreviewUpdater.cs
  2. 1 1
      src/PixiEditor.AvaloniaUI/ViewModels/Document/DocumentViewModel.cs
  3. 2 2
      src/PixiEditor.ChangeableDocument/ChangeInfos/Structure/CreateFolder_ChangeInfo.cs
  4. 2 2
      src/PixiEditor.ChangeableDocument/ChangeInfos/Structure/CreateLayer_ChangeInfo.cs
  5. 6 5
      src/PixiEditor.ChangeableDocument/Changeables/Animations/AnimationData.cs
  6. 5 3
      src/PixiEditor.ChangeableDocument/Changeables/Animations/GroupKeyFrame.cs
  7. 11 7
      src/PixiEditor.ChangeableDocument/Changeables/Animations/KeyFrame.cs
  8. 8 7
      src/PixiEditor.ChangeableDocument/Changeables/Animations/RasterKeyFrame.cs
  9. 153 86
      src/PixiEditor.ChangeableDocument/Changeables/Document.cs
  10. 0 83
      src/PixiEditor.ChangeableDocument/Changeables/Folder.cs
  11. 0 12
      src/PixiEditor.ChangeableDocument/Changeables/Graph/IReadOnlyNode.cs
  12. 2 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/InputProperty.cs
  13. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/INodeProperty.cs
  14. 6 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyFolderNode.cs
  15. 4 2
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyImageNode.cs
  16. 6 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyLayerNode.cs
  17. 27 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyNode.cs
  18. 3 2
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyNodeGraph.cs
  19. 17 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyStructureNode.cs
  20. 46 21
      src/PixiEditor.ChangeableDocument/Changeables/Graph/NodeGraph.cs
  21. 63 14
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FolderNode.cs
  22. 167 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ImageLayerNode.cs
  23. 5 28
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/LayerNode.cs
  24. 5 2
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/MergeNode.cs
  25. 98 13
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Node.cs
  26. 5 3
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/OutputNode.cs
  27. 39 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/StructureNode.cs
  28. 2 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/OutputProperty.cs
  29. 13 11
      src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyDocument.cs
  30. 0 9
      src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyFolder.cs
  31. 4 2
      src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyKeyFrame.cs
  32. 0 11
      src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyLayer.cs
  33. 0 50
      src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyStructureMember.cs
  34. 0 12
      src/PixiEditor.ChangeableDocument/Changeables/Layer.cs
  35. 0 172
      src/PixiEditor.ChangeableDocument/Changeables/RasterLayer.cs
  36. 0 25
      src/PixiEditor.ChangeableDocument/Changeables/StructureMember.cs
  37. 5 4
      src/PixiEditor.ChangeableDocument/Changes/Animation/CreateRasterKeyFrame_Change.cs
  38. 10 9
      src/PixiEditor.ChangeableDocument/Changes/Drawing/ApplyLayerMask_Change.cs
  39. 6 5
      src/PixiEditor.ChangeableDocument/Changes/Drawing/ChangeBrightness_UpdateableChange.cs
  40. 4 4
      src/PixiEditor.ChangeableDocument/Changes/Drawing/CombineStructureMembersOnto_Change.cs
  41. 10 9
      src/PixiEditor.ChangeableDocument/Changes/Drawing/DrawingChangeHelper.cs
  42. 1 1
      src/PixiEditor.ChangeableDocument/Changes/Drawing/FloodFill/FloodFillHelper.cs
  43. 1 1
      src/PixiEditor.ChangeableDocument/Changes/Drawing/FloodFill/FloodFill_Change.cs
  44. 8 7
      src/PixiEditor.ChangeableDocument/Changes/Drawing/ReplaceColor_Change.cs
  45. 3 2
      src/PixiEditor.ChangeableDocument/Changes/Drawing/ShiftLayerHelper.cs
  46. 3 2
      src/PixiEditor.ChangeableDocument/Changes/Drawing/ShiftLayer_UpdateableChange.cs
  47. 3 3
      src/PixiEditor.ChangeableDocument/Changes/Properties/LayerStructure/CreateStructureMemberMask_Change.cs
  48. 4 4
      src/PixiEditor.ChangeableDocument/Changes/Properties/LayerStructure/DeleteStructureMemberMask_Change.cs
  49. 5 4
      src/PixiEditor.ChangeableDocument/Changes/Properties/LayerStructure/LayerLockTransparency_Change.cs
  50. 4 4
      src/PixiEditor.ChangeableDocument/Changes/Properties/LayerStructure/StructureMemberBlendMode_Change.cs
  51. 4 4
      src/PixiEditor.ChangeableDocument/Changes/Properties/LayerStructure/StructureMemberClipToMemberBelow_Change.cs
  52. 4 4
      src/PixiEditor.ChangeableDocument/Changes/Properties/LayerStructure/StructureMemberIsVisible_Change.cs
  53. 4 4
      src/PixiEditor.ChangeableDocument/Changes/Properties/LayerStructure/StructureMemberMaskIsVisible_Change.cs
  54. 4 4
      src/PixiEditor.ChangeableDocument/Changes/Properties/LayerStructure/StructureMemberName_Change.cs
  55. 4 4
      src/PixiEditor.ChangeableDocument/Changes/Properties/LayerStructure/StructureMemberOpacity_UpdateableChange.cs
  56. 6 5
      src/PixiEditor.ChangeableDocument/Changes/Root/CenterContent_Change.cs
  57. 6 5
      src/PixiEditor.ChangeableDocument/Changes/Root/ClipCanvas_Change.cs
  58. 6 5
      src/PixiEditor.ChangeableDocument/Changes/Root/Crop_Change.cs
  59. 8 7
      src/PixiEditor.ChangeableDocument/Changes/Root/FlipImage_Change.cs
  60. 8 7
      src/PixiEditor.ChangeableDocument/Changes/Root/ResizeBasedChangeBase.cs
  61. 6 5
      src/PixiEditor.ChangeableDocument/Changes/Root/ResizeCanvas_Change.cs
  62. 16 15
      src/PixiEditor.ChangeableDocument/Changes/Root/ResizeImage_Change.cs
  63. 21 20
      src/PixiEditor.ChangeableDocument/Changes/Root/RotateImage_Change.cs
  64. 2 2
      src/PixiEditor.ChangeableDocument/Changes/Selection/MagicWand/MagicWand_Change.cs
  65. 5 4
      src/PixiEditor.ChangeableDocument/Changes/Structure/ApplyMask_Change.cs
  66. 6 5
      src/PixiEditor.ChangeableDocument/Changes/Structure/CreateStructureMember_Change.cs
  67. 5 4
      src/PixiEditor.ChangeableDocument/Changes/Structure/MoveStructureMember_Change.cs
  68. 2 2
      src/PixiEditor/Models/Rendering/AffectedAreasGatherer.cs
  69. 1 1
      src/PixiEditor/Models/Rendering/MemberPreviewUpdater.cs

+ 4 - 4
src/PixiEditor.AvaloniaUI/Models/Rendering/MemberPreviewUpdater.cs

@@ -311,7 +311,7 @@ internal class MemberPreviewUpdater
         if (layer.Mask is not null && forMask)
             return FindImageTightBoundsFast(layer.Mask);
 
-        if (layer is IReadOnlyRasterLayer raster)
+        if (layer is IReadOnlyImageNode raster)
         {
             return FindImageTightBoundsFast(raster.GetLayerImageAtFrame(frame));
         }
@@ -510,7 +510,7 @@ internal class MemberPreviewUpdater
                     {
                         foreach (var child in group.Children)
                         {
-                            if (member is IReadOnlyRasterLayer rasterLayer) 
+                            if (member is IReadOnlyImageNode rasterLayer) 
                             {
                                 RenderAnimationFramePreview(rasterLayer, child, affArea.Value);
                             }
@@ -589,7 +589,7 @@ internal class MemberPreviewUpdater
         memberVM.PreviewSurface.DrawingSurface.Canvas.Restore();
     }
 
-    private void RenderAnimationFramePreview(IReadOnlyRasterLayer layer, IKeyFrameHandler keyFrameVM, AffectedArea area)
+    private void RenderAnimationFramePreview(IReadOnlyImageNode node, IKeyFrameHandler keyFrameVM, AffectedArea area)
     {
         if (keyFrameVM.PreviewSurface is null)
         {
@@ -602,7 +602,7 @@ internal class MemberPreviewUpdater
         foreach (var chunk in area.Chunks)
         {
             var pos = chunk * ChunkResolution.Full.PixelSize();
-            if (!layer.GetLayerImageByKeyFrameGuid(keyFrameVM.Id).DrawCommittedChunkOn(chunk, ChunkResolution.Full,
+            if (!node.GetLayerImageByKeyFrameGuid(keyFrameVM.Id).DrawCommittedChunkOn(chunk, ChunkResolution.Full,
                     keyFrameVM.PreviewSurface!.DrawingSurface, pos, ReplacingPaint))
             {
                 keyFrameVM.PreviewSurface!.DrawingSurface.Canvas.DrawRect(pos.X, pos.Y, ChunkyImage.FullChunkSize,

+ 1 - 1
src/PixiEditor.AvaloniaUI/ViewModels/Document/DocumentViewModel.cs

@@ -437,7 +437,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
             return new None();
 
         //TODO: Make sure it's not needed for other layer types
-        IReadOnlyRasterLayer? layer = (IReadOnlyRasterLayer?)Internals.Tracker.Document.FindMember(layerVm.GuidValue);
+        IReadOnlyImageNode? layer = (IReadOnlyImageNode?)Internals.Tracker.Document.FindMember(layerVm.GuidValue);
         if (layer is null)
             return new Error();
 

+ 2 - 2
src/PixiEditor.ChangeableDocument/ChangeInfos/Structure/CreateFolder_ChangeInfo.cs

@@ -22,7 +22,7 @@ public record class CreateFolder_ChangeInfo : CreateStructureMember_ChangeInfo
 
     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, Folder folder)
     {
         var builder = ImmutableList.CreateBuilder<CreateStructureMember_ChangeInfo>();
         for (int i = 0; i < folder.Children.Count; i++)
@@ -49,5 +49,5 @@ public record class CreateFolder_ChangeInfo : CreateStructureMember_ChangeInfo
             folder.MaskIsVisible,
             builder.ToImmutable()
             );
-    }
+    }*/
 }

+ 2 - 2
src/PixiEditor.ChangeableDocument/ChangeInfos/Structure/CreateLayer_ChangeInfo.cs

@@ -22,7 +22,7 @@ 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, Layer layer)
     {
         return new CreateLayer_ChangeInfo(
             parentGuid,
@@ -37,5 +37,5 @@ public record class CreateLayer_ChangeInfo : CreateStructureMember_ChangeInfo
             layer.MaskIsVisible,
             layer is ITransparencyLockable { LockTransparency: true }
             );
-    }
+    }*/
 }

+ 6 - 5
src/PixiEditor.ChangeableDocument/Changeables/Animations/AnimationData.cs

@@ -1,4 +1,5 @@
-using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Animations;
 
@@ -23,8 +24,8 @@ internal class AnimationData : IReadOnlyAnimationData
         }
         else
         {
-            var layer = document.FindMemberOrThrow<Layer>(id);
-            GroupKeyFrame createdGroup = new GroupKeyFrame(layer, keyFrame.StartFrame, document);
+            var node = document.FindNodeOrThrow<Node>(id);
+            GroupKeyFrame createdGroup = new GroupKeyFrame(node, keyFrame.StartFrame, document);
             createdGroup.Children.Add(keyFrame);
             keyFrames.Add(createdGroup);
         }
@@ -34,9 +35,9 @@ internal class AnimationData : IReadOnlyAnimationData
     {
         TryFindKeyFrameCallback<KeyFrame>(createdKeyFrameId, out _, (frame, parent) =>
         {
-            if (document.TryFindMember<Layer>(frame.LayerGuid, out Layer? layer))
+            if (document.TryFindNode<Node>(frame.LayerGuid, out Node? node))
             {
-                layer.RemoveKeyFrame(frame.Id);
+                node.RemoveKeyFrame(frame.Id);
             }
             
             parent?.Children.Remove(frame);

+ 5 - 3
src/PixiEditor.ChangeableDocument/Changeables/Animations/GroupKeyFrame.cs

@@ -1,4 +1,6 @@
 using System.Collections.Immutable;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Animations;
@@ -14,15 +16,15 @@ internal class GroupKeyFrame : KeyFrame, IKeyFrameChildrenContainer
 
     IReadOnlyList<IReadOnlyKeyFrame> IKeyFrameChildrenContainer.Children => Children;
 
-    public GroupKeyFrame(IReadOnlyLayer layer, int startFrame, Document document) : base(layer, startFrame)
+    public GroupKeyFrame(Node node, int startFrame, Document document) : base(node, startFrame)
     {
-        Id = layer.GuidValue;
+        Id = node.Id;
         this.document = document;
     }
 
     public override KeyFrame Clone()
     {
-        var clone = new GroupKeyFrame(TargetLayer, StartFrame, document) { Id = this.Id };
+        var clone = new GroupKeyFrame(TargetNode, StartFrame, document) { Id = this.Id };
         foreach (var child in Children)
         {
             clone.Children.Add(child.Clone());

+ 11 - 7
src/PixiEditor.ChangeableDocument/Changeables/Animations/KeyFrame.cs

@@ -1,4 +1,6 @@
-using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Animations;
 
@@ -19,7 +21,7 @@ public abstract class KeyFrame : IReadOnlyKeyFrame
             }
 
             startFrame = value;
-            TargetLayer.SetKeyFrameLength(Id, startFrame, Duration);
+            TargetNode.SetKeyFrameLength(Id, startFrame, Duration);
         }
     }
 
@@ -34,7 +36,7 @@ public abstract class KeyFrame : IReadOnlyKeyFrame
             }
 
             duration = value;
-            TargetLayer.SetKeyFrameLength(Id, StartFrame, Duration);
+            TargetNode.SetKeyFrameLength(Id, StartFrame, Duration);
         }
     }
     
@@ -52,12 +54,14 @@ public abstract class KeyFrame : IReadOnlyKeyFrame
         }
     }
 
-    public IReadOnlyLayer TargetLayer { get; }
+    public Node TargetNode { get; }
+    
+    IReadOnlyNode IReadOnlyKeyFrame.TargetNode => TargetNode;
 
-    protected KeyFrame(IReadOnlyLayer layer, int startFrame)
+    protected KeyFrame(Node node, int startFrame)
     {
-        TargetLayer = layer;
-        LayerGuid = layer.GuidValue;
+        TargetNode = node;
+        LayerGuid = node.Id;
         this.startFrame = startFrame;
         duration = 1;
         Id = Guid.NewGuid();

+ 8 - 7
src/PixiEditor.ChangeableDocument/Changeables/Animations/RasterKeyFrame.cs

@@ -1,4 +1,5 @@
-using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Animations;
 
@@ -6,16 +7,16 @@ internal class RasterKeyFrame : KeyFrame, IReadOnlyRasterKeyFrame
 {
     public Document Document { get; set; }
 
-    private RasterLayer targetLayer;
+    private ImageLayerNode targetNode;
     private ChunkyImage targetImage;
     
-    public RasterKeyFrame(Guid id, RasterLayer layer, int startFrame, Document document, ChunkyImage? cloneFrom = null)
-        : base(layer, startFrame)
+    public RasterKeyFrame(Guid id, ImageLayerNode node, int startFrame, Document document, ChunkyImage? cloneFrom = null)
+        : base(node, startFrame)
     {
         Id = id;
-        targetLayer = layer;
+        targetNode = node;
         targetImage = cloneFrom?.CloneFromCommitted() ?? new ChunkyImage(document.Size);
-        layer.AddFrame(Id, startFrame, 1, targetImage);
+        node.AddFrame(Id, startFrame, 1, targetImage);
 
         Document = document;
     }
@@ -23,7 +24,7 @@ internal class RasterKeyFrame : KeyFrame, IReadOnlyRasterKeyFrame
     public override KeyFrame Clone()
     {
         var image = targetImage.CloneFromCommitted();
-        return new RasterKeyFrame(Id, targetLayer, StartFrame, Document, image);
+        return new RasterKeyFrame(Id, targetNode, StartFrame, Document, image);
     }
 
     public IReadOnlyChunkyImage Image => targetImage;

+ 153 - 86
src/PixiEditor.ChangeableDocument/Changeables/Document.cs

@@ -1,5 +1,8 @@
 using System.Diagnostics.CodeAnalysis;
 using PixiEditor.ChangeableDocument.Changeables.Animations;
+using PixiEditor.ChangeableDocument.Changeables.Graph;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Numerics;
@@ -8,14 +11,19 @@ namespace PixiEditor.ChangeableDocument.Changeables;
 
 internal class Document : IChangeable, IReadOnlyDocument, IDisposable
 {
-    IReadOnlyFolder IReadOnlyDocument.StructureRoot => StructureRoot;
+    IReadOnlyNodeGraph IReadOnlyDocument.NodeGraph => NodeGraph;
     IReadOnlySelection IReadOnlyDocument.Selection => Selection;
     IReadOnlyAnimationData IReadOnlyDocument.AnimationData => AnimationData;
-    IReadOnlyStructureMember? IReadOnlyDocument.FindMember(Guid guid) => FindMember(guid);
-    bool IReadOnlyDocument.TryFindMember(Guid guid, [NotNullWhen(true)] out IReadOnlyStructureMember? member) => TryFindMember(guid, out member);
-    IReadOnlyList<IReadOnlyStructureMember> IReadOnlyDocument.FindMemberPath(Guid guid) => FindMemberPath(guid);
-    IReadOnlyStructureMember IReadOnlyDocument.FindMemberOrThrow(Guid guid) => FindMemberOrThrow(guid);
-    (IReadOnlyStructureMember, IReadOnlyFolder) IReadOnlyDocument.FindChildAndParentOrThrow(Guid guid) => FindChildAndParentOrThrow(guid);
+    IReadOnlyStructureNode? IReadOnlyDocument.FindMember(Guid guid) => FindMember(guid);
+
+    bool IReadOnlyDocument.TryFindMember(Guid guid, [NotNullWhen(true)] out IReadOnlyStructureNode? member) =>
+        TryFindMember(guid, out member);
+
+    IReadOnlyList<IReadOnlyStructureNode> IReadOnlyDocument.FindMemberPath(Guid guid) => FindMemberPath(guid);
+    IReadOnlyStructureNode IReadOnlyDocument.FindMemberOrThrow(Guid guid) => FindMemberOrThrow(guid);
+
+    (IReadOnlyStructureNode, IReadOnlyFolderNode) IReadOnlyDocument.FindChildAndParentOrThrow(Guid guid) =>
+        FindChildAndParentOrThrow(guid);
 
     IReadOnlyReferenceLayer? IReadOnlyDocument.ReferenceLayer => ReferenceLayer;
 
@@ -23,7 +31,8 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
     /// The default size for a new document
     /// </summary>
     public static VecI DefaultSize { get; } = new VecI(64, 64);
-    internal Folder StructureRoot { get; } = new() { GuidValue = Guid.Empty };
+
+    internal NodeGraph NodeGraph { get; } = new();
     internal Selection Selection { get; } = new();
     internal ReferenceLayer? ReferenceLayer { get; set; }
     internal AnimationData AnimationData { get; }
@@ -32,7 +41,7 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
     public bool VerticalSymmetryAxisEnabled { get; set; }
     public double HorizontalSymmetryAxisY { get; set; }
     public double VerticalSymmetryAxisX { get; set; }
-    
+
     public Document()
     {
         AnimationData = new AnimationData(this);
@@ -40,10 +49,10 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
 
     public void Dispose()
     {
-        StructureRoot.Dispose();
+        NodeGraph.Dispose();
         Selection.Dispose();
     }
-    
+
     /// <summary>
     ///     Creates a surface for layer image.
     /// </summary>
@@ -55,7 +64,7 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
     /// you are lucky enough. Have fun!</remarks>
     public Surface? GetLayerRasterizedImage(Guid layerGuid, int frame)
     {
-        var layer = (IReadOnlyLayer?)FindMember(layerGuid);
+        var layer = (IReadOnlyLayerNode?)FindMember(layerGuid);
 
         if (layer is null)
             throw new ArgumentException(@"The given guid does not belong to a layer.", nameof(layerGuid));
@@ -70,7 +79,7 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
 
         Surface surface = new Surface(tightBounds.Value.Size);
 
-        layer.Rasterize(frame).DrawMostUpToDateRegionOn(
+        layer.Execute(frame).DrawMostUpToDateRegionOn(
             tightBounds.Value,
             ChunkResolution.Full,
             surface.DrawingSurface, VecI.Zero);
@@ -80,7 +89,7 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
 
     public RectI? GetChunkAlignedLayerBounds(Guid layerGuid, int frame)
     {
-        var layer = (IReadOnlyLayer?)FindMember(layerGuid);
+        var layer = (IReadOnlyLayerNode?)FindMember(layerGuid);
 
         if (layer is null)
             throw new ArgumentException(@"The given guid does not belong to a layer.", nameof(layerGuid));
@@ -88,38 +97,45 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
 
         return layer.GetTightBounds(frame);
     }
-    
-    public void ForEveryReadonlyMember(Action<IReadOnlyStructureMember> action) => ForEveryReadonlyMember(StructureRoot, action);
+
+    public void ForEveryReadonlyMember(Action<IReadOnlyStructureNode> action) =>
+        ForEveryReadonlyMember(NodeGraph, action);
 
     /// <summary>
     /// Performs the specified action on each member of the document
     /// </summary>
-    public void ForEveryMember(Action<StructureMember> action) => ForEveryMember(StructureRoot, action);
+    public void ForEveryMember(Action<StructureNode> action) => ForEveryMember(NodeGraph, action);
 
-    private void ForEveryReadonlyMember(IReadOnlyFolder folder, Action<IReadOnlyStructureMember> action)
+    private void ForEveryReadonlyMember(IReadOnlyNodeGraph graph, Action<IReadOnlyStructureNode> action)
     {
-        foreach (var child in folder.Children)
+        graph.TryTraverse((node) =>
         {
-            action(child);
-            if (child is IReadOnlyFolder innerFolder)
-                ForEveryReadonlyMember(innerFolder, action);
-        }
+            if (node is not IReadOnlyStructureNode structureNode)
+            {
+                return;
+            }
+
+            action(structureNode);
+        });
     }
 
-    private void ForEveryMember(Folder folder, Action<StructureMember> action)
+    private void ForEveryMember(NodeGraph graph, Action<StructureNode> action)
     {
-        foreach (var child in folder.Children)
+        graph.TryTraverse((node) =>
         {
-            action(child);
-            if (child is Folder innerFolder)
-                ForEveryMember(innerFolder, action);
-        }
+            if (node is not StructureNode structureNode)
+            {
+                return;
+            }
+
+            action(structureNode);
+        });
     }
 
     /// <summary>
     /// Checks if a member with the <paramref name="guid"/> exists
     /// </summary>
-    /// <param name="guid">The <see cref="StructureMember.GuidValue"/> of the member</param>
+    /// <param name="guid">The <see cref="StructureNode.Id"/> of the member</param>
     /// <returns>True if the member can be found, otherwise false</returns>
     public bool HasMember(Guid guid)
     {
@@ -130,47 +146,78 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
     /// <summary>
     /// Checks if a member with the <paramref name="guid"/> exists and is of type <typeparamref name="T"/>
     /// </summary>
-    /// <param name="guid">The <see cref="StructureMember.GuidValue"/> of the member</param>
+    /// <param name="guid">The <see cref="StructureNode.Id"/> of the member</param>
     /// <returns>True if the member can be found and is of type <typeparamref name="T"/>, otherwise false</returns>
-    public bool HasMember<T>(Guid guid) 
-        where T : StructureMember
+    public bool HasMember<T>(Guid guid)
+        where T : StructureNode
     {
         var list = FindMemberPath(guid);
         return list.Count > 0 && list[0] is T;
     }
-    
+
     /// <summary>
     /// Finds the member with the <paramref name="guid"/> or throws a ArgumentException if not found
     /// </summary>
-    /// <param name="guid">The <see cref="StructureMember.GuidValue"/> of the member</param>
+    /// <param name="guid">The <see cref="StructureNode.Id"/> of the member</param>
     /// <exception cref="ArgumentException">Thrown if the member could not be found</exception>
-    public StructureMember FindMemberOrThrow(Guid guid) => FindMember(guid) ?? throw new ArgumentException($"Could not find member with guid '{guid}'");
+    public StructureNode FindMemberOrThrow(Guid guid) =>
+        FindMember(guid) ?? throw new ArgumentException($"Could not find member with guid '{guid}'");
 
     /// <summary>
     /// Finds the member of type <typeparamref name="T"/> with the <paramref name="guid"/> or throws an exception
     /// </summary>
-    /// <param name="guid">The <see cref="StructureMember.GuidValue"/> of the member</param>
+    /// <param name="guid">The <see cref="StructureNode.Id"/> of the member</param>
     /// <exception cref="ArgumentException">Thrown if the member could not be found</exception>
     /// <exception cref="InvalidCastException">Thrown if the member is not of type <typeparamref name="T"/></exception>
-    public T FindMemberOrThrow<T>(Guid guid) where T : StructureMember => (T?)FindMember(guid) ?? throw new ArgumentException($"Could not find member with guid '{guid}'");
+    public T FindMemberOrThrow<T>(Guid guid) where T : StructureNode => (T?)FindMember(guid) ??
+                                                                        throw new ArgumentException(
+                                                                            $"Could not find member with guid '{guid}'");
+
+    public T FindNodeOrThrow<T>(Guid guid) where T : Node => (T?)FindNode(guid) ??
+                                                             throw new ArgumentException(
+                                                                 $"Could not find node with guid '{guid}'");
 
     /// <summary>
     /// Finds the member with the <paramref name="guid"/> or returns null if not found
     /// </summary>
-    /// <param name="guid">The <see cref="StructureMember.GuidValue"/> of the member</param>
-    public StructureMember? FindMember(Guid guid)
+    /// <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;
     }
 
+    /// <summary>
+    ///     Finds the node with the given <paramref name="guid"/>.
+    /// </summary>
+    /// <param name="guid">The <see cref="Node.Id"/> of the node.</param>
+    /// <returns>The node with the given <paramref name="guid"/> or null if it doesn't exist.</returns>
+    public Node? FindNode(Guid guid)
+    {
+        return NodeGraph.Nodes.FirstOrDefault(x => x.Id == guid);
+    }
+    
+   
+    /// <summary>
+    ///     Tries to find the node with the given <paramref name="id"/> and returns true if it was found.
+    /// </summary>
+    /// <param name="id">The <see cref="Node.Id"/> of the node.</param>
+    /// <param name="node">The node.</param>
+    /// <typeparam name="T">The type of the node.</typeparam>
+    /// <returns>True if the node could be found, otherwise false.</returns>
+    public bool TryFindNode<T>(Guid id, out T node) where T : Node
+    {
+        node = (T?)NodeGraph.Nodes.FirstOrDefault(x => x.Id == id) ?? default;
+        return node != null;
+    }
+
     /// <summary>
     /// Tries finding the member with the <paramref name="guid"/> and returns true if it was found
     /// </summary>
-    /// <param name="guid">The <see cref="StructureMember.GuidValue"/> of the <paramref name="member"/></param>
+    /// <param name="guid">The <see cref="StructureNode.Id"/> of the <paramref name="member"/></param>
     /// <param name="member">The member</param>
     /// <returns>True if the member could be found, otherwise false</returns>
-    public bool TryFindMember(Guid guid, [NotNullWhen(true)] out StructureMember? member)
+    public bool TryFindMember(Guid guid, [NotNullWhen(true)] out StructureNode? member)
     {
         var list = FindMemberPath(guid);
         if (list.Count == 0)
@@ -186,12 +233,12 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
     /// <summary>
     /// Tries finding the member with the <paramref name="guid"/> of type <typeparamref name="T"/> and returns true if it was found
     /// </summary>
-    /// <param name="guid">The <see cref="StructureMember.GuidValue"/> of the <paramref name="member"/></param>
+    /// <param name="guid">The <see cref="StructureNode.Id"/> of the <paramref name="member"/></param>
     /// <param name="member">The member</param>
-    /// <typeparam name="T">The type of the <see cref="StructureMember"/></typeparam>
+    /// <typeparam name="T">The type of the <see cref="StructureNode"/></typeparam>
     /// <returns>True if the member could be found and is of type <typeparamref name="T"/>, otherwise false</returns>
-    public bool TryFindMember<T>(Guid guid, [NotNullWhen(true)] out T? member) 
-        where T : IReadOnlyStructureMember
+    public bool TryFindMember<T>(Guid guid, [NotNullWhen(true)] out T? member)
+        where T : IReadOnlyStructureNode
     {
         if (!TryFindMember(guid, out var structureMember) || structureMember is not T cast)
         {
@@ -203,74 +250,94 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
         return true;
     }
 
+
+
     /// <summary>
     /// Finds a member with the <paramref name="childGuid"/>  and its parent, throws a ArgumentException if they can't be found
     /// </summary>
-    /// <param name="childGuid">The <see cref="StructureMember.GuidValue"/> of the member</param>
+    /// <param name="childGuid">The <see cref="StructureNode.Id"/> of the member</param>
     /// <returns>A value tuple consisting of child (<see cref="ValueTuple{T, T}.Item1"/>) and parent (<see cref="ValueTuple{T, T}.Item2"/>)</returns>
     /// <exception cref="ArgumentException">Thrown if the member and parent could not be found</exception>
-    public (StructureMember, Folder) FindChildAndParentOrThrow(Guid childGuid)
+    public (StructureNode, FolderNode) FindChildAndParentOrThrow(Guid childGuid)
     {
         var path = FindMemberPath(childGuid);
         if (path.Count < 2)
             throw new ArgumentException("Couldn't find child and parent");
-        return (path[0], (Folder)path[1]);
+        return (path[0], (FolderNode)path[1]);
     }
 
     /// <summary>
     /// Finds a member with the <paramref name="childGuid"/> and its parent
     /// </summary>
-    /// <param name="childGuid">The <see cref="StructureMember.GuidValue"/> of the member</param>
+    /// <param name="childGuid">The <see cref="StructureNode.Id"/> of the member</param>
     /// <returns>A value tuple consisting of child (<see cref="ValueTuple{T, T}.Item1"/>) and parent (<see cref="ValueTuple{T, T}.Item2"/>)<para>Child and parent can be null if not found!</para></returns>
-    public (StructureMember?, Folder?) FindChildAndParent(Guid childGuid)
+    public (StructureNode?, FolderNode?) FindChildAndParent(Guid childGuid)
     {
         var path = FindMemberPath(childGuid);
         return path.Count switch
         {
             1 => (path[0], null),
-            > 1 => (path[0], (Folder)path[1]),
+            > 1 => (path[0], (FolderNode)path[1]),
             _ => (null, null),
         };
     }
 
     /// <summary>
-    /// Finds the path to the member with <paramref name="guid"/>, the first element will be the member
+    ///     Finds the path to the node with the given <paramref name="guid"/>.
     /// </summary>
-    /// <param name="guid">The <see cref="StructureMember.GuidValue"/> of the member</param>
-    public List<StructureMember> FindMemberPath(Guid guid)
+    /// <param name="guid">The <see cref="Node.Id"/> of the node.</param>
+    /// <returns>The path to the node.</returns>
+    public List<Node> FindNodePath(Guid guid)
     {
-        var list = new List<StructureMember>();
-        if (FillMemberPath(StructureRoot, guid, list))
-            list.Add(StructureRoot);
+        if (NodeGraph.OutputNode == null) return [];
+
+        var list = new List<Node>();
+        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>
+    /// <param name="guid">The <see cref="StructureNode.Id"/> of the member</param>
+    public List<StructureNode> FindMemberPath(Guid guid)
+    {
+        if (NodeGraph.OutputNode == null) return [];
+
+        var list = new List<Node>();
+        FillNodePath(NodeGraph.OutputNode, guid, list);
+        return list.Cast<StructureNode>().ToList();
+    }
 
-    private bool FillMemberPath(Folder folder, Guid guid, List<StructureMember> toFill)
+    private bool FillNodePath(Node node, Guid guid, List<Node> toFill)
     {
-        if (folder.GuidValue == guid)
+        if (node.Id == guid)
         {
             return true;
         }
 
-        foreach (var member in folder.Children)
+        if (node is StructureNode structureNode)
         {
-            if (member is Layer childLayer && childLayer.GuidValue == guid)
-            {
-                toFill.Add(member);
-                return true;
-            }
-            if (member is Folder childFolder)
+            toFill.Add(structureNode);
+        }
+
+        bool found = false;
+
+        node.TraverseBackwards((newNode) =>
+        {
+            if (newNode is StructureNode strNode && newNode.Id == guid)
             {
-                if (FillMemberPath(childFolder, guid, toFill))
-                {
-                    toFill.Add(childFolder);
-                    return true;
-                }
+                toFill.Add(strNode);
+                found = true;
+                return false;
             }
-        }
-        return false;
+
+            return true;
+        });
+
+        return found;
     }
-    
+
     public List<Guid> ExtractLayers(IList<Guid> members)
     {
         var result = new List<Guid>();
@@ -278,31 +345,31 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
         {
             if (TryFindMember(member, out var structureMember))
             {
-                if (structureMember is Layer layer && !result.Contains(layer.GuidValue))
+                if (structureMember is LayerNode layer && !result.Contains(layer.Id))
                 {
-                    result.Add(layer.GuidValue);
+                    result.Add(layer.Id);
                 }
-                else if (structureMember is Folder folder)
+                else if (structureMember is FolderNode folder)
                 {
                     ExtractLayers(folder, result);
                 }
             }
         }
+
         return result;
     }
 
-    private void ExtractLayers(Folder folder, List<Guid> list)
+    private void ExtractLayers(FolderNode folder, List<Guid> list)
     {
-        foreach (var member in folder.Children)
+        List<Guid> result = new();
+        folder.TraverseBackwards(node =>
         {
-            if (member is Layer layer && !list.Contains(layer.GuidValue))
-            {
-                list.Add(layer.GuidValue);
-            }
-            else if (member is Folder childFolder)
+            if (node is LayerNode layer && !result.Contains(layer.Id))
             {
-                ExtractLayers(childFolder, list);
+                result.Add(layer.Id);
             }
-        }
+
+            return true;
+        });
     }
 }

+ 0 - 83
src/PixiEditor.ChangeableDocument/Changeables/Folder.cs

@@ -1,83 +0,0 @@
-using System.Collections.Immutable;
-using PixiEditor.ChangeableDocument.Changeables.Interfaces;
-using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.Numerics;
-
-namespace PixiEditor.ChangeableDocument.Changeables;
-
-internal class Folder : StructureMember, IReadOnlyFolder
-{
-    // Don't forget to update CreateFolder_ChangeInfo, DocumentUpdater.ProcessCreateStructureMember, and Folder.Clone when adding new properties
-    /// <summary>
-    /// The children of the folder
-    /// </summary>
-    public ImmutableList<StructureMember> Children { get; set; } = ImmutableList<StructureMember>.Empty;
-    IReadOnlyList<IReadOnlyStructureMember> IReadOnlyFolder.Children => Children;
-
-    public override RectI? GetTightBounds(int frame)
-    {
-        if (Children.Count == 0)
-        {
-            return null;
-        }
-
-        var bounds = Children[0].GetTightBounds(frame);
-        for (var i = 1; i < Children.Count; i++)
-        {
-            var childBounds = Children[i].GetTightBounds(frame);
-            if (childBounds == null)
-            {
-                continue;
-            }
-
-            if (bounds == null)
-            {
-                bounds = childBounds;
-            }
-            else
-            {
-                bounds = bounds.Value.Union(childBounds.Value);
-            }
-        }
-
-        return bounds;
-    }
-
-    /// <summary>
-    /// Creates a clone of the folder, its mask and all of its children
-    /// </summary>
-    internal override Folder Clone()
-    {
-        var builder = ImmutableList<StructureMember>.Empty.ToBuilder();
-        for (var i = 0; i < Children.Count; i++)
-        {
-            var child = Children[i];
-            builder.Add(child.Clone());
-        }
-
-        return new Folder
-        {
-            GuidValue = GuidValue,
-            IsVisible = IsVisible,
-            Name = Name,
-            Opacity = Opacity,
-            Children = builder.ToImmutable(),
-            Mask = Mask?.CloneFromCommitted(),
-            BlendMode = BlendMode,
-            ClipToMemberBelow = ClipToMemberBelow,
-            MaskIsVisible = MaskIsVisible
-        };
-    }
-
-    /// <summary>
-    /// Disposes all children and the mask
-    /// </summary>
-    public override void Dispose()
-    {
-        foreach (var child in Children)
-        {
-            child.Dispose();
-        }
-        Mask?.Dispose();
-    }
-}

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

@@ -1,12 +0,0 @@
-namespace PixiEditor.ChangeableDocument.Changeables.Graph;
-
-public interface IReadOnlyNode
-{
-    public string Name { get; }
-    public IReadOnlyCollection<IInputProperty> InputProperties { get; }
-    public IReadOnlyCollection<IOutputProperty> OutputProperties { get; }
-    public IReadOnlyCollection<IReadOnlyNode> ConnectedNodes { get; }
-
-    public void Execute(int frame);
-    public bool Validate();
-}

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

@@ -1,4 +1,5 @@
-using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph;
 

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/INodeProperty.cs → src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/INodeProperty.cs

@@ -1,4 +1,4 @@
-namespace PixiEditor.ChangeableDocument.Changeables.Graph;
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 
 public interface INodeProperty
 {

+ 6 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyFolderNode.cs

@@ -0,0 +1,6 @@
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+
+public interface IReadOnlyFolderNode : IReadOnlyNode
+{
+    
+}

+ 4 - 2
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyRasterLayer.cs → src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyImageNode.cs

@@ -1,6 +1,8 @@
-namespace PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 
-public interface IReadOnlyRasterLayer : ITransparencyLockable
+namespace PixiEditor.ChangeableDocument.Changeables.Interfaces;
+
+public interface IReadOnlyImageNode : IReadOnlyLayerNode, ITransparencyLockable
 {
     /// <summary>
     /// The chunky image of the layer

+ 6 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyLayerNode.cs

@@ -0,0 +1,6 @@
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+
+public interface IReadOnlyLayerNode : IReadOnlyStructureNode
+{
+    
+}

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

@@ -0,0 +1,27 @@
+using PixiEditor.ChangeableDocument.Changeables.Animations;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+
+public interface IReadOnlyNode
+{
+    public Guid Id { get; }
+    public string NodeName { get; }
+    public IReadOnlyCollection<IInputProperty> InputProperties { get; }
+    public IReadOnlyCollection<IOutputProperty> OutputProperties { get; }
+    public IReadOnlyCollection<IReadOnlyNode> ConnectedOutputNodes { get; }
+
+    public ChunkyImage? Execute(KeyFrameTime frame);
+    public bool Validate();
+    
+    /// <summary>
+    ///     Traverses the graph backwards from this node. Backwards means towards the input nodes.
+    /// </summary>
+    /// <param name="action">The action to perform on each node.</param>
+    public void TraverseBackwards(Func<IReadOnlyNode, bool> action);
+
+    /// <summary>
+    ///     Traverses the graph forwards from this node. Forwards means towards the output nodes.
+    /// </summary>
+    /// <param name="action">The action to perform on each node.</param>
+    public void TraverseForwards(Func<IReadOnlyNode, bool> action);
+}

+ 3 - 2
src/PixiEditor.ChangeableDocument/Changeables/Graph/INodeGraph.cs → src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyNodeGraph.cs

@@ -1,9 +1,10 @@
-namespace PixiEditor.ChangeableDocument.Changeables.Graph;
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 
-public interface INodeGraph
+public interface IReadOnlyNodeGraph
 {
     public IReadOnlyCollection<IReadOnlyNode> AllNodes { get; }
     public IReadOnlyNode OutputNode { get; }
     public void AddNode(IReadOnlyNode node);
     public void RemoveNode(IReadOnlyNode node);
+    public bool TryTraverse(Action<IReadOnlyNode> action);
 }

+ 17 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyStructureNode.cs

@@ -0,0 +1,17 @@
+using PixiEditor.ChangeableDocument.Changeables.Animations;
+using PixiEditor.ChangeableDocument.Enums;
+using PixiEditor.Numerics;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+
+public interface IReadOnlyStructureNode : IReadOnlyNode
+{
+    public InputProperty<float> Opacity { get; }
+    public InputProperty<bool> IsVisible { get; }
+    public InputProperty<bool> ClipToMemberBelow { get; }
+    public InputProperty<BlendMode> BlendMode { get; }
+    public InputProperty<ChunkyImage?> Mask { get; }
+    public InputProperty<bool> MaskIsVisible { get; }
+    public string LayerName { get; set; }
+    public RectI? GetTightBounds(KeyFrameTime frameTime);
+}

+ 46 - 21
src/PixiEditor.ChangeableDocument/Changeables/Graph/NodeGraph.cs

@@ -1,15 +1,17 @@
-using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using System.Collections;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph;
 
-public class NodeGraph : INodeGraph
+public class NodeGraph : IReadOnlyNodeGraph, IDisposable
 {
     private readonly List<Node> _nodes = new();
     public IReadOnlyCollection<Node> Nodes => _nodes;
     public OutputNode? OutputNode => Nodes.OfType<OutputNode>().FirstOrDefault();
-    
-    IReadOnlyCollection<IReadOnlyNode> INodeGraph.AllNodes => Nodes;
-    IReadOnlyNode INodeGraph.OutputNode => OutputNode;
+
+    IReadOnlyCollection<IReadOnlyNode> IReadOnlyNodeGraph.AllNodes => Nodes;
+    IReadOnlyNode IReadOnlyNodeGraph.OutputNode => OutputNode;
 
     public void AddNode(Node node)
     {
@@ -17,33 +19,33 @@ public class NodeGraph : INodeGraph
         {
             return;
         }
-        
+
         _nodes.Add(node);
     }
-    
+
     public void RemoveNode(Node node)
     {
         if (!Nodes.Contains(node))
         {
             return;
         }
-        
+
         _nodes.Remove(node);
     }
-    
+
     public ChunkyImage Execute()
     {
-        if(OutputNode == null) return null;
-        
+        if (OutputNode == null) return null;
+
         var queue = CalculateExecutionQueue(OutputNode);
-        
+
         while (queue.Count > 0)
         {
             var node = queue.Dequeue();
-            
+
             node.Execute(0);
         }
-        
+
         return OutputNode.Input.Value;
     }
 
@@ -54,7 +56,7 @@ public class NodeGraph : INodeGraph
         var queueNodes = new Queue<IReadOnlyNode>();
         List<IReadOnlyNode> finalQueue = new();
         queueNodes.Enqueue(outputNode);
-        
+
         while (queueNodes.Count > 0)
         {
             var node = queueNodes.Dequeue();
@@ -62,25 +64,48 @@ public class NodeGraph : INodeGraph
             {
                 continue;
             }
-            
+
             finalQueue.Add(node);
-            
+
             foreach (var input in node.InputProperties)
             {
                 if (input.Connection == null)
                 {
                     continue;
                 }
-                
+
                 queueNodes.Enqueue(input.Connection.Node);
             }
         }
-        
+
         finalQueue.Reverse();
         return new Queue<IReadOnlyNode>(finalQueue);
     }
 
-    void INodeGraph.AddNode(IReadOnlyNode node) => AddNode((Node)node);
+    void IReadOnlyNodeGraph.AddNode(IReadOnlyNode node) => AddNode((Node)node);
+
+    void IReadOnlyNodeGraph.RemoveNode(IReadOnlyNode node) => RemoveNode((Node)node);
 
-    void INodeGraph.RemoveNode(IReadOnlyNode node) => RemoveNode((Node)node);
+    public void Dispose()
+    {
+        foreach (var node in Nodes)
+        {
+            node.Dispose();
+        }
+    }
+
+    public bool TryTraverse(Action<IReadOnlyNode> action)
+    {
+        if(OutputNode == null) return false;
+        
+        var queue = CalculateExecutionQueue(OutputNode);
+        
+        while (queue.Count > 0)
+        {
+            var node = queue.Dequeue();
+            action(node);
+        }
+        
+        return true;
+    }
 }

+ 63 - 14
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FolderNode.cs

@@ -1,23 +1,72 @@
-namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.Changeables.Animations;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.Numerics;
 
-public class FolderNode : Node
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+
+public class FolderNode : StructureNode, IReadOnlyFolderNode
 {
-    public InputProperty<ChunkyImage> Input { get; }
-    public OutputProperty<ChunkyImage> Output { get; }
-    
-    public FolderNode(string name) : base(name)
-    {
-        Input = CreateInput<ChunkyImage>("Input", null);
-        Output = CreateOutput<ChunkyImage>("Output", null);
-    }    
-    
     public override bool Validate()
     {
         return true;
     }
-    
-    public override void OnExecute(int frame)
+
+    public override ChunkyImage? OnExecute(KeyFrameTime frame)
+    {
+    }
+
+    public override RectI? GetTightBounds(KeyFrameTime frameTime)
     {
-        Output.Value = Input.Value;
+        /*if (Children.Count == 0)
+      {
+          return null;
+      }
+
+      var bounds = Children[0].GetTightBounds(frame);
+      for (var i = 1; i < Children.Count; i++)
+      {
+          var childBounds = Children[i].GetTightBounds(frame);
+          if (childBounds == null)
+          {
+              continue;
+          }
+
+          if (bounds == null)
+          {
+              bounds = childBounds;
+          }
+          else
+          {
+              bounds = bounds.Value.Union(childBounds.Value);
+          }
+      }
+
+      return bounds;*/
     }
+
+    /// <summary>
+    /// Creates a clone of the folder, its mask and all of its children
+    /// </summary>
+    /*internal override Folder Clone()
+    {
+        var builder = ImmutableList<StructureMember>.Empty.ToBuilder();
+        for (var i = 0; i < Children.Count; i++)
+        {
+            var child = Children[i];
+            builder.Add(child.Clone());
+        }
+
+        return new Folder
+        {
+            GuidValue = GuidValue,
+            IsVisible = IsVisible,
+            Name = Name,
+            Opacity = Opacity,
+            Children = builder.ToImmutable(),
+            Mask = Mask?.CloneFromCommitted(),
+            BlendMode = BlendMode,
+            ClipToMemberBelow = ClipToMemberBelow,
+            MaskIsVisible = MaskIsVisible
+        };
+    }*/
 }

+ 167 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ImageLayerNode.cs

@@ -0,0 +1,167 @@
+using PixiEditor.ChangeableDocument.Changeables.Animations;
+using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using PixiEditor.Numerics;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+
+public class ImageLayerNode : LayerNode, IReadOnlyImageNode
+{
+    public InputProperty<bool> LockTransparency { get; }
+
+    private List<ImageFrame> frames = new List<ImageFrame>();
+
+    public ImageLayerNode(VecI size)
+    {
+        LockTransparency = CreateInput<bool>("LockTransparency", false);
+        frames.Add(new ImageFrame(Guid.NewGuid(), 0, 0, new(size)));
+    }
+
+    public override bool Validate()
+    {
+        return true;
+    }
+
+    public override RectI? GetTightBounds(KeyFrameTime frameTime)
+    {
+        return Execute(frameTime).FindTightCommittedBounds();
+    }
+
+    public override ChunkyImage OnExecute(KeyFrameTime frame)
+    {
+        // TODO: actual rendering
+    }
+
+    public override void Dispose()
+    {
+        base.Dispose();
+        foreach (var frame in frames)
+        {
+            frame.Image.Dispose();
+        }
+    }
+
+    IReadOnlyChunkyImage IReadOnlyImageNode.GetLayerImageAtFrame(int frame) => GetLayerImageAtFrame(frame);
+
+    IReadOnlyChunkyImage IReadOnlyImageNode.GetLayerImageByKeyFrameGuid(Guid keyFrameGuid) =>
+        GetLayerImageByKeyFrameGuid(keyFrameGuid);
+
+    void IReadOnlyImageNode.SetLayerImageAtFrame(int frame, IReadOnlyChunkyImage newLayerImage) =>
+        SetLayerImageAtFrame(frame, (ChunkyImage)newLayerImage);
+
+    void IReadOnlyImageNode.ForEveryFrame(Action<IReadOnlyChunkyImage> action) => ForEveryFrame(action);
+
+    bool ITransparencyLockable.LockTransparency
+    {
+        get => LockTransparency.Value;
+        set => LockTransparency.Value = value;
+    }
+
+    public void SetKeyFrameLength(Guid keyFrameGuid, int startFrame, int duration)
+    {
+        ImageFrame frame = frames.FirstOrDefault(x => x.KeyFrameGuid == keyFrameGuid);
+        if (frame is not null)
+        {
+            frame.StartFrame = startFrame;
+            frame.Duration = duration;
+        }
+    }
+
+    public void ForEveryFrame(Action<ChunkyImage> action)
+    {
+        foreach (var frame in frames)
+        {
+            action(frame.Image);
+        }
+    }
+
+    public ChunkyImage GetLayerImageAtFrame(int frame)
+    {
+        return frames.FirstOrDefault(x => x.IsInFrame(frame))?.Image ?? frames[0].Image;
+    }
+
+    public ChunkyImage GetLayerImageByKeyFrameGuid(Guid keyFrameGuid)
+    {
+        return frames.FirstOrDefault(x => x.KeyFrameGuid == keyFrameGuid)?.Image ?? frames[0].Image;
+    }
+
+    public void SetLayerImageAtFrame(int frame, ChunkyImage newLayerImage)
+    {
+        ImageFrame existingFrame = frames.FirstOrDefault(x => x.IsInFrame(frame));
+        if (existingFrame is not null)
+        {
+            existingFrame.Image.Dispose();
+            existingFrame.Image = newLayerImage;
+        }
+    }
+    /*
+          /// <summary>
+        /// Creates a clone of the layer, its image and its mask
+        /// </summary>
+        internal override RasterLayer Clone()
+        {
+            List<ImageFrame> clonedFrames = new();
+            foreach (var frame in frameImages)
+            {
+                clonedFrames.Add(new ImageFrame(frame.KeyFrameGuid, frame.StartFrame, frame.Duration,
+                    frame.Image.CloneFromCommitted()));
+            }
+
+            return new RasterLayer(clonedFrames)
+            {
+                GuidValue = GuidValue,
+                IsVisible = IsVisible,
+                Name = Name,
+                Opacity = Opacity,
+                Mask = Mask?.CloneFromCommitted(),
+                ClipToMemberBelow = ClipToMemberBelow,
+                MaskIsVisible = MaskIsVisible,
+                BlendMode = BlendMode,
+                LockTransparency = LockTransparency
+            };
+        }
+        public override void RemoveKeyFrame(Guid keyFrameGuid)
+        {
+            // Remove all in case I'm the lucky winner of guid collision
+            frameImages.RemoveAll(x => x.KeyFrameGuid == keyFrameGuid);
+        }
+
+        public void SetLayerImageAtFrame(int frame, ChunkyImage newLayerImage)
+        {
+            ImageFrame existingFrame = frameImages.FirstOrDefault(x => x.IsInFrame(frame));
+            if (existingFrame is not null)
+            {
+                existingFrame.Image.Dispose();
+                existingFrame.Image = newLayerImage;
+            }
+        }
+
+
+        public void AddFrame(Guid keyFrameGuid, int startFrame, int duration, ChunkyImage frameImg)
+        {
+            ImageFrame newFrame = new(keyFrameGuid, startFrame, duration, frameImg);
+            frames.Add(newFrame);
+        }
+        */
+}
+
+class ImageFrame
+{
+    public int StartFrame { get; set; }
+    public int Duration { get; set; }
+    public ChunkyImage Image { get; set; }
+
+    public Guid KeyFrameGuid { get; set; }
+
+    public ImageFrame(Guid keyFrameGuid, int startFrame, int duration, ChunkyImage image)
+    {
+        StartFrame = startFrame;
+        Duration = duration;
+        Image = image;
+        KeyFrameGuid = keyFrameGuid;
+    }
+
+    public bool IsInFrame(int frame)
+    {
+        return frame >= StartFrame && frame < StartFrame + Duration;
+    }
+}

+ 5 - 28
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/LayerNode.cs

@@ -1,35 +1,12 @@
-using PixiEditor.Numerics;
+using PixiEditor.ChangeableDocument.Changeables.Animations;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
-public class LayerNode : Node
+public abstract class LayerNode : StructureNode, IReadOnlyLayerNode
 {
-    public InputProperty<ChunkyImage?> Background { get; }
-    public OutputProperty<ChunkyImage> Output { get; }
-    public ChunkyImage LayerImage { get; set; }
-    
-    public LayerNode(string name, VecI size) : base(name)
+    public LayerNode(Guid? id = null) : base(id)
     {
-        Background = CreateInput<ChunkyImage>("Background", null);
-        Output = CreateOutput<ChunkyImage>("Image", new ChunkyImage(size));
-        LayerImage = new ChunkyImage(size);
     }
-
-    public override bool Validate()
-    {
-        return true;
-    }
-
-    public override void OnExecute(int frame)
-    {
-        if (Background.Value != null)
-        {
-            Output.Value.EnqueueDrawChunkyImage(VecI.Zero, Background.Value);
-        }
-        
-        Output.Value.EnqueueDrawChunkyImage(VecI.Zero, LayerImage);
-        Output.Value.CommitChanges();
-    }
-
-   
 }

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

@@ -1,4 +1,5 @@
-using PixiEditor.Numerics;
+using PixiEditor.ChangeableDocument.Changeables.Animations;
+using PixiEditor.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
@@ -20,7 +21,7 @@ public class MergeNode : Node
         return Top.Value != null || Bottom.Value != null;
     }
 
-    public override void OnExecute(int frame)
+    public override ChunkyImage? OnExecute(KeyFrameTime frame)
     {
         VecI size = Top.Value?.CommittedSize ?? Bottom.Value.CommittedSize;
         
@@ -37,5 +38,7 @@ public class MergeNode : Node
         }
         
         Output.Value.CommitChanges();
+        
+        return Output.Value;
     }
 }

+ 98 - 13
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Node.cs

@@ -1,22 +1,25 @@
-namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.Changeables.Animations;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 
-public abstract class Node(string name) : IReadOnlyNode
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+
+public abstract class Node(Guid? id = null) : IReadOnlyNode, IDisposable
 {
     private List<InputProperty> inputs = new();
     private List<OutputProperty> outputs = new();
-    
+
     private List<IReadOnlyNode> _connectedNodes = new();
-    
-    public string Name { get; } = name;
-    
+
+    public Guid Id { get; internal set; } = id ?? Guid.NewGuid();
+
     public IReadOnlyCollection<InputProperty> InputProperties => inputs;
     public IReadOnlyCollection<OutputProperty> OutputProperties => outputs;
-    public IReadOnlyCollection<IReadOnlyNode> ConnectedNodes => _connectedNodes;
+    public IReadOnlyCollection<IReadOnlyNode> ConnectedOutputNodes => _connectedNodes;
 
     IReadOnlyCollection<IInputProperty> IReadOnlyNode.InputProperties => inputs;
     IReadOnlyCollection<IOutputProperty> IReadOnlyNode.OutputProperties => outputs;
 
-    public void Execute(int frame)
+    public ChunkyImage? Execute(KeyFrameTime frameTime)
     {
         foreach (var output in outputs)
         {
@@ -25,20 +28,83 @@ public abstract class Node(string name) : IReadOnlyNode
                 connection.Value = output.Value;
             }
         }
-        
-        OnExecute(frame);
+
+        return OnExecute(frameTime);
     }
 
-    public abstract void OnExecute(int frame);
+    public abstract ChunkyImage? OnExecute(KeyFrameTime frameTime);
     public abstract bool Validate();
-    
+    public void RemoveKeyFrame(Guid keyFrameGuid);
+    public void SetKeyFrameLength(Guid keyFrameGuid, int startFrame, int duration);
+    public void AddFrame<T>(Guid keyFrameGuid, int startFrame, int duration, T value);
+
+    public void TraverseBackwards(Func<IReadOnlyNode, bool> action)
+    {
+        var visited = new HashSet<IReadOnlyNode>();
+        var queueNodes = new Queue<IReadOnlyNode>();
+        queueNodes.Enqueue(this);
+
+        while (queueNodes.Count > 0)
+        {
+            var node = queueNodes.Dequeue();
+
+            if (!visited.Add(node))
+            {
+                continue;
+            }
+
+            if (!action(node))
+            {
+                return;
+            }
+
+            foreach (var inputProperty in node.InputProperties)
+            {
+                if (inputProperty.Connection != null)
+                {
+                    queueNodes.Enqueue(inputProperty.Node);
+                }
+            }
+        }
+    }
+
+    public void TraverseForwards(Func<IReadOnlyNode, bool> action)
+    {
+        var visited = new HashSet<IReadOnlyNode>();
+        var queueNodes = new Queue<IReadOnlyNode>();
+        queueNodes.Enqueue(this);
+
+        while (queueNodes.Count > 0)
+        {
+            var node = queueNodes.Dequeue();
+
+            if (!visited.Add(node))
+            {
+                continue;
+            }
+
+            if (!action(node))
+            {
+                return;
+            }
+
+            foreach (var outputProperty in node.OutputProperties)
+            {
+                foreach (var outputNode in ConnectedOutputNodes)
+                {
+                    queueNodes.Enqueue(outputNode);
+                }
+            }
+        }
+    }
+
     protected InputProperty<T> CreateInput<T>(string name, T defaultValue)
     {
         var property = new InputProperty<T>(this, name, defaultValue);
         inputs.Add(property);
         return property;
     }
-    
+
     protected OutputProperty<T> CreateOutput<T>(string name, T defaultValue)
     {
         var property = new OutputProperty<T>(this, name, defaultValue);
@@ -47,4 +113,23 @@ public abstract class Node(string name) : IReadOnlyNode
         property.Disconnected += (input, _) => _connectedNodes.Remove(input.Node);
         return property;
     }
+
+    public virtual void Dispose()
+    {
+        foreach (var input in inputs)
+        {
+            if (input.Value is IDisposable disposable)
+            {
+                disposable.Dispose();
+            }
+        }
+
+        foreach (var output in outputs)
+        {
+            if (output.Value is IDisposable disposable)
+            {
+                disposable.Dispose();
+            }
+        }
+    }
 }

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

@@ -1,4 +1,6 @@
-namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.Changeables.Animations;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
 public class OutputNode : Node
 {
@@ -13,8 +15,8 @@ public class OutputNode : Node
         return Input.Value != null;
     }
     
-    public override void OnExecute(int frame)
+    public override ChunkyImage? OnExecute(KeyFrameTime frame)
     {
-        
+        return Input.Value;
     }
 }

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

@@ -0,0 +1,39 @@
+using PixiEditor.ChangeableDocument.Changeables.Animations;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.ChangeableDocument.Enums;
+using PixiEditor.Numerics;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+
+public abstract class StructureNode : Node, IReadOnlyStructureNode
+{
+    public InputProperty<ChunkyImage?> Background { get; }
+    public InputProperty<float> Opacity { get; } 
+    public InputProperty<bool> IsVisible { get; }
+    public InputProperty<bool> ClipToMemberBelow { get; }
+    public InputProperty<BlendMode> BlendMode { get; } 
+    public InputProperty<ChunkyImage?> Mask { get; }
+    public InputProperty<bool> MaskIsVisible { get; }
+    
+    public OutputProperty<ChunkyImage?> Output { get; }
+    
+    public string LayerName { get; set; } = string.Empty;
+
+    protected StructureNode(Guid? id = null) : base(id)
+    {
+        Background = CreateInput<ChunkyImage?>("Background", null);
+        Opacity = CreateInput<float>("Opacity", 1);
+        IsVisible = CreateInput<bool>("IsVisible", true);
+        ClipToMemberBelow = CreateInput<bool>("ClipToMemberBelow", false);
+        BlendMode = CreateInput<BlendMode>("BlendMode", Enums.BlendMode.Normal);
+        Mask = CreateInput<ChunkyImage?>("Mask", null);
+        MaskIsVisible = CreateInput<bool>("MaskIsVisible", true);
+        
+        Output = CreateOutput<ChunkyImage?>("Output", null);
+    }
+    
+    public abstract override ChunkyImage? OnExecute(KeyFrameTime frameTime);
+    public abstract override bool Validate();
+
+    public abstract RectI? GetTightBounds(KeyFrameTime frameTime);
+}

+ 2 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/OutputProperty.cs

@@ -1,4 +1,5 @@
-using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph;
 

+ 13 - 11
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyDocument.cs

@@ -1,4 +1,6 @@
 using System.Diagnostics.CodeAnalysis;
+using PixiEditor.ChangeableDocument.Changeables.Graph;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Numerics;
 
@@ -9,7 +11,7 @@ public interface IReadOnlyDocument
     /// <summary>
     /// The root folder of the document
     /// </summary>
-    IReadOnlyFolder StructureRoot { get; }
+    IReadOnlyNodeGraph NodeGraph { get; }
 
     /// <summary>
     /// The selection of the document
@@ -46,7 +48,7 @@ public interface IReadOnlyDocument
     /// <summary>
     /// Performs the specified action on each readonly member of the document
     /// </summary>
-    void ForEveryReadonlyMember(Action<IReadOnlyStructureMember> action);
+    void ForEveryReadonlyMember(Action<IReadOnlyStructureNode> action);
     
     public Surface? GetLayerRasterizedImage(Guid layerGuid, int frame);
     public RectI? GetChunkAlignedLayerBounds(Guid layerGuid, int frame);
@@ -54,8 +56,8 @@ public interface IReadOnlyDocument
     /// <summary>
     /// Finds the member with the <paramref name="guid"/> or returns null if not found
     /// </summary>
-    /// <param name="guid">The <see cref="IReadOnlyStructureMember.GuidValue"/> of the member</param>
-    IReadOnlyStructureMember? FindMember(Guid guid);
+    /// <param name="guid">The <see cref="IReadOnlyStructureNode.GuidValue"/> of the member</param>
+    IReadOnlyStructureNode? FindMember(Guid guid);
 
     /// <summary>
     /// Tries finding the member with the <paramref name="guid"/> of type <typeparamref name="T"/> and returns true if it was found
@@ -63,7 +65,7 @@ public interface IReadOnlyDocument
     /// <param name="guid">The <see cref="StructureMember.GuidValue"/> of the <paramref name="member"/></param>
     /// <param name="member">The member</param>
     /// <returns>True if the member could be found, otherwise false</returns>
-    bool TryFindMember<T>(Guid guid, [NotNullWhen(true)] out T? member) where T : IReadOnlyStructureMember;
+    bool TryFindMember<T>(Guid guid, [NotNullWhen(true)] out T? member) where T : IReadOnlyStructureNode;
 
     /// <summary>
     /// Tries finding the member with the <paramref name="guid"/> and returns true if it was found
@@ -71,27 +73,27 @@ public interface IReadOnlyDocument
     /// <param name="guid">The <see cref="StructureMember.GuidValue"/> of the <paramref name="member"/></param>
     /// <param name="member">The member</param>
     /// <returns>True if the member could be found, otherwise false</returns>
-    bool TryFindMember(Guid guid, [NotNullWhen(true)] out IReadOnlyStructureMember? member);
+    bool TryFindMember(Guid guid, [NotNullWhen(true)] out IReadOnlyStructureNode? member);
 
     /// <summary>
     /// Finds the member with the <paramref name="guid"/> or throws a ArgumentException if not found
     /// </summary>
     /// <param name="guid">The <see cref="StructureMember.GuidValue"/> of the member</param>
     /// <exception cref="ArgumentException">Thrown if the member could not be found</exception>
-    IReadOnlyStructureMember FindMemberOrThrow(Guid guid);
+    IReadOnlyStructureNode FindMemberOrThrow(Guid guid);
 
     /// <summary>
     /// Finds a member with the <paramref name="childGuid"/>  and its parent, throws a ArgumentException if they can't be found
     /// </summary>
-    /// <param name="childGuid">The <see cref="IReadOnlyStructureMember.GuidValue"/> of the member</param>
+    /// <param name="childGuid">The <see cref="IReadOnlyStructureNode.GuidValue"/> of the member</param>
     /// <returns>A value tuple consisting of child (<see cref="ValueTuple{T, T}.Item1"/>) and parent (<see cref="ValueTuple{T, T}.Item2"/>)</returns>
     /// <exception cref="ArgumentException">Thrown if the member and parent could not be found</exception>
-    (IReadOnlyStructureMember, IReadOnlyFolder) FindChildAndParentOrThrow(Guid childGuid);
+    (IReadOnlyStructureNode, IReadOnlyFolderNode) FindChildAndParentOrThrow(Guid childGuid);
 
     /// <summary>
     /// Finds the path to the member with <paramref name="guid"/>, the first element will be the member
     /// </summary>
-    /// <param name="guid">The <see cref="IReadOnlyStructureMember.GuidValue"/> of the member</param>
-    IReadOnlyList<IReadOnlyStructureMember> FindMemberPath(Guid guid);
+    /// <param name="guid">The <see cref="IReadOnlyStructureNode.GuidValue"/> of the member</param>
+    IReadOnlyList<IReadOnlyStructureNode> FindMemberPath(Guid guid);
     IReadOnlyReferenceLayer? ReferenceLayer { get; }
 }

+ 0 - 9
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyFolder.cs

@@ -1,9 +0,0 @@
-namespace PixiEditor.ChangeableDocument.Changeables.Interfaces;
-
-public interface IReadOnlyFolder : IReadOnlyStructureMember
-{
-    /// <summary>
-    /// The children of the folder
-    /// </summary>
-    IReadOnlyList<IReadOnlyStructureMember> Children { get; }
-}

+ 4 - 2
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyKeyFrame.cs

@@ -1,4 +1,6 @@
-namespace PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Interfaces;
 
 public interface IReadOnlyKeyFrame
 {
@@ -7,5 +9,5 @@ public interface IReadOnlyKeyFrame
     public Guid LayerGuid { get; }
     public Guid Id { get; }
     public bool IsVisible { get; }
-    public IReadOnlyLayer TargetLayer { get; }
+    public IReadOnlyNode TargetNode { get; }
 }

+ 0 - 11
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyLayer.cs

@@ -1,11 +0,0 @@
-using PixiEditor.ChangeableDocument.Changeables.Animations;
-using PixiEditor.DrawingApi.Core.Numerics;
-
-namespace PixiEditor.ChangeableDocument.Changeables.Interfaces;
-
-public interface IReadOnlyLayer : IReadOnlyStructureMember
-{
-    public ChunkyImage Rasterize(KeyFrameTime frameTime);
-    public void RemoveKeyFrame(Guid keyFrameGuid);
-    public void SetKeyFrameLength(Guid keyFrameGuid, int startFrame, int duration);
-}

+ 0 - 50
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyStructureMember.cs

@@ -1,50 +0,0 @@
-using PixiEditor.ChangeableDocument.Enums;
-using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.Numerics;
-
-namespace PixiEditor.ChangeableDocument.Changeables.Interfaces;
-
-public interface IReadOnlyStructureMember
-{
-    /// <summary>
-    /// Is the member visible. Defaults to true
-    /// </summary>
-    bool IsVisible { get; }
-
-    /// <summary>
-    /// The opacity of the member (Ranging from 0f to 1f)
-    /// </summary>
-    float Opacity { get; }
-    bool ClipToMemberBelow { get; }
-
-    /// <summary>
-    /// The name of the member. Defaults to "Unnamed"
-    /// </summary>
-    string Name { get; }
-
-    /// <summary>
-    /// The guid of the member
-    /// </summary>
-    Guid GuidValue { get; }
-
-    /// <summary>
-    /// The blend mode of the member
-    /// </summary>
-    BlendMode BlendMode { get; }
-
-    /// <summary>
-    /// Is the mask of the member visible. Defaults to true
-    /// </summary>
-    bool MaskIsVisible { get; }
-
-    /// <summary>
-    /// The mask of the member
-    /// </summary>
-    IReadOnlyChunkyImage? Mask { get; }
-
-    /// <summary>
-    ///     The tight bounds of the member. The bounds are the smallest rectangle that contains all the pixels of the member.
-    /// </summary>
-    /// <returns>The tight bounds of the member</returns>
-    public RectI? GetTightBounds(int frame);
-}

+ 0 - 12
src/PixiEditor.ChangeableDocument/Changeables/Layer.cs

@@ -1,12 +0,0 @@
-using PixiEditor.ChangeableDocument.Changeables.Animations;
-using PixiEditor.ChangeableDocument.Changeables.Interfaces;
-using PixiEditor.DrawingApi.Core.Numerics;
-
-namespace PixiEditor.ChangeableDocument.Changeables;
-
-internal abstract class Layer : StructureMember, IReadOnlyLayer
-{
-    public abstract ChunkyImage Rasterize(KeyFrameTime frameTime);
-    public abstract void RemoveKeyFrame(Guid keyFrameGuid);
-    public abstract void SetKeyFrameLength(Guid keyFrameGuid, int startFrame, int duration);
-}

+ 0 - 172
src/PixiEditor.ChangeableDocument/Changeables/RasterLayer.cs

@@ -1,172 +0,0 @@
-using PixiEditor.ChangeableDocument.Changeables.Animations;
-using PixiEditor.ChangeableDocument.Changeables.Interfaces;
-using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.Numerics;
-
-namespace PixiEditor.ChangeableDocument.Changeables;
-
-internal class RasterLayer : Layer, IReadOnlyRasterLayer
-{
-    // Don't forget to update CreateLayer_ChangeInfo, DocumentUpdater.ProcessCreateStructureMember and Layer.Clone when adding new properties
-    public bool LockTransparency { get; set; } = false;
-
-
-    private List<ImageFrame> frameImages = new();
-
-    public RasterLayer(VecI size)
-    {
-        frameImages.Add(new ImageFrame(Guid.NewGuid(), 0, 0, new(size)));
-    }
-
-    public RasterLayer(List<ImageFrame> frames)
-    {
-        frameImages = frames;
-    }
-
-    /// <summary>
-    /// Disposes the layer's image and mask
-    /// </summary>
-    public override void Dispose()
-    {
-        Mask?.Dispose();
-        foreach (var frame in frameImages)
-        {
-            frame.Image.Dispose();
-        }
-    }
-
-    IReadOnlyChunkyImage IReadOnlyRasterLayer.GetLayerImageAtFrame(int frame) => GetLayerImageAtFrame(frame);
-
-    IReadOnlyChunkyImage IReadOnlyRasterLayer.GetLayerImageByKeyFrameGuid(Guid keyFrameGuid) =>
-        GetLayerImageByKeyFrameGuid(keyFrameGuid);
-
-    void IReadOnlyRasterLayer.SetLayerImageAtFrame(int frame, IReadOnlyChunkyImage newLayerImage) =>
-        SetLayerImageAtFrame(frame, (ChunkyImage)newLayerImage);
-
-    void IReadOnlyRasterLayer.ForEveryFrame(Action<IReadOnlyChunkyImage> action) => ForEveryFrame(action);
-
-    public override void SetKeyFrameLength(Guid keyFrameGuid, int startFrame, int duration)
-    {
-        ImageFrame frame = frameImages.FirstOrDefault(x => x.KeyFrameGuid == keyFrameGuid);
-        if (frame is not null)
-        {
-            frame.StartFrame = startFrame;
-            frame.Duration = duration;
-        }
-    }
-
-    public void ForEveryFrame(Action<ChunkyImage> action)
-    {
-        foreach (var frame in frameImages)
-        {
-            action(frame.Image);
-        }
-    }
-
-    public ChunkyImage GetLayerImageAtFrame(int frame)
-    {
-        return Rasterize(frame);
-    }
-
-    public ChunkyImage GetLayerImageByKeyFrameGuid(Guid keyFrameGuid)
-    {
-        return frameImages.FirstOrDefault(x => x.KeyFrameGuid == keyFrameGuid)?.Image ?? frameImages[0].Image;
-    }
-
-    public override ChunkyImage Rasterize(KeyFrameTime frameTime)
-    {
-        if (frameTime.Frame == 0 || frameImages.Count == 1)
-        {
-            return frameImages[0].Image;
-        }
-
-        ImageFrame frame = frameImages.FirstOrDefault(x => x.IsInFrame(frameTime.Frame));
-
-        if (frame == null)
-        {
-            ImageFrame lastFrame = frameImages.MaxBy(x => x.StartFrame + x.Duration);
-            if (frameTime.Frame == lastFrame.StartFrame + lastFrame.Duration)
-            {
-                return lastFrame.Image;
-            }
-        }
-
-        return frame?.Image ?? frameImages[0].Image;
-    }
-
-    public override void RemoveKeyFrame(Guid keyFrameGuid)
-    {
-        // Remove all in case I'm the lucky winner of guid collision
-        frameImages.RemoveAll(x => x.KeyFrameGuid == keyFrameGuid);
-    }
-
-    public override RectI? GetTightBounds(int frame)
-    {
-        return Rasterize(frame).FindTightCommittedBounds();
-    }
-
-    /// <summary>
-    /// Creates a clone of the layer, its image and its mask
-    /// </summary>
-    internal override RasterLayer Clone()
-    {
-        List<ImageFrame> clonedFrames = new();
-        foreach (var frame in frameImages)
-        {
-            clonedFrames.Add(new ImageFrame(frame.KeyFrameGuid, frame.StartFrame, frame.Duration,
-                frame.Image.CloneFromCommitted()));
-        }
-
-        return new RasterLayer(clonedFrames)
-        {
-            GuidValue = GuidValue,
-            IsVisible = IsVisible,
-            Name = Name,
-            Opacity = Opacity,
-            Mask = Mask?.CloneFromCommitted(),
-            ClipToMemberBelow = ClipToMemberBelow,
-            MaskIsVisible = MaskIsVisible,
-            BlendMode = BlendMode,
-            LockTransparency = LockTransparency
-        };
-    }
-
-    public void SetLayerImageAtFrame(int frame, ChunkyImage newLayerImage)
-    {
-        ImageFrame existingFrame = frameImages.FirstOrDefault(x => x.IsInFrame(frame));
-        if (existingFrame is not null)
-        {
-            existingFrame.Image.Dispose();
-            existingFrame.Image = newLayerImage;
-        }
-    }
-
-
-    public void AddFrame(Guid keyFrameGuid, int startFrame, int duration, ChunkyImage frameImg)
-    {
-        ImageFrame newFrame = new(keyFrameGuid, startFrame, duration, frameImg);
-        frameImages.Add(newFrame);
-    }
-}
-
-class ImageFrame
-{
-    public int StartFrame { get; set; }
-    public int Duration { get; set; }
-    public ChunkyImage Image { get; set; }
-
-    public Guid KeyFrameGuid { get; set; }
-
-    public ImageFrame(Guid keyFrameGuid, int startFrame, int duration, ChunkyImage image)
-    {
-        StartFrame = startFrame;
-        Duration = duration;
-        Image = image;
-        KeyFrameGuid = keyFrameGuid;
-    }
-
-    public bool IsInFrame(int frame)
-    {
-        return frame >= StartFrame && frame < StartFrame + Duration;
-    }
-}

+ 0 - 25
src/PixiEditor.ChangeableDocument/Changeables/StructureMember.cs

@@ -1,25 +0,0 @@
-using PixiEditor.ChangeableDocument.Changeables.Interfaces;
-using PixiEditor.ChangeableDocument.Enums;
-using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.Numerics;
-
-namespace PixiEditor.ChangeableDocument.Changeables;
-
-internal abstract class StructureMember : IChangeable, IReadOnlyStructureMember, IDisposable
-{
-    // Don't forget to update CreateStructureMember_ChangeInfo, CreateLayer_ChangeInfo, CreateFolder_ChangeInfo,
-    // DocumentUpdater.ProcessCreateStructureMember, Layer.Clone and Folder.Clone when adding new properties
-    public float Opacity { get; set; } = 1f;
-    public bool IsVisible { get; set; } = true;
-    public bool ClipToMemberBelow { get; set; } = false;
-    public string Name { get; set; } = "Unnamed";
-    public BlendMode BlendMode { get; set; } = BlendMode.Normal;
-    public Guid GuidValue { get; set; }
-    public ChunkyImage? Mask { get; set; } = null;
-    public abstract RectI? GetTightBounds(int frame);
-
-    public bool MaskIsVisible { get; set; } = true;
-    IReadOnlyChunkyImage? IReadOnlyStructureMember.Mask => Mask;
-    internal abstract StructureMember Clone();
-    public abstract void Dispose();
-}

+ 5 - 4
src/PixiEditor.ChangeableDocument/Changes/Animation/CreateRasterKeyFrame_Change.cs

@@ -1,4 +1,5 @@
 using PixiEditor.ChangeableDocument.Changeables.Animations;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.ChangeInfos.Animation;
 
 namespace PixiEditor.ChangeableDocument.Changes.Animation;
@@ -9,7 +10,7 @@ internal class CreateRasterKeyFrame_Change : Change
     private readonly int _frame;
     private readonly Guid? cloneFrom;
     private int? cloneFromFrame;
-    private RasterLayer? _layer;
+    private ImageLayerNode? _layer;
     private Guid createdKeyFrameId;
 
     [GenerateMakeChangeAction]
@@ -33,13 +34,13 @@ internal class CreateRasterKeyFrame_Change : Change
         out bool ignoreInUndo)
     {
         var cloneFromImage = cloneFrom.HasValue
-            ? target.FindMemberOrThrow<RasterLayer>(cloneFrom.Value).GetLayerImageAtFrame(cloneFromFrame ?? 0)
+            ? target.FindMemberOrThrow<ImageLayerNode>(cloneFrom.Value).GetLayerImageAtFrame(cloneFromFrame ?? 0)
             : null;
         
-        RasterLayer targetLayer = target.FindMemberOrThrow<RasterLayer>(_targetLayerGuid);
+        ImageLayerNode targetNode = target.FindMemberOrThrow<ImageLayerNode>(_targetLayerGuid);
         
         var keyFrame =
-            new RasterKeyFrame(createdKeyFrameId, targetLayer, _frame, target, cloneFromImage);
+            new RasterKeyFrame(createdKeyFrameId, targetNode, _frame, target, cloneFromImage);
         target.AnimationData.AddKeyFrame(keyFrame);
         ignoreInUndo = false;
         return new CreateRasterKeyFrame_ChangeInfo(_targetLayerGuid, _frame, createdKeyFrameId, cloneFrom.HasValue);

+ 10 - 9
src/PixiEditor.ChangeableDocument/Changes/Drawing/ApplyLayerMask_Change.cs

@@ -1,4 +1,5 @@
-using PixiEditor.ChangeableDocument.ChangeInfos.Properties;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.ChangeInfos.Properties;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Numerics;
 
@@ -20,24 +21,24 @@ internal class ApplyLayerMask_Change : Change
     public override bool InitializeAndValidate(Document target)
     {
         //TODO: Check if support for different Layer types is needed here.
-        if (!target.TryFindMember<RasterLayer>(layerGuid, out var layer) || layer.Mask is null)
+        if (!target.TryFindMember<ImageLayerNode>(layerGuid, out var layer) || layer.Mask.Value is null)
             return false;
 
         var layerImage = layer.GetLayerImageAtFrame(frame);
         savedLayer = new CommittedChunkStorage(layerImage, layerImage.FindCommittedChunks());
-        savedMask = new CommittedChunkStorage(layer.Mask, layer.Mask.FindCommittedChunks());
+        savedMask = new CommittedChunkStorage(layer.Mask.Value, layer.Mask.Value.FindCommittedChunks());
         return true;
     }
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
     {
-        var layer = target.FindMemberOrThrow<RasterLayer>(layerGuid);
+        var layer = target.FindMemberOrThrow<ImageLayerNode>(layerGuid);
         if (layer.Mask is null)
             throw new InvalidOperationException("Cannot apply layer mask, no mask");
 
         var layerImage = layer.GetLayerImageAtFrame(frame);
         ChunkyImage newLayerImage = new ChunkyImage(target.Size);
-        newLayerImage.AddRasterClip(layer.Mask);
+        newLayerImage.AddRasterClip(layer.Mask.Value);
         newLayerImage.EnqueueDrawChunkyImage(VecI.Zero, layerImage);
         newLayerImage.CommitChanges();
 
@@ -47,8 +48,8 @@ internal class ApplyLayerMask_Change : Change
         layer.SetLayerImageAtFrame(frame, newLayerImage);
         toDispose.Dispose();
 
-        var toDisposeMask = layer.Mask;
-        layer.Mask = null;
+        var toDisposeMask = layer.Mask.Value;
+        layer.Mask.Value = null;
         toDisposeMask.Dispose();
 
         ignoreInUndo = false;
@@ -61,7 +62,7 @@ internal class ApplyLayerMask_Change : Change
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
     {
-        var layer = target.FindMemberOrThrow<RasterLayer>(layerGuid);
+        var layer = target.FindMemberOrThrow<ImageLayerNode>(layerGuid);
         if (layer.Mask is not null)
             throw new InvalidOperationException("Cannot restore layer mask, it already has one");
         if (savedLayer is null || savedMask is null)
@@ -71,7 +72,7 @@ internal class ApplyLayerMask_Change : Change
         savedMask.ApplyChunksToImage(newMask);
         var affectedChunksMask = newMask.FindAffectedArea();
         newMask.CommitChanges();
-        layer.Mask = newMask;
+        layer.Mask.Value = newMask;
 
         var layerImage = layer.GetLayerImageAtFrame(frame);
         

+ 6 - 5
src/PixiEditor.ChangeableDocument/Changes/Drawing/ChangeBrightness_UpdateableChange.cs

@@ -1,4 +1,5 @@
 using ChunkyImageLib.Operations;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Surface;
@@ -46,8 +47,8 @@ internal class ChangeBrightness_UpdateableChange : UpdateableChange
     {
         if (!DrawingChangeHelper.IsValidForDrawing(target, layerGuid, false))
             return false;
-        RasterLayer layer = target.FindMemberOrThrow<RasterLayer>(layerGuid);
-        var layerImage = layer.GetLayerImageAtFrame(frame);
+        ImageLayerNode node = target.FindMemberOrThrow<ImageLayerNode>(layerGuid);
+        var layerImage = node.GetLayerImageAtFrame(frame);
         DrawingChangeHelper.ApplyClipsSymmetriesEtc(target, layerImage, layerGuid, false);
         return true;
     }
@@ -57,9 +58,9 @@ internal class ChangeBrightness_UpdateableChange : UpdateableChange
         if (ignoreUpdate)
             return new None();
         VecI pos = positions[^1];
-        RasterLayer layer = target.FindMemberOrThrow<RasterLayer>(layerGuid);
+        ImageLayerNode node = target.FindMemberOrThrow<ImageLayerNode>(layerGuid);
 
-        var layerImage = layer.GetLayerImageAtFrame(frame);
+        var layerImage = node.GetLayerImageAtFrame(frame);
         int queueLength = layerImage.QueueLength;
         
         ChangeBrightness(ellipseLines, strokeWidth, pos + new VecI(-strokeWidth / 2), correctionFactor, repeat, layerImage);
@@ -96,7 +97,7 @@ internal class ChangeBrightness_UpdateableChange : UpdateableChange
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
     {
-        var layer = target.FindMemberOrThrow<RasterLayer>(layerGuid);
+        var layer = target.FindMemberOrThrow<ImageLayerNode>(layerGuid);
         ignoreInUndo = false;
 
         if (savedChunks is not null)

+ 4 - 4
src/PixiEditor.ChangeableDocument/Changes/Drawing/CombineStructureMembersOnto_Change.cs

@@ -54,12 +54,12 @@ internal class CombineStructureMembersOnto_Change : Change
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
     {
         //TODO: Add support for different Layer types
-        var toDrawOn = target.FindMemberOrThrow<RasterLayer>(targetLayer);
+        var toDrawOn = target.FindMemberOrThrow<ImageNode>(targetLayer);
 
         var chunksToCombine = new HashSet<VecI>();
         foreach (var guid in layersToCombine)
         {
-            var layer = target.FindMemberOrThrow<RasterLayer>(guid);
+            var layer = target.FindMemberOrThrow<ImageNode>(guid);
             var layerImage = layer.GetLayerImageAtFrame(frame);
             chunksToCombine.UnionWith(layerImage.FindAllChunks());
         }
@@ -68,7 +68,7 @@ internal class CombineStructureMembersOnto_Change : Change
         toDrawOnImage.EnqueueClear();
         foreach (var chunk in chunksToCombine)
         {
-            OneOf<Chunk, EmptyChunk> combined = ChunkRenderer.MergeChosenMembers(chunk, ChunkResolution.Full, target.StructureRoot, frame, layersToCombine);
+            OneOf<Chunk, EmptyChunk> combined = ChunkRenderer.MergeChosenMembers(chunk, ChunkResolution.Full, target.NodeGraph, frame, layersToCombine);
             if (combined.IsT0)
             {
                 toDrawOnImage.EnqueueDrawImage(chunk * ChunkyImage.FullChunkSize, combined.AsT0.Surface);
@@ -85,7 +85,7 @@ internal class CombineStructureMembersOnto_Change : Change
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
     {
-        var toDrawOn = target.FindMemberOrThrow<RasterLayer>(targetLayer);
+        var toDrawOn = target.FindMemberOrThrow<ImageNode>(targetLayer);
         var affectedArea = DrawingChangeHelper.ApplyStoredChunksDisposeAndSetToNull(toDrawOn.GetLayerImageAtFrame(frame), ref originalChunks);
         return new LayerImageArea_ChangeInfo(targetLayer, affectedArea);
     }

+ 10 - 9
src/PixiEditor.ChangeableDocument/Changes/Drawing/DrawingChangeHelper.cs

@@ -1,4 +1,5 @@
-using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.DrawingApi.Core.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changes.Drawing;
@@ -41,15 +42,15 @@ internal static class DrawingChangeHelper
         {
             if (member.Mask is null)
                 throw new InvalidOperationException("Trying to draw on a mask that doesn't exist");
-            return member.Mask;
+            return member.Mask.Value;
         }
 
-        if (member is Folder)
+        if (member is FolderNode)
         {
             throw new InvalidOperationException("Trying to draw on a folder");
         }
 
-        if (member is not RasterLayer layer)
+        if (member is not ImageLayerNode layer)
         {
             throw new InvalidOperationException("Trying to draw on a non-raster layer member");
         }
@@ -66,15 +67,15 @@ internal static class DrawingChangeHelper
         {
             if (member.Mask is null)
                 throw new InvalidOperationException("Trying to draw on a mask that doesn't exist");
-            return member.Mask;
+            return member.Mask.Value;
         }
 
-        if (member is Folder)
+        if (member is FolderNode)
         {
             throw new InvalidOperationException("Trying to draw on a folder");
         }
 
-        if (member is not RasterLayer layer)
+        if (member is not ImageLayerNode layer)
         {
             throw new InvalidOperationException("Trying to draw on a non-raster layer member");
         }
@@ -108,9 +109,9 @@ internal static class DrawingChangeHelper
         return drawOnMask switch
         {
             // If it should draw on the mask, the mask can't be null
-            true when member.Mask is null => false,
+            true when member.Mask.Value is null => false,
             // If it should not draw on the mask, the member can't be a folder
-            false when member is Folder => false,
+            false when member is FolderNode => false,
             _ => true
         };
     }

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changes/Drawing/FloodFill/FloodFillHelper.cs

@@ -27,7 +27,7 @@ public static class FloodFillHelper
             var member = document.FindMemberOrThrow(guid);
             if (member is IReadOnlyFolder)
                 return new FloodFillChunkCache(membersToFloodFill, document.StructureRoot, frame);
-            if (member is not IReadOnlyRasterLayer rasterLayer)
+            if (member is not IReadOnlyImageNode rasterLayer)
                 throw new InvalidOperationException("Member is not a raster layer");
             return new FloodFillChunkCache(rasterLayer.GetLayerImageAtFrame(frame));
         }

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changes/Drawing/FloodFill/FloodFill_Change.cs

@@ -41,7 +41,7 @@ internal class FloodFill_Change : Change
         VectorPath? selection = target.Selection.SelectionPath.IsEmpty ? null : target.Selection.SelectionPath;
         HashSet<Guid> membersToReference = new();
         if (referenceAll)
-            target.ForEveryReadonlyMember(member => membersToReference.Add(member.GuidValue));
+            target.ForEveryReadonlyMember(member => membersToReference.Add(member.Id));
         else
             membersToReference.Add(memberGuid);
         var floodFilledChunks = FloodFillHelper.FloodFill(membersToReference, target, selection, pos, color, frame);

+ 8 - 7
src/PixiEditor.ChangeableDocument/Changes/Drawing/ReplaceColor_Change.cs

@@ -1,4 +1,5 @@
-using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
 using PixiEditor.DrawingApi.Core.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changes.Drawing;
@@ -31,16 +32,16 @@ internal class ReplaceColor_Change : Change
         List<IChangeInfo> infos = new();
         target.ForEveryMember(member =>
         {
-            if (member is not RasterLayer layer)
+            if (member is not ImageLayerNode layer)
                 return;
             //TODO: Add support for replacing in different Layer types
             var layerImage = layer.GetLayerImageAtFrame(frame);
             layerImage.EnqueueReplaceColor(oldColor, newColor);
             var affArea = layerImage.FindAffectedArea();
             CommittedChunkStorage storage = new(layerImage, affArea.Chunks);
-            savedChunks[layer.GuidValue] = storage;
+            savedChunks[layer.Id] = storage;
             layerImage.CommitChanges();
-            infos.Add(new LayerImageArea_ChangeInfo(layer.GuidValue, affArea));
+            infos.Add(new LayerImageArea_ChangeInfo(layer.Id, affArea));
         });
         ignoreInUndo = !savedChunks.Any();
         return infos;
@@ -53,11 +54,11 @@ internal class ReplaceColor_Change : Change
         List<IChangeInfo> infos = new();
         target.ForEveryMember(member =>
         {
-            if (member is not RasterLayer layer)
+            if (member is not ImageLayerNode layer)
                 return;
-            CommittedChunkStorage? storage = savedChunks[member.GuidValue];
+            CommittedChunkStorage? storage = savedChunks[member.Id];
             var affArea = DrawingChangeHelper.ApplyStoredChunksDisposeAndSetToNull(layer.GetLayerImageAtFrame(frame), ref storage);
-            infos.Add(new LayerImageArea_ChangeInfo(layer.GuidValue, affArea));
+            infos.Add(new LayerImageArea_ChangeInfo(layer.Id, affArea));
         });
         savedChunks = null;
         return infos;

+ 3 - 2
src/PixiEditor.ChangeableDocument/Changes/Drawing/ShiftLayerHelper.cs

@@ -1,4 +1,5 @@
-using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changes.Drawing;
@@ -7,7 +8,7 @@ internal static class ShiftLayerHelper
 {
     public static AffectedArea DrawShiftedLayer(Document target, Guid layerGuid, bool keepOriginal, VecI delta, int frame)
     {
-        var targetImage = target.FindMemberOrThrow<RasterLayer>(layerGuid).GetLayerImageAtFrame(frame);
+        var targetImage = target.FindMemberOrThrow<ImageLayerNode>(layerGuid).GetLayerImageAtFrame(frame);
         var prevArea = targetImage.FindAffectedArea();
         targetImage.CancelChanges();
         if (!keepOriginal)

+ 3 - 2
src/PixiEditor.ChangeableDocument/Changes/Drawing/ShiftLayer_UpdateableChange.cs

@@ -1,4 +1,5 @@
 using System.Runtime.InteropServices;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Numerics;
 
@@ -54,7 +55,7 @@ internal class ShiftLayer_UpdateableChange : UpdateableChange
         {
             var area = ShiftLayerHelper.DrawShiftedLayer(target, layerGuid, keepOriginal, delta, frame);
             // TODO: Add support for different Layer types
-            var image = target.FindMemberOrThrow<RasterLayer>(layerGuid).GetLayerImageAtFrame(frame);
+            var image = target.FindMemberOrThrow<ImageLayerNode>(layerGuid).GetLayerImageAtFrame(frame);
             
             changes.Add(new LayerImageArea_ChangeInfo(layerGuid, area));
             
@@ -84,7 +85,7 @@ internal class ShiftLayer_UpdateableChange : UpdateableChange
         List<IChangeInfo> changes = new List<IChangeInfo>();
         foreach (var layerGuid in layerGuids)
         {
-            var image = target.FindMemberOrThrow<RasterLayer>(layerGuid).GetLayerImageAtFrame(frame);
+            var image = target.FindMemberOrThrow<ImageLayerNode>(layerGuid).GetLayerImageAtFrame(frame);
             CommittedChunkStorage? originalChunks = originalLayerChunks[layerGuid];
             var affected = DrawingChangeHelper.ApplyStoredChunksDisposeAndSetToNull(image, ref originalChunks);
             changes.Add(new LayerImageArea_ChangeInfo(layerGuid, affected));

+ 3 - 3
src/PixiEditor.ChangeableDocument/Changes/Properties/LayerStructure/CreateStructureMemberMask_Change.cs

@@ -22,7 +22,7 @@ internal class CreateStructureMemberMask_Change : Change
         var member = target.FindMemberOrThrow(targetMember);
         if (member.Mask is not null)
             throw new InvalidOperationException("Cannot create a mask; the target member already has one");
-        member.Mask = new ChunkyImage(target.Size);
+        member.Mask.Value = new ChunkyImage(target.Size);
 
         ignoreInUndo = false;
         return new StructureMemberMask_ChangeInfo(targetMember, true);
@@ -33,8 +33,8 @@ internal class CreateStructureMemberMask_Change : Change
         var member = target.FindMemberOrThrow(targetMember);
         if (member.Mask is null)
             throw new InvalidOperationException("Cannot delete the mask; the target member has no mask");
-        member.Mask.Dispose();
-        member.Mask = null;
+        member.Mask.Value.Dispose();
+        member.Mask.Value = null;
         return new StructureMemberMask_ChangeInfo(targetMember, false);
     }
 }

+ 4 - 4
src/PixiEditor.ChangeableDocument/Changes/Properties/LayerStructure/DeleteStructureMemberMask_Change.cs

@@ -18,7 +18,7 @@ internal class DeleteStructureMemberMask_Change : Change
         if (!target.TryFindMember(memberGuid, out var member) || member.Mask is null)
             return false;
         
-        storedMask = member.Mask.CloneFromCommitted();
+        storedMask = member.Mask.Value.CloneFromCommitted();
         return true;
     }
 
@@ -27,8 +27,8 @@ internal class DeleteStructureMemberMask_Change : Change
         var member = target.FindMemberOrThrow(memberGuid);
         if (member.Mask is null)
             throw new InvalidOperationException("Cannot delete the mask; Target member has no mask");
-        member.Mask.Dispose();
-        member.Mask = null;
+        member.Mask.Value.Dispose();
+        member.Mask.Value = null;
 
         ignoreInUndo = false;
         return new StructureMemberMask_ChangeInfo(memberGuid, false);
@@ -39,7 +39,7 @@ internal class DeleteStructureMemberMask_Change : Change
         var member = target.FindMemberOrThrow(memberGuid);
         if (member.Mask is not null)
             throw new InvalidOperationException("Cannot revert mask deletion; The target member already has a mask");
-        member.Mask = storedMask!.CloneFromCommitted();
+        member.Mask.Value = storedMask!.CloneFromCommitted();
 
         return new StructureMemberMask_ChangeInfo(memberGuid, true);
     }

+ 5 - 4
src/PixiEditor.ChangeableDocument/Changes/Properties/LayerStructure/LayerLockTransparency_Change.cs

@@ -1,4 +1,5 @@
-using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.ChangeInfos.Properties;
 
 namespace PixiEditor.ChangeableDocument.Changes.Properties.LayerStructure;
@@ -17,7 +18,7 @@ internal class LayerLockTransparency_Change : Change
 
     public override bool InitializeAndValidate(Document target)
     {
-        if (!target.TryFindMember<Layer>(layerGuid, out var layer))
+        if (!target.TryFindNode(layerGuid, out Node layer))
             return false;
 
         if (layer is not ITransparencyLockable lockable)
@@ -31,14 +32,14 @@ internal class LayerLockTransparency_Change : Change
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
     {
-        ((ITransparencyLockable)target.FindMemberOrThrow<Layer>(layerGuid)).LockTransparency = newValue;
+        ((ITransparencyLockable)target.FindNodeOrThrow<Node>(layerGuid)).LockTransparency = newValue;
         ignoreInUndo = false;
         return new LayerLockTransparency_ChangeInfo(layerGuid, newValue);
     }
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
     {
-        ((ITransparencyLockable)target.FindMemberOrThrow<Layer>(layerGuid)).LockTransparency = originalValue;
+        ((ITransparencyLockable)target.FindNodeOrThrow<Node>(layerGuid)).LockTransparency = originalValue;
         return new LayerLockTransparency_ChangeInfo(layerGuid, originalValue);
     }
 

+ 4 - 4
src/PixiEditor.ChangeableDocument/Changes/Properties/LayerStructure/StructureMemberBlendMode_Change.cs

@@ -17,16 +17,16 @@ internal class StructureMemberBlendMode_Change : Change
 
     public override bool InitializeAndValidate(Document target)
     {
-        if (!target.TryFindMember(targetGuid, out var member) || member.BlendMode == newBlendMode)
+        if (!target.TryFindMember(targetGuid, out var member) || member.BlendMode.Value == newBlendMode)
             return false;
-        originalBlendMode = member.BlendMode;
+        originalBlendMode = member.BlendMode.Value;
         return true;
     }
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
     {
         var member = target.FindMemberOrThrow(targetGuid);
-        member.BlendMode = newBlendMode;
+        member.BlendMode.Value = newBlendMode;
         ignoreInUndo = false;
         return new StructureMemberBlendMode_ChangeInfo(targetGuid, newBlendMode);
     }
@@ -34,7 +34,7 @@ internal class StructureMemberBlendMode_Change : Change
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
     {
         var member = target.FindMemberOrThrow(targetGuid);
-        member.BlendMode = originalBlendMode;
+        member.BlendMode.Value = originalBlendMode;
         return new StructureMemberBlendMode_ChangeInfo(targetGuid, originalBlendMode);
     }
 

+ 4 - 4
src/PixiEditor.ChangeableDocument/Changes/Properties/LayerStructure/StructureMemberClipToMemberBelow_Change.cs

@@ -16,16 +16,16 @@ internal class StructureMemberClipToMemberBelow_Change : Change
 
     public override bool InitializeAndValidate(Document target)
     {
-        if (!target.TryFindMember(memberGuid, out var member) || member.ClipToMemberBelow == newValue)
+        if (!target.TryFindMember(memberGuid, out var member) || member.ClipToMemberBelow.Value == newValue)
             return false;
-        originalValue = member.ClipToMemberBelow;
+        originalValue = member.ClipToMemberBelow.Value;
         return true;
     }
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
     {
         var member = target.FindMemberOrThrow(memberGuid);
-        member.ClipToMemberBelow = newValue;
+        member.ClipToMemberBelow.Value = newValue;
         ignoreInUndo = false;
         return new StructureMemberClipToMemberBelow_ChangeInfo(memberGuid, newValue);
     }
@@ -33,7 +33,7 @@ internal class StructureMemberClipToMemberBelow_Change : Change
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
     {
         var member = target.FindMemberOrThrow(memberGuid);
-        member.ClipToMemberBelow = originalValue;
+        member.ClipToMemberBelow.Value = originalValue;
         return new StructureMemberClipToMemberBelow_ChangeInfo(memberGuid, originalValue);
     }
 

+ 4 - 4
src/PixiEditor.ChangeableDocument/Changes/Properties/LayerStructure/StructureMemberIsVisible_Change.cs

@@ -16,9 +16,9 @@ internal class StructureMemberIsVisible_Change : Change
 
     public override bool InitializeAndValidate(Document target)
     {
-        if (!target.TryFindMember(targetMember, out var member) || member.IsVisible == newIsVisible)
+        if (!target.TryFindMember(targetMember, out var member) || member.IsVisible.Value == newIsVisible)
             return false;
-        originalIsVisible = member.IsVisible;
+        originalIsVisible = member.IsVisible.Value;
         return true;
     }
 
@@ -26,13 +26,13 @@ internal class StructureMemberIsVisible_Change : Change
     {
         // don't record layer/folder visibility changes - it's just more convenient this way
         ignoreInUndo = true;
-        target.FindMemberOrThrow(targetMember).IsVisible = newIsVisible;
+        target.FindMemberOrThrow(targetMember).IsVisible.Value = newIsVisible;
         return new StructureMemberIsVisible_ChangeInfo(targetMember, newIsVisible);
     }
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
     {
-        target.FindMemberOrThrow(targetMember).IsVisible = originalIsVisible!.Value;
+        target.FindMemberOrThrow(targetMember).IsVisible.Value = originalIsVisible!.Value;
         return new StructureMemberIsVisible_ChangeInfo(targetMember, (bool)originalIsVisible);
     }
 }

+ 4 - 4
src/PixiEditor.ChangeableDocument/Changes/Properties/LayerStructure/StructureMemberMaskIsVisible_Change.cs

@@ -16,17 +16,17 @@ internal class StructureMemberMaskIsVisible_Change : Change
 
     public override bool InitializeAndValidate(Document target)
     {
-        if (!target.TryFindMember(memberGuid, out var member) || member.MaskIsVisible == newMaskIsVisible)
+        if (!target.TryFindMember(memberGuid, out var member) || member.MaskIsVisible.Value == newMaskIsVisible)
             return false;
         
-        originalMaskIsVisible = member.MaskIsVisible;
+        originalMaskIsVisible = member.MaskIsVisible.Value;
         return true;
     }
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
     {
         var member = target.FindMemberOrThrow(memberGuid);
-        member.MaskIsVisible = newMaskIsVisible;
+        member.MaskIsVisible.Value = newMaskIsVisible;
         ignoreInUndo = false;
         return new StructureMemberMaskIsVisible_ChangeInfo(memberGuid, newMaskIsVisible);
     }
@@ -34,7 +34,7 @@ internal class StructureMemberMaskIsVisible_Change : Change
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
     {
         var member = target.FindMemberOrThrow(memberGuid);
-        member.MaskIsVisible = originalMaskIsVisible;
+        member.MaskIsVisible.Value = originalMaskIsVisible;
         return new StructureMemberMaskIsVisible_ChangeInfo(memberGuid, originalMaskIsVisible);
     }
 

+ 4 - 4
src/PixiEditor.ChangeableDocument/Changes/Properties/LayerStructure/StructureMemberName_Change.cs

@@ -17,15 +17,15 @@ internal class StructureMemberName_Change : Change
 
     public override bool InitializeAndValidate(Document target)
     {
-        if (!target.TryFindMember(targetMember, out var member) || member.Name == newName)
+        if (!target.TryFindMember(targetMember, out var member) || member.LayerName == newName)
             return false;
-        originalName = member.Name;
+        originalName = member.LayerName;
         return true;
     }
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
     {
-        target.FindMemberOrThrow(targetMember).Name = newName;
+        target.FindMemberOrThrow(targetMember).LayerName = newName;
 
         ignoreInUndo = false;
         return new StructureMemberName_ChangeInfo(targetMember, newName);
@@ -35,7 +35,7 @@ internal class StructureMemberName_Change : Change
     {
         if (originalName is null)
             throw new InvalidOperationException("No name to revert to");
-        target.FindMemberOrThrow(targetMember).Name = originalName;
+        target.FindMemberOrThrow(targetMember).LayerName = originalName;
         return new StructureMemberName_ChangeInfo(targetMember, originalName);
     }
 

+ 4 - 4
src/PixiEditor.ChangeableDocument/Changes/Properties/LayerStructure/StructureMemberOpacity_UpdateableChange.cs

@@ -26,14 +26,14 @@ internal class StructureMemberOpacity_UpdateableChange : UpdateableChange
     {
         if (!document.TryFindMember(memberGuid, out var member))
             return false;
-        originalOpacity = member.Opacity;
+        originalOpacity = member.Opacity.Value;
         return true;
     }
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> ApplyTemporarily(Document target)
     {
         var member = target.FindMemberOrThrow(memberGuid);
-        member.Opacity = newOpacity;
+        member.Opacity.Value = newOpacity;
         return new StructureMemberOpacity_ChangeInfo(memberGuid, newOpacity);
     }
 
@@ -46,7 +46,7 @@ internal class StructureMemberOpacity_UpdateableChange : UpdateableChange
         }
 
         var member = document.FindMemberOrThrow(memberGuid);
-        member.Opacity = newOpacity;
+        member.Opacity.Value = newOpacity;
 
         ignoreInUndo = false;
         return new StructureMemberOpacity_ChangeInfo(memberGuid, newOpacity);
@@ -58,7 +58,7 @@ internal class StructureMemberOpacity_UpdateableChange : UpdateableChange
             return new None();
 
         var member = document.FindMemberOrThrow(memberGuid);
-        member.Opacity = originalOpacity;
+        member.Opacity.Value = originalOpacity;
 
         return new StructureMemberOpacity_ChangeInfo(memberGuid, originalOpacity);
     }

+ 6 - 5
src/PixiEditor.ChangeableDocument/Changes/Root/CenterContent_Change.cs

@@ -1,4 +1,5 @@
-using PixiEditor.ChangeableDocument.Changes.Drawing;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.Changes.Drawing;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Numerics;
 
@@ -43,7 +44,7 @@ internal class CenterContent_Change : Change
         RectI? currentBounds = null;
         foreach (var layerGuid in affectedLayers)
         {
-            Layer layer = document.FindMemberOrThrow<Layer>(layerGuid);
+            LayerNode layer = document.FindMemberOrThrow<LayerNode>(layerGuid);
             RectI? tightBounds = layer.GetTightBounds(frame);
             if (tightBounds.HasValue)
             {
@@ -69,13 +70,13 @@ internal class CenterContent_Change : Change
         
         foreach (var layerGuid in affectedLayers)
         {
-            RasterLayer layer = target.FindMemberOrThrow<RasterLayer>(layerGuid);
+            ImageLayerNode node = target.FindMemberOrThrow<ImageLayerNode>(layerGuid);
             var chunks = ShiftLayerHelper.DrawShiftedLayer(target, layerGuid, false, shift, frame);
             changes.Add(new LayerImageArea_ChangeInfo(layerGuid, chunks));
 
             // TODO: Adding support for non-raster layer should be easy, add
             
-            var image = layer.GetLayerImageAtFrame(frame);
+            var image = node.GetLayerImageAtFrame(frame);
             originalLayerChunks[layerGuid] = new CommittedChunkStorage(image, image.FindAffectedArea().Chunks);
             image.CommitChanges();
         }
@@ -89,7 +90,7 @@ internal class CenterContent_Change : Change
         List<IChangeInfo> changes = new List<IChangeInfo>();
         foreach (var layerGuid in affectedLayers)
         {
-            var image = target.FindMemberOrThrow<RasterLayer>(layerGuid).GetLayerImageAtFrame(frame);
+            var image = target.FindMemberOrThrow<ImageLayerNode>(layerGuid).GetLayerImageAtFrame(frame);
             CommittedChunkStorage? originalChunks = originalLayerChunks?[layerGuid];
             var affected = DrawingChangeHelper.ApplyStoredChunksDisposeAndSetToNull(image, ref originalChunks);
             changes.Add(new LayerImageArea_ChangeInfo(layerGuid, affected));

+ 6 - 5
src/PixiEditor.ChangeableDocument/Changes/Root/ClipCanvas_Change.cs

@@ -1,4 +1,5 @@
-using PixiEditor.ChangeableDocument.ChangeInfos.Root;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.ChangeInfos.Root;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Numerics;
 
@@ -18,7 +19,7 @@ internal class ClipCanvas_Change : ResizeBasedChangeBase
         RectI? bounds = null;
         target.ForEveryMember((member) =>
         {
-            if (member is Layer layer)
+            if (member is LayerNode layer)
             {
                 var layerBounds = layer.GetTightBounds(frameToClip);
                 if (layerBounds.HasValue)
@@ -43,18 +44,18 @@ internal class ClipCanvas_Change : ResizeBasedChangeBase
         
         target.ForEveryMember((member) =>
         {
-            if (member is RasterLayer layer)
+            if (member is ImageLayerNode layer)
             {
                 layer.ForEveryFrame(img =>
                 {
-                    Resize(img, layer.GuidValue, newBounds.Size, -newBounds.Pos, deletedChunks);
+                    Resize(img, layer.Id, newBounds.Size, -newBounds.Pos, deletedChunks);
                 });
             }
             
             if (member.Mask is null)
                 return;
             
-            Resize(member.Mask, member.GuidValue, newBounds.Size, -newBounds.Pos, deletedMaskChunks);
+            Resize(member.Mask.Value, member.Id, newBounds.Size, -newBounds.Pos, deletedMaskChunks);
         });
 
         ignoreInUndo = false;

+ 6 - 5
src/PixiEditor.ChangeableDocument/Changes/Root/Crop_Change.cs

@@ -1,4 +1,5 @@
-using PixiEditor.ChangeableDocument.ChangeInfos.Root;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.ChangeInfos.Root;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Numerics;
 
@@ -34,17 +35,17 @@ internal class Crop_Change : ResizeBasedChangeBase
 
         target.ForEveryMember((member) =>
         {
-            if (member is RasterLayer layer)
+            if (member is ImageLayerNode layer)
             {
                 layer.ForEveryFrame(frame =>
                 {
-                    Resize(frame, layer.GuidValue, rect.Size, rect.Pos * -1, deletedChunks);
+                    Resize(frame, layer.Id, rect.Size, rect.Pos * -1, deletedChunks);
                 });
             }
-            if (member.Mask is null)
+            if (member.Mask.Value is null)
                 return;
 
-            Resize(member.Mask, member.GuidValue, rect.Size, rect.Pos * -1, deletedMaskChunks);
+            Resize(member.Mask.Value, member.Id, rect.Size, rect.Pos * -1, deletedMaskChunks);
         });
         
         ignoreInUndo = false;

+ 8 - 7
src/PixiEditor.ChangeableDocument/Changes/Root/FlipImage_Change.cs

@@ -1,4 +1,5 @@
 using ChunkyImageLib.Operations;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.ChangeInfos.Root;
 using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.DrawingApi.Core.Numerics;
@@ -100,24 +101,24 @@ internal sealed class FlipImage_Change : Change
 
         target.ForEveryMember(member =>
         {
-            if (membersToFlip.Count == 0 || membersToFlip.Contains(member.GuidValue))
+            if (membersToFlip.Count == 0 || membersToFlip.Contains(member.Id))
             {
-                if (member is RasterLayer layer)
+                if (member is ImageLayerNode layer)
                 {
                     var image = layer.GetLayerImageAtFrame(frame);
                     FlipImage(image);
                     changes.Add(
-                        new LayerImageArea_ChangeInfo(member.GuidValue, image.FindAffectedArea()));
+                        new LayerImageArea_ChangeInfo(member.Id, image.FindAffectedArea()));
                     image.CommitChanges();
                 }
                 // TODO: Add support for non-raster layers
 
-                if (member.Mask is not null)
+                if (member.Mask.Value is not null)
                 {
-                    FlipImage(member.Mask);
+                    FlipImage(member.Mask.Value);
                     changes.Add(
-                        new MaskArea_ChangeInfo(member.GuidValue, member.Mask.FindAffectedArea()));
-                    member.Mask.CommitChanges();
+                        new MaskArea_ChangeInfo(member.Id, member.Mask.Value.FindAffectedArea()));
+                    member.Mask.Value.CommitChanges();
                 }
             }
         });

+ 8 - 7
src/PixiEditor.ChangeableDocument/Changes/Root/ResizeBasedChangeBase.cs

@@ -1,4 +1,5 @@
-using PixiEditor.ChangeableDocument.ChangeInfos.Root;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.ChangeInfos.Root;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Numerics;
 
@@ -43,23 +44,23 @@ internal abstract class ResizeBasedChangeBase : Change
         target.Size = _originalSize;
         target.ForEveryMember((member) =>
         {
-            if (member is RasterLayer layer)
+            if (member is ImageLayerNode layer)
             {
                 layer.ForEveryFrame(img =>
                 {
                     img.EnqueueResize(_originalSize);
-                    deletedChunks[layer.GuidValue].ApplyChunksToImage(img);
+                    deletedChunks[layer.Id].ApplyChunksToImage(img);
                     img.CommitChanges();
                 });
             }
 
             // TODO: Add support for different Layer types?
 
-            if (member.Mask is null)
+            if (member.Mask.Value is null)
                 return;
-            member.Mask.EnqueueResize(_originalSize);
-            deletedMaskChunks[member.GuidValue].ApplyChunksToImage(member.Mask);
-            member.Mask.CommitChanges();
+            member.Mask.Value.EnqueueResize(_originalSize);
+            deletedMaskChunks[member.Id].ApplyChunksToImage(member.Mask.Value);
+            member.Mask.Value.CommitChanges();
         });
 
         target.HorizontalSymmetryAxisY = _originalHorAxisY;

+ 6 - 5
src/PixiEditor.ChangeableDocument/Changes/Root/ResizeCanvas_Change.cs

@@ -1,4 +1,5 @@
-using PixiEditor.ChangeableDocument.ChangeInfos.Root;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.ChangeInfos.Root;
 using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Numerics;
@@ -46,20 +47,20 @@ internal class ResizeCanvas_Change : ResizeBasedChangeBase
 
         target.ForEveryMember((member) =>
         {
-            if (member is RasterLayer layer)
+            if (member is ImageLayerNode layer)
             {
                 layer.ForEveryFrame(img =>
                 {
-                    Resize(img, layer.GuidValue, newSize, offset, deletedChunks);
+                    Resize(img, layer.Id, newSize, offset, deletedChunks);
                 });
             }
 
             // TODO: Check if adding support for different Layer types is necessary
 
-            if (member.Mask is null)
+            if (member.Mask.Value is null)
                 return;
 
-            Resize(member.Mask, member.GuidValue, newSize, offset, deletedMaskChunks);
+            Resize(member.Mask.Value, member.Id, newSize, offset, deletedMaskChunks);
         });
 
         ignoreInUndo = false;

+ 16 - 15
src/PixiEditor.ChangeableDocument/Changes/Root/ResizeImage_Change.cs

@@ -1,4 +1,5 @@
-using PixiEditor.ChangeableDocument.ChangeInfos.Root;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.ChangeInfos.Root;
 using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Surface;
@@ -86,25 +87,25 @@ internal class ResizeImage_Change : Change
 
         target.ForEveryMember(member =>
         {
-            if (member is RasterLayer layer)
+            if (member is ImageLayerNode layer)
             {
                 layer.ForEveryFrame(img =>
                 {
                     ScaleChunkyImage(img);
                     var affected = img.FindAffectedArea();
-                    savedChunks[layer.GuidValue] = new CommittedChunkStorage(img, affected.Chunks);
+                    savedChunks[layer.Id] = new CommittedChunkStorage(img, affected.Chunks);
                     img.CommitChanges();
                 });
             }
 
             // Add support for different Layer types
 
-            if (member.Mask is not null)
+            if (member.Mask.Value is not null)
             {
-                ScaleChunkyImage(member.Mask);
-                var affected = member.Mask.FindAffectedArea();
-                savedMaskChunks[member.GuidValue] = new CommittedChunkStorage(member.Mask, affected.Chunks);
-                member.Mask.CommitChanges();
+                ScaleChunkyImage(member.Mask.Value);
+                var affected = member.Mask.Value.FindAffectedArea();
+                savedMaskChunks[member.Id] = new CommittedChunkStorage(member.Mask.Value, affected.Chunks);
+                member.Mask.Value.CommitChanges();
             }
         });
 
@@ -117,23 +118,23 @@ internal class ResizeImage_Change : Change
         target.Size = originalSize;
         target.ForEveryMember((member) =>
         {
-            if (member is RasterLayer layer)
+            if (member is ImageLayerNode layer)
             {
                 layer.ForEveryFrame(layerImage =>
                 {
                     layerImage.EnqueueResize(originalSize);
                     layerImage.EnqueueClear();
-                    savedChunks[layer.GuidValue].ApplyChunksToImage(layerImage);
+                    savedChunks[layer.Id].ApplyChunksToImage(layerImage);
                     layerImage.CommitChanges();
                 });
             }
 
-            if (member.Mask is not null)
+            if (member.Mask.Value is not null)
             {
-                member.Mask.EnqueueResize(originalSize);
-                member.Mask.EnqueueClear();
-                savedMaskChunks[member.GuidValue].ApplyChunksToImage(member.Mask);
-                member.Mask.CommitChanges();
+                member.Mask.Value.EnqueueResize(originalSize);
+                member.Mask.Value.EnqueueClear();
+                savedMaskChunks[member.Id].ApplyChunksToImage(member.Mask.Value);
+                member.Mask.Value.CommitChanges();
             }
         });
 

+ 21 - 20
src/PixiEditor.ChangeableDocument/Changes/Root/RotateImage_Change.cs

@@ -1,4 +1,5 @@
 using ChunkyImageLib.Operations;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.ChangeInfos.Root;
 using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.DrawingApi.Core.Numerics;
@@ -138,29 +139,29 @@ internal sealed class RotateImage_Change : Change
 
         target.ForEveryMember((member) =>
         {
-            if (guids.Contains(member.GuidValue))
+            if (guids.Contains(member.Id))
             {
-                if (member is RasterLayer layer)
+                if (member is ImageLayerNode layer)
                 {
                     if (frame != null)
                     {
-                        Resize(layer.GetLayerImageAtFrame(frame.Value), layer.GuidValue, deletedChunks, changes);
+                        Resize(layer.GetLayerImageAtFrame(frame.Value), layer.Id, deletedChunks, changes);
                     }
                     else
                     {
                         layer.ForEveryFrame(img =>
                         {
-                            Resize(img, layer.GuidValue, deletedChunks, changes);
+                            Resize(img, layer.Id, deletedChunks, changes);
                         });
                     }
                 }
 
                 // TODO: Add support for different Layer types
 
-                if (member.Mask is null)
+                if (member.Mask.Value is null)
                     return;
 
-                Resize(member.Mask, member.GuidValue, deletedMaskChunks, null);
+                Resize(member.Mask.Value, member.Id, deletedMaskChunks, null);
             }
         });
 
@@ -183,25 +184,25 @@ internal sealed class RotateImage_Change : Change
 
         target.ForEveryMember((member) =>
         {
-            if (member is RasterLayer layer)
+            if (member is ImageLayerNode layer)
             {
                 if (frame != null)
                 {
-                    Resize(layer.GetLayerImageAtFrame(frame.Value), layer.GuidValue, deletedChunks, null);
+                    Resize(layer.GetLayerImageAtFrame(frame.Value), layer.Id, deletedChunks, null);
                 }
                 else
                 {
                     layer.ForEveryFrame(img =>
                     {
-                        Resize(img, layer.GuidValue, deletedChunks, null);
+                        Resize(img, layer.Id, deletedChunks, null);
                     });
                 }
             }
 
-            if (member.Mask is null)
+            if (member.Mask.Value is null)
                 return;
 
-            Resize(member.Mask, member.GuidValue, deletedMaskChunks, null);
+            Resize(member.Mask.Value, member.Id, deletedMaskChunks, null);
         });
 
         return new Size_ChangeInfo(newSize, target.VerticalSymmetryAxisX, target.HorizontalSymmetryAxisY);
@@ -233,22 +234,22 @@ internal sealed class RotateImage_Change : Change
         List<IChangeInfo> revertChanges = new List<IChangeInfo>();
         target.ForEveryMember((member) =>
         {
-            if (membersToRotate.Count > 0 && !membersToRotate.Contains(member.GuidValue)) return;
-            if (member is RasterLayer layer)
+            if (membersToRotate.Count > 0 && !membersToRotate.Contains(member.Id)) return;
+            if (member is ImageLayerNode layer)
             {
                 var layerImage = layer.GetLayerImageAtFrame(frame.Value);
                 layerImage.EnqueueResize(originalSize);
-                deletedChunks[layer.GuidValue].ApplyChunksToImage(layerImage);
-                revertChanges.Add(new LayerImageArea_ChangeInfo(layer.GuidValue, layerImage.FindAffectedArea()));
+                deletedChunks[layer.Id].ApplyChunksToImage(layerImage);
+                revertChanges.Add(new LayerImageArea_ChangeInfo(layer.Id, layerImage.FindAffectedArea()));
                 layerImage.CommitChanges();
             }
 
-            if (member.Mask is null)
+            if (member.Mask.Value is null)
                 return;
-            member.Mask.EnqueueResize(originalSize);
-            deletedMaskChunks[member.GuidValue].ApplyChunksToImage(member.Mask);
-            revertChanges.Add(new LayerImageArea_ChangeInfo(member.GuidValue, member.Mask.FindAffectedArea()));
-            member.Mask.CommitChanges();
+            member.Mask.Value.EnqueueResize(originalSize);
+            deletedMaskChunks[member.Id].ApplyChunksToImage(member.Mask.Value);
+            revertChanges.Add(new LayerImageArea_ChangeInfo(member.Id, member.Mask.Value.FindAffectedArea()));
+            member.Mask.Value.CommitChanges();
         });
 
         DisposeDeletedChunks();

+ 2 - 2
src/PixiEditor.ChangeableDocument/Changes/Selection/MagicWand/MagicWand_Change.cs

@@ -37,8 +37,8 @@ internal class MagicWand_Change : Change
 
         target.ForEveryReadonlyMember(member =>
         {
-            if (memberGuids.Contains(member.GuidValue))
-                membersToReference.Add(member.GuidValue);
+            if (memberGuids.Contains(member.Id))
+                membersToReference.Add(member.Id);
         });
 
         path = MagicWandHelper.DoMagicWandFloodFill(point, membersToReference, target, frame);

+ 5 - 4
src/PixiEditor.ChangeableDocument/Changes/Structure/ApplyMask_Change.cs

@@ -1,4 +1,5 @@
-using PixiEditor.ChangeableDocument.Changes.Drawing;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.Changes.Drawing;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Surface;
 using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
@@ -22,7 +23,7 @@ internal sealed class ApplyMask_Change : Change
     public override bool InitializeAndValidate(Document target)
     {
         var member = target.FindMember(structureMemberGuid);
-        bool isValid = member is not (null or Folder) && member.Mask is not null;
+        bool isValid = member is not (null or FolderNode) && member.Mask.Value is not null;
 
         return isValid;
     }
@@ -30,9 +31,9 @@ internal sealed class ApplyMask_Change : Change
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply,
         out bool ignoreInUndo)
     {
-        var layer = target.FindMemberOrThrow<RasterLayer>(structureMemberGuid)!;
+        var layer = target.FindMemberOrThrow<ImageLayerNode>(structureMemberGuid)!;
         var layerImage = layer.GetLayerImageAtFrame(frame);
-        layerImage.EnqueueApplyMask(layer.Mask!);
+        layerImage.EnqueueApplyMask(layer.Mask.Value!);
         ignoreInUndo = false;
         var layerInfo = new LayerImageArea_ChangeInfo(structureMemberGuid, layerImage.FindAffectedArea());
         savedChunks = new CommittedChunkStorage(layerImage, layerInfo.Area.Chunks);

+ 6 - 5
src/PixiEditor.ChangeableDocument/Changes/Structure/CreateStructureMember_Change.cs

@@ -1,4 +1,5 @@
-using PixiEditor.ChangeableDocument.ChangeInfos.Structure;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.ChangeInfos.Structure;
 using PixiEditor.ChangeableDocument.Enums;
 
 namespace PixiEditor.ChangeableDocument.Changes.Structure;
@@ -27,13 +28,13 @@ internal class CreateStructureMember_Change : Change
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document document, bool firstApply, out bool ignoreInUndo)
     {
-        var folder = document.FindMemberOrThrow<Folder>(parentFolderGuid);
+        var folder = document.FindMemberOrThrow<FolderNode>(parentFolderGuid);
 
-        StructureMember member = type switch
+        StructureNode member = type switch
         {
             // TODO: Add support for other types
-            StructureMemberType.Layer => new RasterLayer(document.Size) { GuidValue = newMemberGuid },
-            StructureMemberType.Folder => new Folder() { GuidValue = newMemberGuid },
+            StructureMemberType.Layer => new ImageLayerNode(document.Size) { Id = newMemberGuid },
+            StructureMemberType.Folder => new FolderNode() { Id = newMemberGuid },
             _ => throw new NotSupportedException(),
         };
 

+ 5 - 4
src/PixiEditor.ChangeableDocument/Changes/Structure/MoveStructureMember_Change.cs

@@ -1,4 +1,5 @@
-using PixiEditor.ChangeableDocument.ChangeInfos.Structure;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.ChangeInfos.Structure;
 
 namespace PixiEditor.ChangeableDocument.Changes.Structure;
 
@@ -24,16 +25,16 @@ internal class MoveStructureMember_Change : Change
     {
         var (member, curFolder) = document.FindChildAndParent(memberGuid);
         var targetFolder = document.FindMember(targetFolderGuid);
-        if (member is null || curFolder is null || targetFolder is not Folder)
+        if (member is null || curFolder is null || targetFolder is not FolderNode)
             return false;
-        originalFolderGuid = curFolder.GuidValue;
+        originalFolderGuid = curFolder.Id;
         originalFolderIndex = curFolder.Children.IndexOf(member);
         return true;
     }
 
     private static void Move(Document document, Guid memberGuid, Guid targetFolderGuid, int targetIndex)
     {
-        var targetFolder = document.FindMemberOrThrow<Folder>(targetFolderGuid);
+        var targetFolder = document.FindMemberOrThrow<FolderNode>(targetFolderGuid);
         var (member, curFolder) = document.FindChildAndParentOrThrow(memberGuid);
 
         curFolder.Children = curFolder.Children.Remove(member);

+ 2 - 2
src/PixiEditor/Models/Rendering/AffectedAreasGatherer.cs

@@ -131,9 +131,9 @@ internal class AffectedAreasGatherer
     {
         if (!tracker.Document.TryFindMember(memberGuid, out var member))
             return;
-        if (member.Mask is not null)
+        if (member.Mask.Value is not null)
         {
-            var chunks = member.Mask.FindAllChunks();
+            var chunks = member.Mask.Value.FindAllChunks();
             AddToMaskPreview(memberGuid, new AffectedArea(chunks));
         }
         if (member is IReadOnlyFolder folder)

+ 1 - 1
src/PixiEditor/Models/Rendering/MemberPreviewUpdater.cs

@@ -177,7 +177,7 @@ internal class MemberPreviewUpdater
             if (member is null)
                 continue;
 
-            if (forMasks && member.Mask is null)
+            if (forMasks && member.Mask.Value is null)
             {
                 newPreviewBitmapSizes.Add(guid, null);
                 continue;