2
0
Эх сурвалжийг харах

Merge branch 'parser4.0-nodes' into avalonia-rewrite

flabbet 1 жил өмнө
parent
commit
76f4f5a950
100 өөрчлөгдсөн 1498 нэмэгдсэн , 956 устгасан
  1. 3 2
      src/ChunkyImageLib/Chunk.cs
  2. 13 6
      src/ChunkyImageLib/ChunkyImage.cs
  3. 2 2
      src/ChunkyImageLib/ChunkyImageEx.cs
  4. 1 0
      src/ChunkyImageLib/ChunkyImageLib.csproj
  5. 2 2
      src/ChunkyImageLib/CommittedChunkStorage.cs
  6. 1 1
      src/ChunkyImageLib/DataHolders/ShapeData.cs
  7. 2 2
      src/ChunkyImageLib/IReadOnlyChunkyImage.cs
  8. 2 2
      src/ChunkyImageLib/Operations/ApplyMaskOperation.cs
  9. 0 1
      src/ChunkyImageLib/Operations/BresenhamLineHelper.cs
  10. 2 2
      src/ChunkyImageLib/Operations/BresenhamLineOperation.cs
  11. 2 2
      src/ChunkyImageLib/Operations/ChunkyImageOperation.cs
  12. 1 1
      src/ChunkyImageLib/Operations/ClearPathOperation.cs
  13. 2 2
      src/ChunkyImageLib/Operations/DrawingSurfaceLineOperation.cs
  14. 3 3
      src/ChunkyImageLib/Operations/EllipseOperation.cs
  15. 2 2
      src/ChunkyImageLib/Operations/ImageOperation.cs
  16. 1 1
      src/ChunkyImageLib/Operations/OperationHelper.cs
  17. 1 1
      src/ChunkyImageLib/Operations/PaintOperation.cs
  18. 3 3
      src/ChunkyImageLib/Operations/PathOperation.cs
  19. 2 2
      src/ChunkyImageLib/Operations/PixelOperation.cs
  20. 2 2
      src/ChunkyImageLib/Operations/PixelsOperation.cs
  21. 1 1
      src/ChunkyImageLib/Operations/RectangleOperation.cs
  22. 1 1
      src/ChunkyImageLib/Operations/ReplaceColorOperation.cs
  23. 4 4
      src/PixiEditor.AvaloniaUI.Desktop/PixiEditor.AvaloniaUI.Desktop.csproj
  24. 1 1
      src/PixiEditor.AvaloniaUI/Helpers/Converters/ImagePathToBitmapConverter.cs
  25. 1 1
      src/PixiEditor.AvaloniaUI/Helpers/Converters/VectorPathToVisibleConverter.cs
  26. 165 335
      src/PixiEditor.AvaloniaUI/Helpers/DocumentViewModelBuilder.cs
  27. 2 2
      src/PixiEditor.AvaloniaUI/Helpers/Extensions/BitmapExtensions.cs
  28. 11 1
      src/PixiEditor.AvaloniaUI/Helpers/Extensions/PixelFormatHelper.cs
  29. 64 95
      src/PixiEditor.AvaloniaUI/Helpers/Extensions/PixiParserDocumentEx.cs
  30. 368 0
      src/PixiEditor.AvaloniaUI/Helpers/Extensions/PixiParserV3DocumentEx.cs
  31. 0 176
      src/PixiEditor.AvaloniaUI/Helpers/SerializableDocumentEx.cs
  32. 106 0
      src/PixiEditor.AvaloniaUI/Helpers/SerializationUtil.cs
  33. 5 0
      src/PixiEditor.AvaloniaUI/Helpers/ServiceCollectionHelpers.cs
  34. 2 1
      src/PixiEditor.AvaloniaUI/Helpers/SurfaceHelpers.cs
  35. 2 2
      src/PixiEditor.AvaloniaUI/Helpers/WriteableBitmapUtility.cs
  36. 1 0
      src/PixiEditor.AvaloniaUI/Models/Clipboard/DataImage.cs
  37. 4 6
      src/PixiEditor.AvaloniaUI/Models/Controllers/ClipboardController.cs
  38. 1 1
      src/PixiEditor.AvaloniaUI/Models/DocumentModels/DocumentStructureHelper.cs
  39. 12 2
      src/PixiEditor.AvaloniaUI/Models/DocumentModels/DocumentUpdater.cs
  40. 3 2
      src/PixiEditor.AvaloniaUI/Models/DocumentModels/Public/DocumentOperationsModule.cs
  41. 1 1
      src/PixiEditor.AvaloniaUI/Models/DocumentModels/UpdateableChangeExecutors/LineToolExecutor.cs
  42. 1 0
      src/PixiEditor.AvaloniaUI/Models/DocumentModels/UpdateableChangeExecutors/PasteImageExecutor.cs
  43. 1 1
      src/PixiEditor.AvaloniaUI/Models/Files/BmpFileType.cs
  44. 3 4
      src/PixiEditor.AvaloniaUI/Models/Files/ImageFileType.cs
  45. 1 1
      src/PixiEditor.AvaloniaUI/Models/Files/JpegFileType.cs
  46. 1 1
      src/PixiEditor.AvaloniaUI/Models/Files/PngFileType.cs
  47. 2 2
      src/PixiEditor.AvaloniaUI/Models/Handlers/IDocument.cs
  48. 1 0
      src/PixiEditor.AvaloniaUI/Models/Handlers/IKeyFrameHandler.cs
  49. 2 1
      src/PixiEditor.AvaloniaUI/Models/Handlers/INodeHandler.cs
  50. 1 1
      src/PixiEditor.AvaloniaUI/Models/Handlers/IReferenceLayerHandler.cs
  51. 1 2
      src/PixiEditor.AvaloniaUI/Models/Handlers/IStructureMemberHandler.cs
  52. 2 1
      src/PixiEditor.AvaloniaUI/Models/IO/Exporter.cs
  53. 1 0
      src/PixiEditor.AvaloniaUI/Models/IO/FileEncoders/IFileEncoder.cs
  54. 2 1
      src/PixiEditor.AvaloniaUI/Models/IO/FileEncoders/UniversalFileEncoder.cs
  55. 7 6
      src/PixiEditor.AvaloniaUI/Models/IO/Importer.cs
  56. 3 2
      src/PixiEditor.AvaloniaUI/Models/Rendering/CanvasUpdater.cs
  57. 3 3
      src/PixiEditor.AvaloniaUI/Models/Rendering/MemberPreviewUpdater.cs
  58. 50 0
      src/PixiEditor.AvaloniaUI/Models/Serialization/Factories/ChunkyImageSerializationFactory.cs
  59. 39 0
      src/PixiEditor.AvaloniaUI/Models/Serialization/Factories/KernelSerializationFactory.cs
  60. 68 0
      src/PixiEditor.AvaloniaUI/Models/Serialization/Factories/SerializationFactory.cs
  61. 46 0
      src/PixiEditor.AvaloniaUI/Models/Serialization/Factories/SurfaceSerializationFactory.cs
  62. 16 0
      src/PixiEditor.AvaloniaUI/Models/Serialization/SerializableKernel.cs
  63. 13 0
      src/PixiEditor.AvaloniaUI/Models/Serialization/SerializationConfig.cs
  64. 9 9
      src/PixiEditor.AvaloniaUI/Models/UserData/RecentlyOpenedDocument.cs
  65. 2 6
      src/PixiEditor.AvaloniaUI/PixiEditor.AvaloniaUI.csproj
  66. 1 1
      src/PixiEditor.AvaloniaUI/Styles/Templates/NodeGraphView.axaml
  67. 160 119
      src/PixiEditor.AvaloniaUI/ViewModels/Document/DocumentViewModel.Serialization.cs
  68. 112 36
      src/PixiEditor.AvaloniaUI/ViewModels/Document/DocumentViewModel.cs
  69. 2 2
      src/PixiEditor.AvaloniaUI/ViewModels/Document/KeyFrameGroupViewModel.cs
  70. 1 0
      src/PixiEditor.AvaloniaUI/ViewModels/Document/KeyFrameViewModel.cs
  71. 2 2
      src/PixiEditor.AvaloniaUI/ViewModels/Document/NodeGraphViewModel.cs
  72. 2 2
      src/PixiEditor.AvaloniaUI/ViewModels/Document/ReferenceLayerViewModel.cs
  73. 1 20
      src/PixiEditor.AvaloniaUI/ViewModels/Document/StructureMemberViewModel.cs
  74. 20 6
      src/PixiEditor.AvaloniaUI/ViewModels/Nodes/NodeViewModel.cs
  75. 0 2
      src/PixiEditor.AvaloniaUI/ViewModels/SubViewModels/AnimationsViewModel.cs
  76. 26 12
      src/PixiEditor.AvaloniaUI/ViewModels/SubViewModels/FileViewModel.cs
  77. 1 1
      src/PixiEditor.AvaloniaUI/ViewModels/Tools/Tools/MoveToolViewModel.cs
  78. 1 1
      src/PixiEditor.AvaloniaUI/ViewModels/ViewModelMain.cs
  79. 2 1
      src/PixiEditor.AvaloniaUI/Views/Dialogs/ExportFilePopup.axaml.cs
  80. 1 1
      src/PixiEditor.AvaloniaUI/Views/Layers/FolderControl.axaml
  81. 1 1
      src/PixiEditor.AvaloniaUI/Views/Layers/LayerControl.axaml
  82. 1 0
      src/PixiEditor.AvaloniaUI/Views/Main/ViewportControls/FixedViewport.axaml.cs
  83. 1 0
      src/PixiEditor.AvaloniaUI/Views/Main/ViewportControls/Viewport.axaml.cs
  84. 1 0
      src/PixiEditor.AvaloniaUI/Views/Nodes/NodeView.cs
  85. 1 1
      src/PixiEditor.AvaloniaUI/Views/Overlays/SelectionOverlay/SelectionOverlay.cs
  86. 3 2
      src/PixiEditor.AvaloniaUI/Views/Rendering/Scene.cs
  87. 1 0
      src/PixiEditor.AvaloniaUI/Views/Visuals/SurfaceControl.cs
  88. 1 0
      src/PixiEditor.AvaloniaUI/Views/Visuals/SurfaceImage.cs
  89. 1 1
      src/PixiEditor.ChangeableDocument/ChangeInfos/Drawing/Selection_ChangeInfo.cs
  90. 9 1
      src/PixiEditor.ChangeableDocument/ChangeInfos/NodeGraph/CreateNode_ChangeInfo.cs
  91. 3 0
      src/PixiEditor.ChangeableDocument/ChangeInfos/NodeGraph/NodeName_ChangeInfo.cs
  92. 1 1
      src/PixiEditor.ChangeableDocument/ChangeInfos/Structure/CreateFolder_ChangeInfo.cs
  93. 1 1
      src/PixiEditor.ChangeableDocument/ChangeInfos/Structure/CreateLayer_ChangeInfo.cs
  94. 2 2
      src/PixiEditor.ChangeableDocument/Changeables/Animations/AnimationData.cs
  95. 16 0
      src/PixiEditor.ChangeableDocument/Changeables/Animations/GroupKeyFrame.cs
  96. 4 3
      src/PixiEditor.ChangeableDocument/Changeables/Animations/KeyFrame.cs
  97. 36 20
      src/PixiEditor.ChangeableDocument/Changeables/Animations/KeyFrameData.cs
  98. 3 2
      src/PixiEditor.ChangeableDocument/Changeables/Document.cs
  99. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Context/NoNodeFuncContextException.cs
  100. 2 2
      src/PixiEditor.ChangeableDocument/Changeables/Graph/InputProperty.cs

+ 3 - 2
src/ChunkyImageLib/Chunk.cs

@@ -1,7 +1,8 @@
 using ChunkyImageLib.DataHolders;
+using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
-using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+using PixiEditor.DrawingApi.Core.Surfaces;
+using PixiEditor.DrawingApi.Core.Surfaces.PaintImpl;
 using PixiEditor.Numerics;
 
 namespace ChunkyImageLib;

+ 13 - 6
src/ChunkyImageLib/ChunkyImage.cs

@@ -4,11 +4,13 @@ using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.Operations;
 using OneOf;
 using OneOf.Types;
+using PixiEditor.Common;
+using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
-using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
-using PixiEditor.DrawingApi.Core.Surface.Vector;
+using PixiEditor.DrawingApi.Core.Surfaces;
+using PixiEditor.DrawingApi.Core.Surfaces.PaintImpl;
+using PixiEditor.DrawingApi.Core.Surfaces.Vector;
 using PixiEditor.Numerics;
 
 [assembly: InternalsVisibleTo("ChunkyImageLibTest")]
@@ -40,7 +42,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, ICloneable
+public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICacheable
 {
     private struct LatestChunkData
     {
@@ -766,8 +768,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
             EnqueueOperation(operation, new(FindAllChunksOutsideBounds(newSize)));
         }
     }
-    
-    
+
+
     public void EnqueueDrawPaint(Paint paint)
     {
         lock (lockObject)
@@ -1408,4 +1410,9 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
             return clone;
         }
     }
+
+    public int GetCacheHash()
+    {
+        return commitCounter + queuedOperations.Count;
+    }
 }

+ 2 - 2
src/ChunkyImageLib/ChunkyImageEx.cs

@@ -1,8 +1,8 @@
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.Operations;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
-using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+using PixiEditor.DrawingApi.Core.Surfaces;
+using PixiEditor.DrawingApi.Core.Surfaces.PaintImpl;
 using PixiEditor.Numerics;
 
 namespace ChunkyImageLib;

+ 1 - 0
src/ChunkyImageLib/ChunkyImageLib.csproj

@@ -18,6 +18,7 @@
   </ItemGroup>
 
   <ItemGroup>
+    <ProjectReference Include="..\PixiEditor.Common\PixiEditor.Common.csproj" />
     <ProjectReference Include="..\PixiEditor.DrawingApi.Core\PixiEditor.DrawingApi.Core.csproj" />
   </ItemGroup>
 

+ 2 - 2
src/ChunkyImageLib/CommittedChunkStorage.cs

@@ -1,7 +1,7 @@
 using ChunkyImageLib.DataHolders;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
-using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+using PixiEditor.DrawingApi.Core.Surfaces;
+using PixiEditor.DrawingApi.Core.Surfaces.PaintImpl;
 using PixiEditor.Numerics;
 
 namespace ChunkyImageLib;

+ 1 - 1
src/ChunkyImageLib/DataHolders/ShapeData.cs

@@ -1,6 +1,6 @@
 using PixiEditor.DrawingApi.Core.ColorsImpl;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surfaces;
 using PixiEditor.Numerics;
 
 namespace ChunkyImageLib.DataHolders;

+ 2 - 2
src/ChunkyImageLib/IReadOnlyChunkyImage.cs

@@ -1,8 +1,8 @@
 using ChunkyImageLib.DataHolders;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
-using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+using PixiEditor.DrawingApi.Core.Surfaces;
+using PixiEditor.DrawingApi.Core.Surfaces.PaintImpl;
 using PixiEditor.Numerics;
 
 namespace ChunkyImageLib;

+ 2 - 2
src/ChunkyImageLib/Operations/ApplyMaskOperation.cs

@@ -1,7 +1,7 @@
 using ChunkyImageLib.DataHolders;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
-using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+using PixiEditor.DrawingApi.Core.Surfaces;
+using PixiEditor.DrawingApi.Core.Surfaces.PaintImpl;
 using PixiEditor.Numerics;
 
 namespace ChunkyImageLib.Operations;

+ 0 - 1
src/ChunkyImageLib/Operations/BresenhamLineHelper.cs

@@ -1,5 +1,4 @@
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
 using PixiEditor.Numerics;
 
 namespace ChunkyImageLib.Operations;

+ 2 - 2
src/ChunkyImageLib/Operations/BresenhamLineOperation.cs

@@ -1,8 +1,8 @@
 using ChunkyImageLib.DataHolders;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
-using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+using PixiEditor.DrawingApi.Core.Surfaces;
+using PixiEditor.DrawingApi.Core.Surfaces.PaintImpl;
 using PixiEditor.Numerics;
 
 namespace ChunkyImageLib.Operations;

+ 2 - 2
src/ChunkyImageLib/Operations/ChunkyImageOperation.cs

@@ -1,7 +1,7 @@
 using ChunkyImageLib.DataHolders;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
-using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+using PixiEditor.DrawingApi.Core.Surfaces;
+using PixiEditor.DrawingApi.Core.Surfaces.PaintImpl;
 using PixiEditor.Numerics;
 
 namespace ChunkyImageLib.Operations;

+ 1 - 1
src/ChunkyImageLib/Operations/ClearPathOperation.cs

@@ -1,6 +1,6 @@
 using ChunkyImageLib.DataHolders;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface.Vector;
+using PixiEditor.DrawingApi.Core.Surfaces.Vector;
 using PixiEditor.Numerics;
 
 namespace ChunkyImageLib.Operations;

+ 2 - 2
src/ChunkyImageLib/Operations/DrawingSurfaceLineOperation.cs

@@ -1,8 +1,8 @@
 using ChunkyImageLib.DataHolders;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
-using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+using PixiEditor.DrawingApi.Core.Surfaces;
+using PixiEditor.DrawingApi.Core.Surfaces.PaintImpl;
 using PixiEditor.Numerics;
 
 namespace ChunkyImageLib.Operations;

+ 3 - 3
src/ChunkyImageLib/Operations/EllipseOperation.cs

@@ -1,9 +1,9 @@
 using ChunkyImageLib.DataHolders;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
-using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
-using PixiEditor.DrawingApi.Core.Surface.Vector;
+using PixiEditor.DrawingApi.Core.Surfaces;
+using PixiEditor.DrawingApi.Core.Surfaces.PaintImpl;
+using PixiEditor.DrawingApi.Core.Surfaces.Vector;
 using PixiEditor.Numerics;
 
 namespace ChunkyImageLib.Operations;

+ 2 - 2
src/ChunkyImageLib/Operations/ImageOperation.cs

@@ -1,7 +1,7 @@
 using ChunkyImageLib.DataHolders;
+using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
-using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+using PixiEditor.DrawingApi.Core.Surfaces.PaintImpl;
 using PixiEditor.Numerics;
 
 namespace ChunkyImageLib.Operations;

+ 1 - 1
src/ChunkyImageLib/Operations/OperationHelper.cs

@@ -1,6 +1,6 @@
 using ChunkyImageLib.DataHolders;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surfaces;
 using PixiEditor.Numerics;
 
 namespace ChunkyImageLib.Operations;

+ 1 - 1
src/ChunkyImageLib/Operations/PaintOperation.cs

@@ -1,5 +1,5 @@
 using ChunkyImageLib.DataHolders;
-using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+using PixiEditor.DrawingApi.Core.Surfaces.PaintImpl;
 using PixiEditor.Numerics;
 
 namespace ChunkyImageLib.Operations;

+ 3 - 3
src/ChunkyImageLib/Operations/PathOperation.cs

@@ -1,9 +1,9 @@
 using ChunkyImageLib.DataHolders;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
-using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
-using PixiEditor.DrawingApi.Core.Surface.Vector;
+using PixiEditor.DrawingApi.Core.Surfaces;
+using PixiEditor.DrawingApi.Core.Surfaces.PaintImpl;
+using PixiEditor.DrawingApi.Core.Surfaces.Vector;
 using PixiEditor.Numerics;
 
 namespace ChunkyImageLib.Operations;

+ 2 - 2
src/ChunkyImageLib/Operations/PixelOperation.cs

@@ -1,8 +1,8 @@
 using ChunkyImageLib.DataHolders;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
-using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+using PixiEditor.DrawingApi.Core.Surfaces;
+using PixiEditor.DrawingApi.Core.Surfaces.PaintImpl;
 using PixiEditor.Numerics;
 
 namespace ChunkyImageLib.Operations;

