Browse Source

it compiles oh god

flabbet 1 year ago
parent
commit
0297dbb73b
88 changed files with 782 additions and 836 deletions
  1. 11 1
      src/ChunkyImageLib/ChunkyImage.cs
  2. 6 1
      src/ChunkyImageLib/Surface.cs
  3. 2 2
      src/PixiEditor.AvaloniaUI/Helpers/DocumentViewModelBuilder.cs
  4. 28 27
      src/PixiEditor.AvaloniaUI/Models/DocumentModels/DocumentStructureHelper.cs
  5. 23 23
      src/PixiEditor.AvaloniaUI/Models/DocumentModels/DocumentUpdater.cs
  6. 11 11
      src/PixiEditor.AvaloniaUI/Models/DocumentModels/Public/DocumentOperationsModule.cs
  7. 37 46
      src/PixiEditor.AvaloniaUI/Models/DocumentModels/Public/DocumentStructureModule.cs
  8. 1 1
      src/PixiEditor.AvaloniaUI/Models/DocumentModels/UpdateableChangeExecutors/BrightnessToolExecutor.cs
  9. 1 1
      src/PixiEditor.AvaloniaUI/Models/DocumentModels/UpdateableChangeExecutors/EraserToolExecutor.cs
  10. 1 1
      src/PixiEditor.AvaloniaUI/Models/DocumentModels/UpdateableChangeExecutors/FloodFillToolExecutor.cs
  11. 1 1
      src/PixiEditor.AvaloniaUI/Models/DocumentModels/UpdateableChangeExecutors/LineToolExecutor.cs
  12. 1 1
      src/PixiEditor.AvaloniaUI/Models/DocumentModels/UpdateableChangeExecutors/MagicWandToolExecutor.cs
  13. 1 1
      src/PixiEditor.AvaloniaUI/Models/DocumentModels/UpdateableChangeExecutors/PasteImageExecutor.cs
  14. 1 1
      src/PixiEditor.AvaloniaUI/Models/DocumentModels/UpdateableChangeExecutors/PenToolExecutor.cs
  15. 1 1
      src/PixiEditor.AvaloniaUI/Models/DocumentModels/UpdateableChangeExecutors/ShapeToolExecutor.cs
  16. 3 3
      src/PixiEditor.AvaloniaUI/Models/DocumentModels/UpdateableChangeExecutors/ShiftLayerExecutor.cs
  17. 1 1
      src/PixiEditor.AvaloniaUI/Models/DocumentModels/UpdateableChangeExecutors/StructureMemberOpacityExecutor.cs
  18. 1 1
      src/PixiEditor.AvaloniaUI/Models/DocumentModels/UpdateableChangeExecutors/TransformSelectedAreaExecutor.cs
  19. 1 1
      src/PixiEditor.AvaloniaUI/Models/DocumentPassthroughActions/AddSoftSelectedMember_PassthroughAction.cs
  20. 1 1
      src/PixiEditor.AvaloniaUI/Models/DocumentPassthroughActions/RemoveSoftSelectedMember_PassthroughAction.cs
  21. 1 1
      src/PixiEditor.AvaloniaUI/Models/DocumentPassthroughActions/RemoveViewport_PassthroughAction.cs
  22. 1 1
      src/PixiEditor.AvaloniaUI/Models/DocumentPassthroughActions/SetSelectedMember_PassthroughAction.cs
  23. 1 1
      src/PixiEditor.AvaloniaUI/Models/Handlers/IDocument.cs
  24. 0 1
      src/PixiEditor.AvaloniaUI/Models/Handlers/IFolderHandler.cs
  25. 7 0
      src/PixiEditor.AvaloniaUI/Models/Handlers/IInputPropertyHandler.cs
  26. 12 0
      src/PixiEditor.AvaloniaUI/Models/Handlers/INodeGraphHandler.cs
  27. 17 0
      src/PixiEditor.AvaloniaUI/Models/Handlers/INodeHandler.cs
  28. 9 0
      src/PixiEditor.AvaloniaUI/Models/Handlers/INodePropertyHandler.cs
  29. 9 0
      src/PixiEditor.AvaloniaUI/Models/Handlers/IOutputPropertyHandler.cs
  30. 1 2
      src/PixiEditor.AvaloniaUI/Models/Handlers/IStructureMemberHandler.cs
  31. 1 1
      src/PixiEditor.AvaloniaUI/Models/Position/ViewportInfo.cs
  32. 46 45
      src/PixiEditor.AvaloniaUI/Models/Rendering/AffectedAreasGatherer.cs
  33. 2 2
      src/PixiEditor.AvaloniaUI/Models/Rendering/CanvasUpdater.cs
  34. 28 26
      src/PixiEditor.AvaloniaUI/Models/Rendering/MemberPreviewUpdater.cs
  35. 5 5
      src/PixiEditor.AvaloniaUI/Styles/Templates/NodeGraphView.axaml
  36. 1 1
      src/PixiEditor.AvaloniaUI/ViewModels/Dock/LayoutManager.cs
  37. 10 3
      src/PixiEditor.AvaloniaUI/ViewModels/Dock/NodeGraphDockViewModel.cs
  38. 20 19
      src/PixiEditor.AvaloniaUI/ViewModels/Document/DocumentViewModel.Serialization.cs
  39. 43 38
      src/PixiEditor.AvaloniaUI/ViewModels/Document/DocumentViewModel.cs
  40. 1 1
      src/PixiEditor.AvaloniaUI/ViewModels/Document/FolderViewModel.cs
  41. 8 2
      src/PixiEditor.AvaloniaUI/ViewModels/Document/LayerViewModel.cs
  42. 66 0
      src/PixiEditor.AvaloniaUI/ViewModels/Document/NodeGraphViewModel.cs
  43. 34 22
      src/PixiEditor.AvaloniaUI/ViewModels/Document/StructureMemberViewModel.cs
  44. 86 23
      src/PixiEditor.AvaloniaUI/ViewModels/Nodes/NodeViewModel.cs
  45. 3 3
      src/PixiEditor.AvaloniaUI/ViewModels/SubViewModels/AnimationsViewModel.cs
  46. 5 5
      src/PixiEditor.AvaloniaUI/ViewModels/SubViewModels/DiscordViewModel.cs
  47. 12 12
      src/PixiEditor.AvaloniaUI/ViewModels/SubViewModels/LayersViewModel.cs
  48. 6 1
      src/PixiEditor.AvaloniaUI/Views/Dock/NodeGraphDockView.axaml
  49. 1 1
      src/PixiEditor.AvaloniaUI/Views/Layers/FolderControl.axaml.cs
  50. 3 3
      src/PixiEditor.AvaloniaUI/Views/Layers/LayerControl.axaml.cs
  51. 1 1
      src/PixiEditor.AvaloniaUI/Views/Layers/LayersManager.axaml
  52. 13 11
      src/PixiEditor.AvaloniaUI/Views/Layers/LayersManager.axaml.cs
  53. 7 60
      src/PixiEditor.AvaloniaUI/Views/Nodes/NodeGraphView.cs
  54. 1 1
      src/PixiEditor.ChangeableDocument/ChangeInfos/Drawing/LayerImageArea_ChangeInfo.cs
  55. 1 1
      src/PixiEditor.ChangeableDocument/ChangeInfos/Drawing/MaskArea_ChangeInfo.cs
  56. 1 1
      src/PixiEditor.ChangeableDocument/ChangeInfos/Properties/LayerLockTransparency_ChangeInfo.cs
  57. 1 1
      src/PixiEditor.ChangeableDocument/ChangeInfos/Properties/StructureMemberBlendMode_ChangeInfo.cs
  58. 1 1
      src/PixiEditor.ChangeableDocument/ChangeInfos/Properties/StructureMemberClipToMemberBelow_ChangeInfo.cs
  59. 1 1
      src/PixiEditor.ChangeableDocument/ChangeInfos/Properties/StructureMemberIsVisible_ChangeInfo.cs
  60. 1 1
      src/PixiEditor.ChangeableDocument/ChangeInfos/Properties/StructureMemberMaskIsVisible_ChangeInfo.cs
  61. 1 1
      src/PixiEditor.ChangeableDocument/ChangeInfos/Properties/StructureMemberMask_ChangeInfo.cs
  62. 1 1
      src/PixiEditor.ChangeableDocument/ChangeInfos/Properties/StructureMemberName_ChangeInfo.cs
  63. 1 1
      src/PixiEditor.ChangeableDocument/ChangeInfos/Properties/StructureMemberOpacity_ChangeInfo.cs
  64. 1 1
      src/PixiEditor.ChangeableDocument/ChangeInfos/Structure/CreateStructureMember_ChangeInfo.cs
  65. 1 1
      src/PixiEditor.ChangeableDocument/ChangeInfos/Structure/DeleteStructureMember_ChangeInfo.cs
  66. 1 1
      src/PixiEditor.ChangeableDocument/ChangeInfos/Structure/MoveStructureMember_ChangeInfo.cs
  67. 10 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/InputProperty.cs
  68. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyFolderNode.cs
  69. 0 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyNode.cs
  70. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyStructureNode.cs
  71. 2 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FolderNode.cs
  72. 2 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ImageLayerNode.cs
  73. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/MergeNode.cs
  74. 35 3
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Node.cs
  75. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/OutputNode.cs
  76. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/StructureNode.cs
  77. 31 14
      src/PixiEditor.ChangeableDocument/Changeables/Graph/OutputProperty.cs
  78. 17 15
      src/PixiEditor.ChangeableDocument/Changes/Drawing/CombineStructureMembersOnto_Change.cs
  79. 10 8
      src/PixiEditor.ChangeableDocument/Changes/Drawing/FloodFill/FloodFillChunkCache.cs
  80. 4 3
      src/PixiEditor.ChangeableDocument/Changes/Drawing/FloodFill/FloodFillHelper.cs
  81. 4 4
      src/PixiEditor.ChangeableDocument/Changes/Properties/LayerStructure/StructureMemberName_Change.cs
  82. 8 5
      src/PixiEditor.ChangeableDocument/Changes/Structure/CreateStructureMember_Change.cs
  83. 17 11
      src/PixiEditor.ChangeableDocument/Changes/Structure/DeleteStructureMember_Change.cs
  84. 15 9
      src/PixiEditor.ChangeableDocument/Changes/Structure/DuplicateLayer_Change.cs
  85. 5 3
      src/PixiEditor.ChangeableDocument/Changes/Structure/MoveStructureMember_Change.cs
  86. 0 320
      src/PixiEditor.ChangeableDocument/Rendering/ChunkRenderer.cs
  87. 6 0
      src/PixiEditor.ChangeableDocument/Rendering/DocumentEvaluator.cs
  88. 5 4
      src/PixiEditor.ChangeableDocument/Rendering/RenderingContext.cs

+ 11 - 1
src/ChunkyImageLib/ChunkyImage.cs

@@ -40,7 +40,7 @@ namespace ChunkyImageLib;
 ///     - Any other blend mode: the latest chunks contain only the things drawn by the queued operations.
 ///         They need to be drawn over the committed chunks to obtain the final image. In this case, operations won't have access to the existing pixels. 
 /// </summary>
-public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
+public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
 {
     private struct LatestChunkData
     {
@@ -1314,4 +1314,14 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
 
         disposed = true;
     }
+
+    public object Clone()
+    {
+        lock (lockObject)
+        {
+            ThrowIfDisposed();
+            ChunkyImage clone = CloneFromCommitted();
+            return clone;
+        }
+    }
 }

+ 6 - 1
src/ChunkyImageLib/Surface.cs

@@ -10,7 +10,7 @@ using PixiEditor.Numerics;
 
 namespace ChunkyImageLib;
 
-public class Surface : IDisposable
+public class Surface : IDisposable, ICloneable
 {
     private bool disposed;
     public IntPtr PixelBuffer { get; }
@@ -231,6 +231,11 @@ public class Surface : IDisposable
     {
         Marshal.FreeHGlobal(PixelBuffer);
     }
+
+    public object Clone()
+    {
+        return new Surface(this);
+    }
 }
 
 public enum ResizeMethod

+ 2 - 2
src/PixiEditor.AvaloniaUI/Helpers/DocumentViewModelBuilder.cs

@@ -143,7 +143,7 @@ internal class DocumentViewModelBuilder : ChildrenBuilder
 
         [NotNull] public MaskBuilder Mask => maskBuilder ??= new MaskBuilder();
 