+ 2 - 2
src/ChunkyImageLib/Operations/PixelsOperation.cs

@@ -3,8 +3,8 @@ using System.Linq;
 using ChunkyImageLib.DataHolders;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
-using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+using PixiEditor.DrawingApi.Core.Surfaces;
+using PixiEditor.DrawingApi.Core.Surfaces.PaintImpl;
 using PixiEditor.Numerics;
 
 namespace ChunkyImageLib.Operations;

+ 1 - 1
src/ChunkyImageLib/Operations/RectangleOperation.cs

@@ -1,6 +1,6 @@
 using ChunkyImageLib.DataHolders;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surfaces;
 using PixiEditor.Numerics;
 
 namespace ChunkyImageLib.Operations;

+ 1 - 1
src/ChunkyImageLib/Operations/ReplaceColorOperation.cs

@@ -1,7 +1,7 @@
 using ChunkyImageLib.DataHolders;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surfaces;
 using PixiEditor.Numerics;
 
 namespace ChunkyImageLib.Operations;

+ 4 - 4
src/PixiEditor.AvaloniaUI.Desktop/PixiEditor.AvaloniaUI.Desktop.csproj

@@ -20,11 +20,11 @@
         <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="$(AvaloniaVersion)"/>
     </ItemGroup>
 
-    <ItemGroup>
-        <ProjectReference Include="..\PixiEditor.AvaloniaUI\PixiEditor.AvaloniaUI.csproj" />
-    </ItemGroup>
-
   <ItemGroup>
     <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
   </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\PixiEditor.AvaloniaUI\PixiEditor.AvaloniaUI.csproj" />
+  </ItemGroup>
 </Project>

+ 1 - 1
src/PixiEditor.AvaloniaUI/Helpers/Converters/ImagePathToBitmapConverter.cs

@@ -36,7 +36,7 @@ internal class ImagePathToBitmapConverter : SingleInstanceConverter<ImagePathToB
         return new Bitmap(AssetLoader.Open(uri));
     }
 
-    public static DrawingApi.Core.Surface.Bitmap LoadDrawingApiBitmapFromRelativePath(string path)
+    public static DrawingApi.Core.Surfaces.Bitmap LoadDrawingApiBitmapFromRelativePath(string path)
     {
         Uri uri = new($"avares://{Assembly.GetExecutingAssembly().FullName}{path}");
         if (!AssetLoader.Exists(uri))

+ 1 - 1
src/PixiEditor.AvaloniaUI/Helpers/Converters/VectorPathToVisibleConverter.cs

@@ -1,6 +1,6 @@
 using System.Globalization;
 using Avalonia.Data.Converters;
-using PixiEditor.DrawingApi.Core.Surface.Vector;
+using PixiEditor.DrawingApi.Core.Surfaces.Vector;
 
 namespace PixiEditor.AvaloniaUI.Helpers.Converters;
 

+ 165 - 335
src/PixiEditor.AvaloniaUI/Helpers/DocumentViewModelBuilder.cs

@@ -1,22 +1,22 @@
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-using System.Linq;
-using System.Runtime.InteropServices;
+using System.Collections;
+using System.Reflection;
 using ChunkyImageLib;
 using ChunkyImageLib.DataHolders;
 using PixiEditor.AvaloniaUI.Helpers.Extensions;
-using PixiEditor.AvaloniaUI.Views.Animations;
-using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.AvaloniaUI.ViewModels.Document;
+using PixiEditor.ChangeableDocument.Changeables.Graph;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.DrawingApi.Core;
 using PixiEditor.Extensions.CommonApi.Palettes;
 using PixiEditor.Numerics;
 using PixiEditor.Parser;
-using PixiEditor.Parser.Helpers;
-using BlendMode = PixiEditor.ChangeableDocument.Enums.BlendMode;
+using PixiEditor.Parser.Graph;
+using PixiEditor.Parser.Skia;
+using NodeGraph = PixiEditor.Parser.Graph.NodeGraph;
 
 namespace PixiEditor.AvaloniaUI.Helpers;
 
-internal class DocumentViewModelBuilder : ChildrenBuilder
+internal class DocumentViewModelBuilder
 {
     public int Width { get; set; }
     public int Height { get; set; }
@@ -27,6 +27,9 @@ internal class DocumentViewModelBuilder : ChildrenBuilder
     public ReferenceLayerBuilder ReferenceLayer { get; set; }
     public List<KeyFrameBuilder> AnimationData { get; set; } = new List<KeyFrameBuilder>();
 
+    public NodeGraphBuilder Graph { get; set; }
+    public string ImageEncoderUsed { get; set; } = "QOI";
+
     public DocumentViewModelBuilder WithSize(int width, int height)
     {
         Width = width;
@@ -55,11 +58,13 @@ internal class DocumentViewModelBuilder : ChildrenBuilder
     public DocumentViewModelBuilder WithPalette<T>(IEnumerable<T> pallet, Func<T, PaletteColor> toColor) =>
         WithPalette(pallet.Select(toColor));
 
-    public DocumentViewModelBuilder WithReferenceLayer<T>(T reference, Action<T, ReferenceLayerBuilder> builder)
+    public DocumentViewModelBuilder WithReferenceLayer<T>(T reference,
+        Action<T, ReferenceLayerBuilder, ImageEncoder?> builder,
+        ImageEncoder? encoder)
     {
         if (reference != null)
         {
-            WithReferenceLayer(x => builder(reference, x));
+            WithReferenceLayer(x => builder(reference, x, encoder));
         }
 
         return this;
@@ -75,296 +80,58 @@ internal class DocumentViewModelBuilder : ChildrenBuilder
 
         return this;
     }
-    
-    public DocumentViewModelBuilder WithAnimationData(AnimationData? animationData, Folder documentRootFolder)
+
+    public DocumentViewModelBuilder WithAnimationData(AnimationData? animationData)
     {
         AnimationData = new List<KeyFrameBuilder>();
 
         if (animationData != null && animationData.KeyFrameGroups.Count > 0)
         {
-            BuildKeyFrames(animationData.KeyFrameGroups.Cast<IKeyFrame>().ToList(), AnimationData, documentRootFolder);
+            BuildKeyFrames(animationData.KeyFrameGroups.ToList(), AnimationData);
         }
 
         return this;
     }
 
-    private static void BuildKeyFrames(List<IKeyFrame> root, List<KeyFrameBuilder> data, Folder documentRootFolder)
-    {
-        foreach (var keyFrame in root)
-        {
-            if (keyFrame is KeyFrameGroup group)
-            {
-                GroupKeyFrameBuilder builder = new GroupKeyFrameBuilder()
-                    .WithVisibility(group.Enabled)
-                    .WithId(group.LayerGuid)
-                    .WithLayerGuid(group.LayerGuid);
-
-                foreach (var child in group.Children)
-                {
-                    if(child is KeyFrameGroup childGroup)
-                    {
-                        builder.WithChild<GroupKeyFrameBuilder>(x => BuildKeyFrames(childGroup.Children, null, documentRootFolder));
-                    }
-                    else if (child is RasterKeyFrame rasterKeyFrame)
-                    {
-                        builder.WithChild<RasterKeyFrameBuilder>(x => x
-                            .WithVisibility(builder.IsVisible)
-                            .WithId(rasterKeyFrame.Guid)
-                            .WithLayerGuid(rasterKeyFrame.LayerGuid)
-                            .WithStartFrame(rasterKeyFrame.StartFrame)
-                            .WithDuration(rasterKeyFrame.Duration)
-                            .WithSurface(Surface.Load(rasterKeyFrame.ImageBytes)));
-                    }
-                }
-
-                data?.Add(builder);
-            }
-
-        }
-    }
-
-    public abstract class StructureMemberBuilder
-    {
-        private MaskBuilder maskBuilder;
-
-        public int OrderInStructure { get; set; }
-
-        public string Name { get; set; }
-
-        public bool IsVisible { get; set; }
-
-        public float Opacity { get; set; }
-
-        public BlendMode BlendMode { get; set; }
-
-        public bool ClipToMemberBelow { get; set; }
-
-        public bool HasMask => maskBuilder is not null;
-
-        [NotNull] public MaskBuilder Mask => maskBuilder ??= new MaskBuilder();
-
-        public Guid Id { get; set; }
-
-        public StructureMemberBuilder()
-        {
-            IsVisible = true;
-            Opacity = 1;
-        }
-
-        public StructureMemberBuilder WithOrderInStructure(int order)
-        {
-            OrderInStructure = order;
-            return this;
-        }
-
-        public StructureMemberBuilder WithName(string name)
-        {
-            Name = name;
-            return this;
-        }
-
-        public StructureMemberBuilder WithVisibility(bool visibility)
-        {
-            IsVisible = visibility;
-            return this;
-        }
-
-        public StructureMemberBuilder WithOpacity(float opacity)
-        {
-            Opacity = opacity;
-            return this;
-        }
-
-        public StructureMemberBuilder WithBlendMode(BlendMode blendMode)
-        {
-            BlendMode = blendMode;
-            return this;
-        }
-
-        public StructureMemberBuilder WithMask(Action<MaskBuilder> mask)
-        {
-            mask(Mask);
-            return this;
-        }
-
-        public StructureMemberBuilder WithMask<T>(T reference, Action<MaskBuilder, T> mask)
-        {
-            return reference != null ? WithMask(x => mask(x, reference)) : this;
-        }
-
-        public StructureMemberBuilder WithGuid(Guid guid)
-        {
-            Id = guid;
-            return this;
-        }
-
-        public StructureMemberBuilder WithClipToBelow(bool value)
-        {
-            ClipToMemberBelow = value;
-            return this;
-        }
-    }
-
-    public class LayerBuilder : StructureMemberBuilder
+    public DocumentViewModelBuilder WithGraph(NodeGraph graph, Action<NodeGraph, NodeGraphBuilder> builder)
     {
-        private int? width;
-        private int? height;
-
-        public SurfaceBuilder? Surface { get; set; }
-
-        public int Width
+        if (graph != null)
         {
-            get => width ?? default;
-            set => width = value;
+            WithGraph(x => builder(graph, x));
         }
 
-        public int Height
-        {
-            get => height ?? default;
-            set => height = value;
-        }
-
-        public int OffsetX { get; set; }
-
-        public int OffsetY { get; set; }
-
-        public bool LockAlpha { get; set; }
-
-        public new LayerBuilder WithName(string name) => base.WithName(name) as LayerBuilder;
-
-        public new LayerBuilder WithVisibility(bool visibility) => base.WithVisibility(visibility) as LayerBuilder;
-
-        public new LayerBuilder WithOpacity(float opacity) => base.WithOpacity(opacity) as LayerBuilder;
-
-        public new LayerBuilder WithBlendMode(BlendMode blendMode) => base.WithBlendMode(blendMode) as LayerBuilder;
-
-        public new LayerBuilder WithClipToBelow(bool value) => base.WithClipToBelow(value) as LayerBuilder;
-
-        public LayerBuilder WithLockAlpha(bool layerLockAlpha)
-        {
-            LockAlpha = layerLockAlpha;
-            return this;
-        }
-
-        public new LayerBuilder WithMask(Action<MaskBuilder> mask) => base.WithMask(mask) as LayerBuilder;
-
-        public new LayerBuilder WithGuid(Guid guid) => base.WithGuid(guid) as LayerBuilder;
-
-        public LayerBuilder WithSurface(Surface surface)
-        {
-            Surface = new(surface);
-            return this;
-        }
-
-        public LayerBuilder WithSize(int width, int height)
-        {
-            Width = width;
-            Height = height;
-            return this;
-        }
-
-        public LayerBuilder WithSize(VecI size) => WithSize(size.X, size.Y);
-
-        public LayerBuilder WithRect(int width, int height, int offsetX, int offsetY)
-        {
-            Width = width;
-            Height = height;
-            OffsetX = offsetX;
-            OffsetY = offsetY;
-            return this;
-        }
-
-        public LayerBuilder WithSurface(Action<SurfaceBuilder> surface)
-        {
-            if (width is null || height is null)
-            {
-                throw new InvalidOperationException(
-                    "You must first set the width and height of the layer. You can do this by calling WithRect() or setting the Width and Height properties.");
-            }
-
-            var surfaceBuilder = new SurfaceBuilder(new Surface(new VecI(Width, Height)));
-            surface(surfaceBuilder);
-            Surface = surfaceBuilder;
-            return this;
-        }
+        return this;
     }
 
-    public class FolderBuilder : StructureMemberBuilder
+    public DocumentViewModelBuilder WithGraph(Action<NodeGraphBuilder> builder)
     {
-        public List<StructureMemberBuilder> Children { get; set; } = new List<StructureMemberBuilder>();
-
-        public new FolderBuilder WithName(string name) => base.WithName(name) as FolderBuilder;
-
-        public new FolderBuilder WithVisibility(bool visibility) => base.WithVisibility(visibility) as FolderBuilder;
-
-        public new FolderBuilder WithOpacity(float opacity) => base.WithOpacity(opacity) as FolderBuilder;
-
-        public new FolderBuilder WithBlendMode(BlendMode blendMode) => base.WithBlendMode(blendMode) as FolderBuilder;
-
-        public new FolderBuilder WithMask(Action<MaskBuilder> mask) => base.WithMask(mask) as FolderBuilder;
-
-        public new FolderBuilder WithGuid(Guid guid) => base.WithGuid(guid) as FolderBuilder;
-
-        public FolderBuilder WithClipToBelow(bool value) => base.WithClipToBelow(value) as FolderBuilder;
-
-        public FolderBuilder WithChildren(Action<ChildrenBuilder> children)
-        {
-            ChildrenBuilder childrenBuilder = new();
-            children(childrenBuilder);
-            Children = childrenBuilder.Children;
-            return this;
-        }
+        var graph = new NodeGraphBuilder();
+        builder(graph);
+        Graph = graph;
+        return this;
     }
 
-    public class SurfaceBuilder
+    public DocumentViewModelBuilder WithImageEncoder(string encoder)
     {
-        public Surface Surface { get; set; }
-
-        public SurfaceBuilder(Surface surface)
-        {
-            Surface = surface;
-        }
-
-        public SurfaceBuilder WithImage(ReadOnlySpan<byte> buffer) => WithImage(buffer, 0, 0);
-
-        public SurfaceBuilder WithImage(ReadOnlySpan<byte> buffer, int x, int y)
-        {
-            if (buffer.IsEmpty) return this;
-
-            Surface.DrawingSurface.Canvas.DrawBitmap(Bitmap.Decode(buffer), x, y);
-            return this;
-        }
+        ImageEncoderUsed = encoder;
+        return this;
     }
 
-    public class MaskBuilder
+    private static void BuildKeyFrames(List<KeyFrameGroup> root, List<KeyFrameBuilder> data)
     {
-        public bool IsVisible { get; set; }
-
-        public SurfaceBuilder Surface { get; set; }
-
-        public MaskBuilder()
+        foreach (KeyFrameGroup group in root)
         {
-            IsVisible = true;
-        }
+            GroupKeyFrameBuilder builder = new GroupKeyFrameBuilder()
+                .WithNodeId(group.NodeId);
 
-        public MaskBuilder WithVisibility(bool isVisible)
-        {
-            IsVisible = isVisible;
-            return this;
-        }
-
-        public MaskBuilder WithSurface(Surface surface)
-        {
-            Surface = new SurfaceBuilder(surface);
-            return this;
-        }
+            foreach (var child in group.Children)
+            {
+                builder.WithChild<KeyFrameBuilder>(x => x
+                    .WithKeyFrameId(child.KeyFrameId)
+                    .WithNodeId(child.NodeId));
+            }
 
-        public MaskBuilder WithSurface(int width, int height, Action<SurfaceBuilder> surface)
-        {
-            var surfaceBuilder = new SurfaceBuilder(new Surface(new VecI(Math.Max(width, 1), Math.Max(height, 1))));
-            surface(surfaceBuilder);
-            Surface = surfaceBuilder;
-            return this;
+            data?.Add(builder);
         }
     }
 
@@ -422,99 +189,162 @@ internal class DocumentViewModelBuilder : ChildrenBuilder
     }
 }
 
-internal class ChildrenBuilder
+internal class KeyFrameBuilder()
 {
-    public List<DocumentViewModelBuilder.StructureMemberBuilder> Children { get; set; } =
-        new List<DocumentViewModelBuilder.StructureMemberBuilder>();
+    public int NodeId { get; set; }
+    public int KeyFrameId { get; set; }
 
-    public ChildrenBuilder WithLayer(Action<DocumentViewModelBuilder.LayerBuilder> layer)
+    public KeyFrameBuilder WithKeyFrameId(int layerId)
     {
-        var layerBuilder = new DocumentViewModelBuilder.LayerBuilder();
-        layer(layerBuilder);
-        Children.Add(layerBuilder);
+        KeyFrameId = layerId;
         return this;
     }
 
-    public ChildrenBuilder WithFolder(Action<DocumentViewModelBuilder.FolderBuilder> folder)
+    public KeyFrameBuilder WithNodeId(int nodeId)
     {
-        var folderBuilder = new DocumentViewModelBuilder.FolderBuilder();
-        folder(folderBuilder);
-        Children.Add(folderBuilder);
+        NodeId = nodeId;
         return this;
     }
 }
 
-internal class KeyFrameBuilder()
+internal class GroupKeyFrameBuilder : KeyFrameBuilder
 {
-    public int StartFrame { get; set; }
-    public int Duration { get; set; }
-    public bool IsVisible { get; set; }
-    public Guid LayerGuid { get; set; }
-    public Guid Id { get; set; }
+    public List<KeyFrameBuilder> Children { get; set; } = new List<KeyFrameBuilder>();
 
-    public KeyFrameBuilder WithStartFrame(int startFrame)
+    public GroupKeyFrameBuilder WithChild<T>(Action<T> child) where T : KeyFrameBuilder, new()
     {
-        StartFrame = startFrame;
+        var childBuilder = new T();
+        child(childBuilder);
+        Children.Add(childBuilder);
         return this;
     }
 
-    public KeyFrameBuilder WithDuration(int duration)
+    public new GroupKeyFrameBuilder WithNodeId(int layerGuid) =>
+        base.WithKeyFrameId(layerGuid) as GroupKeyFrameBuilder;
+}
+
+internal class NodeGraphBuilder
+{
+    public List<NodeBuilder> AllNodes { get; set; } = new List<NodeBuilder>();
+
+
+    public NodeGraphBuilder WithNode(Action<NodeBuilder> nodeBuilder)
     {
-        Duration = duration;
+        var node = new NodeBuilder();
+        nodeBuilder(node);
+
+        AllNodes.Add(node);
+
         return this;
     }
 
-    public KeyFrameBuilder WithVisibility(bool isVisible)
+    public NodeGraphBuilder WithOutputNode(int? toConnectNodeId, string? toConnectPropName)
     {
-        IsVisible = isVisible;
+        var node = this.WithNodeOfType(typeof(OutputNode))
+            .WithId(AllNodes.Count + 1);
+
+        if (toConnectNodeId != null && toConnectPropName != null)
+        {
+            node.WithConnections(new[]
+            {
+                new PropertyConnection
+                {
+                    OutputNodeId = toConnectNodeId.Value,
+                    OutputPropertyName = toConnectPropName,
+                    InputPropertyName = OutputNode.InputPropertyName
+                }
+            });
+        }
+
+        AllNodes.Add(node);
         return this;
     }
 
-    public KeyFrameBuilder WithLayerGuid(Guid layerGuid)
+    public NodeGraphBuilder WithImageLayerNode(string name, Surface image, out int id)
     {
-        LayerGuid = layerGuid;
+        AllNodes.Add(
+            this.WithNodeOfType(typeof(ImageLayerNode))
+                .WithName(name)
+                .WithId(AllNodes.Count + 1)
+                .WithAdditionalData(
+                    new Dictionary<string, object> { { ImageLayerNode.ImageFramesKey, new List<Surface> { image } } }));
+
+        id = AllNodes.Count;
         return this;
     }
 
-    public KeyFrameBuilder WithId(Guid id)
+    public NodeBuilder WithNodeOfType(Type nodeType)
     {
-        Id = id;
-        return this;
-    }
-}
+        var node = new NodeBuilder();
+        node.WithUniqueNodeName(nodeType.GetCustomAttribute<NodeInfoAttribute>().UniqueName);
 
-internal class GroupKeyFrameBuilder : KeyFrameBuilder
-{
-    public List<KeyFrameBuilder> Children { get; set; } = new List<KeyFrameBuilder>();
+        return node;
+    }
 
-    public GroupKeyFrameBuilder WithChild<T>(Action<T> child) where T : KeyFrameBuilder, new()
+    internal class NodeBuilder
     {
-        var childBuilder = new T();
-        child(childBuilder);
-        Children.Add(childBuilder);
-        return this;
-    }
-    
-    public new GroupKeyFrameBuilder WithVisibility(bool isVisible) => base.WithVisibility(isVisible) as GroupKeyFrameBuilder;
-    public new GroupKeyFrameBuilder WithLayerGuid(Guid layerGuid) => base.WithLayerGuid(layerGuid) as GroupKeyFrameBuilder;
-    public new GroupKeyFrameBuilder WithId(Guid id) => base.WithId(id) as GroupKeyFrameBuilder;
-    public new GroupKeyFrameBuilder WithStartFrame(int startFrame) => base.WithStartFrame(startFrame) as GroupKeyFrameBuilder;
-    public new GroupKeyFrameBuilder WithDuration(int duration) => base.WithDuration(duration) as GroupKeyFrameBuilder;
-}
+        public int Id { get; set; }
+        public Vector2 Position { get; set; }
+        public string Name { get; set; }
+        public string UniqueNodeName { get; set; }
+        public Dictionary<string, object> InputValues { get; set; }
+        public KeyFrameData[] KeyFrames { get; set; }
+        public Dictionary<string, object> AdditionalData { get; set; }
+        public Dictionary<int, (string inputPropName, string outputPropName)> InputConnections { get; set; }
 