-        public Guid GuidValue { get; set; }
+        public Guid Id { get; set; }
 
         public StructureMemberBuilder()
         {
@@ -194,7 +194,7 @@ internal class DocumentViewModelBuilder : ChildrenBuilder
 
         public StructureMemberBuilder WithGuid(Guid guid)
         {
-            GuidValue = guid;
+            Id = guid;
             return this;
         }
 

+ 28 - 27
src/PixiEditor.AvaloniaUI/Models/DocumentModels/DocumentStructureHelper.cs

@@ -20,12 +20,13 @@ internal class DocumentStructureHelper
     private string GetUniqueName(string name, IFolderHandler folder)
     {
         int count = 1;
-        foreach (var child in folder.Children)
+        //TODO: implement this
+        /*foreach (var child in folder.Children)
         {
             string childName = child.NameBindable;
             if (childName.StartsWith(name))
                 count++;
-        }
+        }*/
         return $"{name} {count}";
     }
 
@@ -36,8 +37,8 @@ internal class DocumentStructureHelper
         {
             Guid guid = Guid.NewGuid();
             //put member on top
-            internals.ActionAccumulator.AddActions(new CreateStructureMember_Action(doc.StructureRoot.GuidValue, guid, doc.StructureRoot.Children.Count, type));
-            name ??= GetUniqueName(type == StructureMemberType.Layer ? new LocalizedString("NEW_LAYER") : new LocalizedString("NEW_FOLDER"), doc.StructureRoot);
+            //internals.ActionAccumulator.AddActions(new CreateStructureMember_Action(doc.StructureRoot.Id, guid, doc.StructureRoot.Children.Count, type));
+            //name ??= GetUniqueName(type == StructureMemberType.Layer ? new LocalizedString("NEW_LAYER") : new LocalizedString("NEW_FOLDER"), doc.StructureRoot);
             internals.ActionAccumulator.AddActions(new StructureMemberName_Action(guid, name));
             if (finish)
                 internals.ActionAccumulator.AddFinishedActions();
@@ -47,7 +48,7 @@ internal class DocumentStructureHelper
         {
             Guid guid = Guid.NewGuid();
             //put member inside folder on top
-            internals.ActionAccumulator.AddActions(new CreateStructureMember_Action(folder.GuidValue, guid, folder.Children.Count, type));
+            //internals.ActionAccumulator.AddActions(new CreateStructureMember_Action(folder.Id, guid, folder.Children.Count, type));
             name ??= GetUniqueName(type == StructureMemberType.Layer ? new LocalizedString("NEW_LAYER") : new LocalizedString("NEW_FOLDER"), folder);
             internals.ActionAccumulator.AddActions(new StructureMemberName_Action(guid, name));
             if (finish)
@@ -58,11 +59,11 @@ internal class DocumentStructureHelper
         {
             Guid guid = Guid.NewGuid();
             //put member above the layer
-            List<IStructureMemberHandler> path = doc.StructureHelper.FindPath(layer.GuidValue);
+            List<IStructureMemberHandler> path = doc.StructureHelper.FindPath(layer.Id);
             if (path.Count < 2)
                 throw new InvalidOperationException("Couldn't find a path to the selected member");
             IFolderHandler parent = (IFolderHandler)path[1];
-            internals.ActionAccumulator.AddActions(new CreateStructureMember_Action(parent.GuidValue, guid, parent.Children.IndexOf(layer) + 1, type));
+            //internals.ActionAccumulator.AddActions(new CreateStructureMember_Action(parent.Id, guid, parent.Children.IndexOf(layer) + 1, type));
             name ??= GetUniqueName(type == StructureMemberType.Layer ? new LocalizedString("NEW_LAYER") : new LocalizedString("NEW_FOLDER"), parent);
             internals.ActionAccumulator.AddActions(new StructureMemberName_Action(guid, name));
             if (finish)
@@ -76,35 +77,35 @@ internal class DocumentStructureHelper
     {
         if (memberToMoveIntoPath[0] is not IFolderHandler folder || memberToMoveIntoPath.Contains(memberToMovePath[0]))
             return;
-        int index = folder.Children.Count;
-        if (memberToMoveIntoPath[0].GuidValue == memberToMovePath[1].GuidValue) // member is already in this folder
-            index--;
-        internals.ActionAccumulator.AddFinishedActions(new MoveStructureMember_Action(memberToMovePath[0].GuidValue, folder.GuidValue, index));
+        //int index = folder.Children.Count;
+        if (memberToMoveIntoPath[0].Id == memberToMovePath[1].Id) // member is already in this folder
+            //index--;
+        //internals.ActionAccumulator.AddFinishedActions(new MoveStructureMember_Action(memberToMovePath[0].Id, folder.Id, index));
         return;
     }
 
     private void HandleMoveAboveBelow(List<IStructureMemberHandler> memberToMovePath, List<IStructureMemberHandler> memberToMoveRelativeToPath, bool above)
     {
         IFolderHandler targetFolder = (IFolderHandler)memberToMoveRelativeToPath[1];
-        if (memberToMovePath[1].GuidValue == memberToMoveRelativeToPath[1].GuidValue)
+        if (memberToMovePath[1].Id == memberToMoveRelativeToPath[1].Id)
         { // members are in the same folder
-            int indexOfMemberToMove = targetFolder.Children.IndexOf(memberToMovePath[0]);
-            int indexOfMemberToMoveAbove = targetFolder.Children.IndexOf(memberToMoveRelativeToPath[0]);
-            int index = indexOfMemberToMoveAbove;
-            if (above)
-                index++;
-            if (indexOfMemberToMove < indexOfMemberToMoveAbove)
-                index--;
-            internals.ActionAccumulator.AddFinishedActions(new MoveStructureMember_Action(memberToMovePath[0].GuidValue, targetFolder.GuidValue, index));
+            //int indexOfMemberToMove = targetFolder.Children.IndexOf(memberToMovePath[0]);
+            //int indexOfMemberToMoveAbove = targetFolder.Children.IndexOf(memberToMoveRelativeToPath[0]);
+            //int index = indexOfMemberToMoveAbove;
+           // if (above)
+           //     index++;
+           // if (indexOfMemberToMove < indexOfMemberToMoveAbove)
+           //     index--;
+          //  internals.ActionAccumulator.AddFinishedActions(new MoveStructureMember_Action(memberToMovePath[0].Id, targetFolder.Id, index));
         }
         else
         { // members are in different folders
             if (memberToMoveRelativeToPath.Contains(memberToMovePath[0]))
                 return;
-            int index = targetFolder.Children.IndexOf(memberToMoveRelativeToPath[0]);
-            if (above)
-                index++;
-            internals.ActionAccumulator.AddFinishedActions(new MoveStructureMember_Action(memberToMovePath[0].GuidValue, targetFolder.GuidValue, index));
+          //  int index = targetFolder.Children.IndexOf(memberToMoveRelativeToPath[0]);
+         //   if (above)
+          //      index++;
+          //  internals.ActionAccumulator.AddFinishedActions(new MoveStructureMember_Action(memberToMovePath[0].Id, targetFolder.Id, index));
         }
     }
 
@@ -128,13 +129,13 @@ internal class DocumentStructureHelper
             case StructureMemberPlacement.BelowOutsideFolder:
                 {
                     IFolderHandler refFolder = (IFolderHandler)refPath[1];
-                    int refIndexInParent = refFolder.Children.IndexOf(refPath[0]);
-                    if (refIndexInParent > 0 || refPath.Count == 2)
+                 //   int refIndexInParent = refFolder.Children.IndexOf(refPath[0]);
+                  //  if (refIndexInParent > 0 || refPath.Count == 2)
                     {
                         HandleMoveAboveBelow(memberPath, refPath, false);
                         break;
                     }
-                    HandleMoveAboveBelow(memberPath, doc.StructureHelper.FindPath(refPath[1].GuidValue), false);
+                    HandleMoveAboveBelow(memberPath, doc.StructureHelper.FindPath(refPath[1].Id), false);
                 }
                 break;
         }

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

@@ -181,7 +181,7 @@ internal class DocumentUpdater
 
     private void ProcessRemoveSoftSelectedMember(RemoveSoftSelectedMember_PassthroughAction info)
     {
-        IStructureMemberHandler? member = doc.StructureHelper.Find(info.GuidValue);
+        IStructureMemberHandler? member = doc.StructureHelper.Find(info.Id);
         if (member is null || member.Selection == StructureMemberSelectionType.Hard)
             return;
         if (member.Selection != StructureMemberSelectionType.Soft)
@@ -206,7 +206,7 @@ internal class DocumentUpdater
 
     private void ProcessAddSoftSelectedMember(AddSoftSelectedMember_PassthroughAction info)
     {
-        IStructureMemberHandler? member = doc.StructureHelper.Find(info.GuidValue);
+        IStructureMemberHandler? member = doc.StructureHelper.Find(info.Id);
         if (member is null || member.Selection == StructureMemberSelectionType.Hard)
             return;
         member.Selection = StructureMemberSelectionType.Soft;
@@ -216,7 +216,7 @@ internal class DocumentUpdater
 
     private void ProcessSetSelectedMember(SetSelectedMember_PassthroughAction info)
     {
-        IStructureMemberHandler? member = doc.StructureHelper.Find(info.GuidValue);
+        IStructureMemberHandler? member = doc.StructureHelper.Find(info.Id);
         if (member is null || member.Selection == StructureMemberSelectionType.Hard)
             return;
         
@@ -232,13 +232,13 @@ internal class DocumentUpdater
 
     private void ProcessMaskIsVisible(StructureMemberMaskIsVisible_ChangeInfo info)
     {
-        IStructureMemberHandler? member = doc.StructureHelper.FindOrThrow(info.GuidValue);
+        IStructureMemberHandler? member = doc.StructureHelper.FindOrThrow(info.Id);
         member.SetMaskIsVisible(info.IsVisible);
     }
 
     private void ProcessClipToMemberBelow(StructureMemberClipToMemberBelow_ChangeInfo info)
     {
-        IStructureMemberHandler? member = doc.StructureHelper.FindOrThrow(info.GuidValue);
+        IStructureMemberHandler? member = doc.StructureHelper.FindOrThrow(info.Id);
         member.SetClipToMemberBelowEnabled(info.ClipToMemberBelow);
     }
 
@@ -265,19 +265,19 @@ internal class DocumentUpdater
 
     private void ProcessLayerLockTransparency(LayerLockTransparency_ChangeInfo info)
     {
-        ILayerHandler? layer = (ILayerHandler)doc.StructureHelper.FindOrThrow(info.GuidValue);
+        ILayerHandler? layer = (ILayerHandler)doc.StructureHelper.FindOrThrow(info.Id);
         layer.SetLockTransparency(info.LockTransparency);
     }
 
     private void ProcessStructureMemberBlendMode(StructureMemberBlendMode_ChangeInfo info)
     {
-        IStructureMemberHandler? memberVm = doc.StructureHelper.FindOrThrow(info.GuidValue);
+        IStructureMemberHandler? memberVm = doc.StructureHelper.FindOrThrow(info.Id);
         memberVm.SetBlendMode(info.BlendMode);
     }
 
     private void ProcessStructureMemberMask(StructureMemberMask_ChangeInfo info)
     {
-        IStructureMemberHandler? memberVm = doc.StructureHelper.FindOrThrow(info.GuidValue);
+        IStructureMemberHandler? memberVm = doc.StructureHelper.FindOrThrow(info.Id);
 
         memberVm.SetHasMask(info.HasMask);
         // TODO: Make sure HasMask raises property changed internally
@@ -288,12 +288,12 @@ internal class DocumentUpdater
 
     private void ProcessRefreshViewport(RefreshViewport_PassthroughAction info)
     {
-        helper.State.Viewports[info.Info.GuidValue] = info.Info;
+        helper.State.Viewports[info.Info.Id] = info.Info;
     }
 
     private void ProcessRemoveViewport(RemoveViewport_PassthroughAction info)
     {
-        helper.State.Viewports.Remove(info.GuidValue);
+        helper.State.Viewports.Remove(info.Id);
     }
 
     private void ProcessSize(Size_ChangeInfo info)
@@ -329,12 +329,12 @@ internal class DocumentUpdater
         IStructureMemberHandler memberVM;
         if (info is CreateLayer_ChangeInfo layerInfo)
         {
-            memberVM = doc.LayerHandlerFactory.CreateLayerHandler(helper, info.GuidValue);
+            memberVM = doc.LayerHandlerFactory.CreateLayerHandler(helper, info.Id);
             ((ILayerHandler)memberVM).SetLockTransparency(layerInfo.LockTransparency);
         }
         else if (info is CreateFolder_ChangeInfo)
         {
-            memberVM = doc.FolderHandlerFactory.CreateFolderHandler(helper, info.GuidValue);
+            memberVM = doc.FolderHandlerFactory.CreateFolderHandler(helper, info.Id);
         }
         else
         {
@@ -349,7 +349,7 @@ internal class DocumentUpdater
         memberVM.SetMaskIsVisible(info.MaskIsVisible);
         memberVM.SetBlendMode(info.BlendMode);
 
-        parentFolderVM.Children.Insert(info.Index, memberVM);
+        //parentFolderVM.Children.Insert(info.Index, memberVM);
 
         if (info is CreateFolder_ChangeInfo folderInfo)
         {
@@ -373,49 +373,49 @@ internal class DocumentUpdater
         /*doc.OnPropertyChanged(nameof(doc.SelectedStructureMember));
         doc.OnPropertyChanged(nameof(memberVM.Selection));*/
 
-        //doc.InternalRaiseLayersChanged(new LayersChangedEventArgs(info.GuidValue, LayerAction.Add));
+        //doc.InternalRaiseLayersChanged(new LayersChangedEventArgs(info.Id, LayerAction.Add));
     }
 
     private void ProcessDeleteStructureMember(DeleteStructureMember_ChangeInfo info)
     {
-        (IStructureMemberHandler memberVM, IFolderHandler folderVM) = doc.StructureHelper.FindChildAndParentOrThrow(info.GuidValue);
-        folderVM.Children.Remove(memberVM);
+        (IStructureMemberHandler memberVM, IFolderHandler folderVM) = doc.StructureHelper.FindChildAndParentOrThrow(info.Id);
+        //folderVM.Children.Remove(memberVM);
         if (doc.SelectedStructureMember == memberVM)
             doc.SetSelectedMember(null);
         doc.ClearSoftSelectedMembers();
         // TODO: Make sure property changed events are raised internally
-        //doc.InternalRaiseLayersChanged(new LayersChangedEventArgs(info.GuidValue, LayerAction.Remove));
+        //doc.InternalRaiseLayersChanged(new LayersChangedEventArgs(info.Id, LayerAction.Remove));
     }
 
     private void ProcessUpdateStructureMemberIsVisible(StructureMemberIsVisible_ChangeInfo info)
     {
-        IStructureMemberHandler? memberVM = doc.StructureHelper.FindOrThrow(info.GuidValue);
+        IStructureMemberHandler? memberVM = doc.StructureHelper.FindOrThrow(info.Id);
         memberVM.SetIsVisible(info.IsVisible);
     }
 
     private void ProcessUpdateStructureMemberName(StructureMemberName_ChangeInfo info)
     {
-        IStructureMemberHandler? memberVM = doc.StructureHelper.FindOrThrow(info.GuidValue);
+        IStructureMemberHandler? memberVM = doc.StructureHelper.FindOrThrow(info.Id);
         memberVM.SetName(info.Name);
     }
 
     private void ProcessUpdateStructureMemberOpacity(StructureMemberOpacity_ChangeInfo info)
     {
-        IStructureMemberHandler? memberVM = doc.StructureHelper.FindOrThrow(info.GuidValue);
+        IStructureMemberHandler? memberVM = doc.StructureHelper.FindOrThrow(info.Id);
         memberVM.SetOpacity(info.Opacity);
     }
 
     private void ProcessMoveStructureMember(MoveStructureMember_ChangeInfo info)
     {
-        (IStructureMemberHandler memberVM, IFolderHandler curFolderVM) = doc.StructureHelper.FindChildAndParentOrThrow(info.GuidValue);
+        /*(IStructureMemberHandler memberVM, IFolderHandler curFolderVM) = doc.StructureHelper.FindChildAndParentOrThrow(info.Id);
 
         IFolderHandler? targetFolderVM = (IFolderHandler)doc.StructureHelper.FindOrThrow(info.ParentToGuid);
 
         curFolderVM.Children.Remove(memberVM);
-        targetFolderVM.Children.Insert(info.NewIndex, memberVM);
+        targetFolderVM.Children.Insert(info.NewIndex, memberVM);*/
 
         // TODO: Make sure property changed events are raised internally
-        //doc.InternalRaiseLayersChanged(new LayersChangedEventArgs(info.GuidValue, LayerAction.Move));
+        //doc.InternalRaiseLayersChanged(new LayersChangedEventArgs(info.Id, LayerAction.Move));
     }
     
     private void ProcessCreateRasterKeyFrame(CreateRasterKeyFrame_ChangeInfo info)

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

@@ -72,7 +72,7 @@ internal class DocumentOperationsModule : IDocumentOperations
         bool drawOnMask = member is not ILayerHandler layer || layer.ShouldDrawOnMask;
         if (drawOnMask && !member.HasMaskBindable)
             return;
-        Internals.ActionAccumulator.AddActions(new ClearSelectedArea_Action(member.GuidValue, drawOnMask, frame));
+        Internals.ActionAccumulator.AddActions(new ClearSelectedArea_Action(member.Id, drawOnMask, frame));
         if (clearSelection)
             Internals.ActionAccumulator.AddActions(new ClearSelection_Action());
         Internals.ActionAccumulator.AddFinishedActions();
@@ -272,8 +272,8 @@ internal class DocumentOperationsModule : IDocumentOperations
         if (Internals.ChangeController.IsChangeActive)
             return;
         if (!member.MaskIsVisibleBindable)
-            Internals.ActionAccumulator.AddActions(new StructureMemberMaskIsVisible_Action(true, member.GuidValue));
-        Internals.ActionAccumulator.AddFinishedActions(new CreateStructureMemberMask_Action(member.GuidValue));
+            Internals.ActionAccumulator.AddActions(new StructureMemberMaskIsVisible_Action(true, member.Id));
+        Internals.ActionAccumulator.AddFinishedActions(new CreateStructureMemberMask_Action(member.Id));
     }
 
     /// <summary>
@@ -283,7 +283,7 @@ internal class DocumentOperationsModule : IDocumentOperations
     {
         if (Internals.ChangeController.IsChangeActive)
             return;
-        Internals.ActionAccumulator.AddFinishedActions(new DeleteStructureMemberMask_Action(member.GuidValue));
+        Internals.ActionAccumulator.AddFinishedActions(new DeleteStructureMemberMask_Action(member.Id));
     }
     
     /// <summary>
@@ -294,7 +294,7 @@ internal class DocumentOperationsModule : IDocumentOperations
         if (Internals.ChangeController.IsChangeActive)
             return;
         
-        Internals.ActionAccumulator.AddFinishedActions(new ApplyMask_Action(member.GuidValue, frame), new DeleteStructureMemberMask_Action(member.GuidValue));
+        Internals.ActionAccumulator.AddFinishedActions(new ApplyMask_Action(member.Id, frame), new DeleteStructureMemberMask_Action(member.Id));
     }
 
     /// <summary>
@@ -384,14 +384,14 @@ internal class DocumentOperationsModule : IDocumentOperations
         var (child, parent) = Document.StructureHelper.FindChildAndParent(members[0]);
         if (child is null || parent is null)
             return;
-        int index = parent.Children.IndexOf(child);
+        //int index = parent.Children.IndexOf(child);
         Guid newGuid = Guid.NewGuid();
 
         //make a new layer, put combined image onto it, delete layers that were merged
-        Internals.ActionAccumulator.AddActions(
-            new CreateStructureMember_Action(parent.GuidValue, newGuid, index, StructureMemberType.Layer),
+        /*Internals.ActionAccumulator.AddActions(
+            new CreateStructureMember_Action(parent.Id, newGuid, index, StructureMemberType.Layer),
             new StructureMemberName_Action(newGuid, child.NameBindable),
-            new CombineStructureMembersOnto_Action(members.ToHashSet(), newGuid, Document.AnimationHandler.ActiveFrameBindable));
+            new CombineStructureMembersOnto_Action(members.ToHashSet(), newGuid, Document.AnimationHandler.ActiveFrameBindable));*/
         foreach (var member in members)
             Internals.ActionAccumulator.AddActions(new DeleteStructureMember_Action(member));
         Internals.ActionAccumulator.AddActions(new ChangeBoundary_Action());
@@ -580,10 +580,10 @@ internal class DocumentOperationsModule : IDocumentOperations
 
         if (!Document.SelectedStructureMember.HasMaskBindable)
         {
-            Internals.ActionAccumulator.AddActions(new CreateStructureMemberMask_Action(member.GuidValue));
+            Internals.ActionAccumulator.AddActions(new CreateStructureMemberMask_Action(member.Id));
         }
         
-        Internals.ActionAccumulator.AddFinishedActions(new SelectionToMask_Action(member.GuidValue, mode, frame));
+        Internals.ActionAccumulator.AddFinishedActions(new SelectionToMask_Action(member.Id, mode, frame));
     }
 
     public void CropToSelection(int frame, bool clearSelection = true)

+ 37 - 46
src/PixiEditor.AvaloniaUI/Models/DocumentModels/Public/DocumentStructureModule.cs

@@ -20,22 +20,23 @@ internal class DocumentStructureModule
 
     public IStructureMemberHandler? FindFirstWhere(Predicate<IStructureMemberHandler> predicate)
     {
-        return FindFirstWhere(predicate, doc.StructureRoot);
+        return FindFirstWhere(predicate, doc.NodeGraphHandler);
     }
-    private IStructureMemberHandler? FindFirstWhere(Predicate<IStructureMemberHandler> predicate, IFolderHandler folderVM)
+    private IStructureMemberHandler? FindFirstWhere(Predicate<IStructureMemberHandler> predicate, INodeGraphHandler graphVM)
     {
-        foreach (IStructureMemberHandler? child in folderVM.Children)
+        IStructureMemberHandler? result = null;
+        graphVM.TryTraverse(node =>
         {
-            if (predicate(child))
-                return child;
-            if (child is IFolderHandler innerFolderVM)
+            if (node is IStructureMemberHandler structureMemberNode && predicate(structureMemberNode))
             {
-                IStructureMemberHandler? result = FindFirstWhere(predicate, innerFolderVM);
-                if (result is not null)
-                    return result;
+                result = structureMemberNode;
+                return false;
             }
-        }
-        return null;
+
+            return true;
+        });
+        
+        return result;
     }
 
     public (IStructureMemberHandler?, IFolderHandler?) FindChildAndParent(Guid childGuid)
@@ -59,10 +60,9 @@ internal class DocumentStructureModule
     }
     public List<IStructureMemberHandler> FindPath(Guid guid)
     {
-        List<IStructureMemberHandler>? list = new List<IStructureMemberHandler>();
-        if (FillPath(doc.StructureRoot, guid, list))
-            list.Add(doc.StructureRoot);
-        return list;
+        List<INodeHandler>? list = new List<INodeHandler>();
+        FillPath(doc.NodeGraphHandler.OutputNode, guid, list);
+        return list.Cast<IStructureMemberHandler>().ToList();
     }
     
     /// <summary>
@@ -72,52 +72,43 @@ internal class DocumentStructureModule
     public List<ILayerHandler> GetAllLayers()
     {
         List<ILayerHandler> layers = new List<ILayerHandler>();
-        foreach (IStructureMemberHandler? member in doc.StructureRoot.Children)
+        
+        doc.NodeGraphHandler.TryTraverse(node =>
         {
-            if (member is ILayerHandler layer)
+            if (node is ILayerHandler layer)
                 layers.Add(layer);
-            else if (member is IFolderHandler folder)
-                layers.AddRange(GetAllLayers(folder, layers));
-        }
+            return true;
+        });
         
         return layers;
     }
     
-    private List<ILayerHandler> GetAllLayers(IFolderHandler folder, List<ILayerHandler> layers)
+    private bool FillPath(INodeHandler node, Guid guid, List<INodeHandler> toFill)
     {
-        foreach (IStructureMemberHandler? member in folder.Children)
+        if (node.Id == guid)
         {
-            if (member is ILayerHandler layer)
-                layers.Add(layer);
-            else if (member is IFolderHandler innerFolder)
-                layers.AddRange(GetAllLayers(innerFolder, layers));
+            return true;
         }
-        return layers;
-    }
 
-    private bool FillPath(IFolderHandler folder, Guid guid, List<IStructureMemberHandler> toFill)
-    {
-        if (folder.GuidValue == guid)
+        if (node is IStructureMemberHandler structureNode)
         {
-            return true;
+            toFill.Add(structureNode);
         }
-        foreach (IStructureMemberHandler? member in folder.Children)
+
+        bool found = false;
+
+        node.TraverseBackwards(newNode =>
         {
-            if (member is ILayerHandler childLayer && childLayer.GuidValue == guid)
+            if (newNode is IStructureMemberHandler strNode && newNode.Id == guid)
             {
-                toFill.Add(member);
-                return true;
+                toFill.Add(strNode);
+                found = true;
+                return false;
             }
 
-            if (member is IFolderHandler childFolder)
-            {
-                if (FillPath(childFolder, guid, toFill))
-                {
-                    toFill.Add(childFolder);
-                    return true;
-                }
-            }
-        }
-        return false;
+            return true;
+        });
+
+        return found;
     }
 }

+ 1 - 1
src/PixiEditor.AvaloniaUI/Models/DocumentModels/UpdateableChangeExecutors/BrightnessToolExecutor.cs

@@ -25,7 +25,7 @@ internal class BrightnessToolExecutor : UpdateableChangeExecutor
         if (member is not ILayerHandler layer || layer.ShouldDrawOnMask)
             return ExecutionState.Error;
 
-        guidValue = member.GuidValue;
+        guidValue = member.Id;
         repeat = tool.BrightnessMode == BrightnessMode.Repeat;
         toolSize = toolbar.ToolSize;
         correctionFactor = tool.Darken || tool.UsedWith == MouseButton.Right ? -tool.CorrectionFactor : tool.CorrectionFactor;

+ 1 - 1
src/PixiEditor.AvaloniaUI/Models/DocumentModels/UpdateableChangeExecutors/EraserToolExecutor.cs

@@ -35,7 +35,7 @@ internal class EraserToolExecutor : UpdateableChangeExecutor
             return ExecutionState.Error;
 
 
-        guidValue = member.GuidValue;
+        guidValue = member.Id;
         color = GetHandler<IColorsHandler>().PrimaryColor;
         toolSize = toolbar.ToolSize;
 

+ 1 - 1
src/PixiEditor.AvaloniaUI/Models/DocumentModels/UpdateableChangeExecutors/FloodFillToolExecutor.cs

@@ -32,7 +32,7 @@ internal class FloodFillToolExecutor : UpdateableChangeExecutor
             return ExecutionState.Error;
 
         colorsVM.AddSwatch(new PaletteColor(color.R, color.G, color.B));
-        memberGuid = member.GuidValue;
+        memberGuid = member.Id;
         considerAllLayers = fillTool.ConsiderAllLayers;
         color = colorsVM.PrimaryColor;
         var pos = controller!.LastPixelPosition;

+ 1 - 1
src/PixiEditor.AvaloniaUI/Models/DocumentModels/UpdateableChangeExecutors/LineToolExecutor.cs

@@ -42,7 +42,7 @@ internal class LineToolExecutor : UpdateableChangeExecutor
         startPos = controller!.LastPixelPosition;
         strokeColor = colorsVM.PrimaryColor;
         strokeWidth = toolViewModel.ToolSize;
-        memberGuid = member.GuidValue;
+        memberGuid = member.Id;
 
         colorsVM.AddSwatch(new PaletteColor(strokeColor.R, strokeColor.G, strokeColor.B));
 

+ 1 - 1
src/PixiEditor.AvaloniaUI/Models/DocumentModels/UpdateableChangeExecutors/MagicWandToolExecutor.cs

@@ -27,7 +27,7 @@ internal class MagicWandToolExecutor : UpdateableChangeExecutor
         memberGuids = members;
         considerAllLayers = magicWand.DocumentScope == DocumentScope.AllLayers;
         if (considerAllLayers)
-            memberGuids = document!.StructureHelper.GetAllLayers().Select(x => x.GuidValue).ToList();
+            memberGuids = document!.StructureHelper.GetAllLayers().Select(x => x.Id).ToList();
         var pos = controller!.LastPixelPosition;
 
         internals!.ActionAccumulator.AddActions(new MagicWand_Action(memberGuids, pos, mode, document!.AnimationHandler.ActiveFrameBindable));

+ 1 - 1
src/PixiEditor.AvaloniaUI/Models/DocumentModels/UpdateableChangeExecutors/PasteImageExecutor.cs

@@ -46,7 +46,7 @@ internal class PasteImageExecutor : UpdateableChangeExecutor
                     return ExecutionState.Error;
             }
             
-            memberGuid = member.GuidValue;
+            memberGuid = member.Id;
         }
 
         ShapeCorners corners = new(new RectD(pos, image.Size));

+ 1 - 1
src/PixiEditor.AvaloniaUI/Models/DocumentModels/UpdateableChangeExecutors/PenToolExecutor.cs

@@ -33,7 +33,7 @@ internal class PenToolExecutor : UpdateableChangeExecutor
         if (!drawOnMask && member is not ILayerHandler)
             return ExecutionState.Error;
 
-        guidValue = member.GuidValue;
+        guidValue = member.Id;
         color = colorsHandler.PrimaryColor;
         toolSize = toolbar.ToolSize;
         pixelPerfect = penTool.PixelPerfectEnabled;

+ 1 - 1
src/PixiEditor.AvaloniaUI/Models/DocumentModels/UpdateableChangeExecutors/ShapeToolExecutor.cs

@@ -47,7 +47,7 @@ internal abstract class ShapeToolExecutor<T> : UpdateableChangeExecutor where T
         startPos = controller!.LastPixelPosition;
         strokeColor = colorsVM.PrimaryColor;
         strokeWidth = toolbar.ToolSize;
-        memberGuid = member.GuidValue;
+        memberGuid = member.Id;
 
         colorsVM.AddSwatch(new PaletteColor(strokeColor.R, strokeColor.G, strokeColor.B));
         DrawShape(startPos, true);

+ 3 - 3
src/PixiEditor.AvaloniaUI/Models/DocumentModels/UpdateableChangeExecutors/ShiftLayerExecutor.cs

@@ -27,13 +27,13 @@ internal class ShiftLayerExecutor : UpdateableChangeExecutor
 
         if (tool.MoveAllLayers)
         {
-            _affectedMemberGuids.AddRange(document.StructureHelper.GetAllLayers().Select(x => x.GuidValue));
+            _affectedMemberGuids.AddRange(document.StructureHelper.GetAllLayers().Select(x => x.Id));
         }
         else
         {
             if (member != null)
-                _affectedMemberGuids.Add(member.GuidValue);
-            _affectedMemberGuids.AddRange(document!.SoftSelectedStructureMembers.Select(x => x.GuidValue));
+                _affectedMemberGuids.Add(member.Id);
+            _affectedMemberGuids.AddRange(document!.SoftSelectedStructureMembers.Select(x => x.Id));
         }
 
         RemoveDrawOnMaskLayers(_affectedMemberGuids);

+ 1 - 1
src/PixiEditor.AvaloniaUI/Models/DocumentModels/UpdateableChangeExecutors/StructureMemberOpacityExecutor.cs

@@ -9,7 +9,7 @@ internal class StructureMemberOpacityExecutor : UpdateableChangeExecutor
     {
         if (document.SelectedStructureMember is null)
             return ExecutionState.Error;
-        memberGuid = document.SelectedStructureMember.GuidValue;
+        memberGuid = document.SelectedStructureMember.Id;
         StructureMemberOpacity_Action action = new StructureMemberOpacity_Action(memberGuid, document.SelectedStructureMember.OpacityBindable);
         internals.ActionAccumulator.AddActions(action);
         return ExecutionState.Success;

+ 1 - 1
src/PixiEditor.AvaloniaUI/Models/DocumentModels/UpdateableChangeExecutors/TransformSelectedAreaExecutor.cs

@@ -39,7 +39,7 @@ internal class TransformSelectedAreaExecutor : UpdateableChangeExecutor
 
         ShapeCorners corners = new(document.SelectionPathBindable.TightBounds);
         document.TransformHandler.ShowTransform(DocumentTransformMode.Scale_Rotate_Shear_Perspective, true, corners, Type == ExecutorType.Regular);
-        membersToTransform = members.Select(static a => a.GuidValue).ToArray();
+        membersToTransform = members.Select(static a => a.Id).ToArray();
         internals!.ActionAccumulator.AddActions(
             new TransformSelectedArea_Action(membersToTransform, corners, tool.KeepOriginalImage, false, document.AnimationHandler.ActiveFrameBindable));
         return ExecutionState.Success;

+ 1 - 1
src/PixiEditor.AvaloniaUI/Models/DocumentPassthroughActions/AddSoftSelectedMember_PassthroughAction.cs

@@ -2,4 +2,4 @@
 using PixiEditor.ChangeableDocument.ChangeInfos;
 
 namespace PixiEditor.AvaloniaUI.Models.DocumentPassthroughActions;
-internal record class AddSoftSelectedMember_PassthroughAction(Guid GuidValue) : IChangeInfo, IAction;
+internal record class AddSoftSelectedMember_PassthroughAction(Guid Id) : IChangeInfo, IAction;

+ 1 - 1
src/PixiEditor.AvaloniaUI/Models/DocumentPassthroughActions/RemoveSoftSelectedMember_PassthroughAction.cs

@@ -2,4 +2,4 @@
 using PixiEditor.ChangeableDocument.ChangeInfos;
 
 namespace PixiEditor.AvaloniaUI.Models.DocumentPassthroughActions;
-internal record class RemoveSoftSelectedMember_PassthroughAction(Guid GuidValue) : IAction, IChangeInfo;
+internal record class RemoveSoftSelectedMember_PassthroughAction(Guid Id) : IAction, IChangeInfo;

+ 1 - 1
src/PixiEditor.AvaloniaUI/Models/DocumentPassthroughActions/RemoveViewport_PassthroughAction.cs

@@ -2,4 +2,4 @@
 using PixiEditor.ChangeableDocument.ChangeInfos;
 
 namespace PixiEditor.AvaloniaUI.Models.DocumentPassthroughActions;
-internal record class RemoveViewport_PassthroughAction(Guid GuidValue) : IAction, IChangeInfo;
+internal record class RemoveViewport_PassthroughAction(Guid Id) : IAction, IChangeInfo;

+ 1 - 1
src/PixiEditor.AvaloniaUI/Models/DocumentPassthroughActions/SetSelectedMember_PassthroughAction.cs

@@ -2,4 +2,4 @@
 using PixiEditor.ChangeableDocument.ChangeInfos;
 
 namespace PixiEditor.AvaloniaUI.Models.DocumentPassthroughActions;
-internal record class SetSelectedMember_PassthroughAction(Guid GuidValue) : IAction, IChangeInfo;
+internal record class SetSelectedMember_PassthroughAction(Guid Id) : IAction, IChangeInfo;

+ 1 - 1
src/PixiEditor.AvaloniaUI/Models/Handlers/IDocument.cs

@@ -23,7 +23,7 @@ internal interface IDocument : IHandler
     public IReferenceLayerHandler ReferenceLayerHandler { get; }
     public IAnimationHandler AnimationHandler { get; }
     public VectorPath SelectionPathBindable { get; }
-    public IFolderHandler StructureRoot { get; }
+    public INodeGraphHandler NodeGraphHandler { get; }
     public Dictionary<ChunkResolution, Surface> Surfaces { get; set; }
     public DocumentStructureModule StructureHelper { get; }
     public Surface PreviewSurface { get; set; }

+ 0 - 1
src/PixiEditor.AvaloniaUI/Models/Handlers/IFolderHandler.cs

@@ -4,5 +4,4 @@ namespace PixiEditor.AvaloniaUI.Models.Handlers;
 
 internal interface IFolderHandler : IStructureMemberHandler
 {
-    public ObservableCollection<IStructureMemberHandler> Children { get; }
 }

+ 7 - 0
src/PixiEditor.AvaloniaUI/Models/Handlers/IInputPropertyHandler.cs

@@ -0,0 +1,7 @@
+namespace PixiEditor.AvaloniaUI.Models.Handlers;
+
+public interface IInputPropertyHandler : INodePropertyHandler
+{
+    bool INodePropertyHandler.IsInput => true;
+    public IOutputPropertyHandler? Connection { get; set; }
+}

+ 12 - 0
src/PixiEditor.AvaloniaUI/Models/Handlers/INodeGraphHandler.cs

@@ -0,0 +1,12 @@
+using System.Collections.ObjectModel;
+using PixiEditor.AvaloniaUI.ViewModels.Nodes;
+
+namespace PixiEditor.AvaloniaUI.Models.Handlers;
+
+public interface INodeGraphHandler
+{
+   public ObservableCollection<INodeHandler> AllNodes { get; }
+   public ObservableCollection<NodeConnectionViewModel> Connections { get; }
+   public INodeHandler OutputNode { get; }
+   bool TryTraverse(Func<INodeHandler, bool> func);
+}

+ 17 - 0
src/PixiEditor.AvaloniaUI/Models/Handlers/INodeHandler.cs

@@ -0,0 +1,17 @@
+using System.Collections.ObjectModel;
+using ChunkyImageLib;
+using PixiEditor.Numerics;
+
+namespace PixiEditor.AvaloniaUI.Models.Handlers;
+
+public interface INodeHandler
+{
+    public Guid Id { get; }
+    public string NodeName { get; set; }
+    public ObservableCollection<IInputPropertyHandler> Inputs { get; }
+    public ObservableCollection<IOutputPropertyHandler> Outputs { get; }
+    public Surface ResultPreview { get; set; }
+    public VecD Position { get; set; }
+    void TraverseBackwards(Func<INodeHandler, bool> func);
+    void TraverseForwards(Func<INodeHandler, bool> func);
+}

+ 9 - 0
src/PixiEditor.AvaloniaUI/Models/Handlers/INodePropertyHandler.cs

@@ -0,0 +1,9 @@
+namespace PixiEditor.AvaloniaUI.Models.Handlers;
+
+public interface INodePropertyHandler
+{
+    public string Name { get; set; }
+    public object Value { get; set; }
+    public bool IsInput { get; }
+    public INodeHandler Node { get; set; }
+}

+ 9 - 0
src/PixiEditor.AvaloniaUI/Models/Handlers/IOutputPropertyHandler.cs

@@ -0,0 +1,9 @@
+using System.Collections.ObjectModel;
+
+namespace PixiEditor.AvaloniaUI.Models.Handlers;
+
+public interface IOutputPropertyHandler : INodePropertyHandler
+{
+    bool INodePropertyHandler.IsInput => false;
+    public ObservableCollection<IInputPropertyHandler> Connections { get; }
+}

+ 1 - 2
src/PixiEditor.AvaloniaUI/Models/Handlers/IStructureMemberHandler.cs

@@ -9,10 +9,9 @@ using BlendMode = PixiEditor.ChangeableDocument.Enums.BlendMode;
 
 namespace PixiEditor.AvaloniaUI.Models.Handlers;
 
-internal interface IStructureMemberHandler : IHandler
+internal interface IStructureMemberHandler : INodeHandler
 {
     public bool HasMaskBindable { get; }
-    public Guid GuidValue { get; }
     public string NameBindable { get; set; }
     public Surface? MaskPreviewSurface { get; set; }
     public Surface? PreviewSurface { get; set; }

+ 1 - 1
src/PixiEditor.AvaloniaUI/Models/Position/ViewportInfo.cs

@@ -13,6 +13,6 @@ internal readonly record struct ViewportInfo(
     VecD RealDimensions,
     VecD Dimensions,
     ChunkResolution Resolution,
-    Guid GuidValue,
+    Guid Id,
     bool Delayed,
     Action InvalidateVisual);

+ 46 - 45
src/PixiEditor.AvaloniaUI/Models/Rendering/AffectedAreasGatherer.cs

@@ -3,6 +3,7 @@ using ChunkyImageLib;
 using ChunkyImageLib.DataHolders;
 using PixiEditor.AvaloniaUI.Models.DocumentPassthroughActions;
 using PixiEditor.ChangeableDocument;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.ChangeInfos;
 using PixiEditor.ChangeableDocument.ChangeInfos.Animation;
@@ -43,27 +44,27 @@ internal class AffectedAreasGatherer
                     if (info.Area.Chunks is null)
                         throw new InvalidOperationException("Chunks must not be null");
                     AddToMainImage(info.Area);
-                    AddToImagePreviews(info.GuidValue, info.Area, true);
-                    AddToMaskPreview(info.GuidValue, info.Area);
+                    AddToImagePreviews(info.Id, info.Area, true);
+                    AddToMaskPreview(info.Id, info.Area);
                     break;
                 case LayerImageArea_ChangeInfo info:
                     if (info.Area.Chunks is null)
                         throw new InvalidOperationException("Chunks must not be null");
                     AddToMainImage(info.Area);
-                    AddToImagePreviews(info.GuidValue, info.Area);
+                    AddToImagePreviews(info.Id, info.Area);
                     break;
                 case CreateStructureMember_ChangeInfo info:
-                    AddAllToMainImage(info.GuidValue, 0);
-                    AddAllToImagePreviews(info.GuidValue, 0);
-                    AddAllToMaskPreview(info.GuidValue);
+                    AddAllToMainImage(info.Id, 0);
+                    AddAllToImagePreviews(info.Id, 0);
+                    AddAllToMaskPreview(info.Id);
                     break;
                 case DeleteStructureMember_ChangeInfo info:
                     AddWholeCanvasToMainImage();
                     AddWholeCanvasToImagePreviews(info.ParentGuid);
                     break;
                 case MoveStructureMember_ChangeInfo info:
-                    AddAllToMainImage(info.GuidValue, ActiveFrame);
-                    AddAllToImagePreviews(info.GuidValue, ActiveFrame, true);
+                    AddAllToMainImage(info.Id, ActiveFrame);
+                    AddAllToImagePreviews(info.Id, ActiveFrame, true);
                     if (info.ParentFromGuid != info.ParentToGuid)
                         AddWholeCanvasToImagePreviews(info.ParentFromGuid);
                     break;
@@ -74,28 +75,28 @@ internal class AffectedAreasGatherer
                     break;
                 case StructureMemberMask_ChangeInfo info:
                     AddWholeCanvasToMainImage();
-                    AddWholeCanvasToMaskPreview(info.GuidValue);
-                    AddWholeCanvasToImagePreviews(info.GuidValue, true);
+                    AddWholeCanvasToMaskPreview(info.Id);
+                    AddWholeCanvasToImagePreviews(info.Id, true);
                     break;
                 case StructureMemberBlendMode_ChangeInfo info:
-                    AddAllToMainImage(info.GuidValue, ActiveFrame);
-                    AddAllToImagePreviews(info.GuidValue, ActiveFrame, true);
+                    AddAllToMainImage(info.Id, ActiveFrame);
+                    AddAllToImagePreviews(info.Id, ActiveFrame, true);
                     break;
                 case StructureMemberClipToMemberBelow_ChangeInfo info:
-                    AddAllToMainImage(info.GuidValue, ActiveFrame);
-                    AddAllToImagePreviews(info.GuidValue, ActiveFrame, true);
+                    AddAllToMainImage(info.Id, ActiveFrame);
+                    AddAllToImagePreviews(info.Id, ActiveFrame, true);
                     break;
                 case StructureMemberOpacity_ChangeInfo info:
-                    AddAllToMainImage(info.GuidValue, ActiveFrame);
-                    AddAllToImagePreviews(info.GuidValue, ActiveFrame, true);
+                    AddAllToMainImage(info.Id, ActiveFrame);
+                    AddAllToImagePreviews(info.Id, ActiveFrame, true);
                     break;
                 case StructureMemberIsVisible_ChangeInfo info:
-                    AddAllToMainImage(info.GuidValue, ActiveFrame);
-                    AddAllToImagePreviews(info.GuidValue, ActiveFrame, true);
+                    AddAllToMainImage(info.Id, ActiveFrame);
+                    AddAllToImagePreviews(info.Id, ActiveFrame, true);
                     break;
                 case StructureMemberMaskIsVisible_ChangeInfo info:
-                    AddAllToMainImage(info.GuidValue, ActiveFrame, false);
-                    AddAllToImagePreviews(info.GuidValue, ActiveFrame, true);
+                    AddAllToMainImage(info.Id, ActiveFrame, false);
+                    AddAllToImagePreviews(info.Id, ActiveFrame, true);
                     break;
                 case CreateRasterKeyFrame_ChangeInfo info:
                     if (info.CloneFromExisting)
@@ -132,27 +133,27 @@ internal class AffectedAreasGatherer
     private void AddAllToImagePreviews(Guid memberGuid, int frame, bool ignoreSelf = false)
     {
         var member = tracker.Document.FindMember(memberGuid);
-        if (member is IReadOnlyLayer layer)
+        if (member is IReadOnlyLayerNode layer)
         {
-            var chunks = layer.Rasterize(frame).FindAllChunks();
+            var chunks = layer.Execute(frame).FindAllChunks();
             AddToImagePreviews(memberGuid, new AffectedArea(chunks), ignoreSelf);
         }
-        else if (member is IReadOnlyFolder folder)
+        else if (member is IReadOnlyFolderNode folder)
         {
             AddWholeCanvasToImagePreviews(memberGuid, ignoreSelf);
-            foreach (var child in folder.Children)
-                AddAllToImagePreviews(child.GuidValue, frame);
+            /*foreach (var child in folder.Children)
+                AddAllToImagePreviews(child.Id, frame);*/
         }
     }
 
     private void AddAllToMainImage(Guid memberGuid, int frame, bool useMask = true)
     {
         var member = tracker.Document.FindMember(memberGuid);
-        if (member is IReadOnlyLayer layer)
+        if (member is IReadOnlyLayerNode layer)
         {
-            var chunks = layer.Rasterize(frame).FindAllChunks();
-            if (layer.Mask is not null && layer.MaskIsVisible && useMask)
-                chunks.IntersectWith(layer.Mask.FindAllChunks());
+            var chunks = layer.Execute(frame).FindAllChunks();
+            if (layer.Mask.Value is not null && layer.MaskIsVisible.Value && useMask)
+                chunks.IntersectWith(layer.Mask.Value.FindAllChunks());
             AddToMainImage(new AffectedArea(chunks));
         }
         else
@@ -165,19 +166,19 @@ 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)
+        if (member is IReadOnlyFolderNode folder)
         {
-            foreach (var child in folder.Children)
-                AddAllToMaskPreview(child.GuidValue);
+            /*foreach (var child in folder.Children)
+                AddAllToMaskPreview(child.Id);
+        */
         }
     }
 
-
     private void AddToMainImage(AffectedArea area)
     {
         var temp = MainImageArea;
@@ -193,15 +194,15 @@ internal class AffectedAreasGatherer
         for (int i = ignoreSelf ? 1 : 0; i < path.Count - 1; i++)
         {
             var member = path[i];
-            if (!ImagePreviewAreas.ContainsKey(member.GuidValue))
+            if (!ImagePreviewAreas.ContainsKey(member.Id))
             {
-                ImagePreviewAreas[member.GuidValue] = new AffectedArea(area);
+                ImagePreviewAreas[member.Id] = new AffectedArea(area);
             }
             else
             {
-                var temp = ImagePreviewAreas[member.GuidValue];
+                var temp = ImagePreviewAreas[member.Id];
                 temp.UnionWith(area);
-                ImagePreviewAreas[member.GuidValue] = temp;
+                ImagePreviewAreas[member.Id] = temp;
             }
         }
     }
@@ -235,9 +236,9 @@ internal class AffectedAreasGatherer
         for (int i = ignoreSelf ? 1 : 0; i < path.Count - 1; i++)
         {
             var member = path[i];
-            if (!ImagePreviewAreas.ContainsKey(member.GuidValue))
-                ImagePreviewAreas[member.GuidValue] = new AffectedArea();
-            ImagePreviewAreas[member.GuidValue] = AddWholeArea(ImagePreviewAreas[member.GuidValue]);
+            if (!ImagePreviewAreas.ContainsKey(member.Id))
+                ImagePreviewAreas[member.Id] = new AffectedArea();
+            ImagePreviewAreas[member.Id] = AddWholeArea(ImagePreviewAreas[member.Id]);
         }
     }
 
@@ -251,15 +252,15 @@ internal class AffectedAreasGatherer
 
     private void AddWholeCanvasToEveryImagePreview()
     {
-        tracker.Document.ForEveryReadonlyMember((member) => AddWholeCanvasToImagePreviews(member.GuidValue));
+        tracker.Document.ForEveryReadonlyMember((member) => AddWholeCanvasToImagePreviews(member.Id));
     }
 
     private void AddWholeCanvasToEveryMaskPreview()
     {
         tracker.Document.ForEveryReadonlyMember((member) => 
         {
-            if (member.Mask is not null)
-                AddWholeCanvasToMaskPreview(member.GuidValue);
+            if (member.Mask.Value is not null)
+                AddWholeCanvasToMaskPreview(member.Id);
         });
     }
 

+ 2 - 2
src/PixiEditor.AvaloniaUI/Models/Rendering/CanvasUpdater.cs

@@ -195,7 +195,7 @@ internal class CanvasUpdater
             screenSurface.DrawingSurface.Canvas.ClipRect((RectD)globalScaledClippingRectangle);
         }
 