-internal class RasterKeyFrameBuilder : KeyFrameBuilder
-{
-    public DocumentViewModelBuilder.SurfaceBuilder Surface { get; set; }
+        public NodeBuilder WithId(int id)
+        {
+            Id = id;
+            return this;
+        }
 
-    public RasterKeyFrameBuilder WithSurface(Surface surface)
-    {
-        Surface = new DocumentViewModelBuilder.SurfaceBuilder(new Surface(surface));
-        return this;
-    }
+        public NodeBuilder WithPosition(Vector2 position)
+        {
+            Position = position;
+            return this;
+        }
 
-    public new RasterKeyFrameBuilder WithVisibility(bool isVisible) => base.WithVisibility(isVisible) as RasterKeyFrameBuilder;
-    public new RasterKeyFrameBuilder WithLayerGuid(Guid layerGuid) => base.WithLayerGuid(layerGuid) as RasterKeyFrameBuilder;
-    public new RasterKeyFrameBuilder WithId(Guid id) => base.WithId(id) as RasterKeyFrameBuilder;
-    public new RasterKeyFrameBuilder WithStartFrame(int startFrame) => base.WithStartFrame(startFrame) as RasterKeyFrameBuilder;
-    public new RasterKeyFrameBuilder WithDuration(int duration) => base.WithDuration(duration) as RasterKeyFrameBuilder;
+        public NodeBuilder WithName(string name)
+        {
+            Name = name;
+            return this;
+        }
+
+        public NodeBuilder WithUniqueNodeName(string uniqueNodeName)
+        {
+            UniqueNodeName = uniqueNodeName;
+            return this;
+        }
+
+        public NodeBuilder WithInputValues(Dictionary<string, object> values)
+        {
+            InputValues = values;
+            return this;
+        }
+
+        public NodeBuilder WithAdditionalData(Dictionary<string, object> data)
+        {
+            AdditionalData = data;
+            return this;
+        }
+
+        public NodeBuilder WithConnections(PropertyConnection[] nodeInputConnections)
+        {
+            InputConnections = new Dictionary<int, (string, string)>();
+
+            foreach (var connection in nodeInputConnections)
+            {
+                InputConnections.Add(connection.OutputNodeId,
+                    (connection.InputPropertyName, connection.OutputPropertyName));
+            }
+
+            return this;
+        }
+
+        public NodeBuilder WithKeyFrames(KeyFrameData[] keyFrames)
+        {
+            KeyFrames = keyFrames;
+            return this;
+        }
+    }
 }

+ 2 - 2
src/PixiEditor.AvaloniaUI/Helpers/Extensions/BitmapExtensions.cs

@@ -47,10 +47,10 @@ public static class BitmapExtensions
         return new WriteableBitmap(PixelFormats.Bgra8888, AlphaFormat.Premul, address, size, new Vector(96, 96), stride);
     }
 
-    public static DrawingApi.Core.Surface.Bitmap FromStream(Stream stream)
+    public static DrawingApi.Core.Surfaces.Bitmap FromStream(Stream stream)
     {
         using var memoryStream = new MemoryStream();
         stream.CopyTo(memoryStream);
-        return DrawingApi.Core.Surface.Bitmap.Decode(memoryStream.ToArray());
+        return DrawingApi.Core.Surfaces.Bitmap.Decode(memoryStream.ToArray());
     }
 }

+ 11 - 1
src/PixiEditor.AvaloniaUI/Helpers/Extensions/PixelFormatHelper.cs

@@ -1,5 +1,5 @@
 using Avalonia.Platform;
-using PixiEditor.DrawingApi.Core.Surface.ImageData;
+using PixiEditor.DrawingApi.Core.Surfaces.ImageData;
 
 namespace PixiEditor.AvaloniaUI.Helpers.Extensions;
 
@@ -14,6 +14,16 @@ internal static class PixelFormatHelper
 
         throw new NotImplementedException($"Skia does not support the '{format}' format");
     }