-        ChunkRenderer.MergeWholeStructure(chunkPos, resolution, internals.Tracker.Document.StructureRoot, doc.AnimationHandler.ActiveFrameBindable, globalClippingRectangle).Switch(
+        /*ChunkRenderer.MergeWholeStructure(chunkPos, resolution, internals.Tracker.Document.StructureRoot, doc.AnimationHandler.ActiveFrameBindable, globalClippingRectangle).Switch(
             (Chunk chunk) =>
             {
                 screenSurface.DrawingSurface.Canvas.DrawSurface(chunk.Surface.DrawingSurface, chunkPos.Multiply(chunk.PixelSize), ReplacingPaint);
@@ -205,7 +205,7 @@ internal class CanvasUpdater
             {
                 var pos = chunkPos * resolution.PixelSize();
                 screenSurface.DrawingSurface.Canvas.DrawRect(pos.X, pos.Y, resolution.PixelSize(), resolution.PixelSize(), ClearPaint);
-            });
+            });*/
 
         if (globalScaledClippingRectangle is not null)
             screenSurface.DrawingSurface.Canvas.Restore();

+ 28 - 26
src/PixiEditor.AvaloniaUI/Models/Rendering/MemberPreviewUpdater.cs

@@ -11,6 +11,7 @@ using PixiEditor.AvaloniaUI.Models.DocumentModels;
 using PixiEditor.AvaloniaUI.Models.Handlers;
 using PixiEditor.AvaloniaUI.Models.Rendering.RenderInfos;
 using PixiEditor.AvaloniaUI.ViewModels.Document;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Rendering;
 using PixiEditor.DrawingApi.Core.Numerics;
@@ -141,10 +142,10 @@ internal class MemberPreviewUpdater
 
         internals.Tracker.Document.ForEveryReadonlyMember(member =>
         {
-            if (lastMainPreviewTightBounds.ContainsKey(member.GuidValue))
-                clearedLastMainPreviewTightBounds.Add(member.GuidValue, lastMainPreviewTightBounds[member.GuidValue]);
-            if (lastMaskPreviewTightBounds.ContainsKey(member.GuidValue))
-                clearedLastMaskPreviewTightBounds.Add(member.GuidValue, lastMaskPreviewTightBounds[member.GuidValue]);
+            if (lastMainPreviewTightBounds.ContainsKey(member.Id))
+                clearedLastMainPreviewTightBounds.Add(member.Id, lastMainPreviewTightBounds[member.Id]);
+            if (lastMaskPreviewTightBounds.ContainsKey(member.Id))
+                clearedLastMaskPreviewTightBounds.Add(member.Id, lastMaskPreviewTightBounds[member.Id]);
         });
 
         lastMainPreviewTightBounds = clearedLastMainPreviewTightBounds;
@@ -272,7 +273,7 @@ internal class MemberPreviewUpdater
     /// Returns the previosly known committed tight bounds if there are no reasons to believe they have changed (based on the passed <paramref name="currentlyAffectedArea"/>).
     /// Otherwise, calculates the new bounds via <see cref="FindLayerTightBounds"/> and returns them.
     /// </summary>
-    private RectI? GetOrFindMemberTightBounds(IReadOnlyStructureMember member, int atFrame,
+    private RectI? GetOrFindMemberTightBounds(IReadOnlyStructureNode member, int atFrame,
         AffectedArea currentlyAffectedArea, bool forMask)
     {
         if (forMask && member.Mask is null)
@@ -282,7 +283,7 @@ internal class MemberPreviewUpdater
 
         var targetLastCollection = forMask ? lastMaskPreviewTightBounds : lastMainPreviewTightBounds;
 
-        if (targetLastCollection.TryGetValue(member.GuidValue, out RectI tightBounds))
+        if (targetLastCollection.TryGetValue(member.Id, out RectI tightBounds))
             prevTightBounds = tightBounds;
 
         if (prevTightBounds is not null && currentlyAffectedArea.GlobalArea is not null &&
@@ -294,8 +295,8 @@ internal class MemberPreviewUpdater
 
         return member switch
         {
-            IReadOnlyLayer layer => FindLayerTightBounds(layer, atFrame, forMask),
-            IReadOnlyFolder folder => FindFolderTightBounds(folder, atFrame, forMask),
+            IReadOnlyLayerNode layer => FindLayerTightBounds(layer, atFrame, forMask),
+            IReadOnlyFolderNode folder => FindFolderTightBounds(folder, atFrame, forMask),
             _ => throw new ArgumentOutOfRangeException()
         };
     }
@@ -303,13 +304,13 @@ internal class MemberPreviewUpdater
     /// <summary>
     /// Finds the current committed tight bounds for a layer.
     /// </summary>
-    private RectI? FindLayerTightBounds(IReadOnlyLayer layer, int frame, bool forMask)
+    private RectI? FindLayerTightBounds(IReadOnlyLayerNode layer, int frame, bool forMask)
     {
         if (layer.Mask is null && forMask)
             throw new InvalidOperationException();
 
         if (layer.Mask is not null && forMask)
-            return FindImageTightBoundsFast(layer.Mask);
+            return FindImageTightBoundsFast(layer.Mask.Value);
 
         if (layer is IReadOnlyImageNode raster)
         {
@@ -322,23 +323,23 @@ internal class MemberPreviewUpdater
     /// <summary>
     /// Finds the current committed tight bounds for a folder recursively.
     /// </summary>
-    private RectI? FindFolderTightBounds(IReadOnlyFolder folder, int frame, bool forMask)
+    private RectI? FindFolderTightBounds(IReadOnlyFolderNode folder, int frame, bool forMask)
     {
         if (forMask)
         {
             if (folder.Mask is null)
                 throw new InvalidOperationException();
-            return FindImageTightBoundsFast(folder.Mask);
+            return FindImageTightBoundsFast(folder.Mask.Value);
         }
 
-        RectI? combinedBounds = null;
+        /*RectI? combinedBounds = null;
         foreach (var child in folder.Children)
         {
             RectI? curBounds = null;
 
-            if (child is IReadOnlyLayer childLayer)
+            if (child is IReadOnlyLayerNode childLayer)
                 curBounds = FindLayerTightBounds(childLayer, frame, false);
-            else if (child is IReadOnlyFolder childFolder)
+            else if (child is IReadOnlyFolderNode childFolder)
                 curBounds = FindFolderTightBounds(childFolder, frame, false);
 
             if (combinedBounds is null)
@@ -347,7 +348,8 @@ internal class MemberPreviewUpdater
                 combinedBounds = combinedBounds.Value.Union(curBounds.Value);
         }
 
-        return combinedBounds;
+        return combinedBounds;*/
+        return folder.GetTightBounds(frame);
     }
 
     /// <summary>
@@ -434,7 +436,7 @@ internal class MemberPreviewUpdater
                 _ => ChunkResolution.Eighth,
             };
             var pos = chunkPos * resolution.PixelSize();
-            var rendered = ChunkRenderer.MergeWholeStructure(chunkPos, resolution,
+            /*var rendered = ChunkRenderer.MergeWholeStructure(chunkPos, resolution,
                 internals.Tracker.Document.StructureRoot, doc.AnimationHandler.ActiveFrameBindable);
             doc.PreviewSurface.DrawingSurface.Canvas.Save();
             doc.PreviewSurface.DrawingSurface.Canvas.Scale(scaling);
@@ -451,7 +453,7 @@ internal class MemberPreviewUpdater
                 renderedChunk.DrawOnSurface(doc.PreviewSurface.DrawingSurface, pos, SmoothReplacingPaint);
             }
 
-            doc.PreviewSurface.DrawingSurface.Canvas.Restore();
+            doc.PreviewSurface.DrawingSurface.Canvas.Restore();*/
         }
 
         if (somethingChanged)
@@ -502,7 +504,7 @@ internal class MemberPreviewUpdater
 
             if (memberVM is ILayerHandler)
             {
-                RenderLayerMainPreview((IReadOnlyLayer)member, memberVM, affArea.Value, position, scaling);
+                RenderLayerMainPreview((IReadOnlyLayerNode)member, memberVM, affArea.Value, position, scaling);
 
                 if (doc.AnimationHandler.FindKeyFrame(guid, out IKeyFrameHandler? keyFrame))
                 {
@@ -522,7 +524,7 @@ internal class MemberPreviewUpdater
             }
             else if (memberVM is IFolderHandler)
             {
-                RenderFolderMainPreview((IReadOnlyFolder)member, memberVM, affArea.Value, position, scaling);
+                RenderFolderMainPreview((IReadOnlyFolderNode)member, memberVM, affArea.Value, position, scaling);
                 infos.Add(new PreviewDirty_RenderInfo(guid));
             }
             else
@@ -535,7 +537,7 @@ internal class MemberPreviewUpdater
     /// <summary>
     /// Re-render the <paramref name="area"/> of the main preview of the <paramref name="memberVM"/> folder
     /// </summary>
-    private void RenderFolderMainPreview(IReadOnlyFolder folder, IStructureMemberHandler memberVM, AffectedArea area,
+    private void RenderFolderMainPreview(IReadOnlyFolderNode folder, IStructureMemberHandler memberVM, AffectedArea area,
         VecI position, float scaling)
     {
         memberVM.PreviewSurface.DrawingSurface.Canvas.Save();
@@ -547,7 +549,7 @@ internal class MemberPreviewUpdater
             var pos = chunk * ChunkResolution.Full.PixelSize();
             // drawing in full res here is kinda slow
             // we could switch to a lower resolution based on (canvas size / preview size) to make it run faster
-            OneOf<Chunk, EmptyChunk> rendered = ChunkRenderer.MergeWholeStructure(chunk, ChunkResolution.Full, folder,
+            /*OneOf<Chunk, EmptyChunk> rendered = ChunkRenderer.MergeWholeStructure(chunk, ChunkResolution.Full, folder,
                 doc.AnimationHandler.ActiveFrameBindable);
             if (rendered.IsT0)
             {
@@ -559,7 +561,7 @@ internal class MemberPreviewUpdater
             {
                 memberVM.PreviewSurface.DrawingSurface.Canvas.DrawRect(pos.X, pos.Y, ChunkResolution.Full.PixelSize(),
                     ChunkResolution.Full.PixelSize(), ClearPaint);
-            }
+            }*/
         }
 
         memberVM.PreviewSurface.DrawingSurface.Canvas.Restore();
@@ -568,7 +570,7 @@ internal class MemberPreviewUpdater
     /// <summary>
     /// Re-render the <paramref name="area"/> of the main preview of the <paramref name="memberVM"/> layer
     /// </summary>
-    private void RenderLayerMainPreview(IReadOnlyLayer layer, IStructureMemberHandler memberVM, AffectedArea area,
+    private void RenderLayerMainPreview(IReadOnlyLayerNode layer, IStructureMemberHandler memberVM, AffectedArea area,
         VecI position, float scaling)
     {
         memberVM.PreviewSurface.DrawingSurface.Canvas.Save();
@@ -579,7 +581,7 @@ internal class MemberPreviewUpdater
         foreach (var chunk in area.Chunks)
         {
             var pos = chunk * ChunkResolution.Full.PixelSize();
-            if (!layer.Rasterize(doc.AnimationHandler.ActiveFrameBindable).DrawCommittedChunkOn(chunk,
+            if (!layer.Execute(doc.AnimationHandler.ActiveFrameBindable).DrawCommittedChunkOn(chunk,
                     ChunkResolution.Full, memberVM.PreviewSurface.DrawingSurface, pos,
                     scaling < smoothingThreshold ? SmoothReplacingPaint : ReplacingPaint))
                 memberVM.PreviewSurface.DrawingSurface.Canvas.DrawRect(pos.X, pos.Y, ChunkyImage.FullChunkSize,
@@ -663,7 +665,7 @@ internal class MemberPreviewUpdater
             foreach (var chunk in affArea.Value.Chunks)
             {
                 var pos = chunk * ChunkResolution.Full.PixelSize();
-                member.Mask!.DrawMostUpToDateChunkOn
+                member.Mask!.Value.DrawMostUpToDateChunkOn
                 (chunk, ChunkResolution.Full, memberVM.MaskPreviewSurface.DrawingSurface, pos,
                     scaling < smoothingThreshold ? SmoothReplacingPaint : ReplacingPaint);
             }

+ 5 - 5
src/PixiEditor.AvaloniaUI/Styles/Templates/NodeGraphView.axaml

@@ -7,7 +7,7 @@
             <ControlTemplate>
                 <Grid Background="Transparent">
                     <ItemsControl ClipToBounds="False"
-                                  ItemsSource="{TemplateBinding Nodes}">
+                                  ItemsSource="{Binding NodeGraph.AllNodes, RelativeSource={RelativeSource TemplatedParent}}">
                         <ItemsControl.ItemsPanel>
                             <ItemsPanelTemplate>
                                 <Canvas RenderTransformOrigin="0, 0">
@@ -27,7 +27,7 @@
                         <ItemsControl.ItemTemplate>
                             <DataTemplate>
                                 <nodes:NodeView
-                                    DisplayName="{Binding DisplayName}"
+                                    DisplayName="{Binding NodeName}"
                                     Inputs="{Binding Inputs}"
                                     Outputs="{Binding Outputs}"
                                     ResultPreview="{Binding ResultPreview}" />
@@ -35,13 +35,13 @@
                         </ItemsControl.ItemTemplate>
                         <ItemsControl.ItemContainerTheme>
                             <ControlTheme TargetType="ContentPresenter">
-                                <Setter Property="Canvas.Left" Value="{Binding X}" />
-                                <Setter Property="Canvas.Top" Value="{Binding Y}" />
+                                <Setter Property="Canvas.Left" Value="{Binding Position.X}" />
+                                <Setter Property="Canvas.Top" Value="{Binding Position.Y}" />
                             </ControlTheme>
                         </ItemsControl.ItemContainerTheme>
                     </ItemsControl>
                     <ItemsControl
-                        ItemsSource="{TemplateBinding Connections}">
+                        ItemsSource="{Binding NodeGraph.Connections, RelativeSource={RelativeSource TemplatedParent}}">
                         <ItemsControl.ItemsPanel>
                             <ItemsPanelTemplate>
                                 <Canvas RenderTransformOrigin="0, 0">

+ 1 - 1
src/PixiEditor.AvaloniaUI/ViewModels/Dock/LayoutManager.cs

@@ -39,7 +39,7 @@ internal class LayoutManager
             new(mainViewModel.ColorsSubViewModel, mainViewModel.DocumentManagerSubViewModel);
         TimelineDockViewModel timelineDockViewModel = new(mainViewModel.DocumentManagerSubViewModel);
         
-        NodeGraphDockViewModel nodeGraphDockViewModel = new();
+        NodeGraphDockViewModel nodeGraphDockViewModel = new(mainViewModel.DocumentManagerSubViewModel);
 
         RegisterDockable(layersDockViewModel);
         RegisterDockable(colorPickerDockViewModel);

+ 10 - 3
src/PixiEditor.AvaloniaUI/ViewModels/Dock/NodeGraphDockViewModel.cs

@@ -1,13 +1,20 @@
-using PixiEditor.Extensions.Common.Localization;
+using PixiEditor.AvaloniaUI.ViewModels.Document;
+using PixiEditor.Extensions.Common.Localization;
 
 namespace PixiEditor.AvaloniaUI.ViewModels.Dock;
 
-internal class NodeGraphDockViewModel : DockableViewModel
+internal class NodeGraphDockViewModel(DocumentManagerViewModel document) : DockableViewModel
 {
     public const string TabId = "NodeGraph";
-    
+
     public override string Id { get; } = TabId;
     public override string Title => new LocalizedString("NODE_GRAPH_TITLE");
     public override bool CanFloat => true;
     public override bool CanClose => true;
+
+    public DocumentManagerViewModel DocumentManagerSubViewModel
+    {
+        get => document;
+        set => SetProperty(ref document, value);
+    }
 }

+ 20 - 19
src/PixiEditor.AvaloniaUI/ViewModels/Document/DocumentViewModel.Serialization.cs

@@ -7,6 +7,7 @@ using ChunkyImageLib.DataHolders;
 using PixiEditor.AvaloniaUI.Helpers;
 using PixiEditor.AvaloniaUI.Models.Handlers;
 using PixiEditor.AvaloniaUI.Models.IO.FileEncoders;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.DrawingApi.Core.Bridge;
 using PixiEditor.DrawingApi.Core.Numerics;
@@ -30,7 +31,7 @@ internal partial class DocumentViewModel
         
         var doc = Internals.Tracker.Document;
 
-        AddMembers(doc.StructureRoot.Children, doc, root);
+        //AddMembers(doc.StructureRoot.Children, doc, root);
 
         var document = new PixiDocument
         {
@@ -84,53 +85,53 @@ internal partial class DocumentViewModel
         };
     }
 
-    private static void AddMembers(IEnumerable<IReadOnlyStructureMember> members, IReadOnlyDocument document, Folder parent)
+    private static void AddMembers(IEnumerable<IReadOnlyStructureNode> members, IReadOnlyDocument document, Folder parent)
     {
         foreach (var member in members)
         {
-            if (member is IReadOnlyFolder readOnlyFolder)
+            if (member is IReadOnlyFolderNode readOnlyFolder)
             {
                 var folder = ToSerializable(readOnlyFolder);
 
-                AddMembers(readOnlyFolder.Children, document, folder);
+                //AddMembers(readOnlyFolder.Children, document, folder);
 
                 parent.Children.Add(folder);
             }
-            else if (member is IReadOnlyLayer readOnlyLayer)
+            else if (member is IReadOnlyLayerNode readOnlyLayer)
             {
                 parent.Children.Add(ToSerializable(readOnlyLayer, document));
             }
         }
     }
     
-    private static Folder ToSerializable(IReadOnlyFolder folder)
+    private static Folder ToSerializable(IReadOnlyFolderNode folder)
     {
         return new Folder
         {
-            Name = folder.Name,
-            BlendMode = (BlendMode)(int)folder.BlendMode,
-            Enabled = folder.IsVisible,
-            Opacity = folder.Opacity,
-            ClipToMemberBelow = folder.ClipToMemberBelow,
-            Mask = GetMask(folder.Mask, folder.MaskIsVisible)
+            Name = folder.MemberName,
+            BlendMode = (BlendMode)(int)folder.BlendMode.Value,
+            Enabled = folder.IsVisible.Value,
+            Opacity = folder.Opacity.Value,
+            ClipToMemberBelow = folder.ClipToMemberBelow.Value,
+            Mask = GetMask(folder.Mask.Value, folder.MaskIsVisible.Value)
         };
     }
     
-    private static ImageLayer ToSerializable(IReadOnlyLayer layer, IReadOnlyDocument document)
+    private static ImageLayer ToSerializable(IReadOnlyLayerNode layer, IReadOnlyDocument document)
     {
-        var result = document.GetLayerRasterizedImage(layer.GuidValue, 0);
+        var result = document.GetLayerRasterizedImage(layer.Id, 0);
 
-        var tightBounds = document.GetChunkAlignedLayerBounds(layer.GuidValue, 0);
+        var tightBounds = document.GetChunkAlignedLayerBounds(layer.Id, 0);
         using var data = result?.DrawingSurface.Snapshot().Encode();
         byte[] bytes = data?.AsSpan().ToArray();
         var serializable = new ImageLayer
         {
             Width = result?.Size.X ?? 0, Height = result?.Size.Y ?? 0, OffsetX = tightBounds?.X ?? 0, OffsetY = tightBounds?.Y ?? 0,
-            Enabled = layer.IsVisible, BlendMode = (BlendMode)(int)layer.BlendMode, ImageBytes = bytes,
-            ClipToMemberBelow = layer.ClipToMemberBelow, Name = layer.Name,
-            Guid = layer.GuidValue,
+            Enabled = layer.IsVisible.Value, BlendMode = (BlendMode)(int)layer.BlendMode.Value, ImageBytes = bytes,
+            ClipToMemberBelow = layer.ClipToMemberBelow.Value, Name = layer.MemberName,
+            Guid = layer.Id,
             LockAlpha = layer is ITransparencyLockable { LockTransparency: true },
-            Opacity = layer.Opacity, Mask = GetMask(layer.Mask, layer.MaskIsVisible)
+            Opacity = layer.Opacity.Value, Mask = GetMask(layer.Mask.Value, layer.MaskIsVisible.Value)
         };
 
         return serializable;

+ 43 - 38
src/PixiEditor.AvaloniaUI/ViewModels/Document/DocumentViewModel.cs

@@ -8,6 +8,7 @@ using Avalonia.Media.Imaging;
 using ChunkyImageLib;
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.Operations;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
 using PixiEditor.AvaloniaUI.Helpers;
 using PixiEditor.AvaloniaUI.Helpers.Collections;
 using PixiEditor.AvaloniaUI.Helpers.Extensions;
@@ -24,6 +25,7 @@ using PixiEditor.AvaloniaUI.Views.Overlays.SymmetryOverlay;
 using PixiEditor.ChangeableDocument.Actions;
 using PixiEditor.ChangeableDocument.Actions.Generated;
 using PixiEditor.ChangeableDocument.Actions.Undo;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.ChangeInfos;
 using PixiEditor.ChangeableDocument.Enums;
@@ -143,7 +145,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
     public bool HasSavedUndo => Internals.Tracker.HasSavedUndo;
     public bool HasSavedRedo => Internals.Tracker.HasSavedRedo;
 
-    public FolderViewModel StructureRoot { get; }
+    public NodeGraphViewModel NodeGraph { get; }
     public DocumentStructureModule StructureHelper { get; }
     public DocumentToolsModule Tools { get; }
     public DocumentOperationsModule Operations { get; }
@@ -191,7 +193,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
 
     public IReadOnlyCollection<IStructureMemberHandler> SoftSelectedStructureMembers => softSelectedStructureMembers;
     private DocumentInternalParts Internals { get; }
-    IFolderHandler IDocument.StructureRoot => StructureRoot;
+    INodeGraphHandler IDocument.NodeGraphHandler => NodeGraph;
     IDocumentOperations IDocument.Operations => Operations;
     ITransformHandler IDocument.TransformHandler => TransformViewModel;
     ILineOverlayHandler IDocument.LineToolOverlayHandler => LineToolOverlayViewModel;
@@ -214,7 +216,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
         FolderHandlerFactory = new FolderHandlerFactory(this);
         AnimationDataViewModel = new(this, Internals);
 
-        StructureRoot = new FolderViewModel(this, Internals, Internals.Tracker.Document.StructureRoot.GuidValue);
+        NodeGraph = new NodeGraphViewModel(this);
 
         TransformViewModel = new(this);
         TransformViewModel.TransformMoved += (_, args) => Internals.ChangeController.TransformMovedInlet(args);
@@ -264,7 +266,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
         viewModel.Swatches = new ObservableCollection<PaletteColor>(builderInstance.Swatches);
         viewModel.Palette = new ObservableRangeCollection<PaletteColor>(builderInstance.Palette);
 
-        AddMembers(viewModel.StructureRoot.GuidValue, builderInstance.Children);
+        AddMembers(viewModel.NodeGraph.OutputNode.Id, builderInstance.Children);
         AddAnimationData(builderInstance.AnimationData);
 
         acc.AddFinishedActions(new DeleteRecordedChanges_Action());
@@ -275,52 +277,52 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
         void AddMember(Guid parentGuid, DocumentViewModelBuilder.StructureMemberBuilder member)
         {
             acc.AddActions(
-                new CreateStructureMember_Action(parentGuid, member.GuidValue, 0,
+                new CreateStructureMember_Action(parentGuid, member.Id, 0,
                     member is DocumentViewModelBuilder.LayerBuilder
                         ? StructureMemberType.Layer
                         : StructureMemberType.Folder),
-                new StructureMemberName_Action(member.GuidValue, member.Name)
+                new StructureMemberName_Action(member.Id, member.Name)
             );
 
             if (!member.IsVisible)
-                acc.AddActions(new StructureMemberIsVisible_Action(member.IsVisible, member.GuidValue));
+                acc.AddActions(new StructureMemberIsVisible_Action(member.IsVisible, member.Id));
 
-            acc.AddActions(new StructureMemberBlendMode_Action(member.BlendMode, member.GuidValue));
+            acc.AddActions(new StructureMemberBlendMode_Action(member.BlendMode, member.Id));
 
-            acc.AddActions(new StructureMemberClipToMemberBelow_Action(member.ClipToMemberBelow, member.GuidValue));
+            acc.AddActions(new StructureMemberClipToMemberBelow_Action(member.ClipToMemberBelow, member.Id));
 
             if (member is DocumentViewModelBuilder.LayerBuilder layerBuilder)
             {
-                acc.AddActions(new LayerLockTransparency_Action(layerBuilder.GuidValue, layerBuilder.LockAlpha));
+                acc.AddActions(new LayerLockTransparency_Action(layerBuilder.Id, layerBuilder.LockAlpha));
             }
 
             if (member is DocumentViewModelBuilder.LayerBuilder layer && layer.Surface is not null)
             {
-                PasteImage(member.GuidValue, layer.Surface, layer.Width, layer.Height, layer.OffsetX, layer.OffsetY,
+                PasteImage(member.Id, layer.Surface, layer.Width, layer.Height, layer.OffsetX, layer.OffsetY,
                     false, 0);
             }
 
             acc.AddActions(
-                new StructureMemberOpacity_Action(member.GuidValue, member.Opacity),
+                new StructureMemberOpacity_Action(member.Id, member.Opacity),
                 new EndStructureMemberOpacity_Action());
 
             if (member.HasMask)
             {
                 var maskSurface = member.Mask.Surface.Surface;
 
-                acc.AddActions(new CreateStructureMemberMask_Action(member.GuidValue));
+                acc.AddActions(new CreateStructureMemberMask_Action(member.Id));
 
                 if (!member.Mask.IsVisible)
-                    acc.AddActions(new StructureMemberMaskIsVisible_Action(member.Mask.IsVisible, member.GuidValue));
+                    acc.AddActions(new StructureMemberMaskIsVisible_Action(member.Mask.IsVisible, member.Id));
 
-                PasteImage(member.GuidValue, member.Mask.Surface, maskSurface.Size.X, maskSurface.Size.Y, 0, 0, true, 0);
+                PasteImage(member.Id, member.Mask.Surface, maskSurface.Size.X, maskSurface.Size.Y, 0, 0, true, 0);
             }
 
             acc.AddFinishedActions();
 
             if (member is DocumentViewModelBuilder.FolderBuilder { Children: not null } folder)
             {
-                AddMembers(member.GuidValue, folder.Children);
+                AddMembers(member.Id, folder.Children);
             }
         }
 
@@ -337,9 +339,9 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
         {
             foreach (var child in builders.Reverse())
             {
-                if (child.GuidValue == default)
+                if (child.Id == default)
                 {
-                    child.GuidValue = Guid.NewGuid();
+                    child.Id = Guid.NewGuid();
                 }
 
                 AddMember(parentGuid, child);
@@ -405,13 +407,14 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
             {
                 for (int j = 0; j < sizeInChunks.Y; j++)
                 {
-                    var maybeChunk = ChunkRenderer.MergeWholeStructure(new(i, j), ChunkResolution.Full,
+                    // TODO: Implement this
+                    /*var maybeChunk = ChunkRenderer.MergeWholeStructure(new(i, j), ChunkResolution.Full,
                         Internals.Tracker.Document.StructureRoot, frame);
                     if (maybeChunk.IsT1)
                         continue;
                     using Chunk chunk = maybeChunk.AsT0;
                     finalSurface.DrawingSurface.Canvas.DrawSurface(chunk.Surface.DrawingSurface,
-                        i * ChunkyImage.FullChunkSize, j * ChunkyImage.FullChunkSize);
+                        i * ChunkyImage.FullChunkSize, j * ChunkyImage.FullChunkSize);*/
                 }
             }
 
@@ -437,7 +440,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
             return new None();
 
         //TODO: Make sure it's not needed for other layer types
-        IReadOnlyImageNode? layer = (IReadOnlyImageNode?)Internals.Tracker.Document.FindMember(layerVm.GuidValue);
+        IReadOnlyImageNode? layer = (IReadOnlyImageNode?)Internals.Tracker.Document.FindMember(layerVm.Id);
         if (layer is null)
             return new Error();
 
@@ -546,7 +549,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
             if (scope == DocumentScope.AllLayers)
             {
                 VecI chunkPos = OperationHelper.GetChunkPos(pos, ChunkyImage.FullChunkSize);
-                return ChunkRenderer.MergeWholeStructure(chunkPos, ChunkResolution.Full,
+                /*return ChunkRenderer.MergeWholeStructure(chunkPos, ChunkResolution.Full,
                         Internals.Tracker.Document.StructureRoot, frame, new RectI(pos, VecI.One))
                     .Match<Color>(
                         (Chunk chunk) =>
@@ -557,15 +560,17 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
                             return color;
                         },
                         _ => Colors.Transparent
-                    );
+                    );*/
+                // TODO: Implement this
+                return Colors.Transparent;
             }
 
             if (SelectedStructureMember is not LayerViewModel layerVm)
                 return Colors.Transparent;
-            IReadOnlyStructureMember? maybeMember = Internals.Tracker.Document.FindMember(layerVm.GuidValue);
-            if (maybeMember is not IReadOnlyLayer layer)
+            IReadOnlyStructureNode? maybeMember = Internals.Tracker.Document.FindMember(layerVm.Id);
+            if (maybeMember is not IReadOnlyLayerNode layer)
                 return Colors.Transparent;
-            return layer.Rasterize(frame).GetMostUpToDatePixel(pos);
+            return layer.Execute(frame).GetMostUpToDatePixel(pos);
         }
         catch (ObjectDisposedException)
         {
@@ -663,9 +668,9 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
     {
         List<Guid> layerGuids = new List<Guid>();
         if (SelectedStructureMember is not null)
-            layerGuids.Add(SelectedStructureMember.GuidValue);
+            layerGuids.Add(SelectedStructureMember.Id);
 
-        layerGuids.AddRange(softSelectedStructureMembers.Select(x => x.GuidValue));
+        layerGuids.AddRange(softSelectedStructureMembers.Select(x => x.Id));
         return layerGuids;
     }
 
@@ -683,15 +688,15 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
             var foundMember = StructureHelper.Find(member);
             if (foundMember != null)
             {
-                if (foundMember is LayerViewModel layer && selectedMembers.Contains(foundMember.GuidValue) &&
-                    !result.Contains(layer.GuidValue))
+                if (foundMember is LayerViewModel layer && selectedMembers.Contains(foundMember.Id) &&
+                    !result.Contains(layer.Id))
                 {
-                    result.Add(layer.GuidValue);
+                    result.Add(layer.Id);
                 }
-                else if (foundMember is FolderViewModel folder && selectedMembers.Contains(foundMember.GuidValue))
+                else if (foundMember is FolderViewModel folder && selectedMembers.Contains(foundMember.Id))
                 {
-                    if (includeFoldersWithMask && folder.HasMaskBindable && !result.Contains(folder.GuidValue))
-                        result.Add(folder.GuidValue);
+                    if (includeFoldersWithMask && folder.HasMaskBindable && !result.Contains(folder.Id))
+                        result.Add(folder.Id);
                     ExtractSelectedLayers(folder, result, includeFoldersWithMask);
                 }
             }
@@ -710,14 +715,14 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
     {
         foreach (var member in folder.Children)
         {
-            if (member is LayerViewModel layer && !list.Contains(layer.GuidValue))
+            if (member is LayerViewModel layer && !list.Contains(layer.Id))
             {
-                list.Add(layer.GuidValue);
+                list.Add(layer.Id);
             }
             else if (member is FolderViewModel childFolder)
             {
-                if (includeFoldersWithMask && childFolder.HasMaskBindable && !list.Contains(childFolder.GuidValue))
-                    list.Add(childFolder.GuidValue);
+                if (includeFoldersWithMask && childFolder.HasMaskBindable && !list.Contains(childFolder.Id))
+                    list.Add(childFolder.Id);
 
                 ExtractSelectedLayers(childFolder, list, includeFoldersWithMask);
             }

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

@@ -7,5 +7,5 @@ namespace PixiEditor.AvaloniaUI.ViewModels.Document;
 internal class FolderViewModel : StructureMemberViewModel, IFolderHandler
 {
     public ObservableCollection<IStructureMemberHandler> Children { get; } = new();
-    public FolderViewModel(DocumentViewModel doc, DocumentInternalParts internals, Guid guidValue) : base(doc, internals, guidValue) { }
+    public FolderViewModel(DocumentViewModel doc, DocumentInternalParts internals, Guid id) : base(doc, internals, id) { }
 }

+ 8 - 2
src/PixiEditor.AvaloniaUI/ViewModels/Document/LayerViewModel.cs

@@ -18,7 +18,7 @@ internal class LayerViewModel : StructureMemberViewModel, ILayerHandler
         set
         {
             if (!Document.UpdateableChangeActive)
-                Internals.ActionAccumulator.AddFinishedActions(new LayerLockTransparency_Action(GuidValue, value));
+                Internals.ActionAccumulator.AddFinishedActions(new LayerLockTransparency_Action(Id, value));
         }
     }
 
@@ -35,7 +35,13 @@ internal class LayerViewModel : StructureMemberViewModel, ILayerHandler
         }
     }
 
-    public LayerViewModel(DocumentViewModel doc, DocumentInternalParts internals, Guid guidValue) : base(doc, internals, guidValue)
+    public LayerViewModel(DocumentViewModel doc, DocumentInternalParts internals, Guid id) : base(doc, internals, id)
     {
+        NodeName = NameBindable;
+        PropertyChanged += (sender, args) =>
+        {
+            if (args.PropertyName == nameof(NameBindable))
+                NodeName = NameBindable;
+        };
     }
 }

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

@@ -0,0 +1,66 @@
+using System.Collections.ObjectModel;
+using PixiEditor.AvaloniaUI.Models.Handlers;
+using PixiEditor.AvaloniaUI.ViewModels.Nodes;
+
+namespace PixiEditor.AvaloniaUI.ViewModels.Document;
+
+internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler
+{
+    public DocumentViewModel DocumentViewModel { get; }
+    public ObservableCollection<INodeHandler> AllNodes { get; } = new();
+    public ObservableCollection<NodeConnectionViewModel> Connections { get; } = new();
+    public INodeHandler? OutputNode { get; }
+
+    public NodeGraphViewModel(DocumentViewModel documentViewModel)
+    {
+        DocumentViewModel = documentViewModel;
+    }
+
+    public bool TryTraverse(Func<INodeHandler, bool> func)
+    {
+        if (OutputNode == null) return false;
+
+        var queue = CalculateExecutionQueue(OutputNode);
+
+        while (queue.Count > 0)
+        {
+            var node = queue.Dequeue();
+            func(node);
+        }
+
+        return true;
+    }
+    
+    private Queue<INodeHandler> CalculateExecutionQueue(INodeHandler outputNode)
+       {
+           // backwards breadth-first search
+           var visited = new HashSet<INodeHandler>();
+           var queueNodes = new Queue<INodeHandler>();
+           List<INodeHandler> finalQueue = new();
+           queueNodes.Enqueue(outputNode);
+   
+           while (queueNodes.Count > 0)
+           {
+               var node = queueNodes.Dequeue();
+               if (!visited.Add(node))
+               {
+                   continue;
+               }
+   
+               finalQueue.Add(node);
+   
+               foreach (var input in node.Inputs)
+               {
+                   if (input.Connection == null)
+                   {
+                       continue;
+                   }
+   
+                   queueNodes.Enqueue(input.Connection.Node);
+               }
+           }
+   
+           finalQueue.Reverse();
+           return new Queue<INodeHandler>(finalQueue);
+       } 
+}

+ 34 - 22
src/PixiEditor.AvaloniaUI/ViewModels/Document/StructureMemberViewModel.cs

@@ -1,10 +1,13 @@
-using Avalonia.Media.Imaging;
+using System.Collections.ObjectModel;
+using Avalonia.Media.Imaging;
 using ChunkyImageLib;
 using CommunityToolkit.Mvvm.ComponentModel;
 using PixiEditor.AvaloniaUI.Helpers;
 using PixiEditor.AvaloniaUI.Models.DocumentModels;
 using PixiEditor.AvaloniaUI.Models.Handlers;
 using PixiEditor.AvaloniaUI.Models.Layers;
+using PixiEditor.AvaloniaUI.ViewModels.Nodes;
+using PixiEditor.AvaloniaUI.Views.Nodes;
 using PixiEditor.ChangeableDocument.Actions.Generated;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Surface;
@@ -13,111 +16,119 @@ using BlendMode = PixiEditor.ChangeableDocument.Enums.BlendMode;
 
 namespace PixiEditor.AvaloniaUI.ViewModels.Document;
 #nullable enable
-internal abstract class StructureMemberViewModel : ObservableObject, IStructureMemberHandler
+internal abstract class StructureMemberViewModel : NodeViewModel, IStructureMemberHandler
 {
     public DocumentViewModel Document { get; }
     protected DocumentInternalParts Internals { get; }
 
-
     private string name = "";
+
     public void SetName(string name)
     {
         this.name = name;
         OnPropertyChanged(nameof(NameBindable));
     }
+
     public string NameBindable
     {
         get => name;
         set
         {
             if (!Document.UpdateableChangeActive)
-                Internals.ActionAccumulator.AddFinishedActions(new StructureMemberName_Action(GuidValue, value));
+                Internals.ActionAccumulator.AddFinishedActions(new StructureMemberName_Action(Id, value));
         }
     }
 
     private bool isVisible;
+
     public void SetIsVisible(bool isVisible)
     {
         this.isVisible = isVisible;
         OnPropertyChanged(nameof(IsVisibleBindable));
     }
+
     public bool IsVisibleBindable
     {
         get => isVisible;
         set
         {
             if (!Document.UpdateableChangeActive)
-                Internals.ActionAccumulator.AddFinishedActions(new StructureMemberIsVisible_Action(value, GuidValue));
+                Internals.ActionAccumulator.AddFinishedActions(new StructureMemberIsVisible_Action(value, Id));
         }
     }
 
     private bool maskIsVisible;
-    public RectI? TightBounds => Internals.Tracker.Document.FindMember(GuidValue)?.GetTightBounds(Document.AnimationDataViewModel.ActiveFrameBindable);
+
+    public RectI? TightBounds => Internals.Tracker.Document.FindMember(Id)
+        ?.GetTightBounds(Document.AnimationDataViewModel.ActiveFrameBindable);
 
     public void SetMaskIsVisible(bool maskIsVisible)
     {
         this.maskIsVisible = maskIsVisible;
         OnPropertyChanged(nameof(MaskIsVisibleBindable));
     }
+
     public bool MaskIsVisibleBindable
     {
         get => maskIsVisible;
         set
         {
             if (!Document.UpdateableChangeActive)
-                Internals.ActionAccumulator.AddFinishedActions(new StructureMemberMaskIsVisible_Action(value, GuidValue));
+                Internals.ActionAccumulator.AddFinishedActions(
+                    new StructureMemberMaskIsVisible_Action(value, Id));
         }
     }
 
     private BlendMode blendMode;
+
     public void SetBlendMode(BlendMode blendMode)
     {
         this.blendMode = blendMode;
         OnPropertyChanged(nameof(BlendModeBindable));
     }
+
     public BlendMode BlendModeBindable
     {
         get => blendMode;
         set
         {
             if (!Document.UpdateableChangeActive)
-                Internals.ActionAccumulator.AddFinishedActions(new StructureMemberBlendMode_Action(value, GuidValue));
+                Internals.ActionAccumulator.AddFinishedActions(new StructureMemberBlendMode_Action(value, Id));
         }
     }
 
     private bool clipToMemberBelowEnabled;
+
     public void SetClipToMemberBelowEnabled(bool clipToMemberBelowEnabled)
     {
         this.clipToMemberBelowEnabled = clipToMemberBelowEnabled;
         OnPropertyChanged(nameof(ClipToMemberBelowEnabledBindable));
     }
+
     public bool ClipToMemberBelowEnabledBindable
     {
         get => clipToMemberBelowEnabled;
         set
         {
             if (!Document.UpdateableChangeActive)
-                Internals.ActionAccumulator.AddFinishedActions(new StructureMemberClipToMemberBelow_Action(value, GuidValue));
+                Internals.ActionAccumulator.AddFinishedActions(
+                    new StructureMemberClipToMemberBelow_Action(value, Id));
         }
     }
 
     private bool hasMask;
+
     public void SetHasMask(bool hasMask)
     {
         this.hasMask = hasMask;
         OnPropertyChanged(nameof(HasMaskBindable));
     }
+
     public bool HasMaskBindable
     {
         get => hasMask;
     }
-
-    private Guid guidValue;
-    public Guid GuidValue
-    {
-        get => guidValue;
-    }
-
+    
     private float opacity;
 
     public void SetOpacity(float newOpacity)
@@ -125,6 +136,7 @@ internal abstract class StructureMemberViewModel : ObservableObject, IStructureM
         this.opacity = newOpacity;
         OnPropertyChanged(nameof(OpacityBindable));
     }
+
     public float OpacityBindable
     {
         get => opacity;
@@ -134,7 +146,7 @@ internal abstract class StructureMemberViewModel : ObservableObject, IStructureM
                 return;
             float newValue = Math.Clamp(value, 0, 1);
             Internals.ActionAccumulator.AddFinishedActions(
-                new StructureMemberOpacity_Action(GuidValue, newValue),
+                new StructureMemberOpacity_Action(Id, newValue),
                 new EndStructureMemberOpacity_Action());
         }
     }
@@ -171,17 +183,17 @@ internal abstract class StructureMemberViewModel : ObservableObject, IStructureM
     {
         double proportions = tightBoundsSize.Y / (double)tightBoundsSize.X;
         const int prSize = StructureHelpers.PreviewSize;
-        return proportions > 1 ?
-            new VecI(Math.Max((int)Math.Round(prSize / proportions), 1), prSize) :
-            new VecI(prSize, Math.Max((int)Math.Round(prSize * proportions), 1));
+        return proportions > 1
+            ? new VecI(Math.Max((int)Math.Round(prSize / proportions), 1), prSize)
+            : new VecI(prSize, Math.Max((int)Math.Round(prSize * proportions), 1));
     }
 
-    public StructureMemberViewModel(DocumentViewModel doc, DocumentInternalParts internals, Guid guidValue)
+    public StructureMemberViewModel(DocumentViewModel doc, DocumentInternalParts internals, Guid id)
     {
         Document = doc;
         Internals = internals;
 
-        this.guidValue = guidValue;
+        this.id = id;
         PreviewSurface = null;
     }
 }

+ 86 - 23
src/PixiEditor.AvaloniaUI/ViewModels/Nodes/NodeViewModel.cs

@@ -1,51 +1,114 @@
 using System.Collections.ObjectModel;
 using Avalonia;
 using ChunkyImageLib;
+using CommunityToolkit.Mvvm.ComponentModel;
+using PixiEditor.AvaloniaUI.Models.Handlers;
+using PixiEditor.Numerics;
 
 namespace PixiEditor.AvaloniaUI.ViewModels.Nodes;
 
-public class NodeViewModel : ViewModelBase
+public class NodeViewModel : ObservableObject, INodeHandler
 {
-    private string name;
-    private double x;
-    private double y;
-    private ObservableCollection<NodePropertyViewModel> inputs;
-    private ObservableCollection<NodePropertyViewModel> outputs;
+    private string nodeName;
+    private VecD position;
+    private ObservableCollection<IInputPropertyHandler> inputs;
+    private ObservableCollection<IOutputPropertyHandler> outputs;
     private Surface resultPreview;
-    
-    public string Name
+
+    protected Guid id;
+
+    public Guid Id
     {
-        get => name;
-        set => SetProperty(ref name, value);
+        get => id;
     }
-    
-    public double X
+
+    public string NodeName
     {
-        get => x;
-        set => SetProperty(ref x, value);
+        get => nodeName;
+        set => SetProperty(ref nodeName, value);
     }
-    
-    public double Y
+
+    public VecD Position
     {
-        get => y;
-        set => SetProperty(ref y, value);
+        get => position;
+        set => SetProperty(ref position, value);
     }
-    
-    public ObservableCollection<NodePropertyViewModel> Inputs
+
+    public ObservableCollection<IInputPropertyHandler> Inputs
     {
         get => inputs;
         set => SetProperty(ref inputs, value);
     }
-    
-    public ObservableCollection<NodePropertyViewModel> Outputs
+
+    public ObservableCollection<IOutputPropertyHandler> Outputs
     {
         get => outputs;
         set => SetProperty(ref outputs, value);
     }
-    
+
     public Surface ResultPreview
     {
         get => resultPreview;
         set => SetProperty(ref resultPreview, value);
     }
+
+    public void TraverseBackwards(Func<INodeHandler, bool> func)
+    {
+        var visited = new HashSet<INodeHandler>();
+        var queueNodes = new Queue<INodeHandler>();
+        queueNodes.Enqueue(this);
+
+        while (queueNodes.Count > 0)
+        {
+            var node = queueNodes.Dequeue();
+
+            if (!visited.Add(node))
+            {
+                continue;
+            }
+
+            if (!func(node))
+            {
+                return;
+            }
+
+            foreach (var inputProperty in node.Inputs)
+            {
+                if (inputProperty.Connection != null)
+                {
+                    queueNodes.Enqueue(inputProperty.Node);
+                }
+            }
+        }
+    }
+
+    public void TraverseForwards(Func<INodeHandler, bool> func)
+    {
+        var visited = new HashSet<INodeHandler>();
+        var queueNodes = new Queue<INodeHandler>();
+        queueNodes.Enqueue(this);
+
+        while (queueNodes.Count > 0)
+        {
+            var node = queueNodes.Dequeue();
+
+            if (!visited.Add(node))
+            {
+                continue;
+            }
+
+            if (!func(node))
+            {
+                return;
+            }
+
+            foreach (var outputProperty in node.Outputs)
+            {
+                foreach (var connection in outputProperty.Connections)
+                {
+                    queueNodes.Enqueue(connection.Node);
+                }
+            }
+        }
+    }
 }

+ 3 - 3
src/PixiEditor.AvaloniaUI/ViewModels/SubViewModels/AnimationsViewModel.cs

@@ -26,13 +26,13 @@ internal class AnimationsViewModel : SubViewModel<ViewModelMain>
             return;
         }
 
-        int newFrame = GetActiveFrame(activeDocument, activeDocument.SelectedStructureMember.GuidValue);
+        int newFrame = GetActiveFrame(activeDocument, activeDocument.SelectedStructureMember.Id);
        
-        Guid toCloneFrom = duplicate ? activeDocument.SelectedStructureMember.GuidValue : Guid.Empty;
+        Guid toCloneFrom = duplicate ? activeDocument.SelectedStructureMember.Id : Guid.Empty;
         int frameToCopyFrom = duplicate ? activeDocument.AnimationDataViewModel.ActiveFrameBindable : -1;
 
         activeDocument.AnimationDataViewModel.CreateRasterKeyFrame(
-            activeDocument.SelectedStructureMember.GuidValue,
+            activeDocument.SelectedStructureMember.Id,
             newFrame,
             toCloneFrom);
         

+ 5 - 5
src/PixiEditor.AvaloniaUI/ViewModels/SubViewModels/DiscordViewModel.cs

@@ -87,7 +87,7 @@ internal class DiscordViewModel : SubViewModel<ViewModelMain>, IDisposable
 
             if (PixiEditorSettings.Discord.ShowLayerCount.Value)
             {
-                int count = CountLayers(document.StructureRoot);
+                int count = CountLayers(document.NodeGraph);
                 state += count == 1 ? "1 layer" : $"{count} layers";
             }
 
@@ -97,16 +97,16 @@ internal class DiscordViewModel : SubViewModel<ViewModelMain>, IDisposable
         client.SetPresence(richPresence);
     }
 
-    private int CountLayers(FolderViewModel folder)
+    private int CountLayers(NodeGraphViewModel graph)
     {
-        int counter = 0;
-        foreach (var child in folder.Children)
+        int counter = 0; //TODO: Implement this
+        /*foreach (var child in folder.Children)
         {
             if (child is LayerViewModel)
                 counter++;
             else if (child is FolderViewModel innerFolder)
                 counter += CountLayers(innerFolder);
-        }
+        }*/
         return counter;
     }
 

+ 12 - 12
src/PixiEditor.AvaloniaUI/ViewModels/SubViewModels/LayersViewModel.cs

@@ -62,7 +62,7 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
         if (member is null)
             return;
 
-        member.Document.Operations.DeleteStructureMember(member.GuidValue);
+        member.Document.Operations.DeleteStructureMember(member.Id);
     }
 
     [Evaluator.CanExecute("PixiEditor.Layer.HasSelectedMembers")]
@@ -93,8 +93,8 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
         if (doc is null)
             return members;
         if (doc.SelectedStructureMember is not null)
-            members.Add(doc.SelectedStructureMember.GuidValue);
-        members.AddRange(doc.SoftSelectedStructureMembers.Select(static member => member.GuidValue));
+            members.Add(doc.SelectedStructureMember.Id);
+        members.AddRange(doc.SoftSelectedStructureMembers.Select(static member => member.Id));
         return members;
     }
 
@@ -169,7 +169,7 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
 
         if (document?.SelectedStructureMember != null)
         {
-            document.Operations.SetMemberOpacity(document.SelectedStructureMember.GuidValue, (float)value);
+            document.Operations.SetMemberOpacity(document.SelectedStructureMember.Id, (float)value);
         }
     }
 
@@ -180,7 +180,7 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
         var member = Owner.DocumentManagerSubViewModel.ActiveDocument?.SelectedStructureMember;
         if (member is not LayerViewModel layerVM)
             return;
-        member.Document.Operations.DuplicateLayer(member.GuidValue);
+        member.Document.Operations.DuplicateLayer(member.Id);
     }
 
     [Evaluator.CanExecute("PixiEditor.Layer.SelectedMemberIsLayer")]
@@ -196,7 +196,7 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
         var member = doc?.SelectedStructureMember;
         if (member is null)
             return false;
-        var path = doc!.StructureHelper.FindPath(member.GuidValue);
+        var path = doc!.StructureHelper.FindPath(member.Id);
         if (path.Count < 2)
             return false;
         var parent = (FolderViewModel)path[1];
@@ -214,7 +214,7 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
         var member = Owner.DocumentManagerSubViewModel.ActiveDocument?.SelectedStructureMember;
         if (member is null)
             return;
-        var path = doc!.StructureHelper.FindPath(member.GuidValue);
+        var path = doc!.StructureHelper.FindPath(member.Id);
         if (path.Count < 2)
             return;
         var parent = (FolderViewModel)path[1];
@@ -223,13 +223,13 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
         {
             if (curIndex == parent.Children.Count - 1)
                 return;
-            doc.Operations.MoveStructureMember(member.GuidValue, parent.Children[curIndex + 1].GuidValue, StructureMemberPlacement.Above);
+            doc.Operations.MoveStructureMember(member.Id, parent.Children[curIndex + 1].Id, StructureMemberPlacement.Above);
         }
         else
         {
             if (curIndex == 0)
                 return;
-            doc.Operations.MoveStructureMember(member.GuidValue, parent.Children[curIndex - 1].GuidValue, StructureMemberPlacement.Below);
+            doc.Operations.MoveStructureMember(member.Id, parent.Children[curIndex - 1].Id, StructureMemberPlacement.Below);
         }
     }
 
@@ -319,11 +319,11 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
 
     public void MergeSelectedWith(bool above)
     {
-        var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
+        /*var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
         var member = doc?.SelectedStructureMember;
         if (doc is null || member is null)
             return;
-        var (child, parent) = doc.StructureHelper.FindChildAndParent(member.GuidValue);
+        var (child, parent) = doc.StructureHelper.FindChildAndParent(member.Id);
         if (child is null || parent is null)
             return;
         int index = parent.Children.IndexOf(child);
@@ -331,7 +331,7 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
             return;
         if (above && index == parent.Children.Count - 1)
             return;
-        doc.Operations.MergeStructureMembers(new List<Guid> { member.GuidValue, above ? parent.Children[index + 1].GuidValue : parent.Children[index - 1].GuidValue });
+        doc.Operations.MergeStructureMembers(new List<Guid> { member.Id, above ? parent.Children[index + 1].Id : parent.Children[index - 1].GuidValue });*/
     }
 
     [Command.Basic("PixiEditor.Layer.MergeWithAbove", "MERGE_WITH_ABOVE", "MERGE_WITH_ABOVE_DESCRIPTIVE", CanExecute = "PixiEditor.Layer.HasMemberAbove")]

+ 6 - 1
src/PixiEditor.AvaloniaUI/Views/Dock/NodeGraphDockView.axaml

@@ -3,7 +3,12 @@
              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
              xmlns:nodes="clr-namespace:PixiEditor.AvaloniaUI.Views.Nodes"
+             xmlns:dock="clr-namespace:PixiEditor.AvaloniaUI.ViewModels.Dock"
              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:DataType="dock:NodeGraphDockViewModel"
              x:Class="PixiEditor.AvaloniaUI.Views.Dock.NodeGraphDockView">
-    <nodes:NodeGraphView/>
+    <Design.DataContext>
+        <dock:NodeGraphDockViewModel/>
+    </Design.DataContext>
+    <nodes:NodeGraphView NodeGraph="{Binding DocumentManagerSubViewModel.ActiveDocument.NodeGraph}"/>
 </UserControl>

+ 1 - 1
src/PixiEditor.AvaloniaUI/Views/Layers/FolderControl.axaml.cs

@@ -86,7 +86,7 @@ internal partial class FolderControl : UserControl
         Guid? droppedMemberGuid = LayerControl.ExtractMemberGuid(dataObj);
         if (droppedMemberGuid is null)
             return;
-        Folder.Document.Operations.MoveStructureMember((Guid)droppedMemberGuid, Folder.GuidValue, placement);
+        Folder.Document.Operations.MoveStructureMember((Guid)droppedMemberGuid, Folder.Id, placement);
     }
 
     private void Grid_Drop_Top(object sender, DragEventArgs e)

+ 3 - 3
src/PixiEditor.AvaloniaUI/Views/Layers/LayerControl.axaml.cs

@@ -129,9 +129,9 @@ internal partial class LayerControl : UserControl
         object droppedLayer = droppedMemberDataObject.Get(LayerControlDataName);
         object droppedFolder = droppedMemberDataObject.Get(AvaloniaUI.Views.Layers.FolderControl.FolderControlDataName);
         if (droppedLayer is LayerControl layer)
-            return layer.Layer.GuidValue;
+            return layer.Layer.Id;
         else if (droppedFolder is AvaloniaUI.Views.Layers.FolderControl folder)
-            return folder.Folder.GuidValue;
+            return folder.Folder.Id;
         return null;
     }
 
@@ -142,7 +142,7 @@ internal partial class LayerControl : UserControl
         Guid? droppedMemberGuid = ExtractMemberGuid(dataObj);
         if (droppedMemberGuid is null)
             return;
-        Layer.Document.Operations.MoveStructureMember((Guid)droppedMemberGuid, Layer.GuidValue, placement);
+        Layer.Document.Operations.MoveStructureMember((Guid)droppedMemberGuid, Layer.Id, placement);
     }
 
     private void Grid_Drop_Top(object sender, DragEventArgs e)

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

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

+ 13 - 11
src/PixiEditor.AvaloniaUI/Views/Layers/LayersManager.axaml.cs

@@ -38,7 +38,7 @@ internal partial class LayersManager : UserControl
         {
             if (control.Layer is not null && control.Layer.Selection == StructureMemberSelectionType.None)
             {
-                control.Layer.Document.Operations.SetSelectedMember(control.Layer.GuidValue);
+                control.Layer.Document.Operations.SetSelectedMember(control.Layer.Id);
                 control.Layer.Document.Operations.ClearSoftSelectedMembers();
             }
         }
@@ -79,7 +79,7 @@ internal partial class LayersManager : UserControl
         {
             if (control.Folder is not null && control.Folder.Selection == StructureMemberSelectionType.None)
             {
-                control.Folder.Document.Operations.SetSelectedMember(control.Folder.GuidValue);
+                control.Folder.Document.Operations.SetSelectedMember(control.Folder.Id);
                 control.Folder.Document.Operations.ClearSoftSelectedMembers();
             }
         }
@@ -132,8 +132,9 @@ internal partial class LayersManager : UserControl
 
         if (droppedGuid is not null && ActiveDocument is not null)
         {
-            ActiveDocument.Operations.MoveStructureMember((Guid)droppedGuid,
-                ActiveDocument.StructureRoot.Children[0].GuidValue, StructureMemberPlacement.Below);
+            // TODO: Implement this
+            /*ActiveDocument.Operations.MoveStructureMember((Guid)droppedGuid,
+                ActiveDocument.NodeGraph.Children[0].GuidValue, StructureMemberPlacement.Below);*/
             e.Handled = true;
         }
 
@@ -191,7 +192,7 @@ internal partial class LayersManager : UserControl
                 action(child);
             if (matches == 2)
                 return 2;
-            if (child.GuidValue == bound1 || child.GuidValue == bound2)
+            if (child.Id == bound1 || child.Id == bound2)
             {
                 matches++;
                 if (matches == 1)
@@ -213,28 +214,29 @@ internal partial class LayersManager : UserControl
             if (memberVM.Selection == StructureMemberSelectionType.Hard)
                 return;
             else if (memberVM.Selection == StructureMemberSelectionType.Soft)
-                ActiveDocument.Operations.RemoveSoftSelectedMember(memberVM.GuidValue);
+                ActiveDocument.Operations.RemoveSoftSelectedMember(memberVM.Id);
             else if (memberVM.Selection == StructureMemberSelectionType.None)
-                ActiveDocument.Operations.AddSoftSelectedMember(memberVM.GuidValue);
+                ActiveDocument.Operations.AddSoftSelectedMember(memberVM.Id);
         }
         else if (pointerPressedEventArgs.KeyModifiers.HasFlag(KeyModifiers.Shift))
         {
-            if (ActiveDocument.SelectedStructureMember is null || ActiveDocument.SelectedStructureMember.GuidValue == memberVM.GuidValue)
+            // TODO: Implement this
+            /*if (ActiveDocument.SelectedStructureMember is null || ActiveDocument.SelectedStructureMember.GuidValue == memberVM.GuidValue)
                 return;
             ActiveDocument.Operations.ClearSoftSelectedMembers();
             TraverseRange(
                 ActiveDocument.SelectedStructureMember.GuidValue,
                 memberVM.GuidValue,
-                ActiveDocument.StructureRoot,
+                ActiveDocument.NodeGraph,
                 static member =>
                 {
                     if (member.Selection == StructureMemberSelectionType.None)
                         member.Document.Operations.AddSoftSelectedMember(member.GuidValue);
-                });
+                });*/
         }
         else
         {
-            ActiveDocument.Operations.SetSelectedMember(memberVM.GuidValue);
+            ActiveDocument.Operations.SetSelectedMember(memberVM.Id);
             ActiveDocument.Operations.ClearSoftSelectedMembers();
         }
     }

+ 7 - 60
src/PixiEditor.AvaloniaUI/Views/Nodes/NodeGraphView.cs

@@ -3,6 +3,7 @@ using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Controls.Primitives;
 using Avalonia.VisualTree;
+using PixiEditor.AvaloniaUI.Models.Handlers;
 using PixiEditor.AvaloniaUI.ViewModels.Nodes;
 using PixiEditor.AvaloniaUI.ViewModels.Nodes.Properties;
 
@@ -10,74 +11,20 @@ namespace PixiEditor.AvaloniaUI.Views.Nodes;
 
 public class NodeGraphView : Zoombox.Zoombox
 {
-    public static readonly StyledProperty<ObservableCollection<NodeViewModel>> NodesProperty = AvaloniaProperty.Register<NodeGraphView, ObservableCollection<NodeViewModel>>(
-        nameof(Nodes));
+    public static readonly StyledProperty<INodeGraphHandler> NodeGraphProperty = AvaloniaProperty.Register<NodeGraphView, INodeGraphHandler>(
+        nameof(NodeGraph));
 
-    public static readonly StyledProperty<ObservableCollection<NodeConnectionViewModel>> ConnectionsProperty = AvaloniaProperty.Register<NodeGraphView, ObservableCollection<NodeConnectionViewModel>>(
-        nameof(Connections));
-
-    public ObservableCollection<NodeConnectionViewModel> Connections
-    {
-        get => GetValue(ConnectionsProperty);
-        set => SetValue(ConnectionsProperty, value);
-    }
-    
-    public ObservableCollection<NodeViewModel> Nodes
+    public INodeGraphHandler NodeGraph
     {
-        get => GetValue(NodesProperty);
-        set => SetValue(NodesProperty, value);
+        get => GetValue(NodeGraphProperty);
+        set => SetValue(NodeGraphProperty, value);
     }
-    
+
     protected override Type StyleKeyOverride => typeof(NodeGraphView);
 
     public NodeGraphView()
     {
-        NodeViewModel node = new NodeViewModel()
-        {
-            Name = "Node 1",
-            X = 100,
-            Y = 100,
-            Outputs = new ObservableCollection<NodePropertyViewModel>()
-        };
-        
-        NodeViewModel node2 = new NodeViewModel()
-        {
-            Name = "Node 2",
-            X = 500,
-            Y = 100,
-            Inputs = new ObservableCollection<NodePropertyViewModel>()
-        };
-        
-        node.Outputs.Add(new ImageNodePropertyViewModel(node)
-        {
-            Name = "Output 1",
-            Node = node,
-            IsInput = false,
-        });
-        
-        node2.Inputs.Add(new ImageNodePropertyViewModel(node2)
-        {
-            Name = "Input 1",
-            Node = node2,
-            IsInput = true,
-        });
-
-        Nodes =
-        [
-            node,
-            node2,
-        ];
         
-        Connections =
-        [
-            new NodeConnectionViewModel()
-            {
-                InputNode = node2,
-                InputProperty = node2.Inputs[0],
-                OutputNode = node,
-                OutputProperty = node.Outputs[0],
-            },
-        ];
     }
 }
 

+ 1 - 1
src/PixiEditor.ChangeableDocument/ChangeInfos/Drawing/LayerImageArea_ChangeInfo.cs

@@ -2,4 +2,4 @@
 
 namespace PixiEditor.ChangeableDocument.ChangeInfos.Drawing;
 
-public record class LayerImageArea_ChangeInfo(Guid GuidValue, AffectedArea Area) : IChangeInfo;
+public record class LayerImageArea_ChangeInfo(Guid Id, AffectedArea Area) : IChangeInfo;

+ 1 - 1
src/PixiEditor.ChangeableDocument/ChangeInfos/Drawing/MaskArea_ChangeInfo.cs

@@ -2,4 +2,4 @@
 
 namespace PixiEditor.ChangeableDocument.ChangeInfos.Drawing;
 
-public record class MaskArea_ChangeInfo(Guid GuidValue, AffectedArea Area) : IChangeInfo;
+public record class MaskArea_ChangeInfo(Guid Id, AffectedArea Area) : IChangeInfo;

+ 1 - 1
src/PixiEditor.ChangeableDocument/ChangeInfos/Properties/LayerLockTransparency_ChangeInfo.cs

@@ -1,2 +1,2 @@
 namespace PixiEditor.ChangeableDocument.ChangeInfos.Properties;
-public record class LayerLockTransparency_ChangeInfo(Guid GuidValue, bool LockTransparency) : IChangeInfo;
+public record class LayerLockTransparency_ChangeInfo(Guid Id, bool LockTransparency) : IChangeInfo;

+ 1 - 1
src/PixiEditor.ChangeableDocument/ChangeInfos/Properties/StructureMemberBlendMode_ChangeInfo.cs

@@ -1,6 +1,6 @@
 using PixiEditor.ChangeableDocument.Enums;
 
 namespace PixiEditor.ChangeableDocument.ChangeInfos.Properties;
-public record class StructureMemberBlendMode_ChangeInfo(Guid GuidValue, BlendMode BlendMode) : IChangeInfo
+public record class StructureMemberBlendMode_ChangeInfo(Guid Id, BlendMode BlendMode) : IChangeInfo
 {
 }

+ 1 - 1
src/PixiEditor.ChangeableDocument/ChangeInfos/Properties/StructureMemberClipToMemberBelow_ChangeInfo.cs

@@ -1,4 +1,4 @@
 namespace PixiEditor.ChangeableDocument.ChangeInfos.Properties;
-public record class StructureMemberClipToMemberBelow_ChangeInfo(Guid GuidValue, bool ClipToMemberBelow) : IChangeInfo
+public record class StructureMemberClipToMemberBelow_ChangeInfo(Guid Id, bool ClipToMemberBelow) : IChangeInfo
 {
 }

+ 1 - 1
src/PixiEditor.ChangeableDocument/ChangeInfos/Properties/StructureMemberIsVisible_ChangeInfo.cs

@@ -1,5 +1,5 @@
 namespace PixiEditor.ChangeableDocument.ChangeInfos.Properties;
 
-public record class StructureMemberIsVisible_ChangeInfo(Guid GuidValue, bool IsVisible) : IChangeInfo
+public record class StructureMemberIsVisible_ChangeInfo(Guid Id, bool IsVisible) : IChangeInfo
 {
 }

+ 1 - 1
src/PixiEditor.ChangeableDocument/ChangeInfos/Properties/StructureMemberMaskIsVisible_ChangeInfo.cs

@@ -1,4 +1,4 @@
 namespace PixiEditor.ChangeableDocument.ChangeInfos.Properties;
-public record class StructureMemberMaskIsVisible_ChangeInfo(Guid GuidValue, bool IsVisible) : IChangeInfo
+public record class StructureMemberMaskIsVisible_ChangeInfo(Guid Id, bool IsVisible) : IChangeInfo
 {
 }

+ 1 - 1
src/PixiEditor.ChangeableDocument/ChangeInfos/Properties/StructureMemberMask_ChangeInfo.cs

@@ -1,3 +1,3 @@
 namespace PixiEditor.ChangeableDocument.ChangeInfos.Properties;
 
-public record class StructureMemberMask_ChangeInfo(Guid GuidValue, bool HasMask) : IChangeInfo;
+public record class StructureMemberMask_ChangeInfo(Guid Id, bool HasMask) : IChangeInfo;

+ 1 - 1
src/PixiEditor.ChangeableDocument/ChangeInfos/Properties/StructureMemberName_ChangeInfo.cs

@@ -1,3 +1,3 @@
 namespace PixiEditor.ChangeableDocument.ChangeInfos.Properties;
 
-public record class StructureMemberName_ChangeInfo(Guid GuidValue, string Name) : IChangeInfo;
+public record class StructureMemberName_ChangeInfo(Guid Id, string Name) : IChangeInfo;

+ 1 - 1
src/PixiEditor.ChangeableDocument/ChangeInfos/Properties/StructureMemberOpacity_ChangeInfo.cs

@@ -1,5 +1,5 @@
 namespace PixiEditor.ChangeableDocument.ChangeInfos.Properties;
 
-public record class StructureMemberOpacity_ChangeInfo(Guid GuidValue, float Opacity) : IChangeInfo
+public record class StructureMemberOpacity_ChangeInfo(Guid Id, float Opacity) : IChangeInfo
 {
 }

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

@@ -10,7 +10,7 @@ public abstract record class CreateStructureMember_ChangeInfo(
     bool ClipToMemberBelow,
     string Name,
     BlendMode BlendMode,
-    Guid GuidValue,
+    Guid Id,
     bool HasMask,
     bool MaskIsVisible
 ) : IChangeInfo;

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

@@ -1,3 +1,3 @@
 namespace PixiEditor.ChangeableDocument.ChangeInfos.Structure;
 
-public record class DeleteStructureMember_ChangeInfo(Guid GuidValue, Guid ParentGuid) : IChangeInfo;
+public record class DeleteStructureMember_ChangeInfo(Guid Id, Guid ParentGuid) : IChangeInfo;

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

@@ -1,3 +1,3 @@
 namespace PixiEditor.ChangeableDocument.ChangeInfos.Structure;
 
-public record class MoveStructureMember_ChangeInfo(Guid GuidValue, Guid ParentFromGuid, Guid ParentToGuid, int NewIndex) : IChangeInfo;
+public record class MoveStructureMember_ChangeInfo(Guid Id, Guid ParentFromGuid, Guid ParentToGuid, int NewIndex) : IChangeInfo;

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

@@ -19,6 +19,16 @@ public class InputProperty : IInputProperty
         Node = node;
     }
 
+    public InputProperty Clone(Node forNode)
+    {
+        if(Value is ICloneable cloneable)
+            return new InputProperty(forNode, Name, cloneable.Clone());
+        
+        if(!Value.GetType().IsPrimitive && Value.GetType() != typeof(string))
+            throw new InvalidOperationException("Value is not cloneable and not a primitive type");
+        
+        return new InputProperty(forNode, Name, Value);
+    }
 }
 
 

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

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

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

@@ -5,7 +5,6 @@ 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; }

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

@@ -12,6 +12,6 @@ public interface IReadOnlyStructureNode : IReadOnlyNode
     public InputProperty<BlendMode> BlendMode { get; }
     public InputProperty<ChunkyImage?> Mask { get; }
     public InputProperty<bool> MaskIsVisible { get; }
-    public string LayerName { get; set; }
+    public string MemberName { get; set; }
     public RectI? GetTightBounds(KeyFrameTime frameTime);
 }

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

@@ -13,10 +13,12 @@ public class FolderNode : StructureNode, IReadOnlyFolderNode
 
     public override ChunkyImage? OnExecute(KeyFrameTime frame)
     {
+        return Background.Value;
     }
 
     public override RectI? GetTightBounds(KeyFrameTime frameTime)
     {
+        return Background.Value?.FindTightCommittedBounds();
         /*if (Children.Count == 0)
       {
           return null;

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

@@ -28,7 +28,8 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
 
     public override ChunkyImage OnExecute(KeyFrameTime frame)
     {
-        // TODO: actual rendering
+        var imageFrame = frames.FirstOrDefault(x => x.IsInFrame(frame.Frame));
+        return imageFrame?.Image ?? frames[0].Image;
     }
 
     public override void Dispose()

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

@@ -9,7 +9,7 @@ public class MergeNode : Node
     public InputProperty<ChunkyImage?> Bottom { get; }
     public OutputProperty<ChunkyImage> Output { get; }
     
-    public MergeNode(string name) : base(name)
+    public MergeNode() 
     {
         Top = CreateInput<ChunkyImage>("Top", null);
         Bottom = CreateInput<ChunkyImage>("Bottom", null);

+ 35 - 3
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Node.cs

@@ -34,9 +34,21 @@ public abstract class Node(Guid? id = null) : IReadOnlyNode, IDisposable
 
     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 RemoveKeyFrame(Guid keyFrameGuid)
+    {
+        // TODO: Implement
+    }
+
+    public void SetKeyFrameLength(Guid keyFrameGuid, int startFrame, int duration)
+    {
+        // TODO: Implement
+    }
+
+    public void AddFrame<T>(Guid keyFrameGuid, int startFrame, int duration, T value)
+    {
+        // TODO: Implement
+    }
 
     public void TraverseBackwards(Func<IReadOnlyNode, bool> action)
     {
@@ -132,4 +144,24 @@ public abstract class Node(Guid? id = null) : IReadOnlyNode, IDisposable
             }
         }
     }
+    
+    public virtual Node Clone()
+    {
+        var clone = (Node)MemberwiseClone();
+        clone.Id = Guid.NewGuid();
+        clone.inputs = new List<InputProperty>();
+        clone.outputs = new List<OutputProperty>();
+        clone._connectedNodes = new List<IReadOnlyNode>();
+        foreach (var input in inputs)
+        {
+            var newInput = input.Clone(clone);
+            clone.inputs.Add(newInput);
+        }
+        foreach (var output in outputs)
+        {
+            var newOutput = output.Clone(clone);
+            clone.outputs.Add(newOutput);
+        }
+        return clone;
+    }
 }

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

@@ -5,7 +5,7 @@ namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 public class OutputNode : Node
 {
     public InputProperty<ChunkyImage?> Input { get; } 
-    public OutputNode(string name) : base(name)
+    public OutputNode()
     {
         Input = CreateInput<ChunkyImage>("Input", null);
     }

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

@@ -17,7 +17,7 @@ public abstract class StructureNode : Node, IReadOnlyStructureNode
     
     public OutputProperty<ChunkyImage?> Output { get; }
     
-    public string LayerName { get; set; } = string.Empty;
+    public string MemberName { get; set; } = string.Empty;
 
     protected StructureNode(Guid? id = null) : base(id)
     {

+ 31 - 14
src/PixiEditor.ChangeableDocument/Changeables/Graph/OutputProperty.cs

@@ -4,12 +4,13 @@ using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph;
 
 public delegate void InputConnectedEvent(IInputProperty input, IOutputProperty output);
+
 public class OutputProperty : IOutputProperty
 {
     private List<IInputProperty> _connections = new();
     private object _value;
     public string Name { get; }
-    
+
     public Node Node { get; }
     IReadOnlyNode INodeProperty.Node => Node;
 
@@ -23,14 +24,14 @@ public class OutputProperty : IOutputProperty
             {
                 connection.Value = value;
             }
-        }    
+        }
     }
 
     public IReadOnlyCollection<IInputProperty> Connections => _connections;
-    
+
     public event InputConnectedEvent Connected;
     public event InputConnectedEvent Disconnected;
-    
+
     internal OutputProperty(Node node, string name, object defaultValue)
     {
         Name = name;
@@ -38,28 +39,44 @@ public class OutputProperty : IOutputProperty
         _connections = new List<IInputProperty>();
         Node = node;
     }
-    
+
     public void ConnectTo(IInputProperty property)
     {
-        if(Connections.Contains(property)) return;
-        
+        if (Connections.Contains(property)) return;
+
         _connections.Add(property);
         property.Connection = this;
         Connected?.Invoke(property, this);
     }
-    
+
     public void DisconnectFrom(IInputProperty property)
     {
-        if(!Connections.Contains(property)) return;
-        
+        if (!Connections.Contains(property)) return;
+
         _connections.Remove(property);
-        if(property.Connection == this)
+        if (property.Connection == this)
         {
             property.Connection = null;
         }
-        
+
         Disconnected?.Invoke(property, this);
     }
+
+    public OutputProperty Clone(Node clone)
+    {
+        if (Value is not ICloneable && !Value.GetType().IsPrimitive && Value.GetType() != typeof(string))
+            throw new InvalidOperationException("Value is not cloneable and not a primitive type");
+     
+        object value = Value is ICloneable cloneableValue ? cloneableValue.Clone() : Value;
+        
+        var newOutput = new OutputProperty(clone, Name, value);
+        foreach (var connection in Connections)
+        {
+            newOutput.ConnectTo(connection);
+        }
+
+        return newOutput;
+    }
 }
 
 public class OutputProperty<T> : OutputProperty, INodeProperty<T>
@@ -69,8 +86,8 @@ public class OutputProperty<T> : OutputProperty, INodeProperty<T>
         get => (T)base.Value;
         set => base.Value = value;
     }
-    
-    internal OutputProperty(Node node ,string name, T defaultValue) : base(node, name, defaultValue)
+
+    internal OutputProperty(Node node, string name, T defaultValue) : base(node, name, defaultValue)
     {
     }
 }

+ 17 - 15
src/PixiEditor.ChangeableDocument/Changes/Drawing/CombineStructureMembersOnto_Change.cs

@@ -1,4 +1,5 @@
-using PixiEditor.ChangeableDocument.Rendering;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.Rendering;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Numerics;
 
@@ -31,42 +32,43 @@ internal class CombineStructureMembersOnto_Change : Change
             if (!target.TryFindMember(guid, out var member))
                 return false;
             
-            if (member is Layer layer)
-                layersToCombine.Add(layer.GuidValue);
-            else if (member is Folder innerFolder)
+            if (member is LayerNode layer)
+                layersToCombine.Add(layer.Id);
+            else if (member is FolderNode innerFolder)
                 AddChildren(innerFolder, layersToCombine);
         }
 
         return true;
     }
 
-    private void AddChildren(Folder folder, HashSet<Guid> collection)
+    private void AddChildren(FolderNode folder, HashSet<Guid> collection)
     {
-        foreach (var child in folder.Children)
+        //TODO: Implement
+        /*foreach (var child in folder.Children)
         {
-            if (child is Layer layer)
-                collection.Add(layer.GuidValue);
-            else if (child is Folder innerFolder)
+            if (child is LayerNode layer)
+                collection.Add(layer.Id);
+            else if (child is FolderNode innerFolder)
                 AddChildren(innerFolder, collection);
-        }
+        }*/
     }
 
     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<ImageNode>(targetLayer);