+    
+    public static ColorType ToColorType(this SKColorType colorType)
+    { 
+        return (ColorType)colorType;
+    }
+    
+    public static AlphaType ToAlphaType(this SKAlphaType alphaType)
+    {
+        return (AlphaType)alphaType;
+    }
 
     public static bool TryConvertToColorType(this PixelFormat format, out ColorType colorType, out AlphaType alphaType)
     {

+ 64 - 95
src/PixiEditor.AvaloniaUI/Helpers/Extensions/PixiParserDocumentEx.cs

@@ -1,12 +1,14 @@
-using System.Collections.Generic;
+using System.Collections;
 using ChunkyImageLib;
 using PixiEditor.AvaloniaUI.ViewModels.Document;
-using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface.ImageData;
+using PixiEditor.DrawingApi.Core;
+using PixiEditor.DrawingApi.Core.Surfaces.ImageData;
+using PixiEditor.DrawingApi.Skia;
 using PixiEditor.Extensions.CommonApi.Palettes;
 using PixiEditor.Numerics;
 using PixiEditor.Parser;
-using PixiEditor.Parser.Deprecated;
+using PixiEditor.Parser.Graph;
+using PixiEditor.Parser.Skia;
 
 namespace PixiEditor.AvaloniaUI.Helpers.Extensions;
 
@@ -16,115 +18,82 @@ internal static class PixiParserDocumentEx
     {
         return new VecD(vec.X, vec.Y);
     }
-    
-    public static DocumentViewModel ToDocument(this Document document)
-    {
-        return DocumentViewModel.Build(b =>
-        {
-            b.WithSize(document.Width, document.Height)
-                .WithPalette(document.Palette, x => new PaletteColor(x.R, x.G, x.B))
-                .WithSwatches(document.Swatches, x => new(x.R, x.G, x.B))
-                .WithReferenceLayer(document.ReferenceLayer, (r, builder) => builder
-                    .WithIsVisible(r.Enabled)
-                    .WithShape(r.Corners)
-                    .WithIsTopmost(r.Topmost)
-                    .WithSurface(Surface.Load(r.ImageBytes)))
-                .WithAnimationData(document.AnimationData, document.RootFolder);
 
-            BuildChildren(b, document.RootFolder.Children);
-        });
+    public static Vector2 ToVector2(this VecD vec)
+    {
+        return new Vector2() { X = vec.X, Y = vec.Y };
+    }
 
-        void BuildChildren(ChildrenBuilder builder, IEnumerable<IStructureMember> members)
+    public static DocumentViewModel ToDocument(this Document document)
+    {
+        ImageEncoder? encoder = document.GetEncoder();
+        if (encoder == null)
         {
-            foreach (var member in members)
-            {
-                if (member is Folder folder)
-                {
-                    builder.WithFolder(x => BuildFolder(x, folder));
-                }
-                else if (member is ImageLayer layer)
-                {
-                    builder.WithLayer(x => BuildLayer(x, layer));
-                }
-                else
-                {
-                    throw new NotImplementedException($"StructureMember of type '{member.GetType().FullName}' has not been implemented");
-                }
-            }
+            throw new ArgumentException("Document does not have a valid encoder");
         }
 
-        void BuildFolder(DocumentViewModelBuilder.FolderBuilder builder, Folder folder) => builder
-            .WithName(folder.Name)
-            .WithVisibility(folder.Enabled)
-            .WithOpacity(folder.Opacity)
-            .WithBlendMode((PixiEditor.ChangeableDocument.Enums.BlendMode)(int)folder.BlendMode)
-            .WithChildren(x => BuildChildren(x, folder.Children))
-            .WithClipToBelow(folder.ClipToMemberBelow)
-            .WithMask(folder.Mask, (x, m) => x.WithVisibility(m.Enabled).WithSurface(m.Width, m.Height, x => x.WithImage(m.ImageBytes, m.OffsetX, m.OffsetY)));
+        return DocumentViewModel.Build(b => b
+            .WithSize(document.Width, document.Height)
+            .WithImageEncoder(document.ImageEncoderUsed)
+            .WithPalette(document.Palette, color => new PaletteColor(color.R, color.G, color.B))
+            .WithSwatches(document.Swatches, x => new(x.R, x.G, x.B))
+            .WithReferenceLayer(document.ReferenceLayer, BuildReferenceLayer, encoder)
+            .WithGraph(document.Graph, BuildGraph)
+            .WithAnimationData(document.AnimationData));
+    }
 
-        void BuildLayer(DocumentViewModelBuilder.LayerBuilder builder, ImageLayer layer)
+    private static void BuildGraph(NodeGraph graph, NodeGraphBuilder graphBuilder)
+    {
+        if (graph.AllNodes != null)
         {
-            builder
-                .WithName(layer.Name)
-                .WithGuid(layer.Guid)
-                .WithVisibility(layer.Enabled)
-                .WithOpacity(layer.Opacity)
-                .WithBlendMode((PixiEditor.ChangeableDocument.Enums.BlendMode)(int)layer.BlendMode)
-                .WithRect(layer.Width, layer.Height, layer.OffsetX, layer.OffsetY)
-                .WithClipToBelow(layer.ClipToMemberBelow)
-                .WithLockAlpha(layer.LockAlpha)
-                .WithMask(layer.Mask,
-                    (x, m) => x.WithVisibility(m.Enabled).WithSurface(m.Width, m.Height,
-                        x => x.WithImage(m.ImageBytes, m.OffsetX, m.OffsetY)));
-
-            if (layer is { Width: > 0, Height: > 0 })
+            foreach (var node in graph.AllNodes)
             {
-                builder.WithSurface(x => x.WithImage(layer.ImageBytes, 0, 0));
+                graphBuilder.WithNode(x => x
+                    .WithId(node.Id)
+                    .WithPosition(node.Position)
+                    .WithName(node.Name)
+                    .WithUniqueNodeName(node.UniqueNodeName)
+                    .WithKeyFrames(node.KeyFrames)
+                    .WithInputValues(ToDictionary(node.InputPropertyValues))
+                    .WithAdditionalData(node.AdditionalData)
+                    .WithConnections(node.InputConnections));
             }
         }
-        
     }
-    
-    public static SKBitmap RenderOldDocument(this SerializableDocument document)
-    {
-        SKImageInfo info = new(document.Width, document.Height, SKColorType.RgbaF32, SKAlphaType.Unpremul, SKColorSpace.CreateSrgb());
-        using SKSurface surface = SKSurface.Create(info);
-        SKCanvas canvas = surface.Canvas;
-        using SKPaint paint = new();
 
-        foreach (var layer in document)
+    private static Dictionary<string, object> ToDictionary(IEnumerable<NodePropertyValue> properties)
+    {
+        Dictionary<string, object> dict = new();
+        foreach (var property in properties)
         {
-            if (layer.PngBytes == null || layer.PngBytes.Length == 0)
-            {
-                continue;
-            }
-
-            bool visible = document.Layers.GetFinalLayerVisibilty(layer);
-
-            if (!visible)
-            {
-                continue;
-            }
-
-            double opacity = document.Layers.GetFinalLayerOpacity(layer);
+            dict[property.PropertyName] = property.Value;
+        }
 
-            if (opacity == 0)
-            {
-                continue;
-            }
+        return dict;
+    }
 
-            using SKColorFilter filter = SKColorFilter.CreateBlendMode(SKColors.White.WithAlpha((byte)(opacity * 255)), SKBlendMode.DstIn);
-            paint.ColorFilter = filter;
+    private static void BuildReferenceLayer(
+        ReferenceLayer referenceLayer,
+        DocumentViewModelBuilder.ReferenceLayerBuilder layerBuilder,
+        ImageEncoder encoder)
+    {
+        DecodeSurface(referenceLayer.ImageBytes, (int)referenceLayer.Width, (int)referenceLayer.Height, encoder);
 
-            using var image = SKImage.FromEncodedData(layer.PngBytes);
-            
-            canvas.DrawImage(image, layer.OffsetX, layer.OffsetY, paint);
-        }
+        layerBuilder
+            .WithIsVisible(referenceLayer.Enabled)
+            .WithShape(referenceLayer.Corners)
+            .WithIsTopmost(referenceLayer.Topmost)
+            .WithSurface(Surface.Load(referenceLayer.ImageBytes));
+    }
 
-        SKBitmap bitmap = new(info);
+    private static Surface DecodeSurface(byte[] imgBytes, int width, int height, ImageEncoder encoder)
+    {
+        Surface surface = new Surface(new VecI(width, height));
 
-        surface.ReadPixels(info, bitmap.GetPixels(), info.RowBytes, 0, 0);
+        byte[] decoded =
+            encoder.Decode(imgBytes, out SKImageInfo info);
+        surface.DrawBytes(surface.Size, decoded, info.ColorType.ToColorType(), info.AlphaType.ToAlphaType());
 
-        return bitmap;
+        return surface;
     }
 }

+ 368 - 0
src/PixiEditor.AvaloniaUI/Helpers/Extensions/PixiParserV3DocumentEx.cs

@@ -0,0 +1,368 @@
+using System.Diagnostics.CodeAnalysis;
+using ChunkyImageLib;
+using PixiEditor.AvaloniaUI.ViewModels.Document;
+using PixiEditor.DrawingApi.Core;
+using PixiEditor.DrawingApi.Core.Surfaces;
+using PixiEditor.Extensions.CommonApi.Palettes;
+using PixiEditor.Numerics;
+using PixiEditor.Parser;
+using PixiEditor.Parser.Deprecated;
+using BlendMode = PixiEditor.Parser.BlendMode;
+
+namespace PixiEditor.AvaloniaUI.Helpers.Extensions;
+
+internal static class PixiParserV3DocumentEx
+{
+    public static DocumentViewModel ToDocument(this DeprecatedDocument document)
+    {
+        return DocumentViewModel.Build(b =>
+        {
+            /*b.WithSize(document.Width, document.Height)
+                .WithPalette(document.Palette, x => new PaletteColor(x.R, x.G, x.B))
+                .WithSwatches(document.Swatches, x => new(x.R, x.G, x.B))
+                .WithReferenceLayer(document.ReferenceLayer, (r, builder) => builder
+                    .WithIsVisible(r.Enabled)
+                    .WithShape(r.Corners)
+                    .WithIsTopmost(r.Topmost)
+                    .WithSurface(Surface.Load(r.ImageBytes)));
+
+            BuildChildren(b, document.RootFolder.Children);*/
+        });
+
+        void BuildChildren(ChildrenBuilder builder, IEnumerable<IStructureMember> members)
+        {
+            foreach (var member in members)
+            {
+                if (member is Folder folder)
+                {
+                    builder.WithFolder(x => BuildFolder(x, folder));
+                }
+                else if (member is ImageLayer layer)
+                {
+                    builder.WithLayer(x => BuildLayer(x, layer));
+                }
+                else
+                {
+                    throw new NotImplementedException(
+                        $"StructureMember of type '{member.GetType().FullName}' has not been implemented");
+                }
+            }
+        }
+
+        void BuildFolder(FolderBuilder builder, Folder folder) => builder
+            .WithName(folder.Name)
+            .WithVisibility(folder.Enabled)
+            .WithOpacity(folder.Opacity)
+            .WithBlendMode(folder.BlendMode)
+            .WithChildren(x => BuildChildren(x, folder.Children))
+            .WithClipToBelow(folder.ClipToMemberBelow)
+            .WithMask(folder.Mask,
+                (x, m) => x.WithVisibility(m.Enabled).WithSurface(m.Width, m.Height,
+                    x => x.WithImage(m.ImageBytes, m.OffsetX, m.OffsetY)));
+
+        void BuildLayer(LayerBuilder builder, ImageLayer layer)
+        {
+            builder
+                .WithName(layer.Name)
+                .WithGuid(layer.Guid)
+                .WithVisibility(layer.Enabled)
+                .WithOpacity(layer.Opacity)
+                .WithBlendMode(layer.BlendMode)
+                .WithRect(layer.Width, layer.Height, layer.OffsetX, layer.OffsetY)
+                .WithClipToBelow(layer.ClipToMemberBelow)
+                .WithLockAlpha(layer.LockAlpha)
+                .WithMask(layer.Mask,
+                    (x, m) => x.WithVisibility(m.Enabled).WithSurface(m.Width, m.Height,
+                        x => x.WithImage(m.ImageBytes, m.OffsetX, m.OffsetY)));
+
+            if (layer is { Width: > 0, Height: > 0 })
+            {
+                builder.WithSurface(x => x.WithImage(layer.ImageBytes, 0, 0));
+            }
+        }
+    }
+
+
+    internal class ChildrenBuilder
+    {
+        public List<StructureMemberBuilder> Children { get; set; } =
+            new List<StructureMemberBuilder>();
+
+        public ChildrenBuilder WithLayer(Action<LayerBuilder> layer)
+        {
+            var layerBuilder = new LayerBuilder();
+            layer(layerBuilder);
+            Children.Add(layerBuilder);
+            return this;
+        }
+
+        public ChildrenBuilder WithFolder(Action<FolderBuilder> folder)
+        {
+            var folderBuilder = new FolderBuilder();
+            folder(folderBuilder);
+            Children.Add(folderBuilder);
+            return this;
+        }
+    }
+
+
+    public abstract class StructureMemberBuilder
+    {
+        private MaskBuilder maskBuilder;
+
+        public int OrderInStructure { get; set; }
+
+        public string Name { get; set; }
+
+        public bool IsVisible { get; set; }
+
+        public float Opacity { get; set; }
+
+        public BlendMode BlendMode { get; set; }
+
+        public bool ClipToMemberBelow { get; set; }
+
+        public bool HasMask => maskBuilder is not null;
+
+        [NotNull] public MaskBuilder Mask => maskBuilder ??= new MaskBuilder();
+
+        public Guid Id { get; set; }
+
+        public StructureMemberBuilder()
+        {
+            IsVisible = true;
+            Opacity = 1;
+        }
+
+        public StructureMemberBuilder WithOrderInStructure(int order)
+        {
+            OrderInStructure = order;
+            return this;
+        }
+
+        public StructureMemberBuilder WithName(string name)
+        {
+            Name = name;
+            return this;
+        }
+
+        public StructureMemberBuilder WithVisibility(bool visibility)
+        {
+            IsVisible = visibility;
+            return this;
+        }
+
+        public StructureMemberBuilder WithOpacity(float opacity)
+        {
+            Opacity = opacity;
+            return this;
+        }
+
+        public StructureMemberBuilder WithBlendMode(BlendMode blendMode)
+        {
+            BlendMode = blendMode;
+            return this;
+        }
+
+        public StructureMemberBuilder WithMask(Action<MaskBuilder> mask)
+        {
+            mask(Mask);
+            return this;
+        }
+
+        public StructureMemberBuilder WithMask<T>(T reference, Action<MaskBuilder, T> mask)
+        {
+            return reference != null ? WithMask(x => mask(x, reference)) : this;
+        }
+
+        public StructureMemberBuilder WithGuid(Guid guid)
+        {
+            Id = guid;
+            return this;
+        }
+
+        public StructureMemberBuilder WithClipToBelow(bool value)
+        {
+            ClipToMemberBelow = value;
+            return this;
+        }
+    }
+
+    public class LayerBuilder : StructureMemberBuilder
+    {
+        private int? width;
+        private int? height;
+
+        public SurfaceBuilder? Surface { get; set; }
+
+        public int Width
+        {
+            get => width ?? default;
+            set => width = value;
+        }
+
+        public int Height
+        {
+            get => height ?? default;
+            set => height = value;
+        }
+
+        public int OffsetX { get; set; }
+
+        public int OffsetY { get; set; }
+
+        public bool LockAlpha { get; set; }
+
+        public new LayerBuilder WithName(string name) => base.WithName(name) as LayerBuilder;
+
+        public new LayerBuilder WithVisibility(bool visibility) => base.WithVisibility(visibility) as LayerBuilder;
+
+        public new LayerBuilder WithOpacity(float opacity) => base.WithOpacity(opacity) as LayerBuilder;
+
+        public new LayerBuilder WithBlendMode(BlendMode blendMode) => base.WithBlendMode(blendMode) as LayerBuilder;
+
+        public new LayerBuilder WithClipToBelow(bool value) => base.WithClipToBelow(value) as LayerBuilder;
+
+        public LayerBuilder WithLockAlpha(bool layerLockAlpha)
+        {
+            LockAlpha = layerLockAlpha;
+            return this;
+        }
+
+        public new LayerBuilder WithMask(Action<MaskBuilder> mask) => base.WithMask(mask) as LayerBuilder;
+
+        public new LayerBuilder WithGuid(Guid guid) => base.WithGuid(guid) as LayerBuilder;
+
+        public LayerBuilder WithSurface(Surface surface)
+        {
+            Surface = new(surface);
+            return this;
+        }
+
+        public LayerBuilder WithSize(int width, int height)
+        {
+            Width = width;
+            Height = height;
+            return this;
+        }
+
+        public LayerBuilder WithSize(VecI size) => WithSize(size.X, size.Y);
+
+        public LayerBuilder WithRect(int width, int height, int offsetX, int offsetY)
+        {
+            Width = width;
+            Height = height;
+            OffsetX = offsetX;
+            OffsetY = offsetY;
+            return this;
+        }
+
+        public LayerBuilder WithSurface(Action<SurfaceBuilder> surface)
+        {
+            if (width is null || height is null)
+            {
+                throw new InvalidOperationException(
+                    "You must first set the width and height of the layer. You can do this by calling WithRect() or setting the Width and Height properties.");
+            }
+
+            var surfaceBuilder = new SurfaceBuilder(new Surface(new VecI(Width, Height)));
+            surface(surfaceBuilder);
+            Surface = surfaceBuilder;
+            return this;
+        }
+    }
+
+    public class FolderBuilder : StructureMemberBuilder
+    {
+        public List<StructureMemberBuilder> Children { get; set; } = new List<StructureMemberBuilder>();
+
+        public new FolderBuilder WithName(string name) => base.WithName(name) as FolderBuilder;
+
+        public new FolderBuilder WithVisibility(bool visibility) => base.WithVisibility(visibility) as FolderBuilder;
+
+        public new FolderBuilder WithOpacity(float opacity) => base.WithOpacity(opacity) as FolderBuilder;
+
+        public new FolderBuilder WithBlendMode(BlendMode blendMode) => base.WithBlendMode(blendMode) as FolderBuilder;
+
+        public new FolderBuilder WithMask(Action<MaskBuilder> mask) => base.WithMask(mask) as FolderBuilder;
+
+        public new FolderBuilder WithGuid(Guid guid) => base.WithGuid(guid) as FolderBuilder;
+
+        public FolderBuilder WithClipToBelow(bool value) => base.WithClipToBelow(value) as FolderBuilder;
+
+        public FolderBuilder WithChildren(Action<ChildrenBuilder> children)
+        {
+            ChildrenBuilder childrenBuilder = new();
+            children(childrenBuilder);
+            Children = childrenBuilder.Children;
+            return this;
+        }
+    }
+
+    public class SurfaceBuilder
+    {
+        public Surface Surface { get; set; }
+
+        public SurfaceBuilder(Surface surface)
+        {
+            Surface = surface;
+        }
+
+        public SurfaceBuilder WithImage(ReadOnlySpan<byte> buffer) => WithImage(buffer, 0, 0);
+
+        public SurfaceBuilder WithImage(ReadOnlySpan<byte> buffer, int x, int y)
+        {
+            if (buffer.IsEmpty) return this;
+
+            Surface.DrawingSurface.Canvas.DrawBitmap(Bitmap.Decode(buffer), x, y);
+            return this;
+        }
+    }
+
+    public class MaskBuilder
+    {
+        public bool IsVisible { get; set; }
+
+        public SurfaceBuilder Surface { get; set; }
+
+        public MaskBuilder()
+        {
+            IsVisible = true;
+        }
+
+        public MaskBuilder WithVisibility(bool isVisible)
+        {
+            IsVisible = isVisible;
+            return this;
+        }
+
+        public MaskBuilder WithSurface(Surface surface)
+        {
+            Surface = new SurfaceBuilder(surface);
+            return this;
+        }
+
+        public MaskBuilder WithSurface(int width, int height, Action<SurfaceBuilder> surface)
+        {
+            var surfaceBuilder = new SurfaceBuilder(new Surface(new VecI(Math.Max(width, 1), Math.Max(height, 1))));
+            surface(surfaceBuilder);
+            Surface = surfaceBuilder;
+            return this;
+        }
+    }
+
+    internal class RasterKeyFrameBuilder : KeyFrameBuilder
+    {
+        /*public new RasterKeyFrameBuilder WithVisibility(bool isVisible) =>
+            base.WithVisibility(isVisible) as RasterKeyFrameBuilder;*/
+
+        public new RasterKeyFrameBuilder WithLayerGuid(int layerId) =>
+            base.WithKeyFrameId(layerId) as RasterKeyFrameBuilder;
+
+        /*public new RasterKeyFrameBuilder WithStartFrame(int startFrame) =>
+            base.WithStartFrame(startFrame) as RasterKeyFrameBuilder;
+
+        public new RasterKeyFrameBuilder WithDuration(int duration) =>
+            base.WithDuration(duration) as RasterKeyFrameBuilder;*/
+    }
+}

+ 0 - 176
src/PixiEditor.AvaloniaUI/Helpers/SerializableDocumentEx.cs

@@ -1,176 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-using ChunkyImageLib;
-using PixiEditor.AvaloniaUI.ViewModels.Document;
-using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface.ImageData;
-using PixiEditor.Extensions.CommonApi.Palettes;
-using PixiEditor.Numerics;
-using PixiEditor.Parser;
-using PixiEditor.Parser.Collections.Deprecated;
-using PixiEditor.Parser.Deprecated;
-
-namespace PixiEditor.AvaloniaUI.Helpers;
-
-internal static class SerializableDocumentEx
-{
-    public static Vector2 ToVector2(this VecD serializableVector2)
-    {
-        return new Vector2 { X = serializableVector2.X, Y = serializableVector2.Y };
-    }
-    public static Image ToImage(this SerializableLayer serializableLayer)
-    {
-        if (serializableLayer.PngBytes == null)
-        {
-            return null;
-        }
-
-        return Image.FromEncodedData(serializableLayer.PngBytes);
-    }
-
-    public static DocumentViewModel ToDocument(this SerializableDocument serializableDocument)
-    {
-        List<SerializableLayer> builtLayers = new List<SerializableLayer>();
-        DocumentViewModel vm = DocumentViewModel.Build(builder =>
-        {
-            builder
-                .WithSize(serializableDocument.Width, serializableDocument.Height)
-                .WithPalette(serializableDocument.Palette.Select(x => new PaletteColor(x.R, x.G, x.B)).ToList())
-                .WithSwatches(serializableDocument.Swatches.Select(x => new PaletteColor(x.R, x.G, x.B)).ToList());
-
-            if (serializableDocument.Groups != null)
-            {
-                foreach (var group in serializableDocument.Groups)
-                {
-                    builder.WithFolder(folderBuilder =>
-                    {
-                        builtLayers.AddRange(BuildFolder(
-                            folderBuilder,
-                            group,
-                            GatherFolderLayers(group, serializableDocument.Layers),
-                            serializableDocument));
-                    });
-                }
-            }
-
-            BuildLayers(serializableDocument.Layers.Where(x => !builtLayers.Contains(x)), builder, serializableDocument);
-            SortMembersRecursively(builder.Children);
-        });
-
-        return vm;
-    }
-
-    /// <summary>
-    ///     Builds folder and its children.
-    /// </summary>
-    /// <param name="folderBuilder">Folder to build.</param>
-    /// <param name="group">Serialized folder (group), which will be used to build.</param>
-    /// <param name="layers">Layers only in this folder.</param>
-    /// <param name="doc">Document which contains all the serialized data.</param>
-    /// <returns>List of layers which were built.</returns>
-    private static List<SerializableLayer> BuildFolder(DocumentViewModelBuilder.FolderBuilder folderBuilder, SerializableGroup group, List<SerializableLayer> layers, SerializableDocument doc)
-    {
-        List<SerializableLayer> builtLayers = new List<SerializableLayer>(layers);
-        folderBuilder
-            .WithName(group.Name)
-            .WithOpacity(group.Opacity)
-            .WithVisibility(group.IsVisible)
-            .WithOrderInStructure(group.StartLayer);
-
-        folderBuilder.WithChildren(childrenBuilder =>
-            {
-                if (group.Subgroups != null)
-                {
-                    foreach (var subGroup in group.Subgroups)
-                    {
-                        childrenBuilder.WithFolder(subFolderBuilder =>
-                        {
-                            builtLayers.AddRange(BuildFolder(
-                                subFolderBuilder,
-                                subGroup,
-                                GatherFolderLayers(subGroup, doc.Layers),
-                                doc));
-                        });
-                    }
-                }
-
-                BuildLayers(layers, childrenBuilder, doc);
-            });
-
-        return builtLayers;
-    }
-
-    private static void BuildLayers(IEnumerable<SerializableLayer> layers, ChildrenBuilder builder, SerializableDocument document)
-    {
-        if (layers != null)
-        {
-            foreach (var layer in layers)
-            {
-                builder.WithLayer((layerBuilder) =>
-                {
-                    layerBuilder
-                        .WithSize(layer.Width, layer.Height)
-                        .WithName(layer.Name)
-                        .WithOpacity(layer.Opacity)
-                        .WithVisibility(layer.IsVisible)
-                        .WithRect(layer.Width, layer.Height, layer.OffsetX, layer.OffsetY)
-                        .WithSurface((surfaceBuilder) =>
-                        {
-                            if (layer.PngBytes is { Length: > 0 })
-                            {
-                                surfaceBuilder.WithImage(layer.PngBytes);
-                            }
-                            else
-                            {
-                                surfaceBuilder.Surface = new Surface(new VecI(1, 1));
-                            }
-                        })
-                        .WithOrderInStructure(document.Layers.IndexOf(layer));
-                });
-            }
-        }
-    }
-
-    /// <summary>
-    ///     Gathers all layers which are in the folder. Excludes layers which are in subfolders.
-    /// </summary>
-    /// <param name="group">Group which contains folder data.</param>
-    /// <param name="serializableDocumentLayers">All layers in document.</param>
-    /// <returns>List of layers in folder, excluding layers in nested folders.</returns>
-    private static List<SerializableLayer> GatherFolderLayers(SerializableGroup group, LayerCollection serializableDocumentLayers)
-    {
-        List<SerializableLayer> layers = new List<SerializableLayer>();
-
-        for (int i = group.StartLayer; i <= group.EndLayer; i++)
-        {
-            layers.Add(serializableDocumentLayers[i]);
-        }
-
-        if (group.Subgroups is { Count: > 0 })
-        {
-            foreach (var subGroup in group.Subgroups)
-            {
-                var nestedGroupLayers = GatherFolderLayers(subGroup, serializableDocumentLayers);
-                layers.RemoveAll(x => nestedGroupLayers.Contains(x));
-            }
-        }
-
-        return layers;
-    }
-
-    /// <summary>
-    /// Sorts StructureMemberBuilder by its OrderInStructure property.
-    /// </summary>
-    /// <param name="builderChildren">Structure to sort</param>
-    private static void SortMembersRecursively(List<DocumentViewModelBuilder.StructureMemberBuilder> builderChildren)
-    {
-        builderChildren.Sort(Comparer<DocumentViewModelBuilder.StructureMemberBuilder>.Create((a, b) => a.OrderInStructure - b.OrderInStructure));
-        
-        foreach (var child in builderChildren)
-        {
-            if (child is not DocumentViewModelBuilder.FolderBuilder folderBuilder)
-                continue;
-            SortMembersRecursively(folderBuilder.Children);
-        }
-    }
-}

+ 106 - 0
src/PixiEditor.AvaloniaUI/Helpers/SerializationUtil.cs

@@ -0,0 +1,106 @@
+using System.Collections;
+using PixiEditor.AvaloniaUI.Models.Serialization;
+using PixiEditor.AvaloniaUI.Models.Serialization.Factories;
+
+namespace PixiEditor.AvaloniaUI.Helpers;
+
+public static class SerializationUtil
+{
+    public static object SerializeObject(object? value, SerializationConfig config,
+        IReadOnlyList<SerializationFactory> allFactories)
+    {
+        if (value is null)
+        {
+            return null;
+        }
+
+        var factory = allFactories.FirstOrDefault(x => x.OriginalType == value.GetType());
+
+        if (factory != null)
+        {
+            factory.Config = config;
+            return factory.Serialize(value);
+        }
+
+        if (value.GetType().IsValueType || value is string)
+        {
+            return value;
+        }
+
+        throw new ArgumentException(
+            $"Type {value.GetType()} is not serializable and appropriate serialization factory was not found.");
+    }
+
+    public static object Deserialize(object value, SerializationConfig config,
+        IReadOnlyList<SerializationFactory> allFactories)
+    {
+        if (IsComplexObject(value))
+        {
+            object[] arr = ((IEnumerable<object>)value).ToArray();
+            string id = (string)arr.First();
+            object data = arr.Last();
+            var factory = allFactories.FirstOrDefault(x => x.DeserializationId == id);
+
+            if (factory != null)
+            {
+                factory.Config = config;
+                return factory.Deserialize(data is Dictionary<object, object> processableDict
+                    ? ToDictionary(processableDict)
+                    : data);
+            }
+        }
+
+        return value;
+    }
+
+
+    public static Dictionary<string, object> DeserializeDict(Dictionary<string, object> data,
+        SerializationConfig config, List<SerializationFactory> allFactories)
+    {
+        var dict = new Dictionary<string, object>();
+
+        foreach (var (key, value) in data)
+        {
+            if (value is object[] objArr && objArr.Length > 0)
+            {
+                var deserialized = Deserialize(objArr[0], config, allFactories);
+                var targetArr = Array.CreateInstance(deserialized.GetType(), objArr.Length);
+                targetArr.SetValue(deserialized, 0);
+                
+                for (int i = 1; i < objArr.Length; i++)
+                {
+                    targetArr.SetValue(Deserialize(objArr[i], config, allFactories), i);
+                }
+                
+                dict[key] = targetArr;
+            }
+            else
+            {
+                dict[key] = Deserialize(value, config, allFactories);
+            }
+        }
+
+        return dict;
+    }
+
+    private static bool IsComplexObject(object value)
+    {
+        // SerializedObject signature
+        return value is IEnumerable<object> enumerable && enumerable.Count() == 2;
+    }
+
+    private static Dictionary<string, object> ToDictionary(Dictionary<object, object> data)
+    {
+        // input data is probably Dictionary<object, object> with KeyValuePair keys (where key as type is object, but actually is string)
+        // note that this depends if serialized type uses string keys or int
+
+        var dict = new Dictionary<string, object>();
+
+        foreach (KeyValuePair<object, object> item in data)
+        {
+            dict.Add((string)item.Key, item.Value);
+        }
+
+        return dict;
+    }
+}

+ 5 - 0
src/PixiEditor.AvaloniaUI/Helpers/ServiceCollectionHelpers.cs

@@ -14,6 +14,7 @@ using PixiEditor.AvaloniaUI.Models.IO.PaletteParsers.JascPalFile;
 using PixiEditor.AvaloniaUI.Models.Localization;
 using PixiEditor.AvaloniaUI.Models.Palettes;
 using PixiEditor.AvaloniaUI.Models.Preferences;
+using PixiEditor.AvaloniaUI.Models.Serialization.Factories;
 using PixiEditor.AvaloniaUI.ViewModels.Dock;
 using PixiEditor.AvaloniaUI.ViewModels.Document;
 using PixiEditor.AvaloniaUI.ViewModels.Menu;
@@ -111,6 +112,10 @@ internal static class ServiceCollectionHelpers
             .AddSingleton<IoFileType, BmpFileType>()
             .AddSingleton<IoFileType, GifFileType>()
             .AddSingleton<IoFileType, Mp4FileType>()
+            // Serialization Factories
+            .AddSingleton<SerializationFactory, SurfaceSerializationFactory>()
+            .AddSingleton<SerializationFactory, ChunkyImageSerializationFactory>()
+            .AddSingleton<SerializationFactory, KernelSerializationFactory>()
             // Palette Parsers
             .AddSingleton<IPalettesProvider, PaletteProvider>()
             .AddSingleton<PaletteFileParser, JascFileParser>()

+ 2 - 1
src/PixiEditor.AvaloniaUI/Helpers/SurfaceHelpers.cs

@@ -1,8 +1,9 @@
 using Avalonia.Media.Imaging;
 using ChunkyImageLib;
 using PixiEditor.AvaloniaUI.Helpers.Extensions;
+using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface.ImageData;
+using PixiEditor.DrawingApi.Core.Surfaces.ImageData;
 using PixiEditor.Numerics;
 
 namespace PixiEditor.AvaloniaUI.Helpers;

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

@@ -3,8 +3,8 @@ using Avalonia.Media.Imaging;
 using Avalonia.Platform;
 using PixiEditor.AvaloniaUI.Helpers.Extensions;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
-using PixiEditor.DrawingApi.Core.Surface.ImageData;
+using PixiEditor.DrawingApi.Core.Surfaces;
+using PixiEditor.DrawingApi.Core.Surfaces.ImageData;
 using PixiEditor.Numerics;
 
 namespace PixiEditor.AvaloniaUI.Helpers;

+ 1 - 0
src/PixiEditor.AvaloniaUI/Models/Clipboard/DataImage.cs

@@ -1,4 +1,5 @@
 using ChunkyImageLib;
+using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Numerics;
 

+ 4 - 6
src/PixiEditor.AvaloniaUI/Models/Controllers/ClipboardController.cs

@@ -19,8 +19,9 @@ using PixiEditor.AvaloniaUI.Models.Dialogs;
 using PixiEditor.AvaloniaUI.Models.IO;
 using PixiEditor.AvaloniaUI.ViewModels.Document;
 using PixiEditor.ChangeableDocument.Enums;
+using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface.ImageData;
+using PixiEditor.DrawingApi.Core.Surfaces.ImageData;
 using PixiEditor.Numerics;
 using PixiEditor.Parser;
 using PixiEditor.Parser.Deprecated;
@@ -220,12 +221,9 @@ internal static class ClipboardController
                         }
 
                         stream.Position = 0;