+        var toDrawOn = target.FindMemberOrThrow<ImageLayerNode>(targetLayer);
 
         var chunksToCombine = new HashSet<VecI>();
         foreach (var guid in layersToCombine)
         {
-            var layer = target.FindMemberOrThrow<ImageNode>(guid);
+            var layer = target.FindMemberOrThrow<ImageLayerNode>(guid);
             var layerImage = layer.GetLayerImageAtFrame(frame);
             chunksToCombine.UnionWith(layerImage.FindAllChunks());
         }
 
         var toDrawOnImage = toDrawOn.GetLayerImageAtFrame(frame);
         toDrawOnImage.EnqueueClear();
-        foreach (var chunk in chunksToCombine)
+        /*foreach (var chunk in chunksToCombine)
         {
             OneOf<Chunk, EmptyChunk> combined = ChunkRenderer.MergeChosenMembers(chunk, ChunkResolution.Full, target.NodeGraph, frame, layersToCombine);
             if (combined.IsT0)
@@ -74,7 +76,7 @@ internal class CombineStructureMembersOnto_Change : Change
                 toDrawOnImage.EnqueueDrawImage(chunk * ChunkyImage.FullChunkSize, combined.AsT0.Surface);
                 combined.AsT0.Dispose();
             }
-        }
+        }*/
         var affArea = toDrawOnImage.FindAffectedArea();
         originalChunks = new CommittedChunkStorage(toDrawOnImage, affArea.Chunks);
         toDrawOnImage.CommitChanges();
@@ -85,7 +87,7 @@ internal class CombineStructureMembersOnto_Change : Change
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
     {
-        var toDrawOn = target.FindMemberOrThrow<ImageNode>(targetLayer);
+        var toDrawOn = target.FindMemberOrThrow<ImageLayerNode>(targetLayer);
         var affectedArea = DrawingChangeHelper.ApplyStoredChunksDisposeAndSetToNull(toDrawOn.GetLayerImageAtFrame(frame), ref originalChunks);
         return new LayerImageArea_ChangeInfo(targetLayer, affectedArea);
     }

+ 10 - 8
src/PixiEditor.ChangeableDocument/Changes/Drawing/FloodFill/FloodFillChunkCache.cs

@@ -1,4 +1,5 @@
-using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Rendering;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Surface;
@@ -12,7 +13,7 @@ internal class FloodFillChunkCache : IDisposable
     private Paint ReplacingPaint { get; } = new Paint() { BlendMode = BlendMode.Src };
 
     private readonly HashSet<Guid>? membersToRender;
-    private readonly IReadOnlyFolder? structureRoot;
+    private readonly IReadOnlyNodeGraph? graph;
     private readonly IReadOnlyChunkyImage? image;
     private readonly int frame;
 
@@ -23,10 +24,10 @@ internal class FloodFillChunkCache : IDisposable
         this.image = image;
     }
 
-    public FloodFillChunkCache(HashSet<Guid> membersToRender, IReadOnlyFolder structureRoot, int frame)
+    public FloodFillChunkCache(HashSet<Guid> membersToRender, IReadOnlyNodeGraph graph, int frame)
     {
         this.membersToRender = membersToRender;
-        this.structureRoot = structureRoot;
+        this.graph = graph;
         this.frame = frame;
     }
 
@@ -44,14 +45,15 @@ internal class FloodFillChunkCache : IDisposable
             return foundChunk;
 
         // need to get the chunk by merging multiple members
-        if (image is null)
+        //TODO: Implement
+        /*if (image is null)
         {
-            if (structureRoot is null || membersToRender is null)
+            if (graph is null || membersToRender is null)
                 throw new InvalidOperationException();
-            var chunk = ChunkRenderer.MergeChosenMembers(pos, ChunkResolution.Full, structureRoot, frame, membersToRender);
+            var chunk = ChunkRenderer.MergeChosenMembers(pos, ChunkResolution.Full, graph, frame, membersToRender);
             acquiredChunks[pos] = chunk;
             return chunk;
-        }
+        }*/
 
         // there is only a single image, just get the chunk from it
         if (!image.LatestOrCommittedChunkExists(pos))

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

@@ -1,4 +1,5 @@
 using ChunkyImageLib.Operations;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
 using PixiEditor.DrawingApi.Core.Numerics;
@@ -25,13 +26,13 @@ public static class FloodFillHelper
         {
             Guid guid = membersToFloodFill.First();
             var member = document.FindMemberOrThrow(guid);
-            if (member is IReadOnlyFolder)
-                return new FloodFillChunkCache(membersToFloodFill, document.StructureRoot, frame);
+            if (member is IReadOnlyFolderNode)
+                return new FloodFillChunkCache(membersToFloodFill, document.NodeGraph, frame);
             if (member is not IReadOnlyImageNode rasterLayer)
                 throw new InvalidOperationException("Member is not a raster layer");
             return new FloodFillChunkCache(rasterLayer.GetLayerImageAtFrame(frame));
         }
-        return new FloodFillChunkCache(membersToFloodFill, document.StructureRoot, frame);
+        return new FloodFillChunkCache(membersToFloodFill, document.NodeGraph, frame);
     }
 
     public static Dictionary<VecI, Chunk> FloodFill(

+ 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.LayerName == newName)
+        if (!target.TryFindMember(targetMember, out var member) || member.MemberName == newName)
             return false;
-        originalName = member.LayerName;
+        originalName = member.MemberName;
         return true;
     }
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
     {
-        target.FindMemberOrThrow(targetMember).LayerName = newName;
+        target.FindMemberOrThrow(targetMember).MemberName = 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).LayerName = originalName;
+        target.FindMemberOrThrow(targetMember).MemberName = originalName;
         return new StructureMemberName_ChangeInfo(targetMember, originalName);
     }
 

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

@@ -38,7 +38,7 @@ internal class CreateStructureMember_Change : Change
             _ => throw new NotSupportedException(),
         };
 
-        folder.Children = folder.Children.Insert(parentFolderIndex, member);
+        /*folder.Children = folder.Children.Insert(parentFolderIndex, member);
 
         ignoreInUndo = false;
         return type switch
@@ -46,15 +46,18 @@ internal class CreateStructureMember_Change : Change
             StructureMemberType.Layer => CreateLayer_ChangeInfo.FromLayer(parentFolderGuid, parentFolderIndex, (Layer)member),
             StructureMemberType.Folder => CreateFolder_ChangeInfo.FromFolder(parentFolderGuid, parentFolderIndex, (Folder)member),
             _ => throw new NotSupportedException(),
-        };
+        };*/
+        
+        ignoreInUndo = false;
+        return new None();
     }
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document document)
     {
-        Folder folder = document.FindMemberOrThrow<Folder>(parentFolderGuid);
-        StructureMember child = document.FindMemberOrThrow(newMemberGuid);
+        FolderNode folder = document.FindMemberOrThrow<FolderNode>(parentFolderGuid);
+        StructureNode child = document.FindMemberOrThrow(newMemberGuid);
         child.Dispose();
-        folder.Children = folder.Children.RemoveAt(folder.Children.FindIndex(member => member.GuidValue == newMemberGuid));
+        //folder.Children = folder.Children.RemoveAt(folder.Children.FindIndex(member => member.GuidValue == newMemberGuid));
 
         return new DeleteStructureMember_ChangeInfo(newMemberGuid, parentFolderGuid);
     }

+ 17 - 11
src/PixiEditor.ChangeableDocument/Changes/Structure/DeleteStructureMember_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;
 
@@ -7,7 +8,7 @@ internal class DeleteStructureMember_Change : Change
     private Guid memberGuid;
     private Guid parentGuid;
     private int originalIndex;
-    private StructureMember? savedCopy;
+    private StructureNode? savedCopy;
 
     [GenerateMakeChangeAction]
     public DeleteStructureMember_Change(Guid memberGuid)
@@ -21,33 +22,38 @@ internal class DeleteStructureMember_Change : Change
         if (member is null || parent is null)
             return false;
 