-                        using var bitmap = DepractedPixiParser.Deserialize(stream).RenderOldDocument();
-                        var size = new VecI(bitmap.Width, bitmap.Height);
-                        imported = new Surface(size);
-                        imported.DrawBytes(size, bitmap.Bytes, ColorType.RgbaF32, AlphaType.Premul);
+                        var document = DeprecatedPixiParser.Deserialize(stream);
 
-                        System.Diagnostics.Debug.Write(imported.ToString());
+                        imported = Surface.Load(document.PreviewImage);
                     }
                 }
                 else

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

@@ -27,7 +27,7 @@ internal class DocumentStructureHelper
         {
             if (newNode is IStructureMemberHandler structureMemberHandler)
             {
-                string childName = structureMemberHandler.NameBindable;
+                string childName = structureMemberHandler.NodeNameBindable;
                 if (childName.StartsWith(name))
                     count++;
             }

+ 12 - 2
src/PixiEditor.AvaloniaUI/Models/DocumentModels/DocumentUpdater.cs

@@ -18,6 +18,7 @@ using PixiEditor.ChangeableDocument.ChangeInfos.Root;
 using PixiEditor.ChangeableDocument.ChangeInfos.Root.ReferenceLayerChangeInfos;
 using PixiEditor.ChangeableDocument.ChangeInfos.Structure;
 using PixiEditor.ChangeableDocument.Enums;
+using PixiEditor.DrawingApi.Core;
 using PixiEditor.Numerics;
 
 namespace PixiEditor.AvaloniaUI.Models.DocumentModels;
@@ -189,6 +190,9 @@ internal class DocumentUpdater
             case PropertyValueUpdated_ChangeInfo info:
                 ProcessNodePropertyValueUpdated(info);
                 break;
+            case NodeName_ChangeInfo info:
+                ProcessNodeName(info);
+                break;
         }
     }
 
@@ -492,13 +496,13 @@ internal class DocumentUpdater
     {
         T node = new T()
         {
-            NodeName = info.NodeName,
             InternalName = info.InternalName,
             Id = info.Id,
             Document = (DocumentViewModel)doc,
             Internals = helper
         };
-
+        
+        node.SetName(info.NodeName);
         node.SetPosition(info.Position);
         
         List<INodePropertyHandler> inputs = CreateProperties(info.Inputs, node, true);
@@ -588,4 +592,10 @@ internal class DocumentUpdater
         
         property.InternalSetValue(info.Value);
     }
+    
+    private void ProcessNodeName(NodeName_ChangeInfo info)
+    {
+        NodeViewModel node = doc.StructureHelper.FindNode<NodeViewModel>(info.NodeId);
+        node.SetName(info.NewName);
+    }
 }

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

@@ -15,8 +15,9 @@ using PixiEditor.AvaloniaUI.Models.Tools;
 using PixiEditor.ChangeableDocument.Actions.Generated;
 using PixiEditor.ChangeableDocument.Actions.Undo;
 using PixiEditor.ChangeableDocument.Enums;
+using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface.Vector;
+using PixiEditor.DrawingApi.Core.Surfaces.Vector;
 using PixiEditor.Extensions.CommonApi.Palettes;
 using PixiEditor.Numerics;
 
@@ -408,7 +409,7 @@ internal class DocumentOperationsModule : IDocumentOperations
         //make a new layer, put combined image onto it, delete layers that were merged
         Internals.ActionAccumulator.AddActions(
             new CreateStructureMember_Action(parent.Id, newGuid, StructureMemberType.Layer),
-            new StructureMemberName_Action(newGuid, node.NameBindable),
+            new StructureMemberName_Action(newGuid, node.NodeNameBindable),
             new CombineStructureMembersOnto_Action(members.ToHashSet(), newGuid, Document.AnimationHandler.ActiveFrameBindable));
         foreach (var member in members)
             Internals.ActionAccumulator.AddActions(new DeleteStructureMember_Action(member));

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

@@ -4,7 +4,7 @@ using PixiEditor.AvaloniaUI.Models.Tools;
 using PixiEditor.ChangeableDocument.Actions.Generated;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surfaces.PaintImpl;
 using PixiEditor.Extensions.CommonApi.Palettes;
 using PixiEditor.Numerics;
 

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

@@ -3,6 +3,7 @@ using ChunkyImageLib.DataHolders;
 using PixiEditor.AvaloniaUI.Models.Handlers;
 using PixiEditor.AvaloniaUI.Models.Tools;
 using PixiEditor.ChangeableDocument.Actions.Generated;
+using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Numerics;
 

+ 1 - 1
src/PixiEditor.AvaloniaUI/Models/Files/BmpFileType.cs

@@ -1,5 +1,5 @@
 using Avalonia.Media;
-using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surfaces;
 using PixiEditor.Extensions.Common.Localization;
 
 namespace PixiEditor.AvaloniaUI.Models.Files;

+ 3 - 4
src/PixiEditor.AvaloniaUI/Models/Files/ImageFileType.cs

@@ -4,10 +4,9 @@ using PixiEditor.AvaloniaUI.Helpers;
 using PixiEditor.AvaloniaUI.Models.IO;
 using PixiEditor.AvaloniaUI.Models.IO.FileEncoders;
 using PixiEditor.AvaloniaUI.ViewModels.Document;
+using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
-using PixiEditor.DrawingApi.Core.Surface;
-using PixiEditor.DrawingApi.Core.Surface.ImageData;
-using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+using PixiEditor.DrawingApi.Core.Surfaces;
 using PixiEditor.Numerics;
 
 namespace PixiEditor.AvaloniaUI.Models.Files;