-        originalIndex = parent.Children.IndexOf(member);
-        parentGuid = parent.GuidValue;
-        savedCopy = member.Clone();
+        //originalIndex = parent.Children.IndexOf(member);
+        parentGuid = parent.Id;
+        savedCopy = (StructureNode)member.Clone();
         return true;
     }
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document document, bool firstApply, out bool ignoreInUndo)
     {
-        var (member, parent) = document.FindChildAndParentOrThrow(memberGuid);
+        /*var (member, parent) = document.FindChildAndParentOrThrow(memberGuid);
         parent.Children = parent.Children.Remove(member);
         member.Dispose();
         ignoreInUndo = false;
-        return new DeleteStructureMember_ChangeInfo(memberGuid, parentGuid);
+        return new DeleteStructureMember_ChangeInfo(memberGuid, parentGuid);*/
+        
+        ignoreInUndo = false;
+        return new None();
     }
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document doc)
     {
-        var parent = doc.FindMemberOrThrow<Folder>(parentGuid);
+        /*var parent = doc.FindMemberOrThrow<FolderNode>(parentGuid);
 
         var copy = savedCopy!.Clone();
         parent.Children = parent.Children.Insert(originalIndex, copy);
         return copy switch
         {
-            Layer => CreateLayer_ChangeInfo.FromLayer(parentGuid, originalIndex, (Layer)copy),
-            Folder => CreateFolder_ChangeInfo.FromFolder(parentGuid, originalIndex, (Folder)copy),
+            LayerNode => CreateLayer_ChangeInfo.FromLayer(parentGuid, originalIndex, (LayerNode)copy),
+            FolderNode => CreateFolder_ChangeInfo.FromFolder(parentGuid, originalIndex, (FolderNode)copy),
             _ => throw new NotSupportedException(),
-        };
+        };*/
+        
+        return new None();
     }
 
     public override void Dispose()

+ 15 - 9
src/PixiEditor.ChangeableDocument/Changes/Structure/DuplicateLayer_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;
 internal class DuplicateLayer_Change : Change
@@ -13,7 +14,7 @@ internal class DuplicateLayer_Change : Change
     }
     public override bool InitializeAndValidate(Document target)
     {
-        if (!target.TryFindMember<Layer>(layerGuid, out Layer? layer))
+        if (!target.TryFindMember<LayerNode>(layerGuid, out LayerNode? layer))
             return false;
         duplicateGuid = Guid.NewGuid();
         return true;
@@ -21,23 +22,28 @@ internal class DuplicateLayer_Change : Change
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
     {
-        (Layer existingLayer, Folder parent) = ((Layer, Folder))target.FindChildAndParentOrThrow(layerGuid);
+        (LayerNode existingLayer, FolderNode parent) = ((LayerNode, FolderNode))target.FindChildAndParentOrThrow(layerGuid);
 
-        Layer clone = (Layer)existingLayer.Clone();
-        clone.GuidValue = duplicateGuid;
+        LayerNode clone = (LayerNode)existingLayer.Clone();
+        clone.Id = duplicateGuid;
 
-        int index = parent.Children.IndexOf(existingLayer);
+        /*int index = parent.Children.IndexOf(existingLayerNode);
         parent.Children = parent.Children.Insert(index, clone);
 
         ignoreInUndo = false;
-        return CreateLayer_ChangeInfo.FromLayer(parent.GuidValue, index, clone);
+        return CreateLayer_ChangeInfo.FromLayer(parent.Id, index, clone);*/
+//TODO: Implement
+        ignoreInUndo = false;
+        return new None();
     }
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
     {
-        var (member, parent) = target.FindChildAndParentOrThrow(duplicateGuid);
+        /*var (member, parent) = target.FindChildAndParentOrThrow(duplicateGuid);
         parent.Children = parent.Children.Remove(member);
         member.Dispose();
-        return new DeleteStructureMember_ChangeInfo(duplicateGuid, parent.GuidValue);
+        return new DeleteStructureMember_ChangeInfo(duplicateGuid, parent.Id);*/
+
+        return new None();
     }
 }

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

@@ -28,17 +28,19 @@ internal class MoveStructureMember_Change : Change
         if (member is null || curFolder is null || targetFolder is not FolderNode)
             return false;
         originalFolderGuid = curFolder.Id;
-        originalFolderIndex = curFolder.Children.IndexOf(member);
+        // TODO: this too:
+        // originalFolderIndex = curFolder.Children.IndexOf(member);
         return true;
     }
 
     private static void Move(Document document, Guid memberGuid, Guid targetFolderGuid, int targetIndex)
     {
-        var targetFolder = document.FindMemberOrThrow<FolderNode>(targetFolderGuid);
+        // TODO: Implement
+        /*var targetFolder = document.FindMemberOrThrow<FolderNode>(targetFolderGuid);
         var (member, curFolder) = document.FindChildAndParentOrThrow(memberGuid);
 
         curFolder.Children = curFolder.Children.Remove(member);
-        targetFolder.Children = targetFolder.Children.Insert(targetIndex, member);
+        targetFolder.Children = targetFolder.Children.Insert(targetIndex, member);*/
     }
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)

+ 0 - 320
src/PixiEditor.ChangeableDocument/Rendering/ChunkRenderer.cs

@@ -1,320 +0,0 @@
-using ChunkyImageLib.Operations;
-using PixiEditor.ChangeableDocument.Changeables.Interfaces;
-using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
-using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
-using PixiEditor.Numerics;
-
-namespace PixiEditor.ChangeableDocument.Rendering;
-
-public static class ChunkRenderer
-{
-    private static readonly Paint ClippingPaint = new Paint() { BlendMode = BlendMode.DstIn };
-
-    private static RectI? TransformClipRect(RectI? globalClippingRect, ChunkResolution resolution, VecI chunkPos)
-    {
-        if (globalClippingRect is not RectI rect)
-            return null;
-
-        double multiplier = resolution.Multiplier();
-        VecI pixelChunkPos = chunkPos * (int)(ChunkyImage.FullChunkSize * multiplier);
-        return (RectI?)rect.Scale(multiplier).Translate(-pixelChunkPos).RoundOutwards();
-    }
-
-    public static OneOf<Chunk, EmptyChunk> MergeWholeStructure(VecI chunkPos, ChunkResolution resolution, IReadOnlyFolder root, int frame, RectI? globalClippingRect = null)
-    {
-        using RenderingContext context = new();
-        try
-        {
-            RectI? transformedClippingRect = TransformClipRect(globalClippingRect, resolution, chunkPos);
-            return MergeFolderContents(context, chunkPos, resolution, root, frame, new All(), transformedClippingRect);
-        }
-        catch (ObjectDisposedException)
-        {
-            return new EmptyChunk();
-        }
-    }
-
-    public static OneOf<Chunk, EmptyChunk> MergeChosenMembers(VecI chunkPos, ChunkResolution resolution, IReadOnlyFolder root, int frame, HashSet<Guid> members, RectI? globalClippingRect = null)
-    {
-        using RenderingContext context = new();
-        try
-        {
-            RectI? transformedClippingRect = TransformClipRect(globalClippingRect, resolution, chunkPos);
-            return MergeFolderContents(context, chunkPos, resolution, root, frame, members, transformedClippingRect);
-        }
-        catch (ObjectDisposedException)
-        {
-            return new EmptyChunk();
-        }
-    }
-
-    private static OneOf<EmptyChunk, Chunk> RenderLayerWithMask(
-        RenderingContext context,
-        Chunk targetChunk,
-        VecI chunkPos,
-        int frame,
-        ChunkResolution resolution,
-        IReadOnlyLayer layer,
-        OneOf<FilledChunk, EmptyChunk, Chunk> clippingChunk,
-        RectI? transformedClippingRect)
-    {
-        if (
-            clippingChunk.IsT1 ||
-            !layer.IsVisible ||
-            layer.Opacity == 0 ||
-            (layer.Mask is not null && !layer.Mask.LatestOrCommittedChunkExists(chunkPos))
-        )
-            return new EmptyChunk();
-
-        context.UpdateFromMember(layer);
-
-        Chunk renderingResult = Chunk.Create(resolution);
-        if (transformedClippingRect is not null)
-        {
-            renderingResult.Surface.DrawingSurface.Canvas.Save();
-            renderingResult.Surface.DrawingSurface.Canvas.ClipRect((RectD)transformedClippingRect);
-            targetChunk.Surface.DrawingSurface.Canvas.Save();
-            targetChunk.Surface.DrawingSurface.Canvas.ClipRect((RectD)transformedClippingRect);
-        }
-
-        if (!layer.Rasterize(frame).DrawMostUpToDateChunkOn(chunkPos, resolution, renderingResult.Surface.DrawingSurface, VecI.Zero, context.ReplacingPaintWithOpacity))
-        {
-            renderingResult.Dispose();
-            return new EmptyChunk();
-        }
-
-        if (!layer.Mask!.DrawMostUpToDateChunkOn(chunkPos, resolution, renderingResult.Surface.DrawingSurface, VecI.Zero, ClippingPaint))
-        {
-            // should pretty much never happen due to the check above, but you can never be sure with many threads
-            renderingResult.Dispose();
-            return new EmptyChunk();
-        }
-
-        if (clippingChunk.IsT2)
-            OperationHelper.ClampAlpha(renderingResult.Surface.DrawingSurface, clippingChunk.AsT2.Surface.DrawingSurface, transformedClippingRect);
-
-        targetChunk.Surface.DrawingSurface.Canvas.DrawSurface(renderingResult.Surface.DrawingSurface, 0, 0, context.BlendModePaint);
-        if (transformedClippingRect is not null)
-        {
-            renderingResult.Surface.DrawingSurface.Canvas.Restore();
-            targetChunk.Surface.DrawingSurface.Canvas.Restore();
-        }
-
-        return renderingResult;
-    }
-
-    private static OneOf<EmptyChunk, Chunk> RenderLayerSaveResult(
-        RenderingContext context,
-        Chunk targetChunk,
-        VecI chunkPos,
-        int frame,
-        ChunkResolution resolution,
-        IReadOnlyLayer layer,
-        OneOf<FilledChunk, EmptyChunk, Chunk> clippingChunk,
-        RectI? transformedClippingRect)
-    {
-        if (clippingChunk.IsT1 || !layer.IsVisible || layer.Opacity == 0)
-            return new EmptyChunk();
-
-        if (layer.Mask is not null && layer.MaskIsVisible)
-            return RenderLayerWithMask(context, targetChunk, chunkPos, frame, resolution, layer, clippingChunk, transformedClippingRect);
-
-        context.UpdateFromMember(layer);
-        Chunk renderingResult = Chunk.Create(resolution);
-        if (transformedClippingRect is not null)
-        {
-            renderingResult.Surface.DrawingSurface.Canvas.Save();
-            renderingResult.Surface.DrawingSurface.Canvas.ClipRect((RectD)transformedClippingRect);
-            targetChunk.Surface.DrawingSurface.Canvas.Save();
-            targetChunk.Surface.DrawingSurface.Canvas.ClipRect((RectD)transformedClippingRect);
-        }
-        if (!layer.Rasterize(frame).DrawMostUpToDateChunkOn(chunkPos, resolution, renderingResult.Surface.DrawingSurface, VecI.Zero, context.ReplacingPaintWithOpacity))
-        {
-            renderingResult.Dispose();
-            return new EmptyChunk();
-        }
-
-        if (clippingChunk.IsT2)
-            OperationHelper.ClampAlpha(renderingResult.Surface.DrawingSurface, clippingChunk.AsT2.Surface.DrawingSurface, transformedClippingRect);
-        targetChunk.Surface.DrawingSurface.Canvas.DrawSurface(renderingResult.Surface.DrawingSurface, 0, 0, context.BlendModePaint);
-
-        if (transformedClippingRect is not null)
-        {
-            renderingResult.Surface.DrawingSurface.Canvas.Restore();
-            targetChunk.Surface.DrawingSurface.Canvas.Restore();
-        }
-        return renderingResult;
-    }
-
-    private static void RenderLayer(RenderingContext context,
-        Chunk targetChunk,
-        VecI chunkPos,
-        ChunkResolution resolution,
-        IReadOnlyLayer layer,
-        int frame,
-        OneOf<FilledChunk, EmptyChunk, Chunk> clippingChunk,
-        RectI? transformedClippingRect)
-    {
-        if (clippingChunk.IsT1 || !layer.IsVisible || layer.Opacity == 0)
-            return;
-        if (layer.Mask is not null && layer.MaskIsVisible)
-        {
-            var result = RenderLayerWithMask(context, targetChunk, chunkPos, frame, resolution, layer, clippingChunk, transformedClippingRect);
-            if (result.IsT1)
-                result.AsT1.Dispose();
-            return;
-        }
-        // clipping chunk requires a temp chunk anyway so we could as well reuse the code from RenderLayerSaveResult
-        if (clippingChunk.IsT2)
-        {
-            var result = RenderLayerSaveResult(context, targetChunk, chunkPos, frame, resolution, layer, clippingChunk, transformedClippingRect);
-            if (result.IsT1)
-                result.AsT1.Dispose();
-            return;
-        }
-        context.UpdateFromMember(layer);
-
-        if (transformedClippingRect is not null)
-        {
-            targetChunk.Surface.DrawingSurface.Canvas.Save();
-            targetChunk.Surface.DrawingSurface.Canvas.ClipRect((RectD)transformedClippingRect);
-        }
-        layer.Rasterize(frame).DrawMostUpToDateChunkOn(chunkPos, resolution, targetChunk.Surface.DrawingSurface, VecI.Zero, context.BlendModeOpacityPaint);
-        if (transformedClippingRect is not null)
-            targetChunk.Surface.DrawingSurface.Canvas.Restore();
-    }
-
-    private static OneOf<EmptyChunk, Chunk> RenderFolder(
-        RenderingContext context,
-        Chunk targetChunk,
-        VecI chunkPos,
-        ChunkResolution resolution,
-        IReadOnlyFolder folder,
-        int frame,
-        OneOf<FilledChunk, EmptyChunk, Chunk> clippingChunk,
-        OneOf<All, HashSet<Guid>> membersToMerge,
-        RectI? transformedClippingRect)
-    {
-        if (
-            clippingChunk.IsT1 ||
-            !folder.IsVisible ||
-            folder.Opacity == 0 ||
-            folder.Children.Count == 0 ||
-            (folder.Mask is not null && folder.MaskIsVisible && !folder.Mask.LatestOrCommittedChunkExists(chunkPos))
-        )
-            return new EmptyChunk();
-
-        OneOf<Chunk, EmptyChunk> maybeContents = MergeFolderContents(context, chunkPos, resolution, folder, frame, membersToMerge, transformedClippingRect);
-        if (maybeContents.IsT1)
-            return new EmptyChunk();
-        Chunk contents = maybeContents.AsT0;
-
-        if (transformedClippingRect is not null)
-        {
-            contents.Surface.DrawingSurface.Canvas.Save();
-            contents.Surface.DrawingSurface.Canvas.ClipRect((RectD)transformedClippingRect);
-            targetChunk.Surface.DrawingSurface.Canvas.Save();
-            targetChunk.Surface.DrawingSurface.Canvas.ClipRect((RectD)transformedClippingRect);
-        }
-
-        if (folder.Mask is not null && folder.MaskIsVisible)
-        {
-            if (!folder.Mask.DrawMostUpToDateChunkOn(chunkPos, resolution, contents.Surface.DrawingSurface, VecI.Zero, ClippingPaint))
-            {
-                // this shouldn't really happen due to the check above, but another thread could edit the mask in the meantime
-                contents.Dispose();
-                return new EmptyChunk();
-            }
-        }
-
-        if (clippingChunk.IsT2)
-            OperationHelper.ClampAlpha(contents.Surface.DrawingSurface, clippingChunk.AsT2.Surface.DrawingSurface, transformedClippingRect);
-        context.UpdateFromMember(folder);
-        contents.Surface.DrawingSurface.Canvas.DrawSurface(contents.Surface.DrawingSurface, 0, 0, context.ReplacingPaintWithOpacity);
-        targetChunk.Surface.DrawingSurface.Canvas.DrawSurface(contents.Surface.DrawingSurface, 0, 0, context.BlendModePaint);
-
-        if (transformedClippingRect is not null)
-        {
-            contents.Surface.DrawingSurface.Canvas.Restore();
-            targetChunk.Surface.DrawingSurface.Canvas.Restore();
-        }
-
-        return contents;
-    }
-
-    private static OneOf<Chunk, EmptyChunk> MergeFolderContents(
-        RenderingContext context,
-        VecI chunkPos,
-        ChunkResolution resolution,
-        IReadOnlyFolder folder,
-        int frame,
-        OneOf<All, HashSet<Guid>> membersToMerge,
-        RectI? transformedClippingRect)
-    {
-        var folderChildren = folder.Children;
-        if (folderChildren.Count == 0)
-            return new EmptyChunk();
-
-        Chunk targetChunk = Chunk.Create(resolution);
-        targetChunk.Surface.DrawingSurface.Canvas.Clear();
-
-        OneOf<FilledChunk, EmptyChunk, Chunk> clippingChunk = new FilledChunk();
-        for (int i = 0; i < folderChildren.Count; i++)
-        {
-            var child = folderChildren[i];
-
-            // next child might use clip to member below in which case we need to save the clip image
-            bool needToSaveClippingChunk =
-                i < folderChildren.Count - 1 &&
-                !child.ClipToMemberBelow &&
-                folderChildren[i + 1].ClipToMemberBelow;
-
-            // if the current member doesn't need a clip, get rid of it
-            if (!child.ClipToMemberBelow && !clippingChunk.IsT0)
-            {
-                if (clippingChunk.IsT2)
-                    clippingChunk.AsT2.Dispose();
-                clippingChunk = new FilledChunk();
-            }
-
-            // layer
-            if (child is IReadOnlyLayer layer && (membersToMerge.IsT0 || membersToMerge.AsT1.Contains(layer.GuidValue)))
-            {
-                if (needToSaveClippingChunk)
-                {
-                    OneOf<EmptyChunk, Chunk> result = RenderLayerSaveResult(context, targetChunk, chunkPos, frame, resolution, layer, clippingChunk, transformedClippingRect);
-                    clippingChunk = result.IsT0 ? result.AsT0 : result.AsT1;
-                }
-                else
-                {
-                    RenderLayer(context, targetChunk, chunkPos, resolution, layer, frame, clippingChunk, transformedClippingRect);
-                }
-                continue;
-            }
-            else if (child is IReadOnlyLayer && needToSaveClippingChunk)
-            {
-                clippingChunk = new FilledChunk();
-            }
-
-            // folder
-            if (child is IReadOnlyFolder innerFolder)
-            {
-                bool shouldRenderAllChildren = membersToMerge.IsT0 || membersToMerge.AsT1.Contains(innerFolder.GuidValue);
-                OneOf<All, HashSet<Guid>> innerMembersToMerge = shouldRenderAllChildren ? new All() : membersToMerge;
-                if (needToSaveClippingChunk)
-                {
-                    OneOf<EmptyChunk, Chunk> result = RenderFolder(context, targetChunk, chunkPos, resolution, innerFolder, frame, clippingChunk, innerMembersToMerge, transformedClippingRect);
-                    clippingChunk = result.IsT0 ? result.AsT0 : result.AsT1;
-                }
-                else
-                {
-                    RenderFolder(context, targetChunk, chunkPos, resolution, innerFolder, frame, clippingChunk, innerMembersToMerge, transformedClippingRect);
-                }
-            }
-        }
-        if (clippingChunk.IsT2)
-            clippingChunk.AsT2.Dispose();
-        return targetChunk;
-    }
-}

+ 6 - 0
src/PixiEditor.ChangeableDocument/Rendering/DocumentEvaluator.cs

@@ -0,0 +1,6 @@
+namespace PixiEditor.ChangeableDocument.Rendering;
+
+public static class DocumentEvaluator
+{
+    
+}

+ 5 - 4
src/PixiEditor.ChangeableDocument/Rendering/RenderingContext.cs

@@ -1,4 +1,5 @@
-using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
 using PixiEditor.DrawingApi.Core.Surface;
 using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
@@ -12,10 +13,10 @@ internal class RenderingContext : IDisposable
     public Paint BlendModeOpacityPaint = new () { BlendMode = DrawingApiBlendMode.SrcOver };
     public Paint ReplacingPaintWithOpacity = new () { BlendMode = DrawingApiBlendMode.Src };
 
-    public void UpdateFromMember(IReadOnlyStructureMember member)
+    public void UpdateFromMember(IReadOnlyStructureNode member)
     {
-        Color opacityColor = new(255, 255, 255, (byte)Math.Round(member.Opacity * 255));
-        DrawingApiBlendMode blendMode = GetDrawingBlendMode(member.BlendMode);
+        Color opacityColor = new(255, 255, 255, (byte)Math.Round(member.Opacity.Value * 255));
+        DrawingApiBlendMode blendMode = GetDrawingBlendMode(member.BlendMode.Value);
 
         BlendModeOpacityPaint.Color = opacityColor;
         BlendModeOpacityPaint.BlendMode = blendMode;