@@ -94,7 +93,7 @@ internal abstract class ImageFileType : IoFileType
         try
         {
             if (!encoder.SupportsTransparency)
-                bitmap.DrawingSurface.Canvas.DrawColor(Colors.White, DrawingApi.Core.Surface.BlendMode.Multiply);
+                bitmap.DrawingSurface.Canvas.DrawColor(Colors.White, BlendMode.Multiply);
 
             await using var stream = new FileStream(savePath, FileMode.Create);
             await encoder.SaveAsync(stream, bitmap);

+ 1 - 1
src/PixiEditor.AvaloniaUI/Models/Files/JpegFileType.cs

@@ -1,5 +1,5 @@
 using Avalonia.Media;
-using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surfaces;
 using PixiEditor.Extensions.Common.Localization;
 
 namespace PixiEditor.AvaloniaUI.Models.Files;

+ 1 - 1
src/PixiEditor.AvaloniaUI/Models/Files/PngFileType.cs

@@ -1,5 +1,5 @@
 using Avalonia.Media;
-using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surfaces;
 using PixiEditor.Extensions.Common.Localization;
 
 namespace PixiEditor.AvaloniaUI.Models.Files;

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

@@ -7,10 +7,10 @@ using PixiEditor.AvaloniaUI.Models.DocumentModels.Public;
 using PixiEditor.AvaloniaUI.Models.Structures;
 using PixiEditor.AvaloniaUI.Models.Tools;
 using PixiEditor.ChangeableDocument.Rendering;
+using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
-using PixiEditor.DrawingApi.Core.Surface.Vector;
+using PixiEditor.DrawingApi.Core.Surfaces.Vector;
 using PixiEditor.Extensions.CommonApi.Palettes;
 using PixiEditor.Numerics;
 

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

@@ -1,4 +1,5 @@
 using ChunkyImageLib;
+using PixiEditor.DrawingApi.Core;
 
 namespace PixiEditor.AvaloniaUI.Models.Handlers;
 

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

@@ -3,6 +3,7 @@ using System.ComponentModel;
 using ChunkyImageLib;
 using PixiEditor.AvaloniaUI.Models.Structures;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.DrawingApi.Core;
 using PixiEditor.Numerics;
 
 namespace PixiEditor.AvaloniaUI.Models.Handlers;
@@ -10,7 +11,7 @@ namespace PixiEditor.AvaloniaUI.Models.Handlers;
 public interface INodeHandler : INotifyPropertyChanged
 {
     public Guid Id { get; }
-    public string NodeName { get; set; }
+    public string NodeNameBindable { get; set; }
     public string InternalName { get; }
     public ObservableRangeCollection<INodePropertyHandler> Inputs { get; }
     public ObservableRangeCollection<INodePropertyHandler> Outputs { get; }

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

@@ -3,8 +3,8 @@ using Avalonia;
 using Avalonia.Media.Imaging;
 using ChunkyImageLib;
 using ChunkyImageLib.DataHolders;
+using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
 using PixiEditor.Numerics;
 
 namespace PixiEditor.AvaloniaUI.Models.Handlers;

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

@@ -2,8 +2,8 @@
 using Avalonia.Media.Imaging;
 using ChunkyImageLib;
 using PixiEditor.AvaloniaUI.Models.Layers;
+using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
 using PixiEditor.Numerics;
 using BlendMode = PixiEditor.ChangeableDocument.Enums.BlendMode;
 
@@ -12,7 +12,6 @@ namespace PixiEditor.AvaloniaUI.Models.Handlers;
 internal interface IStructureMemberHandler : INodeHandler
 {
     public bool HasMaskBindable { get; }
-    public string NameBindable { get; set; }
     public Surface? MaskPreviewSurface { get; set; }
     public Surface? PreviewSurface { get; set; }
     public bool MaskIsVisibleBindable { get; set; }

+ 2 - 1
src/PixiEditor.AvaloniaUI/Models/IO/Exporter.cs

@@ -7,7 +7,8 @@ using ChunkyImageLib;
 using PixiEditor.AvaloniaUI.Helpers;
 using PixiEditor.AvaloniaUI.Models.Files;
 using PixiEditor.AvaloniaUI.ViewModels.Document;
-using PixiEditor.DrawingApi.Core.Surface.ImageData;
+using PixiEditor.DrawingApi.Core;
+using PixiEditor.DrawingApi.Core.Surfaces.ImageData;
 using PixiEditor.Numerics;
 
 namespace PixiEditor.AvaloniaUI.Models.IO;

+ 1 - 0
src/PixiEditor.AvaloniaUI/Models/IO/FileEncoders/IFileEncoder.cs

@@ -1,6 +1,7 @@
 using System.IO;
 using System.Threading.Tasks;
 using ChunkyImageLib;
+using PixiEditor.DrawingApi.Core;
 
 namespace PixiEditor.AvaloniaUI.Models.IO.FileEncoders;
 

+ 2 - 1
src/PixiEditor.AvaloniaUI/Models/IO/FileEncoders/UniversalFileEncoder.cs

@@ -1,7 +1,8 @@
 using System.IO;
 using System.Threading.Tasks;
 using ChunkyImageLib;
-using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core;
+using PixiEditor.DrawingApi.Core.Surfaces;
 
 namespace PixiEditor.AvaloniaUI.Models.IO.FileEncoders;
 

+ 7 - 6
src/PixiEditor.AvaloniaUI/Models/IO/Importer.cs

@@ -8,17 +8,18 @@ using PixiEditor.AvaloniaUI.Exceptions;
 using PixiEditor.AvaloniaUI.Helpers;
 using PixiEditor.AvaloniaUI.Helpers.Extensions;
 using PixiEditor.AvaloniaUI.ViewModels.Document;
+using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
-using PixiEditor.DrawingApi.Core.Surface.ImageData;
-using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+using PixiEditor.DrawingApi.Core.Surfaces;
+using PixiEditor.DrawingApi.Core.Surfaces.ImageData;
+using PixiEditor.DrawingApi.Core.Surfaces.PaintImpl;
 using PixiEditor.Extensions.Common.Localization;
 using PixiEditor.Extensions.Exceptions;
 using PixiEditor.Numerics;
 using PixiEditor.Parser;
 using PixiEditor.Parser.Deprecated;
 using Bitmap = Avalonia.Media.Imaging.Bitmap;
-using BlendMode = PixiEditor.DrawingApi.Core.Surface.BlendMode;
+using BlendMode = PixiEditor.DrawingApi.Core.Surfaces.BlendMode;
 
 namespace PixiEditor.AvaloniaUI.Models.IO;
 
@@ -102,7 +103,7 @@ internal class Importer : ObservableObject
         {
             try
             {
-                var doc = DepractedPixiParser.Deserialize(path).ToDocument();
+                var doc = DeprecatedPixiParser.Deserialize(path).ToDocument();
                 
                 if (associatePath)
                 {
@@ -130,7 +131,7 @@ internal class Importer : ObservableObject
         {
             try
             {
-                var doc = DepractedPixiParser.Deserialize(file).ToDocument();
+                var doc = DeprecatedPixiParser.Deserialize(file).ToDocument();
                 doc.FullFilePath = originalFilePath;
                 return doc;
             }

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

@@ -7,9 +7,10 @@ using PixiEditor.AvaloniaUI.Models.DocumentModels;
 using PixiEditor.AvaloniaUI.Models.Handlers;
 using PixiEditor.AvaloniaUI.Models.Rendering.RenderInfos;
 using PixiEditor.ChangeableDocument.Rendering;
+using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
-using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+using PixiEditor.DrawingApi.Core.Surfaces;
+using PixiEditor.DrawingApi.Core.Surfaces.PaintImpl;
 using PixiEditor.Numerics;
 
 namespace PixiEditor.AvaloniaUI.Models.Rendering;

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

@@ -14,10 +14,10 @@ using PixiEditor.AvaloniaUI.ViewModels.Document;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Rendering;
+using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
-using PixiEditor.DrawingApi.Core.Surface.ImageData;
-using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+using PixiEditor.DrawingApi.Core.Surfaces;
+using PixiEditor.DrawingApi.Core.Surfaces.PaintImpl;
 using PixiEditor.Numerics;
 
 namespace PixiEditor.AvaloniaUI.Models.Rendering;

+ 50 - 0
src/PixiEditor.AvaloniaUI/Models/Serialization/Factories/ChunkyImageSerializationFactory.cs

@@ -0,0 +1,50 @@
+using ChunkyImageLib;
+using ChunkyImageLib.DataHolders;
+using PixiEditor.DrawingApi.Core;
+using PixiEditor.DrawingApi.Core.Surfaces.PaintImpl;
+using PixiEditor.DrawingApi.Skia;
+using PixiEditor.Numerics;
+using PixiEditor.Parser.Skia;
+
+namespace PixiEditor.AvaloniaUI.Models.Serialization.Factories;
+
+public class ChunkyImageSerializationFactory : SerializationFactory<byte[], ChunkyImage>
+{
+    private static SurfaceSerializationFactory surfaceFactory = new();
+
+    public override byte[] Serialize(ChunkyImage original)
+    {
+        var encoder = Config.Encoder;
+        surfaceFactory.Config = Config;
+
+        Surface surface = new Surface(original.LatestSize);
+        original.DrawMostUpToDateRegionOn(
+            new RectI(0, 0, original.LatestSize.X,
+                original.LatestSize.Y), ChunkResolution.Full, surface.DrawingSurface, new VecI(0, 0), new Paint());
+
+        return surfaceFactory.Serialize(surface);
+    }
+
+    public override bool TryDeserialize(object serialized, out ChunkyImage original)
+    {
+        if (serialized is byte[] imgBytes)
+        {
+            surfaceFactory.Config = Config;
+            if (!surfaceFactory.TryDeserialize(imgBytes, out Surface surface))
+            {
+                original = null;
+                return false;
+            }
+
+            original = new ChunkyImage(surface.Size);
+            original.EnqueueDrawImage(VecI.Zero, surface);
+            original.CommitChanges();
+            return true;
+        }
+
+        original = null;
+        return false;
+    }
+
+    public override string DeserializationId { get; } = "PixiEditor.ChunkyImage";
+}

+ 39 - 0
src/PixiEditor.AvaloniaUI/Models/Serialization/Factories/KernelSerializationFactory.cs

@@ -0,0 +1,39 @@
+using PixiEditor.Numerics;
+
+namespace PixiEditor.AvaloniaUI.Models.Serialization.Factories;
+
+public class KernelSerializationFactory : SerializationFactory<SerializableKernel, Kernel>
+{
+    public override SerializableKernel Serialize(Kernel original)
+    {
+        return new SerializableKernel
+        {
+            Width = original.Width,
+            Height = original.Height,
+            Values = original.AsSpan().ToArray()
+        };    
+    }
+
+    public override bool TryDeserialize(object raw, out Kernel original)
+    {
+        if (raw is not Dictionary<string, object> serialized)
+        {
+            original = null;
+            return false;
+        }
+        
+        if (serialized.ContainsKey("Width") && serialized.ContainsKey("Height") && serialized.ContainsKey("Values"))
+        {
+            int width = ExtractInt(serialized["Width"]);
+            int height = ExtractInt(serialized["Height"]);
+            float[] values = ExtractArray<float>(serialized["Values"]);
+            original = new Kernel(width, height, values);
+            return true;
+        }
+
+        original = null;
+        return false; 
+    }
+
+    public override string DeserializationId { get; } = "PixiEditor.Kernel";
+}

+ 68 - 0
src/PixiEditor.AvaloniaUI/Models/Serialization/Factories/SerializationFactory.cs

@@ -0,0 +1,68 @@
+using MessagePack;
+
+namespace PixiEditor.AvaloniaUI.Models.Serialization.Factories;
+
+public abstract class SerializationFactory
+{
+    public abstract Type OriginalType { get; }
+    public abstract string DeserializationId { get; } 
+    public SerializationConfig Config { get; set; }
+
+    public abstract object Serialize(object original);
+    public abstract object Deserialize(object rawData);
+    
+    protected int ExtractInt(object value)
+    {
+        return value switch
+        {
+            byte b => b,
+            int i => i,
+            long l => (int)l,
+            _ => throw new InvalidOperationException("Value is not an integer.")
+        };
+    }
+
+    protected T[] ExtractArray<T>(object value)
+    {
+        return value switch
+        {
+            T[] arr => arr,
+            object[] objArr => objArr.Cast<T>().ToArray(),
+            _ => throw new InvalidOperationException("Value is not an array.")
+        };
+    }
+}
+
+public abstract class SerializationFactory<TSerializable, TOriginal> : SerializationFactory
+{
+    public abstract TSerializable Serialize(TOriginal original);
+    public abstract bool TryDeserialize(object serialized, out TOriginal original);
+    
+    public override object Serialize(object original)
+    {
+        SerializedObject serialized = new SerializedObject
+        {
+            SerializationId = DeserializationId,
+            Data = Serialize((TOriginal)original)
+        };
+
+        return serialized;
+    }
+    
+    public override object Deserialize(object rawData)
+    {
+        return TryDeserialize(rawData, out TOriginal original) ? original : default;
+    }
+    
+    public override Type OriginalType => typeof(TOriginal);
+}
+
+[MessagePackObject]
+class SerializedObject
+{
+    [Key(0)]
+    public string SerializationId { get; set; }
+    
+    [Key(1)]
+    public object Data { get; set; }
+}

+ 46 - 0
src/PixiEditor.AvaloniaUI/Models/Serialization/Factories/SurfaceSerializationFactory.cs

@@ -0,0 +1,46 @@
+using PixiEditor.AvaloniaUI.Helpers;
+using PixiEditor.DrawingApi.Core;
+using PixiEditor.DrawingApi.Core.Surfaces.ImageData;
+using PixiEditor.DrawingApi.Skia;
+using PixiEditor.Parser;
+using PixiEditor.Parser.Skia;
+
+namespace PixiEditor.AvaloniaUI.Models.Serialization.Factories;
+
+public class SurfaceSerializationFactory : SerializationFactory<byte[], Surface>
+{
+    public override byte[] Serialize(Surface original)
+    {
+        var encoder = Config.Encoder;
+        byte[] result = encoder.Encode(original.ToByteArray(), original.Size.X, original.Size.Y);
+
+        return result;
+    }
+
+    public override bool TryDeserialize(object serialized, out Surface original)
+    {
+        if (serialized is byte[] imgBytes)
+        {
+            original = DecodeSurface(imgBytes, Config.Encoder);
+            return true;
+        }
+        
+        original = null;
+        return false;
+    }
+
+
+    public static Surface DecodeSurface(byte[] imgBytes, ImageEncoder encoder)
+    {
+        byte[] decoded =
+            encoder.Decode(imgBytes, out SKImageInfo info);
+        using Image img = Image.FromPixels(info.ToImageInfo(), decoded);
+        Surface surface = new Surface(img.Size);
+        surface.DrawingSurface.Canvas.DrawImage(img, 0, 0);
+
+        return surface;
+    }
+
+
+    public override string DeserializationId { get; } = "PixiEditor.Surface";
+}

+ 16 - 0
src/PixiEditor.AvaloniaUI/Models/Serialization/SerializableKernel.cs

@@ -0,0 +1,16 @@
+using MessagePack;
+
+namespace PixiEditor.AvaloniaUI.Models.Serialization;
+
+[MessagePackObject]
+public class SerializableKernel
+{
+    [Key("Width")]
+    public int Width { get; set; }
+    
+    [Key("Height")]
+    public int Height { get; set; }
+    
+    [Key("Values")]
+    public float[] Values { get; set; }
+}

+ 13 - 0
src/PixiEditor.AvaloniaUI/Models/Serialization/SerializationConfig.cs

@@ -0,0 +1,13 @@
+using PixiEditor.Parser.Skia;
+
+namespace PixiEditor.AvaloniaUI.Models.Serialization;
+
+public class SerializationConfig
+{
+    public ImageEncoder Encoder { get; set; }
+    
+    public SerializationConfig(ImageEncoder encoder)
+    {
+        Encoder = encoder;
+    }
+}

+ 9 - 9
src/PixiEditor.AvaloniaUI/Models/UserData/RecentlyOpenedDocument.cs

@@ -4,6 +4,7 @@ using System.Linq;
 using ChunkyImageLib;
 using CommunityToolkit.Mvvm.ComponentModel;
 using PixiEditor.AvaloniaUI.Helpers;
+using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Extensions.Exceptions;
 using PixiEditor.Numerics;
@@ -88,8 +89,6 @@ internal class RecentlyOpenedDocument : ObservableObject
 
         if (FileExtension == ".pixi")
         {
-            SerializableDocument serializableDocument;
-
             try
             {
                 var document = PixiParser.Deserialize(filePath);
@@ -106,7 +105,14 @@ internal class RecentlyOpenedDocument : ObservableObject
 
                 try
                 {
-                    serializableDocument = DepractedPixiParser.Deserialize(filePath);
+                    var deprecatedDocument = DeprecatedPixiParser.Deserialize(filePath);
+                    
+                    if (deprecatedDocument.PreviewImage == null || deprecatedDocument.PreviewImage.Length == 0)
+                    {
+                        return null;
+                    }
+                    
+                    return Surface.Load(deprecatedDocument.PreviewImage);
                 }
                 catch
                 {
@@ -115,12 +121,6 @@ internal class RecentlyOpenedDocument : ObservableObject
                 }
             }
 
-            Surface surface = Surface.Combine(serializableDocument.Width, serializableDocument.Height,
-                serializableDocument.Layers
-                    .Where(x => x.Opacity > 0.8)
-                    .Select(x => (x.ToImage(), new VecI(x.OffsetX, x.OffsetY))).ToList());
-
-            return DownscaleToMaxSize(surface);
         }
 
         if (SupportedFilesHelper.IsExtensionSupported(FileExtension))

+ 2 - 6
src/PixiEditor.AvaloniaUI/PixiEditor.AvaloniaUI.csproj

@@ -72,13 +72,13 @@
     <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2"/>
     <PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
     <PackageReference Include="PixiEditor.ColorPicker.Models" Version="1.0.5"/>
-    <PackageReference Include="PixiEditor.Parser" Version="3.4.0"/>
-    <PackageReference Include="PixiEditor.Parser.Skia" Version="3.0.1"/>
     <PackageReference Include="PixiEditor.ColorPicker.AvaloniaUI" Version="1.0.5"/>
   </ItemGroup>
 
   <ItemGroup>
     <ProjectReference Include="..\..\..\PixiDocks\src\PixiDocks.Avalonia\PixiDocks.Avalonia.csproj"/>
+    <ProjectReference Include="..\..\..\PixiParser\src\PixiParser.Skia\PixiParser.Skia.csproj" />
+    <ProjectReference Include="..\..\..\PixiParser\src\PixiParser\PixiParser.csproj" />
     <ProjectReference Include="..\ChunkyImageLib\ChunkyImageLib.csproj"/>
     <ProjectReference Include="..\PixiEditor.AnimationRenderer.Core\PixiEditor.AnimationRenderer.Core.csproj" />
     <ProjectReference Include="..\PixiEditor.AnimationRenderer.FFmpeg\PixiEditor.AnimationRenderer.FFmpeg.csproj" />
@@ -89,11 +89,7 @@
     <ProjectReference Include="..\PixiEditor.Extensions.Runtime\PixiEditor.Extensions.Runtime.csproj"/>
     <ProjectReference Include="..\PixiEditor.Extensions.WasmRuntime\PixiEditor.Extensions.WasmRuntime.csproj"/>
     <ProjectReference Include="..\PixiEditor.Extensions\PixiEditor.Extensions.csproj"/>
-    <ProjectReference Include="..\PixiEditor.OperatingSystem\PixiEditor.OperatingSystem.csproj"/>
-    <ProjectReference Include="..\PixiEditor.Platform.MSStore\PixiEditor.Platform.MSStore.csproj"/>
     <ProjectReference Include="..\PixiEditor.Platform.Standalone\PixiEditor.Platform.Standalone.csproj"/>
-    <ProjectReference Include="..\PixiEditor.Platform.Steam\PixiEditor.Platform.Steam.csproj"/>
-    <ProjectReference Include="..\PixiEditor.Platform\PixiEditor.Platform.csproj"/>
     <ProjectReference Include="..\PixiEditor.UI.Common\PixiEditor.UI.Common.csproj"/>
     <ProjectReference Include="..\PixiEditor.UpdateModule\PixiEditor.UpdateModule.csproj"/>
     <ProjectReference Include="..\PixiEditor.Zoombox\PixiEditor.Zoombox.csproj"/>

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

@@ -38,7 +38,7 @@
                                 <DataTemplate>
                                     <nodes:NodeView
                                         Node="{Binding}"
-                                        DisplayName="{Binding NodeName}"
+                                        DisplayName="{Binding NodeNameBindable}"
                                         Inputs="{Binding Inputs}"
                                         BorderBrush="{Binding InternalName, Converter={converters:NodeInternalNameToStyleConverter}, ConverterParameter='BorderBrush'}"
                                         BorderThickness="2"

+ 160 - 119
src/PixiEditor.AvaloniaUI/ViewModels/Document/DocumentViewModel.Serialization.cs

@@ -1,25 +1,31 @@
-using System.Collections.Generic;
+using System.Collections;
 using System.Drawing;
-using System.IO;
-using System.Linq;
 using ChunkyImageLib;
 using ChunkyImageLib.DataHolders;
+using Microsoft.Extensions.DependencyInjection;
 using PixiEditor.AvaloniaUI.Helpers;
-using PixiEditor.AvaloniaUI.Models.Handlers;
+using PixiEditor.AvaloniaUI.Helpers.Extensions;
+using PixiEditor.AvaloniaUI.Models.IO;
 using PixiEditor.AvaloniaUI.Models.IO.FileEncoders;
+using PixiEditor.AvaloniaUI.Models.Serialization;
+using PixiEditor.AvaloniaUI.Models.Serialization.Factories;
+using PixiEditor.ChangeableDocument.Changeables;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core.Bridge;
-using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
-using PixiEditor.DrawingApi.Core.Surface.ImageData;
+using PixiEditor.DrawingApi.Core.Surfaces;
+using PixiEditor.DrawingApi.Core.Surfaces.ImageData;
 using PixiEditor.Extensions.CommonApi.Palettes;
 using PixiEditor.Numerics;
 using PixiEditor.Parser;
 using PixiEditor.Parser.Collections;
-using BlendMode = PixiEditor.Parser.BlendMode;
+using PixiEditor.Parser.Graph;
+using PixiEditor.Parser.Skia;
+using PixiEditor.Parser.Skia.Encoders;
 using IKeyFrameChildrenContainer = PixiEditor.ChangeableDocument.Changeables.Interfaces.IKeyFrameChildrenContainer;
 using PixiDocument = PixiEditor.Parser.Document;
+using ReferenceLayer = PixiEditor.Parser.ReferenceLayer;
 
 namespace PixiEditor.AvaloniaUI.ViewModels.Document;
 
@@ -27,24 +33,141 @@ internal partial class DocumentViewModel
 {
     public PixiDocument ToSerializable()
     {
-        var root = new Folder();
-        
+        NodeGraph graph = new();
+        ImageEncoder encoder = new QoiEncoder();
         var doc = Internals.Tracker.Document;
+        
+        Dictionary<Guid, int> nodeIdMap = new();
+        Dictionary<Guid, int> keyFrameIdMap = new();
+
+        List<SerializationFactory> factories =
+            ViewModelMain.Current.Services.GetServices<SerializationFactory>().ToList(); // a bit ugly, sorry
 
-        //AddMembers(doc.StructureRoot.Children, doc, root);
+        AddNodes(doc.NodeGraph, graph, nodeIdMap, keyFrameIdMap, new SerializationConfig(encoder), factories);
 
         var document = new PixiDocument
         {
-            Width = Width, Height = Height,
-            Swatches = ToCollection(Swatches), Palette = ToCollection(Palette),
-            RootFolder = root, PreviewImage = (TryRenderWholeImage(0).Value as Surface)?.DrawingSurface.Snapshot().Encode().AsSpan().ToArray(),
+            Width = Width,
+            Height = Height,
+            Swatches = ToCollection(Swatches),
+            Palette = ToCollection(Palette),
+            Graph = graph,
+            PreviewImage =
+                (TryRenderWholeImage(0).Value as Surface)?.DrawingSurface.Snapshot().Encode().AsSpan().ToArray(),
             ReferenceLayer = GetReferenceLayer(doc),
-            AnimationData = ToAnimationData(doc.AnimationData)
+            AnimationData = ToAnimationData(doc.AnimationData, nodeIdMap, keyFrameIdMap),
+            ImageEncoderUsed = encoder.EncodedFormatName
         };
 
         return document;
     }
 
+    private static void AddNodes(IReadOnlyNodeGraph graph, NodeGraph targetGraph, 
+        Dictionary<Guid, int> nodeIdMap,
+        Dictionary<Guid, int> keyFrameIdMap,
+        SerializationConfig config, IReadOnlyList<SerializationFactory> allFactories)
+    {
+        targetGraph.AllNodes = new List<Node>();
+
+        int id = 0;
+        foreach (var node in graph.AllNodes)
+        {
+            nodeIdMap[node.Id] = id + 1;
+            id++;
+        }
+
+        foreach (var node in graph.AllNodes)
+        {
+            NodePropertyValue[] properties = new NodePropertyValue[node.InputProperties.Count];
+
+            for (int i = 0; i < node.InputProperties.Count(); i++)
+            {
+                properties[i] = new NodePropertyValue()
+                {
+                    PropertyName = node.InputProperties[i].InternalPropertyName,
+                    Value = SerializationUtil.SerializeObject(node.InputProperties[i].NonOverridenValue, config, allFactories)
+                };
+            }
+
+            Dictionary<string, object> additionalData = new();
+            node.SerializeAdditionalData(additionalData);
+
+            KeyFrameData[] keyFrames = new KeyFrameData[node.KeyFrames.Count];
+            
+            for (int i = 0; i < node.KeyFrames.Count; i++)
+            {
+                keyFrameIdMap[node.KeyFrames[i].KeyFrameGuid] = i + 1;
+                keyFrames[i] = new KeyFrameData
+                {
+                    Id = i + 1, 
+                    Data = SerializationUtil.SerializeObject(node.KeyFrames[i].Data, config, allFactories), 
+                    AffectedElement = node.KeyFrames[i].AffectedElement,
+                    StartFrame = node.KeyFrames[i].StartFrame, 
+                    Duration = node.KeyFrames[i].Duration,
+                    IsVisible = node.KeyFrames[i].IsVisible
+                };
+            }
+                
+            Dictionary<string, object> converted = ConvertToSerializable(additionalData, config, allFactories);
+
+            List<PropertyConnection> connections = new();
+
+            foreach (var inputProp in node.InputProperties)
+            {
+                if (inputProp.Connection != null)
+                {
+                    connections.Add(new PropertyConnection()
+                    {
+                        OutputNodeId = nodeIdMap[inputProp.Connection.Node.Id],
+                        OutputPropertyName = inputProp.Connection.InternalPropertyName,
+                        InputPropertyName = inputProp.InternalPropertyName
+                    });
+                }
+            }
+
+            Node parserNode = new Node()
+            {
+                Id = nodeIdMap[node.Id],
+                Name = node.DisplayName,
+                UniqueNodeName = node.GetNodeTypeUniqueName(),
+                Position = node.Position.ToVector2(),
+                InputPropertyValues = properties,
+                AdditionalData = converted,
+                KeyFrames = keyFrames,
+                InputConnections = connections.ToArray()
+            };
+
+            targetGraph.AllNodes.Add(parserNode);
+        }
+    }
+
+    private static Dictionary<string, object> ConvertToSerializable(
+        Dictionary<string, object> additionalData,
+        SerializationConfig config,
+        IReadOnlyList<SerializationFactory> allFactories)
+    {
+        Dictionary<string, object> converted = new();
+        foreach (var (key, value) in additionalData)
+        {
+            if (value is IEnumerable enumerable)
+            {
+                List<object> list = new();
+                foreach (var item in enumerable)
+                {
+                    list.Add(SerializationUtil.SerializeObject(item, config, allFactories));
+                }
+
+                converted[key] = list;
+            }
+            else
+            {
+                converted[key] = SerializationUtil.SerializeObject(value, config, allFactories);
+            }
+        }
+
+        return converted;
+    }
+
     private static ReferenceLayer? GetReferenceLayer(IReadOnlyDocument document)
     {
         if (document.ReferenceLayer == null)
@@ -55,13 +178,13 @@ internal partial class DocumentViewModel
         var layer = document.ReferenceLayer!;
 
         var surface = new Surface(new VecI(layer.ImageSize.X, layer.ImageSize.Y));
-        
+
         surface.DrawBytes(surface.Size, layer.ImageBgra8888Bytes.ToArray(), ColorType.Bgra8888, AlphaType.Premul);
 
         var encoder = new UniversalFileEncoder(EncodedImageFormat.Png);
 
         using var stream = new MemoryStream();
-        
+
         encoder.Save(stream, surface);
 
         stream.Position = 0;
@@ -75,9 +198,9 @@ internal partial class DocumentViewModel
             OffsetY = (float)layer.Shape.TopLeft.Y,
             Corners = new Corners
             {
-                TopLeft = layer.Shape.TopLeft.ToVector2(), 
-                TopRight = layer.Shape.TopRight.ToVector2(), 
-                BottomLeft = layer.Shape.BottomLeft.ToVector2(), 
+                TopLeft = layer.Shape.TopLeft.ToVector2(),
+                TopRight = layer.Shape.TopRight.ToVector2(),
+                BottomLeft = layer.Shape.BottomLeft.ToVector2(),
                 BottomRight = layer.Shape.BottomRight.ToVector2()
             },
             Opacity = 1,
@@ -85,129 +208,49 @@ internal partial class DocumentViewModel
         };
     }
 
-    private static void AddMembers(IEnumerable<IReadOnlyStructureNode> members, IReadOnlyDocument document, Folder parent)
-    {
-        foreach (var member in members)
-        {
-            if (member is IReadOnlyFolderNode readOnlyFolder)
-            {
-                var folder = ToSerializable(readOnlyFolder);
-
-                //AddMembers(readOnlyFolder.Children, document, folder);
-
-                parent.Children.Add(folder);
-            }
-            else if (member is IReadOnlyLayerNode readOnlyLayer)
-            {
-                parent.Children.Add(ToSerializable(readOnlyLayer, document));
-            }
-        }
-    }
-    
-    private static Folder ToSerializable(IReadOnlyFolderNode folder)
-    {
-        return new Folder
-        {
-            Name = folder.MemberName,
-            BlendMode = (BlendMode)(int)folder.BlendMode.Value,
-            Enabled = folder.IsVisible.Value,
-            Opacity = folder.Opacity.Value,
-            ClipToMemberBelow = folder.ClipToPreviousMember.Value,
-            Mask = GetMask(folder.Mask.Value, folder.MaskIsVisible.Value)
-        };
-    }
-    
-    private static ImageLayer ToSerializable(IReadOnlyLayerNode layer, IReadOnlyDocument document)
-    {
-        var result = document.GetLayerRasterizedImage(layer.Id, 0);
-
-        var tightBounds = document.GetChunkAlignedLayerBounds(layer.Id, 0);
-        using var data = result?.Encode();
-        byte[] bytes = data?.AsSpan().ToArray();
-        var serializable = new ImageLayer
-        {
-            Width = result?.Width ?? 0, Height = result?.Height ?? 0, OffsetX = tightBounds?.X ?? 0, OffsetY = tightBounds?.Y ?? 0,
-            Enabled = layer.IsVisible.Value, BlendMode = (BlendMode)(int)layer.BlendMode.Value, ImageBytes = bytes,
-            ClipToMemberBelow = layer.ClipToPreviousMember.Value, Name = layer.MemberName,
-            Guid = layer.Id,
-            LockAlpha = layer is ITransparencyLockable { LockTransparency: true },
-            Opacity = layer.Opacity.Value, Mask = GetMask(layer.Mask.Value, layer.MaskIsVisible.Value)
-        };
-
-        return serializable;
-    }
-
-    private static Mask GetMask(IReadOnlyChunkyImage mask, bool maskVisible)
-    {
-        if (mask == null) 
-            return null;
-        
-        var maskBound = mask.FindChunkAlignedMostUpToDateBounds();
-
-        if (maskBound == null)
-        {
-            return new Mask();
-        }
-        
-        var surface = DrawingBackendApi.Current.SurfaceImplementation.Create(new ImageInfo(
-            maskBound.Value.Width,
-            maskBound.Value.Height));
-                
-        mask.DrawMostUpToDateRegionOn(new RectI(0, 0, maskBound.Value.Width, maskBound.Value.Height), ChunkResolution.Full, surface, new VecI(0, 0));
-
-        return new Mask
-        {
-            Width = maskBound.Value.Width, Height = maskBound.Value.Height,
-            OffsetX = maskBound.Value.X, OffsetY = maskBound.Value.Y,
-            Enabled = maskVisible, ImageBytes = surface.Snapshot().Encode().AsSpan().ToArray()
-        };
-    }
-
     private ColorCollection ToCollection(IList<PaletteColor> collection) =>
         new(collection.Select(x => Color.FromArgb(255, x.R, x.G, x.B)));
 
-    private AnimationData ToAnimationData(IReadOnlyAnimationData animationData)
+    private AnimationData ToAnimationData(IReadOnlyAnimationData animationData, Dictionary<Guid, int> nodeIdMap, Dictionary<Guid, int> keyFrameIds)
     {
         var animData = new AnimationData();
         animData.KeyFrameGroups = new List<KeyFrameGroup>();
-        BuildKeyFrames(animationData.KeyFrames, animData);
-        
+        BuildKeyFrames(animationData.KeyFrames, animData, nodeIdMap, keyFrameIds);
+
         return animData;
     }
 
-    private static void BuildKeyFrames(IReadOnlyList<IReadOnlyKeyFrame> root, AnimationData animationData)
+    private static void BuildKeyFrames(IReadOnlyList<IReadOnlyKeyFrame> root, AnimationData animationData,
+        Dictionary<Guid, int> nodeIdMap, Dictionary<Guid, int> keyFrameIds)
     {
         foreach (var keyFrame in root)
         {
-            if(keyFrame is IKeyFrameChildrenContainer container)
+            if (keyFrame is IKeyFrameChildrenContainer container)
             {
                 KeyFrameGroup group = new();
-                group.LayerGuid = keyFrame.LayerGuid;
+                group.NodeId = nodeIdMap[keyFrame.NodeId];
                 group.Enabled = keyFrame.IsVisible;
-                
+
                 foreach (var child in container.Children)
                 {
-                    if (child is IKeyFrameChildrenContainer groupKeyFrame)
+                    if (child is IReadOnlyRasterKeyFrame rasterKeyFrame)
                     {
-                        BuildKeyFrames(groupKeyFrame.Children, null);
-                    }
-                    else if (child is IReadOnlyRasterKeyFrame rasterKeyFrame)
-                    {
-                        BuildRasterKeyFrame(rasterKeyFrame, group);
+                        BuildRasterKeyFrame(rasterKeyFrame, group, nodeIdMap, keyFrameIds);
                     }
                 }
-                
+
                 animationData?.KeyFrameGroups.Add(group);
             }
         }
     }
 
-    private static void BuildRasterKeyFrame(IReadOnlyRasterKeyFrame rasterKeyFrame, KeyFrameGroup group)
+    private static void BuildRasterKeyFrame(IReadOnlyRasterKeyFrame rasterKeyFrame, KeyFrameGroup group,
+        Dictionary<Guid, int> idMap, Dictionary<Guid, int> keyFrameIds)
     {
         var bounds = rasterKeyFrame.Image.FindChunkAlignedMostUpToDateBounds();
 
         DrawingSurface surface = null;
-                        
+
         if (bounds != null)
         {
             surface = DrawingBackendApi.Current.SurfaceImplementation.Create(
@@ -218,12 +261,10 @@ internal partial class DocumentViewModel
                 new VecI(0, 0));
         }
 
-        group.Children.Add(new RasterKeyFrame()
+        group.Children.Add(new ElementKeyFrame()
         {
-            LayerGuid = rasterKeyFrame.LayerGuid,
-            StartFrame = rasterKeyFrame.StartFrame,
-            Duration = rasterKeyFrame.Duration,
-            ImageBytes = surface?.Snapshot().Encode().AsSpan().ToArray(),
+            NodeId = idMap[rasterKeyFrame.NodeId],
+            KeyFrameId = keyFrameIds[rasterKeyFrame.Id],
         });
     }
 }

+ 112 - 36
src/PixiEditor.AvaloniaUI/ViewModels/Document/DocumentViewModel.cs

@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System.Collections;
+using System.Collections.Generic;
 using System.Collections.Immutable;
 using System.Collections.ObjectModel;
 using System.IO;
@@ -9,6 +10,7 @@ using ChunkyImageLib;
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.Operations;
 using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.Extensions.DependencyInjection;
 using PixiEditor.AvaloniaUI.Helpers;
 using PixiEditor.AvaloniaUI.Helpers.Collections;
 using PixiEditor.AvaloniaUI.Helpers.Extensions;
@@ -18,6 +20,8 @@ using PixiEditor.AvaloniaUI.Models.DocumentModels.Public;
 using PixiEditor.AvaloniaUI.Models.DocumentPassthroughActions;
 using PixiEditor.AvaloniaUI.Models.Handlers;
 using PixiEditor.AvaloniaUI.Models.Position;
+using PixiEditor.AvaloniaUI.Models.Serialization;
+using PixiEditor.AvaloniaUI.Models.Serialization.Factories;
 using PixiEditor.AvaloniaUI.Models.Structures;
 using PixiEditor.AvaloniaUI.Models.Tools;
 using PixiEditor.AvaloniaUI.ViewModels.Document.TransformOverlays;
@@ -31,17 +35,21 @@ using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.ChangeInfos;
+using PixiEditor.ChangeableDocument.Changes.NodeGraph;
 using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.ChangeableDocument.Rendering;
+using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
-using PixiEditor.DrawingApi.Core.Surface.ImageData;
-using PixiEditor.DrawingApi.Core.Surface.Vector;
+using PixiEditor.DrawingApi.Core.Surfaces.ImageData;
+using PixiEditor.DrawingApi.Core.Surfaces.Vector;
 using PixiEditor.Extensions.Common.Localization;
 using PixiEditor.Extensions.CommonApi.Palettes;
 using PixiEditor.Numerics;
+using PixiEditor.Parser;
+using PixiEditor.Parser.Skia;
 using Color = PixiEditor.DrawingApi.Core.ColorsImpl.Color;
 using Colors = PixiEditor.DrawingApi.Core.ColorsImpl.Colors;
+using Node = PixiEditor.Parser.Graph.Node;
 using Point = Avalonia.Point;
 
 namespace PixiEditor.AvaloniaUI.ViewModels.Document;
@@ -246,6 +254,9 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
         var builderInstance = new DocumentViewModelBuilder();
         builder(builderInstance);
 
+        Dictionary<int, Guid> mappedNodeIds = new();
+        Dictionary<int, Guid> mappedKeyFrameIds = new();
+
         var viewModel = new DocumentViewModel();
         viewModel.Operations.ResizeCanvas(new VecI(builderInstance.Width, builderInstance.Height), ResizeAnchor.Center);
 
@@ -271,19 +282,96 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
         viewModel.Swatches = new ObservableCollection<PaletteColor>(builderInstance.Swatches);
         viewModel.Palette = new ObservableRangeCollection<PaletteColor>(builderInstance.Palette);
 
-        Guid outputNodeGuid = Guid.NewGuid();
+        SerializationConfig config =
+            new SerializationConfig(BuiltInEncoders.Encoders[builderInstance.ImageEncoderUsed]);
+
+        List<SerializationFactory> allFactories =
+            ViewModelMain.Current.Services.GetServices<SerializationFactory>().ToList();
 
-        acc.AddActions(new CreateNode_Action(typeof(OutputNode), outputNodeGuid));
+        AddNodes(builderInstance.Graph);
 
-        AddMembers(outputNodeGuid, builderInstance.Children);
-        AddAnimationData(builderInstance.AnimationData);
+        if (builderInstance.Graph.AllNodes.Count == 0)
+        {
+            Guid outputNodeGuid = Guid.NewGuid();
+            acc.AddActions(new CreateNode_Action(typeof(OutputNode), outputNodeGuid));
+        }
+
+        AddAnimationData(builderInstance.AnimationData, mappedNodeIds, mappedKeyFrameIds);
 
         acc.AddFinishedActions(new DeleteRecordedChanges_Action());
         viewModel.MarkAsSaved();
 
         return viewModel;
 
-        void AddMember(Guid parentGuid, DocumentViewModelBuilder.StructureMemberBuilder member)
+
+        void AddNodes(NodeGraphBuilder graph)
+        {
+            foreach (var node in graph.AllNodes)
+            {
+                AddNode(node.Id, node);
+            }
+
+            foreach (var node in graph.AllNodes)
+            {
+                Guid nodeGuid = mappedNodeIds[node.Id];
+                if (node.InputConnections != null)
+                {
+                    foreach (var connection in node.InputConnections)
+                    {
+                        if (mappedNodeIds.TryGetValue(connection.Key, out Guid outputNodeId))
+                        {
+                            acc.AddActions(new ConnectProperties_Action(nodeGuid, outputNodeId,
+                                connection.Value.inputPropName, connection.Value.outputPropName));
+                        }
+                    }
+                }
+            }
+        }
+
+        void AddNode(int id, NodeGraphBuilder.NodeBuilder serializedNode)
+        {
+            Guid guid = Guid.NewGuid();
+            mappedNodeIds.Add(id, guid);
+            acc.AddActions(new CreateNodeFromName_Action(serializedNode.UniqueNodeName, guid));
+
+            if (serializedNode.InputValues != null)
+            {
+                foreach (var propertyValue in serializedNode.InputValues)
+                {
+                    object value = SerializationUtil.Deserialize(propertyValue.Value, config, allFactories);
+                    acc.AddActions(new UpdatePropertyValue_Action(guid, propertyValue.Key, value));
+                }
+            }
+
+            if (serializedNode.KeyFrames != null)
+            {
+                foreach (var keyFrame in serializedNode.KeyFrames)
+                {
+                    Guid keyFrameGuid = Guid.NewGuid();
+                    mappedKeyFrameIds.Add(keyFrame.Id, keyFrameGuid);
+                    acc.AddActions(
+                        new SetKeyFrameData_Action(
+                            guid,
+                            keyFrameGuid,
+                            SerializationUtil.Deserialize(keyFrame.Data, config, allFactories),
+                            keyFrame.StartFrame,
+                            keyFrame.Duration, keyFrame.AffectedElement, keyFrame.IsVisible));
+                }
+            }
+
+            if (serializedNode.AdditionalData != null && serializedNode.AdditionalData.Count > 0)
+            {
+                acc.AddActions(new DeserializeNodeAdditionalData_Action(guid,
+                    SerializationUtil.DeserializeDict(serializedNode.AdditionalData, config, allFactories)));
+            }
+
+            if (!string.IsNullOrEmpty(serializedNode.Name))
+            {
+                acc.AddActions(new SetNodeName_Action(guid, serializedNode.Name));
+            }
+        }
+
+        /*void AddMember(Guid parentGuid, DocumentViewModelBuilder.StructureMemberBuilder member)
         {
             acc.AddActions(
                 new CreateStructureMember_Action(parentGuid, member.Id,
@@ -333,18 +421,18 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
             {
                 AddMembers(member.Id, folder.Children);
             }
-        }
+        }*/
 
-        void PasteImage(Guid guid, DocumentViewModelBuilder.SurfaceBuilder surface, int width, int height, int offsetX,
+        /*void PasteImage(Guid guid, DocumentViewModelBuilder.SurfaceBuilder surface, int width, int height, int offsetX,
             int offsetY, bool onMask, int frame, Guid? keyFrameGuid = default)
         {
             acc.AddActions(
                 new PasteImage_Action(surface.Surface, new(new RectD(new VecD(offsetX, offsetY), new(width, height))),
                     guid, true, onMask, frame, keyFrameGuid ?? default),
                 new EndPasteImage_Action());
-        }
+        }*/
 
-        void AddMembers(Guid parentGuid, IEnumerable<DocumentViewModelBuilder.StructureMemberBuilder> builders)
+        /*void AddMembers(Guid parentGuid, IEnumerable<DocumentViewModelBuilder.StructureMemberBuilder> builders)
         {
             foreach (var child in builders.Reverse())
             {
@@ -355,39 +443,27 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
 
                 AddMember(parentGuid, child);
             }
-        }
+        }*/
 
-        void AddAnimationData(List<KeyFrameBuilder> data)
+        void AddAnimationData(List<KeyFrameBuilder> data, Dictionary<int, Guid> mappedIds,
+            Dictionary<int, Guid> mappedKeyFrameIds)
         {
             foreach (var keyFrame in data)
             {
-                if (keyFrame is RasterKeyFrameBuilder rasterKeyFrameBuilder)
+                if (keyFrame is GroupKeyFrameBuilder groupKeyFrameBuilder)
+                {
+                    AddAnimationData(groupKeyFrameBuilder.Children, mappedIds, mappedKeyFrameIds);
+                }
+                else
                 {
-                    if (rasterKeyFrameBuilder.Id == default)
-                    {
-                        rasterKeyFrameBuilder.Id = Guid.NewGuid();
-                    }
-
                     acc.AddActions(
                         new CreateRasterKeyFrame_Action(
-                            rasterKeyFrameBuilder.LayerGuid,
-                            rasterKeyFrameBuilder.Id,
-                            rasterKeyFrameBuilder.StartFrame, -1, default),
-                        new KeyFrameLength_Action(rasterKeyFrameBuilder.Id, rasterKeyFrameBuilder.StartFrame,
-                            rasterKeyFrameBuilder.Duration),
-                        new EndKeyFrameLength_Action());
-
-                    PasteImage(rasterKeyFrameBuilder.LayerGuid, rasterKeyFrameBuilder.Surface,
-                        rasterKeyFrameBuilder.Surface.Surface.Size.X,
-                        rasterKeyFrameBuilder.Surface.Surface.Size.Y, 0, 0, false, rasterKeyFrameBuilder.StartFrame,
-                        rasterKeyFrameBuilder.Id);
+                            mappedIds[keyFrame.NodeId],
+                            mappedKeyFrameIds[keyFrame.KeyFrameId],
+                            -1, -1, default));
 
                     acc.AddFinishedActions();
                 }
-                else if (keyFrame is GroupKeyFrameBuilder groupKeyFrameBuilder)
-                {
-                    AddAnimationData(groupKeyFrameBuilder.Children);
-                }
             }
         }
     }

+ 2 - 2
src/PixiEditor.AvaloniaUI/ViewModels/Document/KeyFrameGroupViewModel.cs

@@ -12,7 +12,7 @@ internal class KeyFrameGroupViewModel : KeyFrameViewModel, IKeyFrameGroupHandler
     public override int StartFrameBindable => Children.Count > 0 ? Children.Min(x => x.StartFrameBindable) : 0;
     public override int DurationBindable => Children.Count > 0 ? Children.Max(x => x.StartFrameBindable + x.DurationBindable) - StartFrameBindable : 0;
 
-    public string LayerName => Document.StructureHelper.Find(LayerGuid).NameBindable;
+    public string LayerName => Document.StructureHelper.Find(LayerGuid).NodeNameBindable;
 
     public override void SetVisibility(bool isVisible)
     {
@@ -32,7 +32,7 @@ internal class KeyFrameGroupViewModel : KeyFrameViewModel, IKeyFrameGroupHandler
     {
         Document.StructureHelper.Find(LayerGuid).PropertyChanged += (sender, args) =>
         {
-            if (args.PropertyName == nameof(StructureMemberViewModel.NameBindable))
+            if (args.PropertyName == nameof(StructureMemberViewModel.NodeNameBindable))
             {
                 OnPropertyChanged(nameof(LayerName));
             }

+ 1 - 0
src/PixiEditor.AvaloniaUI/ViewModels/Document/KeyFrameViewModel.cs

@@ -3,6 +3,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
 using PixiEditor.AvaloniaUI.Models.DocumentModels;
 using PixiEditor.AvaloniaUI.Models.Handlers;
 using PixiEditor.ChangeableDocument.Actions.Generated;
+using PixiEditor.DrawingApi.Core;
 
 namespace PixiEditor.AvaloniaUI.ViewModels.Document;
 

+ 2 - 2
src/PixiEditor.AvaloniaUI/ViewModels/Document/NodeGraphViewModel.cs

@@ -34,9 +34,9 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler
 
     public void AddNode(INodeHandler node)
     {
-        if (OutputNode == null)
+        if (OutputNode == null && node.InternalName == typeof(OutputNode).GetCustomAttribute<NodeInfoAttribute>().UniqueName)
         {
-            OutputNode = node; // TODO: this is not really correct yet, a way to check what node type is added is needed
+            OutputNode = node; 
         }
 
         AllNodes.Add(node);

+ 2 - 2
src/PixiEditor.AvaloniaUI/ViewModels/Document/ReferenceLayerViewModel.cs

@@ -11,9 +11,9 @@ using PixiEditor.AvaloniaUI.Models.DocumentModels;
 using PixiEditor.AvaloniaUI.Models.Handlers;
 using PixiEditor.AvaloniaUI.ViewModels.Tools.Tools;
 using PixiEditor.ChangeableDocument.Actions.Generated;
+using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
-using PixiEditor.DrawingApi.Core.Surface.ImageData;
+using PixiEditor.DrawingApi.Core.Surfaces.ImageData;
 using PixiEditor.Numerics;
 
 namespace PixiEditor.AvaloniaUI.ViewModels.Document;

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

@@ -9,8 +9,8 @@ using PixiEditor.AvaloniaUI.Models.Layers;
 using PixiEditor.AvaloniaUI.ViewModels.Nodes;
 using PixiEditor.AvaloniaUI.Views.Nodes;
 using PixiEditor.ChangeableDocument.Actions.Generated;
+using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
 using PixiEditor.Numerics;
 using BlendMode = PixiEditor.ChangeableDocument.Enums.BlendMode;
 
@@ -18,30 +18,11 @@ namespace PixiEditor.AvaloniaUI.ViewModels.Document;
 #nullable enable
 internal abstract class StructureMemberViewModel : NodeViewModel, IStructureMemberHandler
 {
-    private string name = "";
-
     public StructureMemberViewModel()
     {
         
     }
 
-    public virtual void SetName(string name)
-    {
-        this.name = name;
-        OnPropertyChanged(nameof(NameBindable));
-        NodeName = NameBindable;
-    }
-
-    public string NameBindable
-    {
-        get => name;
-        set
-        {
-            if (!Document.UpdateableChangeActive)
-                Internals.ActionAccumulator.AddFinishedActions(new StructureMemberName_Action(Id, value));
-        }
-    }
-
     private bool isVisible;
 
     public void SetIsVisible(bool isVisible)

+ 20 - 6
src/PixiEditor.AvaloniaUI/ViewModels/Nodes/NodeViewModel.cs

@@ -9,12 +9,13 @@ using PixiEditor.AvaloniaUI.ViewModels.Document;
 using PixiEditor.ChangeableDocument.Actions.Generated;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using PixiEditor.DrawingApi.Core;
 using PixiEditor.Numerics;
 
 namespace PixiEditor.AvaloniaUI.ViewModels.Nodes;
 internal class NodeViewModel : ObservableObject, INodeHandler
 {
-    private string nodeName;
+    private string nodeNameBindable;
     private VecD position;
     private ObservableRangeCollection<INodePropertyHandler> inputs = new();
     private ObservableRangeCollection<INodePropertyHandler> outputs = new();
@@ -29,10 +30,17 @@ internal class NodeViewModel : ObservableObject, INodeHandler
         init => id = value;
     }
 
-    public string NodeName
+    public string NodeNameBindable
     {
-        get => nodeName;
-        set => SetProperty(ref nodeName, value);
+        get => nodeNameBindable;
+        set
+        {
+            if (!Document.UpdateableChangeActive)
+            {
+                Internals.ActionAccumulator.AddFinishedActions(
+                    new SetNodeName_Action(Id, value));
+            }
+        } 
     }
 
     public string InternalName { get; init; }
@@ -84,9 +92,9 @@ internal class NodeViewModel : ObservableObject, INodeHandler
         
     }
 
-    public NodeViewModel(string nodeName, Guid id, VecD position, DocumentViewModel document, DocumentInternalParts internals)
+    public NodeViewModel(string nodeNameBindable, Guid id, VecD position, DocumentViewModel document, DocumentInternalParts internals)
     {
-        this.nodeName = nodeName;
+        this.nodeNameBindable = nodeNameBindable;
         this.id = id;
         this.position = position;
         Document = document;
@@ -98,6 +106,12 @@ internal class NodeViewModel : ObservableObject, INodeHandler
         position = newPosition;
         OnPropertyChanged(nameof(PositionBindable));
     }
+    
+    public void SetName(string newName)
+    {
+        nodeNameBindable = newName;
+        OnPropertyChanged(nameof(NodeNameBindable));
+    }
 
     public void TraverseBackwards(Func<INodeHandler, bool> func)
     {

+ 0 - 2
src/PixiEditor.AvaloniaUI/ViewModels/SubViewModels/AnimationsViewModel.cs

@@ -5,8 +5,6 @@ using PixiEditor.AvaloniaUI.Models.Commands.Attributes.Commands;
 using PixiEditor.AvaloniaUI.Models.IO;
 using PixiEditor.AvaloniaUI.ViewModels.Dock;
 using PixiEditor.AvaloniaUI.ViewModels.Document;
-using PixiEditor.DrawingApi.Core.Surface;
-using PixiEditor.DrawingApi.Core.Surface.ImageData;
 using PixiEditor.Numerics;
 
 namespace PixiEditor.AvaloniaUI.ViewModels.SubViewModels;

+ 26 - 12
src/PixiEditor.AvaloniaUI/ViewModels/SubViewModels/FileViewModel.cs

@@ -1,6 +1,7 @@
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
+using System.Reflection;
 using System.Threading.Tasks;
 using Avalonia;
 using Avalonia.Controls;
@@ -19,6 +20,9 @@ using PixiEditor.AvaloniaUI.ViewModels.Document;
 using PixiEditor.AvaloniaUI.Views;
 using PixiEditor.AvaloniaUI.Views.Dialogs;
 using PixiEditor.AvaloniaUI.Views.Windows;
+using PixiEditor.ChangeableDocument.Changeables.Graph;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Extensions.Common.Localization;
 using PixiEditor.Extensions.CommonApi.UserPreferences.Settings.PixiEditor;
@@ -26,6 +30,7 @@ using PixiEditor.Extensions.Exceptions;
 using PixiEditor.Numerics;
 using PixiEditor.OperatingSystem;
 using PixiEditor.Parser;
+using PixiEditor.Parser.Graph;
 using PixiEditor.UI.Common.Fonts;
 
 namespace PixiEditor.AvaloniaUI.ViewModels.SubViewModels;
@@ -243,10 +248,12 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
 
         var doc = NewDocument(b => b
             .WithSize(image.Size)
-            .WithLayer(l => l
-                .WithName("Image")
-                .WithSize(image.Size)
-                .WithSurface(image)));
+            .WithGraph(x => x
+                .WithImageLayerNode(
+                    new LocalizedString("PASTED_IMAGE_NAME"),
+                    image, out int id)
+                .WithOutputNode(id, "Output")
+            ));
 
         if (associatePath)
         {
@@ -256,6 +263,7 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
         AddRecentlyOpened(path);
     }
 
+
     /// <summary>
     /// Opens a regular image file from path, creates a document from it, and adds it to the system.
     /// </summary>
@@ -263,10 +271,12 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
     {
         DocumentViewModel doc = NewDocument(b => b
             .WithSize(surface.Size)
-            .WithLayer(l => l
-                .WithName("Image")
-                .WithSize(surface.Size)
-                .WithSurface(surface)));
+            .WithGraph(x => x
+                .WithImageLayerNode(
+                    new LocalizedString("PASTED_IMAGE_NAME"),
+                    surface, out int id)
+                .WithOutputNode(id, "Output")
+            ));
 
         if (path == null)
         {
@@ -289,9 +299,12 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
         {
             NewDocument(b => b
                 .WithSize(newFile.Width, newFile.Height)
-                .WithLayer(l => l
-                    .WithName(new LocalizedString("BASE_LAYER_NAME"))
-                    .WithSurface(new Surface(new VecI(newFile.Width, newFile.Height)))));
+                .WithGraph(x => x
+                    .WithImageLayerNode(
+                        new LocalizedString("BASE_LAYER_NAME"),
+                        new Surface(new VecI(newFile.Width, newFile.Height)), out int id)
+                    .WithOutputNode(id, "Output")
+                ));
         }
     }
 
@@ -378,7 +391,8 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
             };
             if (await info.ShowDialog())
             {
-                var result = await Exporter.TrySaveUsingDataFromDialog(doc, info.FilePath, info.ChosenFormat, info.ExportConfig);
+                var result =
+                    await Exporter.TrySaveUsingDataFromDialog(doc, info.FilePath, info.ChosenFormat, info.ExportConfig);
                 if (result.result == SaveResult.Success)
                     IOperatingSystem.Current.OpenFolder(result.finalPath);
                 else

+ 1 - 1
src/PixiEditor.AvaloniaUI/ViewModels/Tools/Tools/MoveToolViewModel.cs

@@ -7,7 +7,7 @@ using PixiEditor.AvaloniaUI.Models.Handlers.Tools;
 using PixiEditor.AvaloniaUI.ViewModels.Tools.ToolSettings.Toolbars;
 using PixiEditor.AvaloniaUI.Views.Overlays.BrushShapeOverlay;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface.Vector;
+using PixiEditor.DrawingApi.Core.Surfaces.Vector;
 using PixiEditor.Extensions.Common.Localization;
 using PixiEditor.Numerics;
 using PixiEditor.UI.Common.Fonts;

+ 1 - 1
src/PixiEditor.AvaloniaUI/ViewModels/ViewModelMain.cs

@@ -106,7 +106,7 @@ internal partial class ViewModelMain : ViewModelBase, ICommandsHandler
     public ActionDisplayList ActionDisplays { get; }
     public bool UserWantsToClose { get; private set; }
 
-    public ViewModelMain(IServiceProvider serviceProvider)
+    public ViewModelMain()
     {
         Current = this;
         ActionDisplays = new ActionDisplayList(() => OnPropertyChanged(nameof(ActiveActionDisplay)));

+ 2 - 1
src/PixiEditor.AvaloniaUI/Views/Dialogs/ExportFilePopup.axaml.cs

@@ -10,9 +10,10 @@ using PixiEditor.AvaloniaUI.Helpers;
 using PixiEditor.AvaloniaUI.Models.Files;
 using PixiEditor.AvaloniaUI.Models.IO;
 using PixiEditor.AvaloniaUI.ViewModels.Document;
+using PixiEditor.DrawingApi.Core;
 using PixiEditor.Extensions.Common.Localization;
 using PixiEditor.Numerics;
-using Image = PixiEditor.DrawingApi.Core.Surface.ImageData.Image;
+using Image = PixiEditor.DrawingApi.Core.Surfaces.ImageData.Image;
 
 namespace PixiEditor.AvaloniaUI.Views.Dialogs;
 

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

@@ -101,7 +101,7 @@
                                 x:Name="editableTextBlock"
                                 d:Text="New Folder" FontSize="14"
                                 VerticalAlignment="Center"
-                                Text="{Binding Folder.NameBindable, ElementName=folderControl, Mode=TwoWay}" />
+                                Text="{Binding Folder.NodeNameBindable, ElementName=folderControl, Mode=TwoWay}" />
                             
                             <StackPanel Orientation="Horizontal">
                                 <TextBlock d:Text="100" Foreground="White" FontSize="11">

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

@@ -112,7 +112,7 @@
                             x:Name="editableTextBlock"
                             VerticalAlignment="Center"
                             d:Text="New Layer" FontSize="14"
-                            Text="{Binding Layer.NameBindable, ElementName=uc, Mode=TwoWay}" />
+                            Text="{Binding Layer.NodeNameBindable, ElementName=uc, Mode=TwoWay}" />
 
                         <StackPanel Orientation="Horizontal">
                             <TextBlock d:Text="100" Foreground="White" FontSize="11">

+ 1 - 0
src/PixiEditor.AvaloniaUI/Views/Main/ViewportControls/FixedViewport.axaml.cs

@@ -10,6 +10,7 @@ using ChunkyImageLib.DataHolders;
 using PixiEditor.AvaloniaUI.Models.DocumentModels;
 using PixiEditor.AvaloniaUI.Models.Position;
 using PixiEditor.AvaloniaUI.ViewModels.Document;
+using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Numerics;
 

+ 1 - 0
src/PixiEditor.AvaloniaUI/Views/Main/ViewportControls/Viewport.axaml.cs

@@ -17,6 +17,7 @@ using PixiEditor.AvaloniaUI.ViewModels.Document;
 using PixiEditor.AvaloniaUI.Views.Overlays;
 using PixiEditor.AvaloniaUI.Views.Rendering;
 using PixiEditor.AvaloniaUI.Views.Visuals;
+using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Numerics;
 using PixiEditor.Zoombox;

+ 1 - 0
src/PixiEditor.AvaloniaUI/Views/Nodes/NodeView.cs

@@ -12,6 +12,7 @@ using PixiEditor.AvaloniaUI.Models.Handlers;
 using PixiEditor.AvaloniaUI.Models.Structures;
 using PixiEditor.AvaloniaUI.ViewModels.Nodes;
 using PixiEditor.AvaloniaUI.Views.Nodes.Properties;
+using PixiEditor.DrawingApi.Core;
 
 namespace PixiEditor.AvaloniaUI.Views.Nodes;
 

+ 1 - 1
src/PixiEditor.AvaloniaUI/Views/Overlays/SelectionOverlay/SelectionOverlay.cs

@@ -6,7 +6,7 @@ using Avalonia.Media;
 using Avalonia.Styling;
 using PixiEditor.AvaloniaUI.Animation;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface.Vector;
+using PixiEditor.DrawingApi.Core.Surfaces.Vector;
 using PixiEditor.Numerics;
 
 namespace PixiEditor.AvaloniaUI.Views.Overlays.SelectionOverlay;

+ 3 - 2
src/PixiEditor.AvaloniaUI/Views/Rendering/Scene.cs

@@ -19,12 +19,13 @@ using PixiEditor.AvaloniaUI.ViewModels.Document;
 using PixiEditor.AvaloniaUI.Views.Overlays;
 using PixiEditor.AvaloniaUI.Views.Overlays.Pointers;
 using PixiEditor.AvaloniaUI.Views.Visuals;
+using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Skia;
 using PixiEditor.Extensions.UI.Overlays;
 using PixiEditor.Numerics;
-using Bitmap = PixiEditor.DrawingApi.Core.Surface.Bitmap;
-using Image = PixiEditor.DrawingApi.Core.Surface.ImageData.Image;
+using Bitmap = PixiEditor.DrawingApi.Core.Surfaces.Bitmap;
+using Image = PixiEditor.DrawingApi.Core.Surfaces.ImageData.Image;
 using Point = Avalonia.Point;
 
 namespace PixiEditor.AvaloniaUI.Views.Rendering;

+ 1 - 0
src/PixiEditor.AvaloniaUI/Views/Visuals/SurfaceControl.cs

@@ -8,6 +8,7 @@ using Avalonia.Rendering.SceneGraph;
 using Avalonia.Skia;
 using Avalonia.Threading;
 using ChunkyImageLib;
+using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Numerics;
 

+ 1 - 0
src/PixiEditor.AvaloniaUI/Views/Visuals/SurfaceImage.cs

@@ -1,6 +1,7 @@
 using Avalonia;
 using Avalonia.Media;
 using ChunkyImageLib;
+using PixiEditor.DrawingApi.Core;
 
 namespace PixiEditor.AvaloniaUI.Views.Visuals;
 

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

@@ -1,4 +1,4 @@
-using PixiEditor.DrawingApi.Core.Surface.Vector;
+using PixiEditor.DrawingApi.Core.Surfaces.Vector;
 
 namespace PixiEditor.ChangeableDocument.ChangeInfos.Drawing;
 

+ 9 - 1
src/PixiEditor.ChangeableDocument/ChangeInfos/NodeGraph/CreateNode_ChangeInfo.cs

@@ -1,5 +1,6 @@
 using System.Collections;
 using System.Collections.Immutable;
+using System.Reflection;
 using PixiEditor.ChangeableDocument.Changeables.Graph;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
@@ -36,8 +37,15 @@ public record CreateNode_ChangeInfo(
                     return CreateFolder_ChangeInfo.FromFolder(folderNode);
             }
         }
+
+        string internalName = node.GetType().GetCustomAttribute<NodeInfoAttribute>()?.UniqueName;
+
+        if (string.IsNullOrEmpty(internalName))
+        {
+            throw new ArgumentException("Node does not have a unique name attribute. Please add [NodeInfo(\"UNIQUE_NAME\")] to the node class.");
+        }
         
-        return new CreateNode_ChangeInfo(node.InternalName, node.DisplayName, node.Position,
+        return new CreateNode_ChangeInfo(internalName, node.DisplayName, node.Position,
             node.Id,
             CreatePropertyInfos(node.InputProperties, true, node.Id), CreatePropertyInfos(node.OutputProperties, false, node.Id));
     }

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

@@ -0,0 +1,3 @@
+namespace PixiEditor.ChangeableDocument.ChangeInfos.NodeGraph;
+
+public record NodeName_ChangeInfo(Guid NodeId, string NewName) : IChangeInfo;

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

@@ -27,7 +27,7 @@ public record class CreateFolder_ChangeInfo : CreateStructureMember_ChangeInfo
     internal static CreateFolder_ChangeInfo FromFolder(FolderNode folder)
     {
         return new CreateFolder_ChangeInfo(
-            folder.InternalName,
+            folder.GetNodeTypeUniqueName(),
             folder.Opacity.Value,
             folder.IsVisible.Value,
             folder.ClipToPreviousMember.Value,

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

@@ -33,7 +33,7 @@ public record class CreateLayer_ChangeInfo : CreateStructureMember_ChangeInfo
     internal static CreateLayer_ChangeInfo FromLayer(LayerNode layer)
     {
         return new CreateLayer_ChangeInfo(
-            layer.InternalName,
+            layer.GetNodeTypeUniqueName(),
             layer.Opacity.Value,
             layer.IsVisible.Value,
             layer.ClipToPreviousMember.Value,

+ 2 - 2
src/PixiEditor.ChangeableDocument/Changeables/Animations/AnimationData.cs

@@ -17,7 +17,7 @@ internal class AnimationData : IReadOnlyAnimationData
 
     public void AddKeyFrame(KeyFrame keyFrame)
     {
-        Guid id = keyFrame.LayerGuid;
+        Guid id = keyFrame.NodeId;
         if (TryFindKeyFrameCallback(id, out GroupKeyFrame group))
         {
             group.Children.Add(keyFrame);
@@ -35,7 +35,7 @@ internal class AnimationData : IReadOnlyAnimationData
     {
         TryFindKeyFrameCallback<KeyFrame>(createdKeyFrameId, out _, (frame, parent) =>
         {
-            if (document.TryFindNode<Node>(frame.LayerGuid, out Node? node))
+            if (document.TryFindNode<Node>(frame.NodeId, out Node? node))
             {
                 node.RemoveKeyFrame(frame.Id);
             }

+ 16 - 0
src/PixiEditor.ChangeableDocument/Changeables/Animations/GroupKeyFrame.cs

@@ -14,6 +14,22 @@ internal class GroupKeyFrame : KeyFrame, IKeyFrameChildrenContainer
     public override int Duration => Children.Count > 0 ? Children.Max(x => x.StartFrame + x.Duration) - StartFrame : 0;
     public override int StartFrame => Children.Count > 0 ? Children.Min(x => x.StartFrame) : 0;
 
+    public override bool IsVisible
+    {
+        get
+        {
+            return isVisible;
+        }
+        set
+        {
+            isVisible = value;
+            foreach (var child in Children)
+            {
+                child.IsVisible = value;
+            }
+        }
+    }
+
     IReadOnlyList<IReadOnlyKeyFrame> IKeyFrameChildrenContainer.Children => Children;
 
     public GroupKeyFrame(Node node, int startFrame, Document document) : base(node, startFrame)

+ 4 - 3
src/PixiEditor.ChangeableDocument/Changeables/Animations/KeyFrame.cs

@@ -42,15 +42,16 @@ public abstract class KeyFrame : IReadOnlyKeyFrame
     
     public int EndFrame => StartFrame + Duration;
     
-    public Guid LayerGuid { get; }
+    public Guid NodeId { get; }
     public Guid Id { get; set; }
 
-    public bool IsVisible
+    public virtual bool IsVisible
     {
         get => isVisible;
         set
         {
             isVisible = value;
+            TargetNode.SetKeyFrameVisibility(Id, isVisible);
         }
     }
 
@@ -61,7 +62,7 @@ public abstract class KeyFrame : IReadOnlyKeyFrame
     protected KeyFrame(Node node, int startFrame)
     {
         TargetNode = node;
-        LayerGuid = node.Id;
+        NodeId = node.Id;
         this.startFrame = startFrame;
         duration = 1;
         Id = Guid.NewGuid();

+ 36 - 20
src/PixiEditor.ChangeableDocument/Changeables/Animations/KeyFrameData.cs

@@ -1,43 +1,59 @@
-namespace PixiEditor.ChangeableDocument.Changeables.Animations;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using PixiEditor.Common;
 
-public abstract class KeyFrameData : IDisposable
+namespace PixiEditor.ChangeableDocument.Changeables.Animations;
+
+public class KeyFrameData : IDisposable, IReadOnlyKeyFrameData
 {
     public int StartFrame { get; set; }
     public int Duration { get; set; }
     public Guid KeyFrameGuid { get; }
+    public string AffectedElement { get; set; }
+    public object Data { get; set; }
+    public bool IsVisible { get; set; } = true;
+
+    private int _lastCacheHash;
+
+    public bool RequiresUpdate
+    {
+        get
+        {
+            if (Data is ICacheable cacheable)
+            {
+                return cacheable.GetCacheHash() != _lastCacheHash;
+            }
+
+            return false;
+        }
+        set
+        {
+            if (Data is ICacheable cacheable)
+            {
+                _lastCacheHash = cacheable.GetCacheHash();
+            }
+        }
+    }
 
-    public abstract bool RequiresUpdate { get; set; }
 
-    public KeyFrameData(Guid keyFrameGuid, int startFrame, int duration)
+    public KeyFrameData(Guid keyFrameGuid, int startFrame, int duration, string affectedElement)
     {
         KeyFrameGuid = keyFrameGuid;
         StartFrame = startFrame;
         Duration = duration;
+        AffectedElement = affectedElement;
     }
 
     public bool IsInFrame(int frame)
     {
-        return frame >= StartFrame && frame <= StartFrame + Duration;
-    }
-
-    public abstract void Dispose();
-}
-
-public abstract class KeyFrameData<T> : KeyFrameData
-{
-    public T Data { get; set; }
-
-    public KeyFrameData(Guid keyFrameGuid, T data, int startFrame, int duration) : base(keyFrameGuid, startFrame,
-        duration)
-    {
-        Data = data;
+        return IsVisible && frame >= StartFrame && frame <= StartFrame + Duration;
     }
 
-    public override void Dispose()
+    public void Dispose()
     {
         if (Data is IDisposable disposable)
         {
             disposable.Dispose();
-        } 
+        }
     }
 }

+ 3 - 2
src/PixiEditor.ChangeableDocument/Changeables/Document.cs

@@ -5,9 +5,10 @@ using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Rendering;
+using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface.ImageData;
-using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+using PixiEditor.DrawingApi.Core.Surfaces.ImageData;
+using PixiEditor.DrawingApi.Core.Surfaces.PaintImpl;
 using PixiEditor.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changeables;

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Context/NoNodeFuncContextException.cs

@@ -1,4 +1,4 @@
-namespace PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Context;
 
 public class NoNodeFuncContextException : Exception
 {

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

@@ -1,8 +1,8 @@
-using System.Reflection;
-using PixiEditor.ChangeableDocument.Changeables.Graph.Context;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Context;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.Changes.NodeGraph;
+using PixiEditor.Common;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph;
 

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно